第九章
1.抽象類和接口的區別?
說這個問題之前,必須得說明抽象類和抽象方法。首先抽象方法是必須在抽象類中,不能在普通方法中。但是,抽象類可以包含一個或多個抽象方法,也可以沒有抽象方法。抽象方法中也可以存在不是抽象的方法。
抽象類:(爲了繼承而存在,本身是沒什麼意義)
1.抽象方法必須爲public或者protected(因爲使用了private,則不能被子類所繼承,子類無法實現該方法)。
2.抽象類是不能直接用來創建對象。
3.如果一個類繼承於一個抽象類,子類必須實現父類的抽象方法。如果子類沒有完全實現父類的抽象方法,那這個子類也必須定義爲抽象類。
接口:
1.在接口中顯示地將方法定義爲public,即使不這麼做,也會默認這麼做的。當實現一個接口時,必須將接口裏的方法定義爲public。
2.接口裏變量只能定義爲public static final。
3.接口中的方法只能爲抽象的。
區別:
分語法層面和設計層面兩個層面說
語法層面:1.抽象類可以提供成員方法的實現細節,而接口中只能存在抽象方法。
2.抽象類中的成員變量可以是各種類型,但接口中的成員變量必須由public static final修飾。
3.抽象類中可以有靜態代碼塊和靜態方法,但接口中不能含有靜態代碼塊和靜態方法。
4.一個類只能繼承一個抽象類,但是一個類卻可以實現多個接口。
5.實現接口的非抽象類必須要實現該接口的所有方法。抽象類可以不用實現。
設計層面:1.抽象類是“是不是”的關係,而接口是“有沒有”的關係。
2.抽象類作爲許多子類的父類是一種模板式設計,而接口則是輻射式設計。
2.C++有多重繼承, 雖然JAVA沒有多重繼承的類,但有多重接口
這裏引用《JAVA編程思想》的話:“C++中,組合多個類接口的行爲被稱爲多重繼承,但它會讓你揹負很多的包袱,因爲每個類都有一個具體的實現。”
怎麼理解?爲什麼JAVA中不允許多重繼承的類?
abstract class a{
abstract void b();
void c(){
System.out.println("c()");
}
abstract void d();
abstract void e();
abstract void f();
}
abstract class b extends a{
abstract void b();
abstract void d();
abstract void e();
void f(){
System.out.println("f()");
}
/*abstract void f();*/
}
abstract class c extends a{
abstract void b();
abstract void d();
abstract void e();
abstract void f();
}
public class Main {
public static void main(String args[]){
}
}
以上代碼中,b和c類都繼承了a類,並且都有f的方法,只不過b類中f方法已經具體實現了,而c類中f方法只是抽象方法。只是你要是多重繼承b類和c類,當調用f方法時是調用b類和c類的f方法?這裏就出現問題了,但如果是實現接口就不會,因爲接口中所有方法都是抽象方法都沒有具體實現。
除此之外,JAVA多重繼承除了使用接口來實現外,還可以使用內部類。這個後面說
3.設計模式 適配器模式 工廠設計模式 策略設計模式
這個內容在之後補充
第十章
1.什麼叫內部類 ?內部類的一些特點,內部類和外部類的關係
內部類就是在一類裏又有一個類。內部類可以訪問其外圍所有成員,而不需要任何特殊條件,並且還擁有擁有外部類所有元素的訪問權。爲什麼可以做到如此?編譯器會默認爲成員內部類添加了一個指向外部類對象的引用,這樣內部類就有了外部類對象一樣訪問所有元素的權利。在擁有外部類對象之前,是不能創建內部類對象的。
2.爲什麼要用內部類
解決JAVA多重繼承的問題。因爲JAVA一個類只能繼承一個類(單繼承),之前接口解決了一部分這個問題,一類可以實現多個接口,但還是不方便,實現一個接口就必須實現它裏面的所有方法。而內部類就解決了這個問題,一個類可以有好多內部類,每個內部類都可以繼承一個類,變相地實現了多重繼承。
出個題:25 20 18
class Outer {
public int age = 18;
class Inner {
public int age = 20;
public viod showAge() {
int age = 25;
System.out.println(age);//空1
System.out.println(this.age);//空2
System.out.println(Outer.this.age);//空3
}
}
}
3.怎麼在外部類的外面創建內部類使用
格式:
//成員內部類不是靜態的:
外部類名.內部類名 對象名 = new 外部類名.new 內部類名();
//成員內部類是靜態的:
外部類名.內部類名 對象名 = new 外部類名.內部類名();
4.匿名內部類
我們在開發的時候,會看到抽象類,或者接口作爲參數。而這個時候,實際需要的是一個子類對象。如果該方法僅僅調用一次,我們就可以使用匿名內部類的格式簡化。
舉個例子:
public interface Demo {
void demoMethod();
}
public class MyDemo{
public test(Demo demo){
System.out.println("test method");
}
public static void main(String[] args) {
MyDemo md = new MyDemo();
//這裏我們使用匿名內部類的方式將接口對象作爲參數傳遞到test方法中去了
md.test(new Demo){
public void demoMethod(){
System.out.println("具體實現接口")
}
}
}
}
上述的例子就描述了爲什麼需要匿名內部類,起始就是簡化代碼,不用你去起名字了。在Java中,通常就是編寫一個接口,然後你來實現這個接口,然後把這個接口的一個對象作以參數的形式傳到另一個程序方法中, 然後通過接口調用你的方法,匿名內部類就可以很好的展現了這一種回調功能。
5.局部內部類和靜態內部類
1.局部內部類:就是定義在一個方法或者一個作用域裏面的類
class Outer {
public void method(){
class Inner {
}
}
}
局部內部類的特點:只能在所在的方法裏被使用,見下面的例子
//在局部位置,可以創建內部類對象,通過對象調用和內部類方法
class Outer {
private int age = 20;
public void method() {
final int age2 = 30;
class Inner {
public void show() {
System.out.println(age);
//從內部類中訪問方法內變量age2,需要將變量聲明爲最終類型。
System.out.println(age2);
}
}
Inner i = new Inner();
i.show();
}
}
2.靜態內部類:static是不能用來修飾類的,但是成員內部類可以看做外部類中的一個成員,所以可以用static修飾,這種用static修飾的內部類我們稱作靜態內部類,也稱作嵌套內部類.
特點:即使沒有外部類對象,也可以創建靜態內部類對象,而外部類的非static成員必須依賴於對象的調用,靜態成員則可以直接使用類調用,不必依賴於外部類的對象,所以靜態內部類只能訪問靜態的外部屬性和方法。
class Outter {
int age = 10;
static age2 = 20;
public Outter() {
}
static class Inner {
public method() {
System.out.println(age);//錯誤
System.out.println(age2);//正確
}
}
}
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
inner.method();
}
}
第十一、十五章泛型總結
泛型概念:泛型是一種未知的數據類型,當我們不知道使用什麼數據類型的時候,可以使用泛型。
不使用泛型
1.爲什麼要有泛型
好處:可以儲存任意類型的數據 壞處:不安全、可能出現異常
使用泛型
好處:1.避免了類型轉換的麻煩,存入的數據是什麼類型,讀取出來也是相應的類型。2.把運行期的異常,放到了編譯期來排查。
壞處:泛型規定的存入數據類型,就不能存其他類型的數據。
舉個例子:比如我們寫一個座標值,那這個座標可以整數,也可以包含小數的,但是它的意義都是表示座標的。
如果我們不用泛型,那就得有一個數據類型,寫一個相關的類:
//設置Integer類型的點座標
class IntegerPoint{
private Integer x ; // 表示X座標
private Integer y ; // 表示Y座標
public void setX(Integer x){
this.x = x ;
}
public void setY(Integer y){
this.y = y ;
}
public Integer getX(){
return this.x ;
}
public Integer getY(){
return this.y ;
}
}
//設置Float類型的點座標
class FloatPoint{
private Float x ; // 表示X座標
private Float y ; // 表示Y座標
public void setX(Float x){
this.x = x ;
}
public void setY(Float y){
this.y = y ;
}
public Float getX(){
return this.x ;
}
public Float getY(){
return this.y ;
}
}
但如果使用泛型了:變成同一數據類型Object,兼容了所有的數據類型。
class ObjectPoint{
private Object x ;
private Object y ;
public void setX(Object x){
this.x = x ;
}
public void setY(Object y){
this.y = y ;
}
public Object getX(){
return this.x ;
}
public Object getY(){
return this.y ;
}
}
在使用的時候,只要創建這個類對象,比如我們要存入Integer這個整數的包裝類
ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX=(Integer)integerPoint.getX();
2.泛型類
基本使用和格式,以下面爲例:
//定義
class Point<T>{// 此處可以隨便寫標識符號 比如E
private T x ;
private T y ;
public void setX(T x){//泛型作爲參數
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//泛型作爲返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};
//IntegerPoint使用
Point<Integer> p = new Point<Integer>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());
//FloatPoint使用
Point<Float> p = new Point<Float>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
普通類構造函數是這樣的:Point p = new Point() ; 而泛型類的構造則需要在類名後添加上<String>,即一對尖括號,中間寫上要傳入的類型。同時在類申明時也要寫上<>,例如class Point<T>。在泛型類創建自己對象的時候,就必須指明泛型具體是什麼數據類型,比如是Integer或者Float。
這裏的SetX方法並不是泛型方法,只是它的參數裏有泛型。
3.泛型接口
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T x);
}
上面就是一個泛型接口。既然是接口,就不能直接使用,需要實現類去實現接口。2種可能,一種實現類本身不是一個泛型類,另一種實現類本身就是一個泛型類。
Ⅰ.非泛型類實現泛型接口
class InfoImpl implements Info<String>{ // 定義泛型接口的子類
private String var ; // 定義屬性
public InfoImpl(String var){ // 通過構造方法設置屬性內容
this.setVar(var) ;
}
@Override
public void setVar(String var){
this.var = var ;
}
@Override
public String getVar(){
return this.var ;
}
}
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl i = new InfoImpl("harvic");
System.out.println(i.getVar()) ;
}
};
這裏在實現的時候,就規定了T到底是什麼,這裏是String類型。爲什麼?因爲它是非泛型類,類裏面不能含有泛型,得是確定的數據類型。
Ⅱ.泛型類實現泛型接口
泛型接口中泛型變量T一定會在泛型類裏,但泛型類中泛型變量不一定只有一個。它們之間是一個包含關係
下面這個例子,泛型類的泛型變量就只有一個,那肯定就是泛型接口中的泛型變量。
interface Info<T>{ // 在接口上定義泛型
public T getVar() ; // 定義抽象方法,抽象方法的返回值就是泛型類型
public void setVar(T var);
}
class InfoImpl<T> implements Info<T>{ // 定義泛型接口的子類
private T var ; // 定義屬性
public InfoImpl(T var){ // 通過構造方法設置屬性內容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
}
//使用
public class GenericsDemo24{
public static void main(String arsg[]){
InfoImpl<String> i = new InfoImpl<String>("harvic");
System.out.println(i.getVar()) ;
}
};
下面這個泛型類的泛型變量有三個。包含的情況
class InfoImpl<T,K,U> implements Info<U>{ // 定義泛型接口的子類
private U var ;
private T x;
private K y;
public InfoImpl(U var){ // 通過構造方法設置屬性內容
this.setVar(var) ;
}
public void setVar(U var){
this.var = var ;
}
public U getVar(){
return this.var ;
}
}
//使用
public class GenericsDemo24{
public void main(String arsg[]){
InfoImpl<Integer,Double,String> i = new InfoImpl<Integer,Double,String>("harvic");
System.out.println(i.getVar()) ;
}
}
3.泛型方法
格式:
修飾符 <泛型> 返回值類型 方法名(參數列表 使用泛型){
}
下面說幾個注意點:
1.泛型方法所在類既可以是泛型類,也可以不是,兩者沒關係。
2.static方法無法使用泛型類中的變量參數,所以如果static方法想有使用泛型能力,那它本身必須就是泛型方法。
3.在泛型類創建一個新對象的時候,必須要指定泛型具體的數據類型,而泛型方法是不需要的。
下面就具體舉一些實例:
public class StaticFans {
//靜態函數
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函數
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
在一個非泛型類裏,定義了兩個泛型方法,只不過一個是靜態方法,一個是普通方法,但定義方法時,沒有區別。只是在調用方法時有一點區別。
下面就是調用:
//靜態方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二
//常規方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
靜態方法調用不需要創建類對象,直接類名.方法名,上面靜態和常規方法都有兩種調用方式,但推薦都是用第二種,這樣可以很清楚地反應你這個泛型具體是什麼數據類型。
4.泛型擦除
Java的泛型不同於C++,JAVA的泛型是僞泛型。這是因爲Java在編譯期間,所有的泛型信息都會被擦掉,如在代碼中定義List<Object>
和List<String>
等類型,在編譯後都會變成List
,JVM看到的只是List,而由泛型附加的類型信息對JVM是看不到的。Java編譯器會在編譯時儘可能的發現可能出錯的地方,但是仍然無法在運行時刻出現的類型轉換異常的情況。
舉個例子:
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<String>();
list1.add("abc");
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(123);
System.out.println(list1.getClass() == list2.getClass());
}
}
這裏會打印出來的是true,雖然一個是ArrayList<String>,另一個是ArrayList<Integer>。但在運行時,都會被擦除成ArrayList,所以將它們倆比較是否一致時,會得到肯定的答覆。這裏肯定就有疑問,爲什麼JAVA中泛型在運行過程要擦除呢?
其實是無奈之舉,因爲JAVA最初沒有泛型,加入泛型這個新特性之後,爲了對老代碼的支持,所以纔有在運行時,泛型擦除的情況。
下面來看幾種寫法:
ArrayList<String> list = new ArrayList<String>();
ArrayList<String> list1 = new ArrayList(); //第一種 情況
ArrayList list2 = new ArrayList<String>(); //第二種 情況
第1行最標準的寫法,這種寫法肯定沒有問題,可以正常使用這個泛型。第2行可以行,實現的效果與第1行是完全一致。第3行寫法是錯誤的,無法編譯。因爲類型檢查就是編譯時完成的,new ArrayList()
只是在內存中開闢了一個存儲空間,可以存儲任何類型對象,而真正設計類型檢查的是它的引用(等號左邊)list1還是list2。
同樣再看兩種寫法:左右泛型不一致,但是Object類是String類的原始類。
ArrayList<String> list1 = new ArrayList<Object>(); //編譯錯誤
ArrayList<Object> list2 = new ArrayList<String>(); //編譯錯誤
上面兩個都是編譯錯誤,第一個左邊引用指向是String,右邊開闢的空間存入是Object類,這不就矛盾了。第二個稍微好點,起碼存入String類,而左邊指向的是Object類,但是這樣同樣違反了泛型設計的初衷。
5.類型擦除與多態的衝突(難點)
首先,設定一個泛型類
class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
讓一個子類去繼承這個泛型類,在繼承時父類泛型具體爲Date。
class DateInter extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
這時候就有個問題,就是子類中的兩個方法setValue和getValue是重寫還是重載父類的方法?(衝突)
這裏編譯器給的重寫override,但是在編譯的時候,父類泛型被擦除了,由原來的Data變成了Object。子類泛型依舊是Data,方法中的參數數據類型改變了,這就不是重寫而是重載。
可是由於種種原因,虛擬機並不能將泛型類型變爲Date
,只能將類型擦除掉,變爲原始類型Object
。這樣,我們的本意是進行重寫,實現多態。可是類型擦除後,只能變爲了重載。這樣,類型擦除就和多態有了衝突。JVM知道你的本意嗎?知道!!!可是它能直接實現嗎,不能!!!如果真的不能的話,那我們怎麼去重寫我們想要的Date
類型參數的方法啊。
於是JVM採用了一個特殊的方法,來完成這項功能,那就是橋方法。(暫時不知道)
6.通配符和上下界
通配符 ?:代表任意的數據類型(做程序不定下具體的數據類型)
使用方式:不能創建對象使用,只能作爲方法的參數使用。(是實參非形參 =Integer 、Float),通常與上下界一起使用。
泛型上界限定:<? extend E> 代表使用泛型只能是E類的子類或者本身。最高只能是E類。
泛型下界限定:<? super E> 代表使用泛型只能是E類的父類或者本身。最低只能是E類。
參考:《JAVA編程思想 第4版》、幾篇非常優秀的博客。