泛型

1.定义

泛型最精准的定义:==参数化类型==。具体点说就是处理的数据类型不是固定的,而是可以作为参数传入。定义泛型类、泛型接口、泛型方法,这样,同一套代码,可以用于多种数据类型。(看到过有人面试问这个,感觉这个解释泛型最言简意赅)

2.实例

2.1 标识符号的字母规范.

虽然标识符号可以随意取,但为了提高可读性,一般遵循以下规则.

E — Element,常用在java Collection里,如:List,Iterator,Set K,V — Key,Value,代表Map的键值对 N — Number,数字 T — Type,类型,如String,Integer等等

2.2 泛型类
public class Generic<T>{ 
    //key这个成员变量的类型为T,T的类型由外部指定  
    private T key;

    public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
        this.key = key;
    }

    public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
        return key;
    }
}
复制代码

泛型的字母可以随意取,但是自己可以规范点,Key,Value 可以用(K, V)Element 可以用E。

2.3 泛型接口
public interface Generator<T> {
    public T next();
}
复制代码
2.4 泛型方法
public < E > void printArray( E[] inputArray )
   {
      // 输出数组元素            
         for ( E element : inputArray ){        
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
复制代码

3.泛型通配符

3.1 上界限定通配符 <? extends E>

接收E类型以及其子类 被该泛型通配符限制的数组==只能取不能存==。因为该泛型为其子类,不能确定究竟几级子类,因此不能加数据。只能取,取出的数据能保证一定能转成E类型。

3.2 下界限定通配符 <?super E>

接收E类型以及其父类型。因此==只能存不能取==。如果要取的话只能是保证为Object类型的数据,这样没意义。而存的话能存E以及E的子类。

3.3 无限通配符 <?>

无界通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无界通配符修饰的容器持有的是某种具体的类型。举个例子,在List类型的引用中,不能向其中添加Object, 而List类型的引用就可以添加Object类型的变量。

4.擦除带来的问题

在泛型代码内部,无法获得任何有关泛型参数类型的信息。
复制代码

1 泛型类型不能显式地运用在运行时类型的操作当中,例如:转型、instanceof 和 new。因为在运行时,所有参数的类型信息都丢失了。

public class Erased<T> {
    private final int SIZE = 100;
    public static void f(Object arg) {
        //编译不通过
        if (arg instanceof T) {
        }
        //编译不通过
        T var = new T();
        //编译不通过
        T[] array = new T[SIZE];
        //编译不通过
        T[] array = (T) new Object[SIZE];
    }
}
复制代码

2 泛型声明的对象在类内部不能获取某个类的特定方法。代码如下

class HasF {
    public void f() {
        System.out.println("HasF.f()");
    }
}
public class Manipulator<T> {
    private T obj;

    public Manipulator(T obj) {
        this.obj = obj;
    }

    public void manipulate() {
        obj.f(); //无法编译 找不到符号 f()
    }

    public static void main(String[] args) {
        HasF hasF  = new HasF();
        Manipulator<HasF> manipulator = new Manipulator<>(hasF);
        manipulator.manipulate();

    }
复制代码

在这个例子中由于泛型在代码内部是无法知道其确切类型,因而也就不能知道obj 是否有f()方法。上面这个问题解决方法可以给T范型加入上边界即 T extends HasF,这样就解决这个问题了。

5.擦除补偿

5.1 类型判断问题
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
    Class<T> kind;
    public ClassTypeCapture(Class<T> kind) {
        this.kind = kind;
    }
    public boolean f(Object arg) {
        return kind.isInstance(arg);
    }
    public static void main(String[] args) {
        ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.print(ctt2.f(new House()));
    }
}

复制代码

泛型参数的类型无法用instanceof关键字来做判断。所以我们使用类类型来构造一个类型判断器,判断一个实例是否为特定的类型。

5.2 创建类型实例

Erased.java中不能new T()的原因有两个,一是因为擦除,不能确定类型;而是无法确定T是否包含无参构造函数。

为了避免这两个问题,我们使用显式的工厂模式:

interface IFactory<T> {
    T create();
}

class Foo2<T> {
    private T x;

    public <F extends IFactory<T>> Foo2(F factory) {
        x = factory.create();
    }
}

class IntegerFactory implements IFactory<Integer> {
    @Override
    public Integer create() {
        return new Integer(0);
    }
}

class Widget {
    public static class Factory implements IFactory<Widget> {
        @Override
        public Widget create() {
            return new Widget();
        }
    }
}

public class FactoryConstraint {
    public static void main(String[] args) {
        new Foo2<Integer>(new IntegerFactory());
        new Foo2<Widget>(new Widget.Factory());
    }
}

复制代码

通过特定的工厂类实现特定的类型能够解决实例化类型参数的需求。

5.3 创建泛型数组

一般不建议创建泛型数组。尽量使用ArrayList来代替泛型数组。但是在这里还是给出一种创建泛型数组的方法。

public class GenericArrayWithTypeToken<T> {
    private T[] array;

    @SuppressWarnings("unchecked")
    public GenericArrayWithTypeToken(Class<T> type, int sz) {
        array = (T[]) Array.newInstance(type, sz);
    }

    public void put(int index, T item) {
        array[index] = item;
    }

    public T[] rep() {
        return array;
    }

    public static void main(String[] args) {
        GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(Integer.class, 10);
        Integer[] ia = gai.rep();
    }
}

复制代码

这里我们使用的还是传参数类型,利用类型的newInstance方法创建实例的方式。

6.其他注意点

  1. 任何基本类型都不能作为类型参数
  2. 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass还是new MyClass创建的对象,都是共享一个静态变量。(擦除导致) 3.泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException(String)和MyException(Integer)的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。

参考文章:

  1. www.jianshu.com/p/4caf2567f…
  2. www.jianshu.com/p/7a1b09e62…
  3. juejin.im/post/584d36…
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值