logo头像

我有一个梦想

JAVA泛型整理

本文于 1432 天之前发表,文中内容可能已经过时。

JAVA泛型整理

[TOC]

概述

泛型的本质是参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)

操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类泛型接口泛型方法

特性

1
2
3
4
5
6
7
8
9
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
Log.d("泛型测试","类型相同");
}

输出结果:D/泛型测试: 类型相同

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上可以看成是多个不同的类型,实际上都是相同的基本类型。

类型与区别

  • 表示不确定的java类型,是类通配符,代表所有类型。?不会进行类型推断
  • T(type)表示具体的一个java类型
  • K V(key value)分别代表java键值中的Key Value
  • E(element)代表Element
  • N(number)代表数值类型
  1. List<? extends T>和List <? super T>有什么区别?
  • List<? extends T>可以接受任何继承自T的类型的List
  • List<? super T>可以接受任何T的父类构成的List
  • 例如List<? extends Number>可以接受List或List
  1. ObjectT的区别?

​ Object是一个实打实的类,并没有泛指谁,只有指定的Object类的,而T可以泛指Object,指任何java类 型,范围更广

  1. 泛型的类型只能是类类型,不能是简单类型(简单八大数据类型)

如果有泛型方法和非泛型方法,都满足条件,会执行非泛型方法

泛型类

1
2
3
4
5
6
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....

}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}

泛型接口

1
2
3
4
//定义一个泛型接口
public interface Generator<T> {
public T next();
}

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
public <T> T genericMethod(Class<T> tClass)throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}

​ 这不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?

1
2
3
4
5

//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类
public void showKeyValue2(Generic<?> obj){
Log.d("泛型测试","key value is " + obj.getKey());
}

泛型类的中的泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GenerateTest<T>{
public void show_1(T t){
System.out.println(t.toString());
}

//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}

//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}

泛型方法和可变参数

1
2
3
4
5
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}
1
printMsg("111",222,"aaaa","2323.4",55.55);

静态方法与泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){

}
}

无论何时,如果你能做到,你就该尽量使用泛型方法。也就是说,如果使用泛型方法将整个类泛型化,
那么就应该使用泛型方法。另外对于一个static的方法而已,无法访问泛型类型的参数。
所以如果static方法要使用泛型能力,就必须使其成为泛型方法。

Sun的文档

1
2
3
4
5
6
7
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泛型的擦除机制,在运行时JVM是不知道泛型信息的,所以可以给oa[1]赋上一个ArrayList而不会出现异常,
但是在取出数据的时候却要做一次类型转换,所以就会出现ClassCastException,如果可以进行泛型数组的声明,
上面说的这种情况在编译期将不会出现任何的警告和错误,只有在运行时才会出错。
而对泛型数组的声明进行限制,对于这样的情况,可以在编译期提示代码有类型安全问题,比没有任何提示要强很多。

编译器:把源码交给编译器编译成计算机可以执行的文件的过程(把java代码编成class文件的过程,编译期只是做一些翻译的功能,并没有把代码放在内存中运行起来,而只是)

运行期:把编译后的文件交给计算机执行

以下是ok的

1
2
3
4
5
6
7
List<?>[] lsa = new List<?>[10]; // OK, array of unbounded wildcard type.    
Object o = lsa;
Object[] oa = (Object[]) o;
List<Integer> li = new ArrayList<Integer>();
li.add(new Integer(3));
oa[1] = li; // Correct.
Integer i = (Integer) lsa[1].get(0); // OK

泛型通配符

形参实参的区别

  • 形参是形式参数,接收调用者传递的参数
  • 实参是实体参数,调用时传递出的参数

T?`的区别?

  • T主要用于声明泛型类或泛型方法
  • 主要用于使用泛型类或泛型方法
  • T在同一个地方就代表一个具体的类型,而就代表所有类型,可以理解为所有类型的父类