【Java编程思想】第15章 泛型

一、泛型出现的原因

1.1、容器

List list = new ArrayList();
        
list.add("1");
list.add(123);

String a = (String) list.get(0);
int b = (int) list.get(1);

比如上面的集合中第一次放入了字符串"1",第二次放入了int类型数字123,所以这个集合可以放入任何类型,但是取出来的时候就会非常麻烦,需要你知道特定的位置放入的是什么类型,然后进行强转,这使用非常麻烦。

自从jdk1.5后,ArrayList定义变成了

public class ArrayList extends AbstractList

 public void add(E e) {}
 public E get(int index) {}
List list = new ArrauList();//创建的时候指定类型

也就说说给集合限定了一种类型,以后这个集合只能存储这种类型,当然取出来的时候也就不需要强转了,因为知道了肯定是某种类型

泛型最引人注目的一个原因就是,从新创造了容器类

1.2、泛型类和接口

容器就是泛型类的一种经典应用,同样可以应用到内部类、抽象类、接口

1.3、泛型方法

class A{
	 /**
	     * 方法更通用,这个时候T虽然也是声明一个类型,但是该方法基本上是重载了很多次。
	     * 这样的方法才是通用方法。
	     *
	     * 创接类的时候无需指定方法的参数类型,编译器会通过类型参数推断
	     * @param t
	     * @param 
	     */
	    public static  void test(T t){
	        System.out.println(t.getClass().getName());
	    }
    }

比如上面一个方法,想获得参数所属类的名字,但是类型会特别多,有8中基本类型和自定义的无数类型。这个时候这个test方法需要重载多少次。通过泛型就可以解决这个问题。
注意一个知识点:比如类使用泛型,会显示的指定这个泛型是什么,new ArrayList,但是方法就不需要,因为方法会参数类型推断,自动判断类型,当然也可以显示的指定类型 比如 A.test(“a”)

二、擦除

Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
sout(c1==c2);// true

上面例子的结果是true,也就说说他们是相同的类,但是他们的行为却不同,c1只能存放字符串,而c2只能存放int,相同的类行为却不相容

原因
泛型只是编译器的小把戏,他也是语法糖,就是编译器做了一点手脚,并不是新的功能,语法。在编译后这些泛型就被擦除了,所以这两个类才回相同。

下面看擦除后的表现:

package com.hfview.ErasedType;

/**
 * 泛型擦除到object后不能调用特殊方法
 *
 * @author: zhw
 * @since: 2019/3/19 14:58
 */
public class Manipulator {

    private T obj;

    public Manipulator(T obj){
        this.obj = obj;
    }

    public void test(){
        //obj.f(); 
    }
}

class HasF{

    public void f(){
        System.out.println("HasF.f()");
    };

}

刚开始可能为疑惑为什么不能调用obj.f();方法,这是因为擦除后,泛型类型参数将擦除到他的第一个边界,本例中就是擦除到Object,Object哪有f()方法。

擦除是java的一种妥协,java是受c++启发编成的,c++就不会擦除,因为泛型在jdk1.5才出现的,在1.5后很多类都引入了泛型,如果没有擦除,jdk1.5之前的代码和jdk1.5之后的类都不能同用了,java考虑的向前兼容,才回使用擦除,这样就不会对以前的代码产生问题。

下面证明泛型为什么只是语法糖:

package com.hfview.ErasedType;

import org.apache.poi.ss.formula.functions.T;

/**
 *  通过字节码观察擦除只是语法糖
 *
 * @author: zhw
 * @since: 2019/3/19 15:40
 */
public class GenericBase {

    private Object element;

    public void set(Object arg){
        element = arg;
    }

    public Object get(){
        return element;
    }

    public static void main(String[] args) {
        Derived2 derived2 = new Derived2();
        System.out.println(derived2.get());
    }

}

class Derived2 extends GenericBase{

}

使用javap -c之后我把main方法贴出来

 public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class com/hfview/ErasedType/Derived2
       3: dup
       4: invokespecial #4                  // Method com/hfview/ErasedType/Derived2."":()V
       7: astore_1
       8: new           #5                  // class com/hfview/ErasedType/GenericBase
      11: dup
      12: invokespecial #6                  // Method "":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokevirtual #7                  // Method set:(Ljava/lang/Object;)V
      21: aload_2
      22: invokevirtual #8                  // Method get:()Ljava/lang/Object;
      25: checkcast     #3                  // class com/hfview/ErasedType/Derived2
      28: astore_3
      29: return
}

第25行很明显的用到了强转

下面我把类改造成泛型

package com.hfview.ErasedType;

import org.apache.poi.ss.formula.functions.T;

/**
 *  通过字节码观察擦除只是语法糖
 *
 * @author: zhw
 * @since: 2019/3/19 15:40
 */
public class GenericBase {

    private T element;

    public void set(T arg){
        element = arg;
    }

    public T get(){
        return element;
    }

    public static void main(String[] args) {
        Derived2 derived2 = new Derived2();
        GenericBase genericBase = new GenericBase();
        genericBase.set(derived2);

        Derived2 derived3 = (Derived2) genericBase.get();
    }

}

class Derived2{

}



使用javap -c 查看字节码发现,这个字节码竟然和上面不适用泛型是一摸一样的。

public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class com/hfview/ErasedType/Derived2
       3: dup
       4: invokespecial #4                  // Method com/hfview/ErasedType/Derived2."":()V
       7: astore_1
       8: new           #5                  // class com/hfview/ErasedType/GenericBase
      11: dup
      12: invokespecial #6                  // Method "":()V
      15: astore_2
      16: aload_2
      17: aload_1
      18: invokevirtual #7                  // Method set:(Ljava/lang/Object;)V
      21: aload_2
      22: invokevirtual #8                  // Method get:()Ljava/lang/Object;
      25: checkcast     #3                  // class com/hfview/ErasedType/Derived2
      28: astore_3
      29: return
}

泛型就是语法糖,他只是把强转动作在编译的时候自动加上了

三、边界

3.1、extends

如果需要调用泛型的某些方法,

//限制1
public interface IHasColor {
    Color getColor();
}
//限制2
public class Dimension {
    public int x,y,z;
}

//抽取公共部分
public class HoldItem {
    T item;
    public HoldItem(T item){
        this.item = item;
    }
    T getItem(){
        return item;
    }
}

//限制1的应用
public class Colored extends HoldItem{

    public Colored(T item) {
        super(item);
    }

    /**
     * 因为擦除到边界IHasColor,所以可以放翁getColor()方法
     * @return
     */
    Color color(){
        return item.getColor();
    }
}

//限制2的应用
public class ColorDimension extends Colored{

    public ColorDimension(T item) {
        super(item);
    }

    /**
     * 边界限定为Dimension和IHasColor 就可以调用getClor也可以是调用
     * Dimension中的x
     * @return
     */
    int getX(){
        return item.x;
    }

}

//继承2个限制的实现
public class Bounded extends Dimension implements IHasColor{

    @Override
    public Color getColor() {
        return Color.BLACK;
    }
}

//调用
public class Main {

    public static void main(String[] args) {

        Bounded bounded = new Bounded();

        ColorDimension colorDimension = new ColorDimension<>(bounded);

        colorDimension.color();
    }

}

如果想调用泛型的特殊方法,因为会擦除到边界,所以可以调用到边界的方法,没有定义边界那就是Object。

3.2 通配符

3.2.1 协变

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class CovariantArrays {
    public static void main(String[] args) {       
        Fruit[] fruit = new Apple[10];
        fruit[0] = new Apple(); // OK
        fruit[1] = new Jonathan(); // OK
        // Runtime type is Apple[], not Fruit[] or Orange[]:
        try {
            // Compiler allows you to add Fruit:
            fruit[0] = new Fruit(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        try {
            // Compiler allows you to add Oranges:
            fruit[0] = new Orange(); // ArrayStoreException
        } catch(Exception e) { System.out.println(e); }
        }
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~

main 方法中的第一行,创建了一个 Apple 数组并把它赋给 Fruit 数组的引用。这是有意义的,Apple 是 Fruit 的子类,一个 Apple 对象也是一种 Fruit 对象,所以一个 Apple 数组也是一种 Fruit 的数组。这称作数组的协变,Java 把数组设计为协变的。

尽管 Apple[] 可以 “向上转型” 为 Fruit[],但数组元素的实际类型还是 Apple,我们只能向数组中放入 Apple或者 Apple 的子类。在上面的代码中,向数组中放入了 Fruit 对象和 Orange 对象。对于编译器来说,这是可以通过编译的,但是在运行时期,JVM 能够知道数组的实际类型是 Apple[],所以当其它对象加入数组的时候就会抛出异常。

泛型设计的目的之一是要使这种运行时期的错误在编译期就能发现

ArrayList flist = new ArrayList();

编译器自动认为这种是错的,在编译期间就能发现错误

那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。

3.2.2 ? extends T

俗称上界边界符

List list = new ArrayList();

这样就能建立起"向上转型了",注意这个是打引号的。
解释一下 ? extends Fruit

  • 首先这个整体代表某一种类型,注意不是范围的概念
  • 该种类型是Fruit子类(包括自己)的一种

这就很尴尬了,因为你如果调用list.add(new Apple());编译器就会觉得很奇怪,因为编译器都不知道我到底是哪种类型,现在你突然想加入一个Apple对象,有可能是对的,但也有可能是错的,作为严谨的编译器怎么能允许出现可能出错,所以编译器就编译不通过。实际上这种情况除了添加null是允许的,添加任何对象编译器都会报错。

那这个东西有啥用????。

  • 首先确实"向上转型了",而且可以调用get(i) 方法,而且用Fruit f = list.get(i) 肯定不会出错,也就说说确实已经建立起了关系。
  • 可以调用某些特殊的方法,比如public boolean contains(Object o) 这个方法的对象是Object,所以调用 list.contains(new Apple()) 是可以调用的

3.2.3 ? super T

俗称下界边界符

List list = new ArrayList();

解释一下 ? super Fruit

  • 首先这个整体代表某一种类型,注意不是范围的概念
  • 该种类型是Apple父类(注意这个父类包括爷爷、太爷等等,包括自己)的一种

如果是List list = new ArrayList(),那么我向该list中添加apple的子类肯定没问题,因为肯定可以向上转型成apple。
同样 ? super Apple 都是apple的父类的所以添加apple和apple的子类肯定也是没问题的

list .add(new Apple());
list .add(new Jonathan());

3.2.3 ?

无边界通配符

还有一种通配符是无边界通配符,它的使用形式是一个单独的问号:List,也就是没有任何限定。不做任何限制,跟不用类型参数的 List 有什么区别呢?

List list 表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 List list ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。

你可能感兴趣的:(读JAVA编程思想有感)