面试系列之Java泛型

Java泛型

其本质是参数化类型,就是所操作的数据类型被指定为一个参数(type parameter),这种参数可以用在类、接口、和方法的创建中,分别称为泛型类、泛型接口和泛型方法。

泛型作用

  • 泛化:可以用T代表任意类型
  • 类型安全:泛型的一个主要目标就是提高Java程序的类型安全,使用泛型可以使编译器知道变量的类型限制。如果不用泛型,则必须使用强制类型转换。
  • 消除强制类型转换:可以消除源代码中许多强制类型转换,使代码更加易读

使用泛型注意事项

1、使用泛型时,泛型类型必须是引用数据类型,不能为基本数据类型。Java中的普通方法、构造方法、静态方法中都可以使用泛型,方法使用泛型之前必须先对泛型进行声明,可以使用任意字母,一般都要大写。

2、运行时类型检查,不同类型的泛型类是等价的

3、泛型类不可以继承Exception类,即泛型类不可以作为异常被抛出

4、不可以定义泛型数组

5、不可以用泛型构造对象,first = new T()错误

6、在static方法中不可以使用泛型,泛型变量也不可以用static关键字来修饰

常见的泛型的类型表示

常见的参数通常有 :

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(表示Java 类,包括基本的类和我们自定义的类)
  • K - Key(表示键,比如Map中的key)
  • V - Value(表示值)
  • ? - (表示不确定的java类型)

泛型最常用的场景是集合和接口

泛型集合

以ArrayList为例:

如果不定义泛型,泛型类型实际上就是Object类型,使用时需要进行强制转换

 List list = new ArrayList();
        list.add("name");
        list.add(20);
        list.add(2.5);

        String obj = (String)list.get(0);
        Integer age = (Integer)list.get(0);

如果定义了泛型<String>,我们就可以直接使用,无需进行转换

 List<String> list = new ArrayList();
        list.add("hello");
        list.add("world");
        String hello = list.get(0);
        String world = list.get(0);

泛型接口

可以在接口中定义泛型类型,实现此接口的类必须实现正确的泛型类型。

public interface Comparable<T> {
    public int compareTo(T o);
}

泛型类

编写一个泛型类:先按正常的类进行编写,然后全部用T进行替换;

public class Speak<T> {

    private T type;

    private T level;

    public Speak(T type, T level) {
        this.type = type;
        this.level = level;
    }

    public T getType() {
        return type;
    }
    
    public T getLevel() {
        return level;
    }

}

需要注意的是,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T

会编译错误,如图:

多个泛型类型

public class Product<T,K> {

    private  T name;
    private  K num;

    public Product(T name, K num) {
        this.name = name;
        this.num = num;
    }

    public T getName() {
        return name;
    }
    public K getNum() {
        return num;
    }
}

java语言的泛型实现方式是擦拭法,意思是指虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

例如我们编写的Speak<T>,编译器看到的代码:

public class Speak<T> {

    private T type;
    private T level;
    public Speak(T type, T level) {
        this.type = type;
        this.level = level;
    }
    public T getType() {
        return type;
    }

    public T getLevel() {
        return level;
    }

}

虚拟机看到的代码:

public class Speak {

    private Object type;
    private Object level;
    public Speak(Object type, Object level) {
        this.type = type;
        this.level = level;
    }
    public Object getType() {
        return type;
    }

    public Object getLevel() {
        return level;
    }

}

Java使用擦拭方实现泛型,做的工作:

  • 编译器把类型<T>视为Object
  • 编译器根据<T>实现安全的强制转型

Java的泛型是编译器在编译时实行的,编译器内部永远把所有类型的T当作Object处理,但是在需要转型的时候,编译器会根据T的类型自动为我们实行安全的强制转换。

了解了Java泛型的原理,我们就知道了Java泛型的局限:

1、<T>不能是基本类型,如int,因为实际类型是Object,Object无法持有基本类型

List<int> list  =new ArrayList<>();  //编译错误

2、无法取得带泛型的Class

Speak<String> str = new Speak<String>("hello","world");
        Speak<Integer> ss = new Speak<String>(123,456);

        Class p  = str.getClass();
        Class t  = ss.getClass();

        System.out.println(p == t); //true
        System.out.println(p == Speak.class); //true

因为T是Object类型,我们对Speak<String>和Speak<Integer>获取Class时,获取到的是同一个Class,就是Speak的Class。

所有的泛型实例,getClass返回同一个Class实例。

3、无法判断带泛型的类型

Speak<Integer> p = new Speak<>(123, 456);
// Compile error:
if (p instanceof Speak<String>) {
}

4、不能实例化T类型

new T() 不允许

泛型继承

一个类可以继承自一个泛型类

public class Chinese extends Speak<T>{
    
}

在父类是泛型类型的情况下,编译器就必须把类型T保存到子类的class文件中,不然编译器就不知道chinse只能存取T的类型。因此在继承了泛型类型的情况下,子类可以获取父类的泛型类型。

extends 通配符

public static void main(String[] args) {

        Speak<Integer> str = new Speak<>(234,900);
        Speak<Double> ss = new Speak<>(234,900);
        int a = add(str);
        int b = add(ss);
        System.out.println(a);
        
    }

    static int add(Speak<? extends Number> p) {
        Number first = p.getType();
        Number last = p.getLevel();
        return first.intValue() + last.intValue();
    }

除了可传入Speak<Integer> ,还有Speak<Double>,因为Integer和Double都是Number的子类。

只能接受该类型以及子类。

使用extends 限定T的类型

在定义泛型Speak<T>的时候,也可以使用extends 通配符限定T的类型:

public class Speak<T extends Number>{
    
}

super通配符

public static void main(String[] args) {

        Speak<Integer> str = new Speak<>(234,900);
        Speak<Number> str = new Speak<>(234,900);
        int a = add(str);
        System.out.println(a);

    }

    static int add(Speak<? super Integer> p) {
        Number first = p.getType();
        Number last = p.getLevel();
        return first.intValue() + last.intValue();
    }

可以传入Integer的父类Number

只能接受该类型以及父类

extends和super的对比

public class Collections {
    // 把src的每个元素复制到dest中:
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        for (int i=0; i<src.size(); i++) {
            T t = src.get(i);
            dest.add(t);
        }
    }
}

参考文档:

java泛型_百度百科

泛型和反射 - 廖雪峰的官方网站

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值