Java 泛型

泛型的本质

Java泛型是JDK1.5引入的新特性,泛型提供了编译时类型安全检查。该机制允许程序在编译时检测到非法的类型。

泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个参数的类型就可以在使用时决定了,这种参数类型可以用在类、接口、方法上

为什么需要泛型?

在Java推出泛型之前,可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用集合的过程中,程序员需要明确的知道存储每个元素的数据类型,不然很容易发生ClassCastException,泛型的好处是在编译期间进行类型检查,并且所有强制转化都是自动和隐式的。

  • 保证了类型的安全:编译器在编译期间就会做类型检查,告知是否插入错误的类型对象,使得程序更安全
  • 消除强制转换:没有设置泛型的类型默认为 Object,当我们获取元素时需要进行一个强转,当使用泛型时则不需要强转操作。
  • 避免不必要的装箱拆箱操作,提高程序的性能:非泛型时,将简单类型作为 Object 类型传递时会引起装箱和拆箱操作,这两个过程都会有极大的消耗
  • 提高代码的重用性

使用泛型

泛型有三种使用方式:泛型类、泛型接口和泛型方法

泛型类

  • 如果没有指定具体的数据类型,则默认是 Object。
  • 泛型的参数类型只能是类类型,不能是基本类型。
  • 泛型类在逻辑上可以看作是多个不同的类型,但实际上都是相同的类型
public class Generic<T> {
}

泛型类型必须是引用类型,即非基本数据类型,泛型类型可以有多个,每个参数使用逗号分隔

public class Generic<T> {

    private T key;

    public Generic() {
    }

    public Generic(T key) {
        this.key = key;
    }
    // get set toString...
}
Generic<Integer> generic = new Generic<>(123);
Generic<String> generic1 = new Generic<>("admin");
System.out.println(generic.getClass());
System.out.println(generic1.getClass());
System.out.println(generic.getClass() == generic1.getClass());

// conslog result
class Generic
class Generic
true

泛型类派生子类

创建子类先要创建父类对象,如果子类也是泛型类,那么子类的泛型标识就要和父类的泛型一致

//父类
public class Parent<E> {
    private E value;
    public E getValue() {
        return value;
    }
    public void setValue(E value) {
        this.value = value;
    }
}

/**
 * 泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类一致。
 * @param <T>
 */
public class ChildFirst<T> extends Parent<T> {
    @Override
    public T getValue() {
        return super.getValue();
    }
}

若子类不是泛型类,那么父类就要明确泛型的数据类型

/**
 * 泛型类派生子类,如果子类不是泛型类,那么父类要明确数据类型
 */
public class ChildSecond extends Parent<Integer> {
    @Override
    public Integer getValue() {
        return super.getValue();
    }
    @Override
    public void setValue(Integer value) {
        super.setValue(value);
    }
}

泛型接口

public interface GenericInterface<T> {
    void show(T t);
    
    T getValue(T t);
}

实现类也是泛型类,实现类和接口的泛型类型要一致

/**
 * 泛型接口
 * @param <T>
 */
public interface Generator<T> {
    T getKey();
}
/**
 * 泛型接口的实现类,是一个泛型类,
 * 那么要保证实现接口的泛型类泛型标识包含泛型接口的泛型标识
 * @param <T>
 * @param <E>
 */
public class Pair<T,E> implements Generator<T> {

    private T key;
    private E value;

    public Pair(T key, E value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public T getKey() {
        return key;
    }

    public E getValue() {
        return value;
    }
}

实现类不是泛型类,接口要明确数据类型

/**
 * 实现泛型接口的类,不是泛型类,需要明确实现泛型接口的数据类型。
 */
public class Apple implements Generator<String> {
    @Override
    public String getKey() {
        return "hello generic";
    }
}

泛型方法

泛型方法是在调用方法的时候指明泛型的具体类型。

// 只有 public 与 返回值之间有 <T> 才表示该方法为泛型方法,泛型类中使用的泛型成员方法并不是泛型方法
public <T> void showGeneric(T t){
}
  • 泛型方法能使方法独立于类而产生变化,该方法的泛型与类上面的泛型没有任何关系
  • 如果static方法要使用泛型能力,就必须使其成为泛型方法
     /**
     * 静态的泛型方法,采用多个泛型类型
     * @param t
     * @param e
     * @param k
     * @param <T>
     * @param <E>
     * @param <K>
     */
    public static <T,E,K> void printType(T t, E e, K k) {
        System.out.println(t + "\t" + t.getClass().getSimpleName());
        System.out.println(e + "\t" + e.getClass().getSimpleName());
        System.out.println(k + "\t" + k.getClass().getSimpleName());
    }

类型通配符

类型通配符一般是使用"?"代替具体的类型实参。所以,类型通配符是类型实参,而不是类型形参

类型通配符的上限

要求该泛型的类型,只能是实参类型,或实参类型的子类类型。

要声明使用该类通配符, 采用的形式, 这里的E就是该泛型的上边界。

public class GrandFather {
}

public class Father extends GrandFather{
}

public class Son extends Father{
}

类型通配符下限

要求泛型的类型只能是实参类型或者实参类型的父类型

 

 类型擦除

        泛型是1.5引入的,在这之前是没有泛型的,但是泛型能够与之前的代码兼容,是因为泛型信息只处在编译阶段,在进入JVM之前,与泛型相关的信息都会被擦除。

无限制泛型擦除

public class Generic<T> {
    private T key;
    // 省略 get set 等方法,下面同样
}
Class<Generic> clazz = Generic.class;
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
    System.out.println(field.getName() + " : " + field.getType().getSimpleName());
});

// conslog result 
key : Object

有限制类型擦除

 

public class Generic<T extends Number> {
    private T key;
}
Class<Generic> clazz = Generic.class;
Field[] fields = clazz.getDeclaredFields();
Arrays.stream(fields).forEach(field -> {
    System.out.println(field.getName() + " : " + field.getType().getSimpleName());
});

// conslog result 
key : Number

泛型方法

 

桥接方法

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值