Java泛型

    技术2022-07-16  64

    1.泛型

           泛型的本质是参数化类型,参数化类型就是把操作的数据类型指定为一个参数,将类型由原来的的具体的类型参数化,将类型定义为参数类型(类型形参),在使用/调用时,传入具体的类型(类型实参),这种可以用在类、接口、方法的创建中,分别称为泛型类、泛型接口、泛型方法。.

            我们把一个对象放入集合中,集合不会记住此对象的类型,当我们从集合中取出此对象时,把对象的编译类型变成了object类型,但运行时类型任然为其本身类型。因此,取出对象时,人为的强制转换类型为具体的目标类型, 很容易出现java.lang.ClassCastException异常。

    2.泛型类

    2.1泛型类的定义

           泛型类的声明需要在类名后面添加参数声明部分,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。泛型参数也被称为类型变量。用于指定一个泛型名称的标识符。这些类被称为参数化的类。

    class 类名称<泛型名称:可以随便写任意标识号,标识指定的是泛型的类型>{ private 泛型标识/*(成员变量类型)*/var ... } }

    2.2泛型类示例演示

    public class Generic<T>{此处T可以随便写为任意标识,常见的如T、E、KV等形式的参数 private T key;//在泛型类实例化对象时必须指定泛型参数T的具体类型,没有指定默认为Object类型 public Generic(T key){//泛型构造方法形参key的类型也为T,T的类型由实例化对象过程中指定 this.key=key; } public T getKey(){ return key; } } public class TestDemo{atic public static void main(String[] args){ 泛型的类型参数只能是类类型(包括自定义类),不能是基本数据类型 传入的实参类型必须与泛型的类型参数相同。 Generic<Integer> genericInteger=new Generic<>(10); Integer value=genericInteger.getKey(); Generienc<String> genericString=new Generic<>("key_value"); String result=genericString.getKey(); } }

    定义的泛型类,不一定传入泛型类型实参,在使用泛型的时候传入泛型实参,则会根据传入的泛型实参做相应的限制。此时泛型才会起到相应的限制作用。如果不传入泛型实参,在泛型类中的使用泛型方法或成员变量定义的类型可以使任意类型。

    2.3泛型类注意点

    (1)对类型进行自动检查

    (2)自动对类型进行类型转换 

    (3)泛型类型参数只能是引用数据类型,不能是基本数据类型

    (4)不能对确切的泛型类型使用Instanceof操作。(在运行时判断对象是否是特定类的一个实例)

    (5)不能直接new泛型数组

    (6)不能产生泛型类型对象

    (7)在static中,不能使用泛型类型参数,因为static不依赖对象存在,所以无法推出static泛型类型参数。

    3.泛型接口

         泛型接口和泛型类的定义和使用基本相同。

    3.1泛型接口定义

    public interface Generator<T>{ T next(); }

    3.2泛型接口示例演示

    class FruitGenerator<E> implements Generator<E>{ public E next(){ return null; } }

    4泛型方法

       泛型方法所在的类可以是泛型类也可以不是泛型类,方法中,通过指定类型参数,可以声明泛型方法,提高其复用性。泛型方法的参数化列表放在方法类符的后面返回值之前。

    4.1泛型方法定义

    public class TestDemo{ /* *泛型方法的基本介绍 *tclass 传入的泛型实参 *return T返回值为String类型 *说明: *(1)public与返回值中间String非常重要,可以理解为声明此方法为泛型方法 *(2)只有声明了<T>的方法才是泛型方法,泛型类中使用的泛型的成员方法并不是泛型方法 *(3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T *(4)与泛型类的定义一样,此处可以写为任意标示,常见的如K、E、T、V等形式标识泛型 */ public static<T> String genericMethod(class<T> tclass){ return tclass.getName(); } public static void main(String[] args){ String result=genericMethod(String.class); Sysytem.out.println(result); } }

    4.2泛型方法注意点

    如果泛型方法的泛型参数和类型的泛型参数相同,编译期生成警告。

    因为泛型方法的泛型参数隐藏了外部类型的泛型参数。

    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)); } }

    5.类型擦除机制

    5.1类型参数定义

         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方法,却可以存储字符串,说明泛型实例在编译之后被擦除了,只保留了原始类型。

    5.2类型擦除示例分析

            什么是原始类型,原始类型就是擦除泛型信息最后体现在字节码中的类型,是变量的真正类型。无论何时定义一个泛型信息,都会被自动的擦除到原始类型,使用其限定类型,无限定类型的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只能是实现基接口的派生类

    6.通配符

         

    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 } }

    为了解决上述错误,引入未知类型<?>(通配符)的概念

    6.1通配符边界定义

       <? extends 基类>: 其中?未知类型限定为指定类型或者指定类的派生类。指定上界。

       <? super 派生类>:其中?未知类型限定为指定类型或者指定类的基类。指定下界。

    6.2通配符示例演示

         

    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); }

     

    Processed: 0.010, SQL: 9