泛型的本质是参数化类型,参数化类型就是把操作的数据类型指定为一个参数,将类型由原来的的具体的类型参数化,将类型定义为参数类型(类型形参),在使用/调用时,传入具体的类型(类型实参),这种可以用在类、接口、方法的创建中,分别称为泛型类、泛型接口、泛型方法。.
我们把一个对象放入集合中,集合不会记住此对象的类型,当我们从集合中取出此对象时,把对象的编译类型变成了object类型,但运行时类型任然为其本身类型。因此,取出对象时,人为的强制转换类型为具体的目标类型, 很容易出现java.lang.ClassCastException异常。
泛型类的声明需要在类名后面添加参数声明部分,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。泛型参数也被称为类型变量。用于指定一个泛型名称的标识符。这些类被称为参数化的类。
class 类名称<泛型名称:可以随便写任意标识号,标识指定的是泛型的类型>{ private 泛型标识/*(成员变量类型)*/var ... } }定义的泛型类,不一定传入泛型类型实参,在使用泛型的时候传入泛型实参,则会根据传入的泛型实参做相应的限制。此时泛型才会起到相应的限制作用。如果不传入泛型实参,在泛型类中的使用泛型方法或成员变量定义的类型可以使任意类型。
(1)对类型进行自动检查
(2)自动对类型进行类型转换
(3)泛型类型参数只能是引用数据类型,不能是基本数据类型
(4)不能对确切的泛型类型使用Instanceof操作。(在运行时判断对象是否是特定类的一个实例)
(5)不能直接new泛型数组
(6)不能产生泛型类型对象
(7)在static中,不能使用泛型类型参数,因为static不依赖对象存在,所以无法推出static泛型类型参数。
泛型接口和泛型类的定义和使用基本相同。
泛型方法所在的类可以是泛型类也可以不是泛型类,方法中,通过指定类型参数,可以声明泛型方法,提高其复用性。泛型方法的参数化列表放在方法类符的后面返回值之前。
如果泛型方法的泛型参数和类型的泛型参数相同,编译期生成警告。
因为泛型方法的泛型参数隐藏了外部类型的泛型参数。
public class MyTest<T>{//泛型类 private T value; public <T>void fun(){//泛型方法,警告 } }直接可以通过"方法名()"的形式进行泛型方法的调用。编译器根据传入的方法实参推出形参类型。使用约束对泛型类型参数起到约束的作用。
示例:
public class TestDemo{ private static<T extends Comparable<T>>void swap(T[] arr,int index1,int index2){ T temp = arr[index1]; arr[index1]=arr[index2]; arr[index2]=T temp; } public static <T extends Comparable<T>> void bubbleSort(T[] arr){ boolean flag; for(int i=0;i<arr.length,i++){ flag=false; for( int j=0;j<arr.length-1-i;j++){ if(arr[j].compareTo(arr[j+1])>0){ flag=true; swap(arr,j,j+1); } } if(!flag){ break; } } } public static void main(String[] args){ Integer[] arr = {0,1,0,2,1,3}; bubbleSort(arr); System.out.println(Arrays.toString(arr)); } }java的泛型是伪泛型,因为在编译期间,所有的泛型信息都会被擦除,java中的泛型基本都是在编译器这个层次来实现的。在生成的Java字节码是不包含泛型中的类型信息的。使用泛型的时候加上的泛型参数,编译器在编译的时候去掉。这个过程就称为类型擦除。
例如在代码中定义的List<object>和List<String>,在编译后都会编译成List。JVM看到的只有List。而由泛型附加的类型信息对Jvm来说是不可见的。
import java.util.ArrayList; public class TestDemo{ public static void main(String[] args){ ArrayList<Stirng> arrayList1 = new ArrayList<String>(); arrayList1.add("abc"); ArrayList<Integer> arraylist2= new ArrayList<Integer>(); arraylist2.add(123); System.out.println(arrayList1.getClass()===arrayList2.getclass()); } } 通过arrayList1对象和arrayList2对象的getclass()方法获取他们的类信息,结果显示true,说明他们的泛型类型都被擦除掉了,只剩下原始类型。测试类型信息:
public static TestDemo{ public static void main(String[] args){ ArrayList<Integer> arrayList3 = new ArrayList<Integer>(); arrayList3.add(1);//调用add方法只能存储整形,因为泛型类型的实例为Integer arrayList3.getClass().getMethod("add",Object.class).invoke(arrayList3,"asd"); for(int i=0;i<arrayList3.size();i++){ System.out.println(arrayList3.get(i)); } } }在程序中定义了ArrayList泛型类型实例化为Intege的对象,如果直接调用add方法,那么只能存储整型的数据,当我们用反射调用add方法,却可以存储字符串,说明泛型实例在编译之后被擦除了,只保留了原始类型。
什么是原始类型,原始类型就是擦除泛型信息最后体现在字节码中的类型,是变量的真正类型。无论何时定义一个泛型信息,都会被自动的擦除到原始类型,使用其限定类型,无限定类型的Object替换。
先检查,再编译,以及检查编译的对象以及引用传递的问题。Java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除的,再进行编译的,所以当我们创建一个String类型的泛型实例对象ArrayList<String> arrayList = new ArrayList<String>();在所创建的arrayList数组中,是不能使用add方法添加Integer类型的数据的。
public static void main(String[] args){ ArrayList<String> arrsyList = new ArrayList<String>(); arrayList.add("123"); arrayList.add(123);//编译错误,关于泛型变量的使用,是会在编译之前检查的 }
泛型中出现代码兼容以及引用传值的问题
import java.util.ArrayList; public class TestDemo{ public static void main(String[] args){ ArrayList<String> arrayList1 = new ArrayList();//第一种的使用泛型参数效果一样 ArrayList arrayList2 = new ArrayLsit<String>();//第二种情况完全没效果 //以上两种写法都会出现编译时警告 arrayList1.add("1");//编译通过 arrayList2.add(1);//编译错误 String str = arrayList1.get(0);//返回类型就是String arrayList2.add("1");//编译通过 arrayList2.add(1);//编译通过 Object object = arrayList2.get(0);返回类型就是Object } }出现上述错误的原因:
类型检查实在编译时完成的,new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。而真正涉及类型检查的是他的引用,所以arrayList1能完成泛型类型的检查,而引用arrayList2没有使用泛型,所以不行,因此,可以明白类型检查是针对引用的,用这个引用调用泛型方法,会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
默认情况下,没有约束条件的泛型类型参数(Sample<T>)T被称为未绑定类型参数,当创建未绑定类型的参数的泛型类实例时,可以给参数类型指定任意类型。想要让泛型参数支持特定类型时,可以使用关键字extends或者super关键字对泛型参数进行约束。
extends主要限定泛型参数的上界:
<T extends 基类>:T只能是基类或者基类的派生类
<T extends 基接口>:T只能是实现基接口的派生类
import java.util.ArrayList; import java.util.Iterator; pulbic class TestDemo{ public static void show(ArrayList<Object> arrayLsit>){ Iterator<Object> iterator = arrayList.iterator(); while(iterator.hasnext()){ System.out.println(iterator.next()); } } public static main(Stirng[] args){ Object o1 = new Integer(10); Object o2 = new String("图论"); ArrayList<Integer> arrayList = new ArrayList<Integer>(); show(arrayList);//Error } }
为了解决上述错误,引入未知类型<?>(通配符)的概念
<? extends 基类>: 其中?未知类型限定为指定类型或者指定类的派生类。指定上界。
<? super 派生类>:其中?未知类型限定为指定类型或者指定类的基类。指定下界。
import java.util.ArrayList; impprt java.util.Iterator; public class TestDemo{ public static void show(ArrayList<?> arrayList){ Iterator<?> iterator = arrayList.iterator<>(); while(iterator.hasNext()){ System.out.println(iterator.next()); } } public static void main(String[] args){ Object o1 = new Integer(10);//向上造型 Object o2 = new String("图论");//向上造型 arrayList.add(1); arrayList.add(2); show(arrayList); } }
未知类型参数<?>一般用于泛型方法的参数,可以声明未知类型集合的变量。但不能直接创建未知类型<?>集合的对象实例,也不能像未知类型<?>集合的变量中添加元素(null除外,因为null匹配任何对象实例)。
import java.util.ArrayList; import java.util.Iterator; public class TestDemo{ public static void show(ArrayList<? extends Number> arrayList){ //定义泛型参数是Number或者Number的子类即可 Iterator<?> itearator = arrayList.iterator(); while(iterator.hasNext()){ System.out.prinltn(iterator.next()); } } public static void main(String[] args){ ArrayList<Integer> arrayList = new ArrayList<Integer>();//Integer是Number的子类 show(arrayList); }