多态和泛型的区别是:是否解耦类或方法与所使用的类型之间的约束。

1实现泛化的方法

  1. 使用多态:将方法的参数类型设为基类,那么该方法就可以接受从这个基类中导出的任何类作为参数。
  2. 使用泛型:暂时不指定类型,而是稍后再决定具体使用什么类型。
//使用多态时
public class Holder{
	private Object a;
	public holder(Object a){this.a = a;}
	public void set(Object a) {this.a = a;}
	public static void main(String[] args){
		Holder h2 = new Holder(new Automobile());
		Automobile a = (Automobile)h2.get();
		h2.set("Not an Automobile");
		String s = (String)h2.get();
		h2.set(1);
		Integer x = (Integer)h2.get();
	}
}

//使用泛型的时候
public class Holder2<T>{
	private T a;
	public Holder2(T a){this.a = a;}
	public void set(T a){this.a = a;}
	public T get(){return a;}
	public static void main(String[] args){
		Holder2<Automoblie> h2 = new Holder2<Automobile>(new Automobile());
		Automobile a = h2.get();
	}
}

java泛型本质上只有类泛型一种,因为所有的方法泛型都可以转换成类泛型,并且方法泛型依赖于类泛型。
java泛型可以简单的看成是“带类型参数的类”。
编译器处理泛型的3个步骤: 1.类型检查,2.类型擦除,3.添加转型代码。

2类泛型和方法泛型。

java泛型的核心概念是:告诉编译器你想使用的类型,然后编译器帮你处理一切细节。

  • 类泛型:声明时,在类名的后面使用类型参数,并用尖括号把类型参数括起来。在创建这个类的实例的时候,指定具体要使用的类型,然后编译器会处理一切。
  • 方法泛型:需要将泛型参数列表置于返回值之前(从中可以看出泛型是带类型参数的类)。当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法时,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为“类型参数推断”(type argument inference)。
  • 还可以对泛型使用边界。(T extends MyClass)。

泛型方法相对与泛型类的优势:
泛型方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否用于泛型方法,与其所在的类是否是泛型没有关系。所以,原则上,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。

注意:对于一个static方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。

3实例化泛型类和调用泛型方法

除了可以在创建这个类的实例的时候指定具体要使用的类型,还可以在继承的时候指定具体要使用的类型。

public class Test {
    static class GenericBase<T> {
        private T element;
        public void  set(T arg){element = arg;}
        public T get(){return element;}
    }
//    static class GenericBase {
//        private Object element;
//        public void  set(Object arg){element = arg;}
//        public Object get(){return element;}
//    }
    //但是如果被继承的类中包含带?的类型参数就会报异常
    static class Derived extends GenericBase<String>{

    }

    //如果没有指定T,编译时会使用Object来替代T.
    static class Devived2 extends GenericBase{}

    public static void main(String[] args) {
        Devived2 devived2 = new Devived2();
        devived2.set(new Devived2());
        //运行时可以获取它本来的类型
        System.out.println(devived2.get().getClass().getSimpleName());
        //但是在编译时,会报不兼容类型异常,因为编译器检测到的deviced2.get()方法返回的类型是Object。
//        Devived2 devived21 = devived2.get();

        Derived derived = new Derived();
        String s = derived.get();
    }
}

3-1.擦除机制

import java.util.*;

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class c1 = new ArrayList<String>().getClass();
        Class c2 = new ArrayList<Integer>().getClass();

        System.out.println(c1 == c2);


        List<String> list = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
    }
}
//结果:
true
//编译器虽然使用了擦除机制,但是泛型的类型信息还是会存放到字节码文件中,并存放到Class对象中。对于泛型类的定义,编译器并不执行擦除,
//编译器会执行这3个步骤:先进行类型检查,然后进行类型擦除(替换为它们的非泛型上界),最后添加强制类型转换代码。
//擦除指的是移除方法的类型信息。“对象”进入和离开方法的地点称为擦除边界,边界是编译器进行类型检查和插入转型代码的地方。
//非泛型上界:T可以被指定为MyClass不带泛型参数的(被擦除为Object),也可以被指定为MyClass<String>带类型参数的(被擦除为MyClass),还可以被指定为? extends MyClass(被擦除为MyClass)。
[E]
[K, V]

擦除主要的正当理由是从非泛型代码到泛型代码的转变过程,以及在不破坏现有类库的情况下,将泛型融入java代码。擦除使得现有的非泛型客户端代码能够在不改变的情况下继续使用,直到客户端准备好用泛型重写这些代码。

擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了。

public class Erased<T> {
    private final static int SIZE = 100;
    public  void f(Object arg){
        if (arg instanceof T){}  //Error
        T var = new T();   //error
        T[] array = new T[SIZE];  //error
        T[] arrys = (T[])new Object[SIZE];  //unchecked warning
    }
}

注意:因为擦除在方法中移除了类型信息,所以边界(对象进入和离开方法的地点)是编译器执行类型检查并插入转型代码的地方。

3-2通配符

注意:通配符被限制为单一边界。

//You can do this
List<? extends SuperHearing> audioBoys;
//But you can't do this
List<? extends SuperHearing&SuperSmell>  dogBoys;

不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型。因为我们讨论的是容器的类型而不是容器持有的类型。

//编译时会报错
List<Fruit> fList = new ArrayList<Apple>();

与数组不同,泛型没有内建的协变类型。这时因为数组在语言中是安全定义的,因此内建了编译期和运行时的检查,但是在使用泛型时,编译器和运行时系统都不知道你想使用类型参数做些什么,以及应该采用什么样的规则。

但是,有时你想要在两个类型之间建立某种类型的向上转型关系,这正是通配符的作用:

//wildcards allow covariance
List<? extends Fruit> fList = new ArrayList<Apple>();
//编译时错误,不能添加任何类型的对象
//fList.add(new Apple());
//fList.add(new Fruit());
//fList.add(new Object());
flist.add(null);   //legal but unintersting
Fruit f = fList.get(0);

注意:
List<? extends Fruit>读作:可以获取任何继承Fruit的类型的List。
通配符可以是任何事物,而编译器无法验证“任何事物”的类型安全性。

编译器会直接拒绝对参数列表中涉及通配符的方法的调用。所以,为了禁止对在类型中使用了通配符的情况下的函数调用,我们需要在参数列表中使用类型参数。

超类型通配符
可以使用<? super MyClass>和<? super T>
根据如何能够向一个泛型类型“写入”以及如何能够从一个泛型类型“读取”,来着手思考子类型和超类型边界。

java泛型学习笔记-LMLPHP
无界通配符

<?>意味着“任何事物”,因此使用无界通配符基本等价于使用原生类型。

4使用Collections工具类对泛型进行检查

因为可以向JavaSE5之前的代码传递泛型容器,所以旧式代码仍旧有可能会破坏你的容器,JavaSE5的java.util.Collections中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是静态方法:checkedCollection()、checkedList()、checkedMap()、checkedStoredMap()、checkedStoredSet()这些方法每一个都会将你希望动态检查的容器当做第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。

这些方法会在你试图插入类型不正确的对象时抛出ClassCastException,这与泛型之前的容器形成了对比。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CheckedList {

    static  class Pet{}

    static class Cat extends Pet{}

    static class Dog extends  Pet{}

    static void oldStyleMethod(List probablyDogs){
        probablyDogs.add(new Cat());
    }


    public static void main(String[] args) {
        List<Dog> dogs1 = new ArrayList<>();
        oldStyleMethod(dogs1);

        List<Dog> dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
        try{
            oldStyleMethod(dogs2);
        }catch (Exception e){
            System.out.println(e);
        }

        List<Pet> pets = Collections.checkedList(new ArrayList<>(), Pet.class);
        pets.add(new Dog());
        pets.add(new Cat());
    }
}

10-04 11:38