《JAVA編程思想》第九、十、十一(泛型)、十五章節總結

第九章

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版》、幾篇非常優秀的博客。

https://www.cnblogs.com/jpfss/p/9928747.html

https://www.cnblogs.com/wuqinglong/p/9456193.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章