Java泛型详解

一、概念

没有引入泛型之前

在没有泛型类之前。ArrayList类只维护一个Object引用数组:

public class ArrayList{
    private Object[] elementData;
    public Object get(int i){...}
    public void add(Object o){...}
}

这种方法存在两个问题。获取一个值时必须进行强制类型转换

ArrayList files = new ArrayList();
String filename = (String)files.get(0);

此外,这里也没有错误检查。可以向数组列表中添加任何类的值

files.add(new File("./img/a.txt"));

对于这个调用,编译和运行都不会出错。不过,如果你将get的结果强制类型转换为String类型,就会产生错误。显而易见,这是非常危险的。

引入泛型之后

public class ArrayList<T>{
    private T[] elementData;
    public T get(int i){...}
    public void add(T o){...}
}

ArrayList类现在有一个类型参数用来指示元素的类型:

ArrayList<String> files = new ArrayList<String>();//上面的T此时就相当于String

编译器充分利用这个类型信息。调用get的时候,不再需要强制类型转换。编译器知道返回值类型为String,而不是Object:

String filename = files.get(0);

编译器还知道ArrayList的add方法有一个类型为String的参数,这比有一个Object类型的参数要安全得多。现在编译器会检查,防止你插入错误类型的对象。例如,下面的语法:

files.add(new File("./img/a.txt"));

是无法通过编译的。

看到这里你应该对于泛型的作用有了一定得了解,总结一下:

  • 在编译的时候检查类型安全

  • 所有的强制转换都是自动和隐式的

  • 提高代码的重用率(这个在上面没有提到。想一想为什么)

二、泛型的使用方式

泛型一般有三种使用方式:泛型类泛型接口泛型方法

1.泛型类

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}

如何实例化泛型类:

Generic<Integer> genericInteger = new Generic<Integer>(123456);

​ Java1.7以后,后面的<>中的具体的数据类型可以省略不写(菱形语法)

Generic<Integer> genericInteger = new Generic<>(123456);

注意:

  1. 泛型的类型参数只能是引用类型,不能是基本数据类型

  2. 泛型类,如果没有指定具体的数据类型,此时,操作类型是Object

    List list = new ArrayList();
    

从泛型类派生子类

子类也是泛型类,子类和父类的泛型类型要一致

class ChildGeneric<T> extends Generic<T>

//父类
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();
    }
}

子类不是泛型类,父类要明确泛型的数据类型

class ChildGeneric extends Generic<String>

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

2.泛型接口

public interface Generator<T> {
    T method();
}

从泛型接口派生子类

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

/**
 * 泛型接口
 * @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";
    }
}

3.泛型方法

public static <E> void sort(E[] arr){
    T small = arr[0];
    if(small.compareTo(b) > 0)
        
}

但是,现在有一个问题。请看sort方法的代码。变量small得类型为T,这意味着他可以是任何类型的变量。如何知道它一定有一个compareTo方法呢?

解决这个问题的办法就是限制变量T只能实现Comparable接口(该接口包含了comparaTo方法 )

public static <E extends Comparable> void sort(E[] arr){...}

注意:Comparable是一个接口,为什么不用implements而是使用extends?我也不知道,就是这样设计的,记住就好了。

三、类型通配符

1.什么是类型通配符

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

使用场景

我们现在有一个方法 print 可以打印 List 集合。当我们集合的泛型是Integer时

public static void main (String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(123);
    list.add(456);
    print(list);
}
private static void print(List<Integer> context){
    System.out.println(context);
}

当我们集合的泛型是String时

public static void main (String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("123");
    list.add("456");
    print(list);
}
private static void print(List<String> context){
    System.out.println(context);
}

发现了没?是不是很麻烦,这个时候使用类型通配符就好多了

public static void main (String[] args) {
    ArrayList<String> list = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    list.add("123");
    list.add("456");
    list2.add(123);
    list2.add(345);
    print(list);
    print(list2);
}
private static void print(List<?> context){
    System.out.println(context);
}

2.类型通配符的上限

语法:

  • 类/接口<? extends 实参类型>
  • 要求该泛型的类型,只能是实参类型,或实参类型的子类类型。
private static void print(List<? extends Vehicle> context){
    //表示这个类型必须是Vehicle的子类
    System.out.println(context);
}

3.类型通配符的下限

语法:

  • 类/接口<? super 实参类型>
  • 要求该泛型的类型,只能是实参类型,或实参类型的父类类型。
private static void print(List<? super Car> context){
    //表示这个类型必须是Car的父类或者Car本身
    System.out.println(context);
}

四、类型擦除

1.概念

泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除。

学习类型擦除之前,要明白原始类型这一概念。

无论何时提供一个泛型类型,都会自动提供一个相应的原始类型。例如,现在有一个Pair类

public class Pair<T> {
    private T first;
    private T second;

    public Pair (T first, T second) {
        this.first = first;
        this.second = second;
    }
}

Pair类的原始类型如下:

public class Pair {
    private Object first;
    private Object second;

    public Pair (Object first, Object second) {
        this.first = first;
        this.second = second;
    }
}

Pair或Pair,擦除类型之后,他们都会变成上面的原始类型

2.分类:

无限制类型擦除

在这里插入图片描述

有限制类型擦除

在这里插入图片描述

擦除方法中类型定义的参数

在这里插入图片描述

桥接方法

在这里插入图片描述

五、反射与泛型

/**
 * 泛型与反射
 */
public class Test {
	public static void main(String[] args) throws Exception {
	     Class<Person> personClass = Person.class;
	     Constructor<Person> constructor = personClass.getConstructor();
	     Person person = constructor.newInstance();
	 }
}

不使用泛型

public class Test {
	public static void main(String[] args) throws Exception {
	     Class personClass = Person.class;
         Constructor constructor = personClass.getConstructor();
         Person person = (Person) constructor.newInstance();
	}
}

参考:
https://blog.csdn.net/Beyondczn/article/details/107093693
https://javaguide.cn/
《Java核心技术》

  • 12
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值