java核心技术笔记——泛型

1、泛型类:

public class Pair<T>{
    private T first;
    private T sceond;
    puiblic Pair(){first=null;second=null;}
    public T getFirst(){return first;}
    ...
    public void setFirst(T newValue){first=newValue}
    ...
}

当然也可以设计不同类型的泛型,public class Pair<T,U>{...},上述的都是定义泛型类,用具体的类型带入进去就可以实例化泛型。比如Pair<String>,里面的类型都将替换为具体String.

2、泛型方法:

class ArrayAlg{
    public static <T> T getMiddle(T...a){
        return a;
    }
}

泛型方法可以定义在泛型类中,也可以定义在普通的类中,注意类型变量<T>放在修饰符后面,返回类型的前面。有时候类型String的话可以省略,编译器能够推断出。

3、类型变量的限定:

有时候可能对泛型变量做些条件限制,比如:

public static <T> T min(T[] a){

if(a[0].compareTo(a[1])).....

}

这时候就需要对T限制条件实现了Comparable接口的类型。即public static <T extends Comparable> T min(T[] a)。但是实际上comparable接口本身就是一个泛型。如果想实现多个限定,用&,即public static <T extends Comparable & Serializable> T min(T[] a).注意这里用的是extends 而不是implements,用&而不是逗号。

4、泛型和虚拟机。

扫描二维码关注公众号,回复: 3361885 查看本文章

a:类型擦除

定义一个泛型的类,会相应的生成一个原始类型,比如上面的泛型类的原始类型是

public class Pair{
    private Object first;
    private Object  sceond;
    puiblic Pair(){first=null;second=null;}
    public Object  getFirst(){return first;}
    ...
    public void setFirst(Object  newValue){first=newValue}
    ...
}

因为T没有限定,所以就用Object替换为原始类型,如果有了限定,比如public class Pair<T extends Comparable & Serializable> {},则用Comparable替换为原始类型,如果切换限定,即T extends Serializable &Comparable的话,原始类型就会用Serializable替换,但是这样的话,编译器在必要时要向Comparable接口插入强制类型转换,所以为了提高效率,一般将标签接口(没有方法的接口)放在后面。

泛型方法也会擦除:比如public static <T extends Comparable> T min(T[] a)擦除后为:public static  Comparable  min(Comparable  [] a)。

如果类继承在擦除的时候继承自父类擦除的方法和自己本类擦除后的方法会冲突,

class DateInterval extends Pair<LocalDate>{
    public void setSecond(LocalDate second){
        ....
    }
}
//擦除后
class DateInterval extends Pair{
    public void setSecond(LocalDate second){
        ....
    }
} 
//继承的
public void setSecond(Object second)
//最终选用桥方法
public void ssetSecond(Object second){setSecond((Date) second)}

b:翻译泛型表达式,

虚拟机类型擦除后,返回的可能为原始类型,那么程序调用泛型方法时,编译器就会自动加入强制类型转换,转换为正确类型。比如调用Pair<Employee>的getFirst方法会返回Object类型,这时候编译器会强制类型转换为Employee类型。

对于翻译泛型方法的时候如果遇到了冲突,编译器会调用桥方法。不过桥方法可能会变得很奇怪,比如说DateInterval也覆盖了getSecond方法,那么在DateInterval中擦除后就会有两个getsecond方法,LocalDate getsecond()和Object getsecond(),虽然再类中不可以出现这种情况,不过虚拟机却是可以正确处理。

总之:

虚拟机中没有泛型,只有普通的类和方法;所有的类型参数都用他们的限定类型替换;桥方法被合成保持多态;为了保持类型安全性,必要时插入强制类型转换。

5、使用泛型的约束和局限:

a、不能用基本类型实例化类型参数,因为类型擦除后,object不能存储基本类型的值。

b、运行时类型查询只适用于原始类型,比如a instanceof Pair<String> //error、a instanceof Pair<T> //error或者强制类型转换Pair<String> p=(Pair<String>)a;//warning 同理getClass()返回的也是原始类型,所以Pair<String> a;Pair<Person> b,a.getClass()==b.getClass();

c、不能创建参数化类型的数组,因为擦除后比如说Pair<String> table =new Pair<String>[10];//error 为Pair[]可以转换为Object[],

Object a=table,a[0]="hello"//error(因为不是pair类型,无法通过数组存储检查);a[0]=new Pair<Employee>();是可以通过数组存储检查,但是最后还是会导致类型错误(即能存储进去一个对象,但是调用pair类的方法的时候就会报类型转换错误)。处于这个问题,还是不要件数组,不过可以生明数组,然后初始化原始类型。

d、varargs警告,这是针对参数个数可变的方法,其实参数可变是通过虚拟机的处理实现的,比如说其实是建立了一个参数数组,但是前面说过不能建立泛型数组,不过这种情况特殊,编译器会给出警告,这个警告可以通过@Suppress Warnings("unchecked")或者@SafeVarargs来消除警告。

e、不能实例化类型变量,不能使用象new T(..)或者new T[...]或者T.class。是因为类型擦除会将T替换成Object,编写程序的本意肯定不是创建Object对象。解决办法就是调用者提供一个构造器表达式例如:

Pair<String> p=Pair.makePair(String::new);

public static <T> Pair<T> makePair(Supplier<T> str){
//Supplier<T>是一个函数式接口,表示一个无参数而且返回类型为T的函数
    return new Pair<>(str.get(),str.get());
}

比较传统的方式是用反射,t.class.newInstance();但是因为不能出现t.class,所以就要向下面这么设计API

public static <T> Pair<T> makePair(Class<T> str){
    try{return new Pair<>(str.newInstance(),str.newInstance());}
    catch(Exception e){return null;}
}
//调用
Pair<String> p=Pair.makePair(String.class);

f、不能构造泛型数组,因为擦出后构造的就不是想要的数组类型,如果当数组作为一个类的私有类型实力域的话就可以直接声明为Object[]然后在获取的时候加入强制类型转换。但是如果方法的返回类型为T[],最好是提供一个数组构造器,比如String [] ss=ArrayAlg.minmiax(String::new,"Tom","Dick","Harry");其中构造器表达式String::new 指示一个函数,给定所需长度会构造一个指定长度的String数组。也可以利用反射

public static <T extends Comparable> T[] minmax(T...a){
    T[] mm=(T[]) Array.newInstance(a.getClass.getComponentType(),2);
    ...
}

g、泛型类的静态域或方法中的类型变量无效。即不能再静态域或者方法中引用类型变量,比如定义private static T a;//error 或者在静态方法中调用a都是错误的。

h、不能抛出或者捕获泛型类对象,实际上甚至泛型类扩展Throwable都是不合法。既不能public class Problem<T> extends Exception{}//error,catch中也不能引用类型变量当做参数,但是可以在catch字句中使用类型变量。即

catch(T e){..}//error
public static <T extends Throwable> void doWork(T t)Throws T{//ok
    ....
    catch(Throwable a){t.initCause(a);throw t;}
}

6、泛型与继承

无论T和S什么关系,Pair<T>和Pair<S>没关系,但是Pair<T>是原始类型Pair的一个子类型,即Pair<manager> a=new Pair<>(cefo,cfo);Pair b=a;//ok 泛型类可以拓展或者实现其他泛型类,象普通类一样。

7、通配符  ?

可以这样定义Pair<? extends Employee>  那么就可以实现Pair<Employee>或者Pair<Manager>,通配符定义的变量的方法也相当于通配符实现,比如Pair<? extends Employee>  a= new Pair(ceo,cfo);a 里面的方法:?extends Employee getFirst();void setFirst(? extends Employee);

? super Manager  代表限制为所有Manager的超类型,

?无限制通配符,Pair<?> 里会有 ?getFirst()和void setFirst(?) ,其中get只能将返回值赋给一个Object,set方法不能被调用。使用这个类型主要是为了普遍化一些简单的操作。比如public static boolean hasNulls(Pair<?> p){return p.getFirst()==null||p.getSecond()==null;}

8、反射与泛型

反射中也可以应用到泛型,比如Class<T> 里面有T newInstance(); T cast(Object obj);等等泛型方法。详见java.lang.Class<T>,有时候也可以使用Class<T>参数类型进行匹配,比如public static <T> Pair<T> makePair(Class<T> c) throws InstantiationException{return new Pair<>(c.newInstance(),c.newInstance())}.如果调用nakePair(Employee.class)将返回一个Pair<Employee>,Employee.class是一个Class<Employee>的对象。

猜你喜欢

转载自blog.csdn.net/cm1362946343/article/details/82817031