《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

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