泛型
泛型是一种未知的数据类型,当我们不知道使用什么数据类型的时候,可以使用泛型
泛型也可以看成是一个变量,用来接收数据类型
E e:element 元素
T t:Type 类型
ArrayList集合在定义的时候,不知道集合中都会存储什么类型的数据,所以类型使用泛型,E是未知的数据类型
创建集合的时候,就会确定泛型的数据类型
使用泛型的好处
创建集合对象,不使用泛型
好处:集合不使用泛型,默认的类型就是object,可以存储任意类型的数据
弊端:不安全,会引发异常
ArrayList list = new ArrayList();
list.add("abc");
list.add(1);
//使用迭代器,遍历集合,获取迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
//想要使用String特有的方法,获取字符串长度,多态 Object obj = "abc"
//需要向下转型
//当输出元素1的长度时,会抛出ClassCastException类型转换异常,不能把Integer类型转换为String类型
String s = (String)obj;
System.out.println(((String) obj).length());
}
创建集合对象,使用泛型
好处:1)避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型;
2)把运行期异常(代码运行之后会抛出异常),提升到了编译期(编码阶段会检查)
弊端:泛型是什么类型,只能存储什么类型的数据
泛型的定义与使用
含有泛型的类
/*
泛型是一个未知的数据类型,当我们不确定什么数据类型的时候,可以使用泛型
泛型可以接收任意的数据类型,可以是Integer、String、Student...
创建对象的时候确定泛型的数据类型
*/
//泛型类
public class Use_fanxing<E>{
public E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
//使用泛型类
//不写泛型,默认为Object类型
Use_fanxing obj = new Use_fanxing();
obj.setName("只能是字符串");
Object obj1 = obj.getName();
System.out.println(obj1);
//使用泛型创建use_fanxing对象
Use_fanxing<Integer> in1 = new Use_fanxing<>(); //泛型使用Integer
in1.setName(789);
Integer num = in1.getName(); //由于自动拆箱,也可定义为int类型
Use_fanxing<String> str1 = new Use_fanxing<>(); //泛型使用String
str1.setName("小鸟");
String nameStr = str1.getName();
泛型类也可以派生子类
1)子类也是泛型类,子类和父类的泛型类型要一致
//父类用的T,子类也要用T作为泛型标志
class ChildGeneric<T> extends Generic<T>
//子类可以对父类的泛型进行扩展,但必须包含父类的泛型标志
class ChildGeneric<T,E,K> extends Generic<T>
2)子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
含有泛型的方法
修饰符与返回值中间非常重要,可以理解为声明此方法为泛型方法。
/*
泛型定义在方法的修饰符和返回值类型之间
格式: 修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型))
*/
public class Methodfanxing {
//定义一个含有泛型的方法
public <E> void methodFanxing(E e){
System.out.println(e);
}
//定义一个含有泛型的静态方法
static <E> void jingtaiFanxing(E e){
System.out.println(e);
}
}
Methodfanxing methodfanxing = new Methodfanxing();
methodfanxing.methodFanxing(10);
methodfanxing.methodFanxing(true);
methodfanxing.methodFanxing("abc");
Methodfanxing.jingtaiFanxing("静态方法,不建议创建对象");
//-------------------------------------------------------------
public class Methodfanxing<E> {
//定义一个泛型类中含有泛型的成员方法
public void methodFanxing1(E e){
System.out.println(e);
}
//定义一个含有泛型的方法
public <E> void methodFanxing2(E e){
System.out.println(e);
}
}
Methodfanxing<Integer> methodfanxing = new Methodfanxing();
methodfanxing.methodFanxing1(10); //10
methodfanxing.methodFanxing2("abc"); //abc
1)必须保证权限修饰符和返回值之间的泛型标识符 和 参数列表中的泛型标识符相同。
2)即使泛型类和泛型方法中的泛型标识符相同,但是两个也互不影响,独立存在。(泛型方法能使方法独立于类而产生变化)
3)如果泛型类中定义了成员方法,成员方法中使用了泛型类中的泛型标识符,那么这两个标识符必须相同,因为是由泛型类传递进来的,成员方法只是使用者。
4)泛型类中的成员方法或变量如果使用了泛型类传递进来的泛型,那么该方法和变量不能被定义为static,因为泛型是通过泛型类实例化时指定的,但是static类型的成员变量和方法在类初始化之前加载。
5)泛型方法可以定义为static类型,如果static方法要使用泛型能力,就必须使其成为泛型方法。
6)泛型方法还可以支持可变参数。
public <E> void print(E...e){
for(E e1 : e){
System.out.println(e);
}
}
含有泛型的接口
含有泛型的接口,有两种使用方式
1)定义接口的实现类,实现接口,指定接口的泛型
2)接口使用什么泛型,实现类就使用什么泛型,类跟着接口走,就相当于定义一个含有泛型的类,创建对象的时候确定泛型的类型
//定义泛型接口
public interface FanxingInterface<E> {
public abstract void method1(E e);
public abstract void method2(int m);
}
实现含泛型的接口
//方式 1)定义实现类时,指定接口的泛型
public class FanxingInterfaceimp implements FanxingInterface<String>{
@Override
public void method1(String str) {
System.out.println("定义实现类的时候,指定接口泛型"+str);
}
@Override
public void method2(int m) {
System.out.println(m);
}
}
FanxingInterfaceimp fimp = new FanxingInterfaceimp();
fimp.method1("iosnsk");
fimp.method2(10);
//方式 2)接口使用什么泛型,实现类就使用什么泛型
public class FanxingInterfaceimp1<E> implements FanxingInterface<E> {
@Override
public void method1(E e) {
System.out.println("接口使用什么泛型,实现类就使用什么泛型"+e);
}
@Override
public void method2(int m) {
System.out.println(m);
}
}
FanxingInterfaceimp1<Integer> fimp1 = new FanxingInterfaceimp1<>();
FanxingInterfaceimp1<String> fimp2 = new FanxingInterfaceimp1<>();
fimp1.method1(100);
fimp1.method2(1000);
泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符的使用,不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。
此时只能接收数据,不能往该集合中存储数据。不能创建对象使用,只能作为方法的参数使用,而且类型通配符时类型实参,而不是类型形参
/*
不能创建对象使用,只能作为方法的参数使用
*/
//泛型通配符
ArrayList<String> str = new ArrayList<>();
str.add("a");
str.add("b");
ArrayList<Integer> num = new ArrayList<>();
num.add(1);
num.add(2);
printArray(str);
printArray(num);
//遍历ArrayList集合时,不需要知道使用了什么数据类型
public static void printArray(ArrayList<?> list){
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()){
Object o = iterator.next();
System.out.println(o);
}
通配符的高级使用----受限泛型
泛型上限:
格式:类型名称<? extends 类> 对象名称
意义:只能接收该类型及其子类
泛型下限:
格式:类型名称<? super 类> 对象名称
意义:只能接收该类型及其父类
Collection<Integer> list1 = new ArrayList<>();
Collection<String> list2 = new ArrayList<>();
Collection<Number> list3 = new ArrayList<>();
Collection<Object> list4 = new ArrayList<>();
getElement1(list1);
getElement1(list2);//报错
getElement1(list3);
getElement1(list4);//报错
getElement2(list1);//报错
getElement2(list2);//报错
getElement2(list3);
getElement2(list4);
//泛型的上限,此时的泛型?,必须时Number类型或者其子类
public static void getElement1(Collection<? extends Number> coll){}
//泛型的上限,此时的泛型?,必须时Number类型或者其父类
public static void getElement2(Collection<? super Number> coll){}
类型擦除
泛型是 Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为类型擦除。
ArrayList<Integer> intNum = new ArrayList<>();
ArrayList<String> str = new ArrayList<>();
System.out.println(intNum.getClass().getSimpleName()); //ArrayList
System.out.println(intNum.getClass() == str.getClass()); //true
Erasure<String> erasure = new Erasure<>();
erasure.setKey("hello");
System.out.println(erasure.getClass().getField("key").getType());
//class java.lang.Object
无限制类型擦除时,最终会被转换为Object类型
Erasure<Integer> erasure = new Erasure<>(); System.out.println(erasure.getClass().getField("key").getType());
//class java.lang.Number
有限制擦除时,最终会被转换为上限的类型
Class<? extends Erasure> aClass = erasure.getClass();
Method[] methods = aClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName()+":"+method.getReturnType());
}//Number
InfoImpl info = new InfoImpl();
Class<? extends InfoImpl> infoClass = info.getClass();
Method[] methods = infoClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + "==>" + method.getReturnType());
}
/*输出
info==>class java.lang.Integer
info==>class java.lang.Object
*/
桥方法可以解决类型擦除和多态之间的冲突。
泛型深入
1)类型擦除之后,在class文件中保存的是原始类型。 原始类型就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型。无论何时定义一个泛型类型,相应的原始类型都会被自动地提供。类型变量被擦除,并使用其限定类型(无限定的变量用Object)替换。
2)类型擦除之后,可以避免类型膨胀的问题,如:每次使用一个泛型类都会给定其泛型参数,而且每次的泛型类型很大概率是不一样的,每次由于泛型参数不同使类发生改变,会导致类过大。
3) java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,在进行编译的。
4) 既然类型擦除了,如何保证我们只能使用泛型变量限定的类型呢?
//第一种情况
ArrayList<String> arrayList1=new ArrayList();
arrayList1.add("1");//编译通过
arrayList1.add(1);//编译错误
String str1=arrayList1.get(0);//返回类型就是String
//第二种情况
ArrayList arrayList2=new ArrayList<String>();
arrayList2.add("1");//编译通过
arrayList2.add(1);//编译通过
Object object=arrayList2.get(0);//返回类型就是Object
new ArrayList<String>().add("11");//编译通过
new ArrayList<String>().add(22);//编译错误
String string=new ArrayList<String>().get(0);//返回类型就是String
由上可知, new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是它的引用,因为我们是使用它引用arrayList1 来调用它的方法,比如说调用add()方法,所以arrayList1引用能完成泛型类型的检查。
5)泛型擦除了,但又如何获取泛型的实际类型?
//源代码
public class cachu {
public static void main(String[] args){
List<String> list = new ArrayList<>();
list.add("abc");
String str = list.get(0);
System.out.println(list);
System.out.println(str);
}
}
//class文件
public class cachu {
public cachu() {
}
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("abc");
String str = (String)list.get(0);
System.out.println(list);
System.out.println(str);
}
}
通过源代码和class文件可以知道, Java中的泛型只在程序源代码中存在, 在编译后的字节码文件中就已经替换为原来的原生类型, 并在相应的地方插入强制类型装换代码。
反射获取泛型信息
泛型擦除后,类,字段和方法的形参泛型信息是会保存到Signature中的(使用javap -verbose class文件 查看class文件的结构)。
使用 ParameterizedType ,即参数化类型
public class reflect_fanxing {
public ArrayList<String> list;
}
try {
reflect_fanxing fanxing = new reflect_fanxing();
Class<? extends reflect_fanxing> aClass = fanxing.getClass();
Field field = aClass.getDeclaredField("list");
//获取list属性的类型
Type type = field.getGenericType();
if (type instanceof ParameterizedType){
//强制转为ParameterizedType对象
ParameterizedType parameterizedType = (ParameterizedType) type;
//获取原始类型
Type rawType = parameterizedType.getRawType();
//获取list的类型的所有泛型信息
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
System.out.println("list的原始类型:"+rawType);
System.out.println("list的泛型:"+ Arrays.toString(actualTypeArguments));
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}