Java泛型浅析

102 阅读3分钟

为什么要用泛型:

1、同一套代码能够兼容不同的类型

2、能够在编译前确定一个类能接收哪种类型,不至于运行时再确认

3、规避杂乱地使用Object变量,产生的强转异常

ArrayList的add方法即是一个典型案例:

public boolean add(E var1) {
        this.ensureCapacityInternal(this.size + 1);
        this.elementData[this.size++] = var1;
        return true;
    }

这种情况下,只需要创建ArrayList时候声明泛型类型,该方法就能被指定参数类型;否则ArrayList需要如下多个重载方法:

public boolean add(String var1);
public boolean add(Boolean var1);
public boolean add(Integer var1);
...

基本使用方法

ArrayList<String> arrayList = new ArrayList<>();

泛型声明如果不明确使用,则默认为Object类型 JDK7开始后面的类型声明可以省略.泛型声明类型只能被引用数据类型填充

1、 在类上声明泛型

类中使用到E类型的地方会被限定类型

public class NormalGeneric<T>{
    private T data;

    public NormalGeneric(T data) {
        this.data = data;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        NormalGeneric<String> normalGeneric = new NormalGeneric<>("Heria");
        System.out.println(normalGeneric.getData());
    }
}

2、 在方法中声明泛型

可以和类中声明的泛型不一致,方法调用时候,传入什么类型就当做什么类型处理

public class EObj<T> {
        public <E> void show(E e){
            System.out.println(e);
        }
    }
public static void main(String[] args) {
        TestClass testClass = new TestClass();
        TestClass.EObj eObj = testClass.new EObj<String>();
        eObj.show("pdddd");
        eObj.show(14);
        eObj.show(true);
//这里还涉及到自动装箱和拆箱的机制
    }
public class GenericFunction {

    private <T> void showData(T t) {
        System.out.println(t.toString());
    }

    private <K> K getOtherData(K... ks) {//真正意义上的泛型方法
        return (ks[ks.length / 3]);
    }

    private <T, K> K getOtherData2(T... ts) {
        return null;
    }

    public static void main(String[] args) {
        GenericFunction genericFunction = new GenericFunction();
        System.out.println(genericFunction.<String>getOtherData("2", "3", "4"));
        System.out.println(genericFunction.getOtherData(new TheoryClass<>(), new TheoryClass<>(), new TheoryClass<>()));
    }
}

3、在接口中声明泛型

public interface FanAdapter<E> {
    void show(E e);
}
//实现方案一,将类型提前声明
private class FanAdapterImp implements FanAdapter<String>{
        @Override
        public void show(String s) {

        }
    }
//实现方案二,将泛型带到子类中
//集合框架常用该设计
private class FanAdapterImp<E> implements FanAdapter<E>{
        @Override
        public void show(E e) {
            
        }
    }

需要注意的点:

  • 如果泛型类型在类上声明,不能在未指定具体类型时候就在构造方法中生成泛型类型对象
public class Restrict<T> {
    private T mData;

    public Restrict() {
        //T t = new T(); // 不可用
    }
}
  • 泛型参数类型之间的关系不能决定泛型类之间的关系
Restrict<String> restrict1 = new Restrict<>();
Restrict<Double> restrict2 = new Restrict<>();
System.out.println(restrict1.getClass() == restrict2.getClass());
System.out.println(restrict1.getClass());
System.out.println(restrict2.getClass());
        
输出/*true
class com.example.fanxing.Restrict
class com.example.fanxing.Restrict*/
//泛型类型获取类型时候获取的是原生类型
TongPei<Fruit> tongPei = new TongPei<>();
TongPei<Orange> orangeTongPei = new TongPei<>();
//Fruit和Orange是父子类关系,但是tongPei = orangeTongPei会报错
  • 泛型不能被捕获,但是能主动抛出
private <T extends  Throwable> void function (T t) {
        try{
                
        }catch (T t){//不能被捕获
            
        }
    }

    private <T extends  Throwable> void function2 (T t) throws T {
        try{

        }catch (Throwable e){//可以被主动抛出
            throw t;
        }
    }
  • 静态声明方法中不能直接使用泛型类上声明的泛型类型,但如果把静态方法声明为一个泛型方法就没问题
public class Restrict<T> {
    private T mData;

    public static T function() { // 不可用,泛型类型在实例化对象时候才确定,静态方法类型确定在实例化之前
        return null;
    }

    public static <E> void function2() {//ok

    }
}

4、泛型中的通配符

既然泛型参数类型不能决定泛型类的继承关系,但实际开发中我们需要他们能够具备这样但关系,比如我们希望一个声明了参数类型为TongPei<Fruit>的方法,传入TongPei<Orange>也行得通,因为Orange是Fruit的子类,那么就需要通配符帮忙

  • 通配符<?>,匹配任意类型
//指定的泛型中前后类型必须一致
        Collection<String> collection = new ArrayList<String>();//correct
        Collection<String> collection1 = new ArrayList<Object>();//wrong
        ArrayList<String> arrayList = new ArrayList<Object>();//wrong
        //?通配符下的泛型类型可以引用任何类型
        Collection<?> collection3 = new ArrayList<Boolean>();
        Collection<?> collection4 = new ArrayList<Integer>();
        ArrayList<?> collection2 = new ArrayList<String>();
  • 向下限定 可接受E和E的子类
        ArrayList<? extends Animal> arrayList = new ArrayList<Animal>();
        ArrayList<? extends Animal> arrayList1 = new ArrayList<Cat>();
        ArrayList<? extends Animal> arrayList2 = new ArrayList<Dog>();
        ArrayList<? extends Animal> arrayList3 = new ArrayList<String>();//wrong
  • <* super E> 向上限定 可接受E和E的父类
        ArrayList<? super Animal> arrayList1 = new ArrayList<Object>();
        ArrayList<? super Animal> arrayList2 = new ArrayList<Animal>();
        ArrayList<? super Animal> arrayList3 = new ArrayList<Cat>();//wrong

需要注意的点:

/ 限定泛型的继承类型 规则同Java继承规范,即只能继承一个类同时可以继承多个接口
public class Extends<T extends ArrayList&Comparable&Runnable> {
}

class Exrtends2 {
    private <E extends ArrayList&Comparable&Runnable> void extrendsFun() {

    }
}

5、泛型实现原理

//JDK实现泛型的原理是类型擦除
//下面的例子中会把T统一修改成Object
public class TheoryClass<T> {
    private T mData;

    public T getmData() {
        return mData;
    }

    public void setmData(T mData) {
        this.mData = mData;
    }

    public static void main(String[] args) {
        TheoryClass<String> theoryClass = new TheoryClass<>();
        TheoryClass<Double> theoryClass1 = new TheoryClass<>();

    }
}

//这种情况下泛型会被统一擦除成Arraylist,如果都是接口会被统一擦除成实现的第一个接口
//如果在泛型的第某个方法中用到了Comparable接口,那么泛型参数前会被添加一个强制转型声明
class TheoryClass2<T extends ArrayList&Comparable> {

        }