1.泛型的概念
泛型的好处主要有两个:
举个例子,泛型究竟是怎么诞生的:
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
Log.d("泛型测试","item = " + item);
}
运行结果:
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
ArrayList可以存放任意类型,例子中添加了一个String类型,又添加了一个Integer类型,例子中我们从Arraylist中获取值用String类型去获取Integer不能自动类型转换,程序崩溃了。为了解决类似这样的问题(在编译阶段就可以解决),泛型应运而生。
为了避免在运行时会出现这种情况,我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。
List<String> arrayList = new ArrayList<String>();
这样子,ArrayList里面只能存储String类型的值。
2.泛型方法
下面是定义泛型方法的规则:
java 中泛型标记符:
泛型标记符在概念上都是相同的,它们都是用来指定泛型列,接口或者方法可以接受的类型。你可以根据自己的喜好或者代码的上下文来随意选择使用哪个标记符。
下面我们来做一个题目来练习一下:
public class Main {
//泛型方法PrintArray
public static < E > void printArray(E[] inputArray){
//输出数组元素
for(E element:inputArray){
System.out.printf("%s",element);
}
System.out.println();
}
public static void main(String[] args){
Integer[] intArray ={1,2,3,4,5};
Double[] doubles ={1.1,2.2,3.3,4.4};
Character[] characters ={'H','E','L','L','O'};
System.out.println("整数数组元素为、:");
printArray(intArray);//传递一个整数数组
System.out.println("\n双精度型数组元素为:");
printArray(doubles);
System.out.println("\n字符型数组元素为:");
printArray(characters);
}
}
运行结果为:
整数数组元素为、:
12345
双精度型数组元素为:
1.12.23.34.4
字符型数组元素为:
HELLO
泛型的基本使用:
在类名之后使用尖括号声明类型参数,声明的类型参数可以像普通类型一样用在类型声明处使用,到使用时再决定其具体类型,然后编译器会帮我们处理一些类型类型转换的细节。
public class Holder<T> {
T val;
public Holder(T val) {
this.val = val;
}
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
public static void main(String[] args) {
Holder<String> strHolder = new Holder<String>("abc");
String s = h.getVal();
}
}
在使用时指定了的 Holder
的类型参数为 String
。可以将 getVal()
的返回值直接赋给一个 String
变量,而不用显示的转型。在使用 setVal
时也必须传入 String
类或其子类,若入参不是 String
或其子类那么编译时会报错。
在Java7之前 new
参数化类型时需要指定类型,但在Java7之后 new
操作可以不用显示指定类型,编译器会自动推导出来:
Holder<String> h = new Holder<>("abc");
2.1泛型方法的使用
泛型类,在创建类的对象的时候确定类型参数的具体类型;
泛型方法,在调用方法的时候再确定类型参数的具体类型。
举例如下:
public class Demo {
public static void main(String args[]) {
GenericMethod d = new GenericMethod(); // 创建 GenericMethod 对象
String str = d.fun("汤姆"); // 给GenericMethod中的泛型方法传递字符串
int i = d.fun(30); // 给GenericMethod中的泛型方法传递数字,自动装箱
System.out.println(str); // 输出 汤姆
System.out.println(i); // 输出 30
GenericMethod.show("Lin");// 输出: 静态泛型方法 Lin
}
}
class GenericMethod {
// 普通的泛型方法
public <T> T fun(T t) { // 可以接收任意类型的数据
return t;
}
// 静态的泛型方法
public static <E> void show(E one){
System.out.println("静态泛型方法 " + one);
}
}
3.认识泛型的写法
先从一个简单的泛型类开始:
public class Main {
class point<T>{//此处可以随便写标识符号,T是type的简称
private T var;//var的类型由T指定,即:有外部指定
public T getVar(){
return var;
}
public void setVar(T var){//设置的类型也由外部决定
this.var=var;
}
}
public void main(String[] args) {
point<String> p =new point<String>();//里面的var类型为String类型
p.setVar("it");
System.out.println(p.getVar().length());
}
}
多元泛型:
public class Main {
static class Notepad<K,V>{
private K key;
private V value;
public K getKey() {
return key;
}
public void setKey(K key) {
this.key = key;
}
public V getValue() {
return value;
}
public void setValue(V value) {
this.value = value;
}
}
public static void main(String[] args) {
Notepad<String,Integer> t =null;
t=new Notepad<String,Integer>();
t.setKey("汤姆");
t.setValue(20);
System.out.println("姓名"+t.getKey());
System.out.println("年龄"+t.getValue());
}
}
多个类型参数使用逗号分隔:
public class Holder<A, B, C> {
public A v1;
public B v2;
public C v3;
public Holder(A v1, B v2, C v3) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
public static void main(String[] args) {
Holder<String, Integer, Float> h = new Holder<>("abc", 1, 2.5);
}
}
简单的泛型接口:
public class Main {
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
static 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 static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
}
非泛型的类(或者泛型类)中定义泛型方法
class Arraylist<E> {
public <T> T[] toArray(T[] a) {
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
}
4.泛型通配符
泛型通配符是JAVA泛型中的一个概念,它允许你在编写代码的时候对泛型类型参数的类型进行限制。
为什么要使用通配符?
这里的?代表类型是未知的,所以编译器不知道要检查哪种类型,因此不允许你向这样的列表中添加任何的元素。
Object是所有类的超类,因此任何类型的对象都可以安全的转换为Object类型。
无限制通配符:
上界限定通配符:
下界限定通配符:
非泛型的类(或者泛型类)中定义泛型方法
class Arraylist<E> {
public <T> T[] toArray(T[] a) {
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
}
}
5.类型擦除
1.无限制类型擦除
当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如<T>
和<?>
的类型参数都被替换为Object。
2.有限制类型擦除
当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或者下界,比如形如<T extends Number>
和<? extends Number>
的类型参数被替换为Number
,<? super Number>
被替换为Object。
3.擦除方法定义中的类型参数
擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例。
如何证明类型擦除呢?
举个例子:
public class Main {
static class Box<T>{
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public static void main(String[] args) {
Box<Integer> integerBox=new Box<>();
integerBox.setT(123);
Integer value =integerBox.getT();
System.out.println("value: "+value);//输出结果为123
//尽管Box<Integer>在编译的时候是Integer类型,但是在运行的时候它只是Box
System.out.println(integerBox instanceof Box<Integer>);//编译的时候就报错了
System.out.println(integerBox instanceof Box);//ture
}
}
原始类型相等
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数组,不过一个是ArrayList<String>
泛型类型的,只能存储字符串;一个是ArrayList<Integer>
泛型类型的,只能存储整数,最后,我们通过list1对象和list2对象的getClass()
方法获取他们的类的信息,最后发现结果为true。说明泛型类型String和Integer都被擦除掉了,只剩下原始类型。
4.如何理解泛型的编译期检查?
Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("123");
list.add(123);//编译错误
}
在上面的程序中,使用add方法添加一个整型,在IDE中,直接会报错,说明这就是在编译之前的检查,因为如果是在编译之后检查,类型擦除后,原始类型为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
}
}
通过上面的例子,我们可以明白,类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
泛型中参数话类型为什么不考虑继承关系?
在Java中,像下面形式的引用传递是不允许的:
ArrayList<String> list1 = new ArrayList<Object>(); //编译错误
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
实际上,在第4行代码的时候,就会有编译错误。那么,我们先假设它编译没错。那么当我们使用list2引用用get()方法取值的时候,返回的都是String类型的对象(上面提到了,类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object类型的对象,这样就会有ClassCastException
了。所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递。(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)。