第一章:泛型概述
一:泛型概念
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。
泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
二:泛型类
泛型标识可以理解称为一个类型的形参,我们拿着这个泛型标识可以去声明泛型变量+方法的返回值类型和参数类型。
常用的泛型标识T、E、K、V其中KV都是结合使用。
1:泛型类使用
package com.dashu.nettytest; public class Generic<T> { private T t; public Generic(T t){ this.t = t; } public T getT(){ return t; } public void setT(T t){ this.t = t; } }
这里的泛型就是一个泛型标识,它做了成员变量的类型,普通方法的入参和出参,构造方法的入参。
我们在创建对象的时候来指定T的具体类型,假如我们传入的是String类型,那么就说明我们要在这个类里边玩String类型了。
这里T是有外部使用类的时候传入的。
创建泛型类对象时如果并没有指定泛型,那么这里边的泛型将会按照Object类型来进行操作。
泛型类不支持基本数据类型,仅仅支持引用类型。我们真正使用T的时候,是转换成了Object,在使用类里边的泛型成员的时候,会在一个适当的机会将Object传递过来的数据类型转换为适当的类型,而我们的基本数据类型并不是继承自Object,玩具发进行转换。
使用泛型之后的对象的Class对象也都是一个。他们的Class对象的地址值也是一个。
2:泛型类派生子类
1):子类也是泛型类
这里我们定义的父类,我们使用E作为泛型标志。
package com.dashu.nettytest; public class Parent<E> { private E value ; public E getValue(){ return this.value; } public void setValue(E e){ this.value = e; } }
父类是泛型类,子类也是泛型类的这种情况,子类和父类的泛型标志要一样来标识子类和父类是同一个泛型。
假如我们在这里不给父类声明泛型变量的话:
public class Son<T> extends Parent { public Object getValue(){ return super.getValue(); } }
不给父类声明泛型变量,那么他默认就是Object那么我们使用开发工具将父类的getValue()方法重写之后就返回的是Object类型的变量。
假如我们给父类声明变量但是父子泛型变量不一致的话:
public class Son<T> extends Parent<E> { public E getValue(){ return super.getValue(); } }
就会发生下面的问题:
所以,子类继承泛型父类的时候,父子类要是都使用泛型功能的的话,标志必须要一致。这样做的话也是好理解的,我们创建子类对象的时候,必须先去创建父类对象,创建父类对象的话就会把泛型标识传递给父类。当然,子类泛型和父类泛型一致即可,我们也可以在子类的泛型当中进行扩展,只要保证父类的泛型标志在子类当中都有即可。
2):子类不是泛型类
子类不是泛型类,父类必须明确泛型的数据类型
public class Son extends Parent<Integer>{ }
我们如果定义的时候这样定义就会报错:
编译报错原因就是必须要指定具体的引用类型,而且这里也明确提示了,请创建E这个类,说明编译器在这里也认为E是一个具体类型,但是没有被创建而已。
三:泛型接口
实现类不是泛型类,接口要明确数据类型
实现类也是泛型类,实现类和接口的泛型类型要一致。
声明泛型接口和普通接口是一样的,声明泛型接口,添加的泛型可以作为方法的入参和返回值。
1:实现类不是泛型类
package com.dashu.nettytest; public interface Generator<T> { T getKey(); } class Apple implements Generator { @Override public Object getKey() { return null; } } class Banana implements Generator<String> { @Override // public Object getKey() { //指定了泛型类型为String类型之后,这里不能返回Object了,重写返回类型要一致。 public String getKey() { return null; } }
子类不是一个泛型类,必须指明接口泛型,如果不指明的话默认就是Object。
2:实现类也是泛型类
子类是泛型类的话,父类如果也要使用泛型类的话,父子类型需要一致。当然子类可以拓展,但是必须包含父类的泛型标志。
四:泛型方法
泛型类是在实例化类的时候,指明泛型的具体类型
泛型方法是在调用方法的时候指明泛型的具体类型。
1:概念
我们上述案例中的方法不是泛型方法,只不过是一个普通的成员方法,只不过类型使用了泛型类的类型。
修饰符后边和返参结果之前的那个区域叫做泛型列表,只有声明了这个区域的方法才叫泛型方法。
方形类中使用了泛型的成员方法并不是泛型方法。
表明该方法将使用泛型类型T,此时才可以在方法汇总使用泛型类型T
T可以为任意标识,常见的就是T、E、K、V
2:具体使用
class Test{ public <B> B getArrayList(ArrayList<B> shift){ return shift.get(0); } } class Test{ public <B,N,M> B getArrayList(ArrayList<B> shift){ return shift.get(0); } }
上述这两种写法都是没有问题的。泛型方法的泛型类是在方法被调用的时候被指定的。
3:静态泛型方法
下面是之前我一直不会的泛型方法和静态修饰符连用:
class Test{ public static final <B,N,M> B getArrayList(ArrayList<B> shift){ return shift.get(0); } public static void main(String[] args) { // ArrayList<Object> list = new ArrayList(); // Object arrayList = getArrayList(list); ArrayList<String> list = new ArrayList(); String arrayList = getArrayList(list); } }
这里我们入参当中反省是Object方法执行后返参就是Object,String的话返参就是String
4:泛型方法与可变参数
class Shit{ public static final <B> B getArrayList(B...b){ for (B b1 : b) { System.out.println(b); } return null; } public static void main(String[] args) { ArrayList<String> list = new ArrayList(); String arrayList = getArrayList("a","b","c"); Integer shit = getArrayList(1,2,3); } }
总结:
1:泛型方法可以独立于泛型类使用泛型的能力
2:如果static方法需要使用泛型鞥哪里,就必须成为泛型方法,不能简单使用泛型类的泛型变变量。
第二章:泛型通配符
一:什么是类型通配符
类型通配符就是一个?代替具体的类型实参,类型通配符是类型实参,而不是类型形参。
二:类型通配符上限
class Box<E>{ private E first; public E getFirst(){ return this.first; } public void setFirst(E first){ this.first = first; } } class Test { public static void main(String[] args) { Box<Integer> box = new Box<>(); showBox(box); } public static void showBox(Box<? extends Number> box){ Number first = box.getFirst(); System.out.println(first); } }
类/接口 <? extends 实参类型> 要求该泛型的类型,只能是是参类型或者是实参类型的子类类型。
这里要注意的是,类型通配符上限的这样的list是不能填充元素的。因为我们采用的是上线通配符。不知道里边的类型有多下限,所以限制填充元素。
三:类型通配符下限
类/接口<? super 实参类型> 要求该泛型的类型,只能是实参类型或者是实参类型的父类类型。
类型通配符下限这种方式,可以填充元素,但是不能保证元素的类型的 约束要求,因为可以把实参类型的子类给添加进去。
集合遍历的时候,都会使用Object来接收。
第三章:类型擦除
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为–类型擦除
Java中的泛型信息,只存在于编译阶段,进入到JVM之后就没有所谓的泛型类型了。
一:无限制类型擦除
泛型标识是T,生成字节码文件过程中T都用Object替换了。我们通过反射拿对应成员变量的类型的时候,就变成了Object。
二:有限制类型擦除
有上限类型,就把T转换成了上限类型,我们通过反射拿对应成员变量的类型的时候,就变成了Number
三:擦除方法中类型参数
这里T变Number,反射去拿就行。
四:桥接方法
生成了一个桥接方法,来保证实现关系。
package com.dashu.nettytest; import java.lang.reflect.Method; public interface Apple <T>{ T info (T t); } class infoImpl implements Apple<Integer>{ @Override public Integer info(Integer integer) { return integer; } } class Test999{ public static void main(String[] args) { Class<infoImpl> infoClass = infoImpl.class; Method[] declaredMethods = infoClass.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println(declaredMethod.getName()+":"+declaredMethod.getReturnType().getSimpleName()); } } //info:Integer //info:Object // //Process finished with exit code 0 }
有两个info方法,保证泛型,还有一个桥接方法。字节码反编译之后,没有这个了。
第四章:泛型与数组
泛型数组的创建
可以声明带泛型的数组的引用,但是不能直接创建带泛型的数组的对象。
可以通过java.lang.relect.Array的newInstance(Class,int)来创建T[]数组
一:带泛型数组的对象不可以直接创建
如图所示,直接创建的
话是无法编译过去的。我们可以声明左边这样的引用。
非泛型的可以直接创建这是没有任何问题的。
class Test999{ public static void main(String[] args) { ArrayList[] list = new ArrayList[5]; ArrayList<String>[] listArr = list; ArrayList<Integer> intList = new ArrayList<>(); intList.add(100); list[0] = intList; String s = listArr[0].get(0); System.out.println(s); //Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String // at com.dashu.nettytest.Test999.main(Apple.java:28) // //Process finished with exit code 1 } }
这种方式使用数组是不安全的。我们声明的是泛型的ArrayList数组,指向了一个原生类型的ArrayList的数组对象,原生类型的话,可以添加任何类型的元素,导致后续类型转换报错。我们应该这么写:
class Test999{ public static void main(String[] args) { // ArrayList[] list = new ArrayList[5]; // ArrayList<String>[] listArr = list; ArrayList<String>[] listArr = new ArrayList[5]; ArrayList<String> stringList = new ArrayList<>(); stringList.add("abc"); // list[0] = intList; listArr[0] = stringList; String s = listArr[0].get(0); System.out.println(s); //abc } }
我们使用泛型数组的时候,我们都是拿着泛型类型去创建一个这样的引用,而创建的对象呢不能创建泛型的数据类型,而是创建原生的数据类型赋值给引用。操作的时候,我们拿着引用去操作即可。
泛型在编译器会进行泛型擦除,而数组会在整个编译器期间持有他的数据类型,他们在设计上就是有冲突的,所以不能直接创建带有泛型类型的数组对象。
我们可以通过反射的方式创建一个泛型的数组。
二:通过反射创建泛型的数组
这个是不允许的,类型都没搞清楚,就直接new对象,肯定不行。
应该这么来写:
class Test999{ public static void main(String[] args) { Fruit<String> fruit = new Fruit<>(String.class,3); fruit.put(0,"aaa"); fruit.put(1,"bbb"); fruit.put(2,"ccc"); System.out.println("Arrays.toString(fruit.getArray()) = " + Arrays.toString(fruit.getArray())); } //Arrays.toString(fruit.getArray()) = [aaa, bbb, ccc] } class Fruit<T> { private T[] array; public Fruit(Class<T> clz, int length){ array =(T[]) Array.newInstance(clz,length); } public void put(int index,T t){ array[index] = t; } public T get(int index){ return array[index]; } public T[] getArray(){ return array; } }
最好不用泛型数组,要用泛型集合代替。
第五章:泛型与反射
一:反射中常用的泛型类
Class
Constructor
二:泛型对反射的好处
class Test999{ public static void main(String[] args) throws Exception { Class<Person> personClass = Person.class; Constructor<Person> constructor = personClass.getConstructor(); Person person = constructor.newInstance(); } } class Person { private String name; public String getName(){ return name; } public void setName(String name){ this.name = name; } }