Java中泛型的使用方式

81 阅读4分钟

最近在学Kotlin语法,学习到泛型章节发现有一些困难,于是回去复习了一下Java的泛型做一个对比学习,在此记录一下。

泛型

1. 概念

泛型(generics)是JDK5中引入的新特性,泛型提供了编译时类型安全的检测机制,该机制允许我们在编译时期检测到非法的数据结构类型。

//不做泛型的限制,ArrayList能存储多种数据结构类型。
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add(100);
arrayList.add(true);
//使用<T>限制ArrayList的接收类型,在编译时期就可以进行数据结构的检查。
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("a");
stringArrayList.add("b");
stringArrayList.add("c");

ArrayList<Integer> integerArrayList = new ArrayList<>();
integerArrayList.add(100);
integerArrayList.add(200);
integerArrayList.add(300);

泛型的本质就是将数据类型参数化,也就是所操作的数据类型被指定为一个参数。

2. 泛型类

1. 泛型类的定义

/**
 * 泛型类的定义
 * @param <T> 泛型标识--类型形参
 *           T 创建对象的时候再指定具体的参数类型
 */
public class Generic<T> {
    //T是由于外部使用类时来指定
    private T key;

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

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }

    @Override
    public String toString() {
        return "Generic{" +
                "key=" + key +
                '}';
    }
}

2. 泛型类的使用

//泛型类在创建对象的时候,再来指定操作的具体数据类型。
Generic<String> stringGeneric = new Generic<>("a");
Generic<Integer> integerGeneric = new Generic<>(100);

//泛型类在创建对象的时候,没有指定类型,将按照object类型来接收。
Generic generic = new Generic("abc");

//泛型类不支持基本数据类型。
//Generic<int> intGeneric = new Generic<int>();

//同一泛型类,根据不同的数据类型创建的对象,本质上是同一个类型。
System.out.println(stringGeneric.getClass()); //Generic
System.out.println(integerGeneric.getClass()); //Generic
System.out.println(stringGeneric.getClass() == integerGeneric.getClass()); //true

3. Attention

  • 泛型类,如果没有指定具体的数据类型,操作类型是Object
  • 泛型的类型参数只能是类类型,不能是基本数据类型
  • 泛型类型再逻辑上可以看成是多个不同的类型,但实际上都是相同类型

3. 泛型类派生子类

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

class ChildGeneric<T> extends Generic<T>

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

class ChildGeneric extend Generic<String>

4. 泛型类接口

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

public class Apple implements Generator<String>

2. 实现类也是泛型类,实现类和接口的泛型类型要保持一致

public class Pair<T> implements Generator<T>

//泛型类的实现类的参数也可以添加泛型,但是必须包含父类泛型的类型。
public class Pair<T,E> implements Generator<T>

5. 泛型方法

1. 泛型方法能使方法独立于类而产生变化

/**
* 定义泛型方法
* @param list 参数
* @param <E> 泛型标识,具体类型由调用方法时指定。
* @return
*/
public <E> E getProduct(ArrayList<E> list) {
    return list.get(random.nextInt(list.size()));
}

2. 如果static方法要使用泛型能力,就必须使其成为泛型方法

//这里使用了可变参数,表示能接收到多个泛型。
public static <E> void print(E... e) {
    for (int i = 0; i < e.length; i++) {
        System.out.println(e[i]);
    }
}

6. 类型通配符

1. 类型通配符一般是使用 "?" 代替具体的类型实参

public static void showBox(Box<?> box) {
    Object first = box.getFirst();
    System.out.println(first);
}
public class Box<E> {
    private E first;

    public E getFirst() {
        return first;
    }

    public void setFirst(E first) {
        this.first = first;
    }
}

使用 "?" 代替具体的参数,可以使方法可以接收不同的数据类型。

Box<Number> box1 = new Box<>();
box1.setFirst(100);
showBox(box1);

Box<Integer> box2 = new Box<>();
box2.setFirst(200);
showBox(box2);

2. 通配符是类型实参数,而不是类型形参

3. 类型通配符的上限

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

//类/接口<? extends 实参类型>
public static void showAnimal(List<? extends Cat> list)

4. 类型通配符的下限

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

//类/接口<? super 实参类型>
public static void showAnimal(List<? super Cat> list)

7. 类型擦除

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

1. 无限制类型擦除

将 擦除成 Object

public class Erasure<T> {
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }
}
//类型擦除
public class Erasure {
    private Object key;

    public Object getKey() {
        return key;
    }

    public void setKey(Object key) {
        this.key = key;
    }
}

2. 有限制类型擦除

将 <T extends 父类> 擦除成父类

public class Erasure<T extends Number> {
    private T key;

    public T getKey() {
        return key;
    }

    public void setKey(T key) {
        this.key = key;
    }
}
//类型擦除
public class Erasure {
    private Number key;

    public Number getKey() {
        return key;
    }

    public void setKey(Number key) {
        this.key = key;
    }
}

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

将 <T extends 父类> 擦除成父类

public <T extends List> T show(T t)
//类型擦除
public Number show(Number t)

4. 桥接方法

实现泛型接口的时,编译器会生成桥接接口。

//泛型接口
public interface Info<T> {
	T info (T t);
}
//泛型接口的实现类
public class InfoImpl implements Info<Integer> {
	@Override
	public Integer info(Integer value) {
		rerurn value;
	}
}
//类型擦除
public interface Info{
	Object info(Object t);
}

public class InfoImpl implements Info {
	public Integer info(Integer value) {
		return value;
	}
	
	//桥接方法,保持接口和类的实现关系。
	@Override
	public Object info(Object var) {
		return info((Integer) var);
	}
}