Java笔记六 泛型
什么是泛型?为什么要使用泛型?
泛型,即“参数化类型”
。泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的基本使用
- 泛型类
- 多元泛型
- 泛型接口
- 泛型方法
1 返回值前面加<T>
2 泛型方法,就代表着我们不知道具体的类型是什么,也不知道构造方法如何,因此没有办法去new一个对象,但可以利用反射创建对象。
3 调用泛型方法时,可以指定泛型,也可以不指定泛型
调用泛型方法
public class Test {
public static void main(String[] args) {
/**不指定泛型的时候*/
int i = Test.add(1, 2); //这两个参数都是Integer,所以T为Integer类型
Number f = Test.add(1, 1.2); //这两个参数一个是Integer,一个是Float,所以取同一父类的最小级,为Number
Object o = Test.add(1, "asd"); //这两个参数一个是Integer,一个是String,所以取同一父类的最小级,为Object
/**指定泛型的时候*/
int a = Test.<Integer>add(1, 2); //指定了Integer,所以只能为Integer类型或者其子类
int b = Test.<Integer>add(1, 2.2); //编译错误,指定了Integer,不能为Float
Number c = Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float
}
//这是一个简单的泛型方法
public static <T> T add(T x,T y){
return y;
}
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
为什么要使用泛型方法呢?
因为泛型类要在实例化的时候就指明类型,如果想换一种类型,不得不重新new一次,可能不够灵活;而泛型方法可以在调用的时候指明类型,更加灵活。
擦除
<? extends A>
表示该类型参数可以是A或者A的子类类型。编译时擦除到类型A,即用A类型(原始类型)代替类型参数。
- 原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
泛型的上下限
<? extends A>
<? super A>
- 多个限制使用&符号
private <E extends Comparable<? super E>> E max(List<? extends E> e1) {
if (e1 == null){
return null;
}
//迭代器返回的元素属于 E 的某个子类型
Iterator<? extends E> iterator = e1.iterator();
E result = iterator.next();
while (iterator.hasNext()){
E next = iterator.next();
if (next.compareTo(result) > 0){
result = next;
}
}
return result;
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
泛型数组
Java泛型这个特性是从JDK 1.5才开始加入的,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”,将所有的泛型表示(尖括号中的内容)都替换为具体的类型。
泛型的类型擦除原则
- 消除<>及其包围的部分。
- 根据类型参数的上下界推断并替换所有的类型参数为原生态类型。无限制通配符或没有上下界限定则替换为Object, 在上下界限定则根据子类替换原则取父类。
- 必要时插入强制类型转换代码。
- 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。
泛型的编译期检查,泛型变量的使用,是会在编译之前检查的。
Java编译器是通过先检查代码中泛型的类型
,然后在进行类型擦除,再进行编译。
证明:
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("123");
list.add(123);//编译错误
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
如果是在编译之后检查,类型擦除后,原始类型为Object,是应该允许任意引用类型添加的。可实际上却不是这样的。
- 类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
public class Test {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList();
list1.add("1"); //编译通过
list1.add(1); //编译错误
String str1 = list1.get(0); //返回类型就是String
ArrayList list2 = new ArrayList<String>();
list2.add("1"); //编译通过
list2.add(1); //编译通过
Object object = list2.get(0); //返回类型就是Object
new ArrayList<String>().add("11"); //编译通过
new ArrayList<String>().add(22); //编译错误
String str2 = new ArrayList<String>().get(0); //返回类型就是String
}
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
泛型中的引用传递的问题
不允许
ArrayList<String> list1 = new ArrayList<Object>(); //编译错误
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
桥方法
子类中真正覆盖父类两个方法的就是这两个我们看不到的编译器自动生成的桥方法。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
如何理解泛型类型不能实例化?
T test = new T(); // ERROR
Java在编译期没法确定泛型参数化类型,找不到对应的字节码文件,所以泛型类型不能实例化。
反射实现实例化泛型:
static <T> T newTclass (Class < T > clazz) throws InstantiationException, IllegalAccessException {
T obj = clazz.newInstance();
return obj;
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html
泛型数组
在java中是”不能创建一个确切的泛型类型的数组”的。
数组的类型不可以是类型变量,除非是采用通配符的方式
//错误
List<String>[] ls = new ArrayList<String>[10];
//正确
List<?>[] ls = new ArrayList<?>[10];
List<String>[] ls = new ArrayList[10];
eg.
List<String>[] lsa = new List<String>[10]; // Not really allowed.
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Unsound, but passes run time store check
String s = lsa[1].get(0); // Run-time error: ClassCastException.
原因:
- 运行时,泛型信息已经被擦除,在运行时JVM是不知道泛型信息的。所以可以给oa[1]赋上一个ArrayList而不会出现异常。
- 取出数据的时候却要做一次类型转换,会出现ClassCastException
- 如果可以进行泛型数组的声明,上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
异常与泛型
编译的时候,泛型信息全都会被擦除掉
- 不能抛出也不能捕获泛型类的对象。事实上,泛型类扩展Throwable都不合法。
- 不能再catch子句中使用泛型变量
- 在异常声明中可以使用类型变量。
public static<T extends Throwable> void doWork(T t) throws T {
try{
...
} catch(Throwable realCause) {
t.initCause(realCause);
throw t;
}
}
------
著作权归@pdai所有
原文链接:https://pdai.tech/md/java/basic/java-basic-x-generic.html