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



