Java泛型

本文围绕Java泛型展开,介绍了泛型的概念、使用场景及编写方法。阐述了擦拭法实现泛型的方式及局限,还讲解了extends和super通配符的使用及区别。此外,说明了泛型与反射的关系,如部分反射API是泛型,带泛型数组的创建需强制转型等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是泛型
  为什么需要泛型
  泛型的继承关系
  总结
使用泛型
  不使用泛型时
  使用泛型时
  举个栗子
  总结
编写泛型
  须知
  编写步骤
  静态方法
  多个类型
  总结
擦拭法
  介绍
  局限
  泛型继承
  总结
extends通配符
  需求
  注意事项
  总结
super通配符
  介绍
  extends和super
  无限定通配符
  总结
泛型和反射
  反射中的泛型
  泛型数组
  总结

什么是泛型

为什么需要泛型

泛型就是定义一种模版,例如ArrayList<T>

  • 在代码中为用到的类创建对应的ArrayList<类型>
ArrayList<String> strList = new ArrayList<String>();
  • 编译器针对类型作检查
strList.add("hello");
strList.add(new Integer(123));// compile error
泛型的继承关系

不能把ArrayList向上转型为ArrayList或List

ArrayList<Integer> integerList = new ArrayList<Integer>();
integerList.add(new Integer(123));
// 向上转型,实际类型还是Integer
ArrayList<Number> numberList = integerList;
// Number可以接收Float类型
numberList.add(new Float(12.34));
// 取值出现异常
Integer n = integerList.get(1); // ClassCastException

ArrayList和ArrayList两者没有继承关系

总结
  • 泛型就是编写模版代码来适应任意类型
  • 不必对类型进行强制转换
  • 编译器将对类型进行检查
  • 注意泛型的继承关系:
    • 可以把ArrayList向上转型为List(T不能变)
    • 不能把ArrayList向上转型为ArrayList

使用泛型

不使用泛型时
List list = new ArrayList();
list.add("hello");
list.add("World");
Object first = list.get(0);
Object second = list.get(1);
  • List的接口变为Object类型:
    • void add(Object)
    • Object get(int)
  • 编译器警告
  • 此时只能把当作Object使用
使用泛型时

定义泛型类型为

List<String> list = new ArrayList<String>();
list.add("Hello");
list.add("World");
String first = list.get(0);
String second = list.get(1);
  • List的泛型接口变为强类型:
    • void add(String)
    • String get(int)

可以省略编译器能自动推断出的类型

List<Number> list = new ArrayList<Number>();
// 可以省略后面的Number,编译器可以自动推断类型
List<Number> list = new ArrayList<>();
栗子

Array.sort()可以对Object[]数组进行排序:

String[] strs = {"Apple", "Pear", "Orange"};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// {"Apple", "Orange", "Pear"}

待排序的元素需要实现Comparable泛型接口:

public interface Comparable<T> {
  	/**
  	*	返回-1:当前实例比参数o小
  	* 返回0:当前实例与参数o相等
  	*	返回1:当前实例比参数o大
  	*/
  	int compareTo(T o);
}
// 待排序元素类实现Comparable<T>接口
class Person implements Comparable<Person> {
  	private String name ;
  	private int score;
  
  	public Person(String name, int score) {
      	this.name = name;
      	this.score = score;
    }
  	// 重写compareTo方法
  	public int compareTo(Person o) {
      	if (this.score < o.score) {
          	return -1;
        } else if (this.score > o.score) {
          	return 1;
        } else {
           return this.name.compareTo(o.name);
        }
    }
  	@Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}
public class  Main {
  	public static void main(String[] args) {
        Person[] list = {new Person("Bob", 89), new Person("Alice", 97), new Person("Lisa", 68)};
      	Arrays.sort(list);
      	System.out.println(Arrays.toString(list));
				// [Person{name='Lisa', score=68}, Person{name='Bob', score=89}, Person{name='Alice', score=97}]
    }
}
总结
  • 使用泛型时,把泛型参数替换为需要的class类型:
List<String> list = new ArrayList<String>();
  • 可以省略编译器能自动推断出的类型
List<String> list = new ArrayList<>();
  • 不指定泛型参数类型时,编译器会给出警告,且只能将视为Object类型

编写泛型

须知
  • 编写泛型类比普通类要复杂
  • 泛型类一般用在集合类中ArrayList
  • 很少需要编写泛型类
编写步骤
  • 按照某种类型(例如String)编写类
public class Pair {
  	private String first;
  	private String last;
  	public Pair(String first, String last) {
      	this.first = first;
      	this.last = last;
    }
  	public String getFirst() {
      	return first;
    }
  	public String getLast() {
      	return last;
    }
}
  • 标记所有的特定类型(String)
  • 把特定类型替换为T,并申明
public class Pair<T> {
  	private T first;
  	private T last;
  	public Pair(T first, T last) {
      	this.first = first;
      	this.last = last;
    }
  	public T getFirst() {
      	return first;
    }
  	public T getLast() {
      	return last;
    }
}
静态方法

泛型类型不能用于静态方法:

  • 会有编译错误
  • 编译器无法在静态字段或静态方法中使用泛型类型
// 这样会有编译错误
public static Pair<T> create(T first, T last) {
  	return new Pair<T>(first, last);
}
// 可以在static后面再加一个<T>
// 新加的<T>相当于新申明一个泛型,跟类申明的<T>不是同一个
// 所以最好把这个<T>修改为<K>
public static <T> Pair<T> create(T first, T last) {
  	return new Pair<T>(first, last);
}
public static <K> Pair<T> create(K first, K last) {
  	return new Pair<K>(first, last);
}
多个类型

定义泛型时可以定义多个类型

  • 使用<T, K>
public class Pair<T, K> {
  	private T first;
  	private K last;
  	public Pair(T first, K last) {
      	this.first = first;
      	this.last = last;
    }
  	public T getFirst() {
      	return first;
    }
  	public K getLast() {
      	return last;
    }
}

Pair<String, Integer> p = new Pair<>("test", 123);
总结
  • 编写泛型时,需要定义泛型类型
public class Pair<T>{...}
  • 静态方法不能饮用泛型类型,必须定义其他类型来实现泛型
public static <K> Pair<K> create(K first, K last){...}
  • 泛型可以同时定义多种类型<T, K>
public class Pair<T, K>{...}

擦拭法

介绍

Java泛型的实现方式是擦拭法(Type Erasure)

  • 编译器把类型视为Object
  • 编译器根据实现安全的强制转型
public static void main(String[] arge) {
  	Pair<String> pair = new Pair<>("Xiao", "Ming");
  	String first = pair.getFirst();
}
// 编译器实际处理代码
public static void main(String[] arge) {
  	// 把泛型视为Object类型
  	Pair pair = new Pair("Xiao", "Ming");
  	// 在需要使用时进行强制转型
  	String first = (String)pair.getFirst();
}
局限

擦拭法的局限:

  • 不能是基本类型,比如int
  • Object字段无法持有基本类型
  • 无法取得带泛型的Class
Pair<String> s = ...
Pair<Integer> i = ...
Class c1 = s.getClass();
Class c2 = i.getClass();
// c1、c2获取的都是Pair.class
  • 无法判断带泛型的class
// Compile error
if (s instanceof Pair<String>.class) {
}
  • 不能实例话T类型
  • 因为擦拭后实际上是new Object()
public class Pair<T> {
  	private T first;
  	private T last;
  	public Pair() {
      	// Compile error
      	first = new T():
      	last = new T();
    }
}
Pair<String> pair = new Pair<>():
  • 实例话T类型必须借助Class

    public class Pair<T> {
      	private T first;
      	private T last;
      	public Pair(Class<T> clazz) {
          	first = clazz.newInstance();
          	last = clazz.newInstance();
        }
    }
    Pair<String> pair = new Pair<>(String.class);
    
泛型继承

泛型可以继承自泛型类

  • 父类的类型是Pair
  • 子类的类型是IntPair
  • 子类可以获取父类的泛型类型Integer
public class IntPair extends Pair<Integer> {
  
}
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
  	ParameterizedType pt = (ParameterizedType)t;
  	Type[] types = pt.getActualTypeArguments();
  	Type firstType = types[0];
  	Class<?> typeClass = (Class<?>)firstType;
  	System.out.println(typeClass);	// Integer
}

JDK定义的Type接口实现类关系
在这里插入图片描述

总结
  • Java的泛型采用擦拭法实现
  • 擦拭法决定了泛型:
    • 不能是基本类型,例如:int
    • 不能获取带泛型类型的Class,例如:Pair。class
    • 不能判断带泛型类型的类型,例如:x instanceof Pair
    • 不能实例话 T 类型, 例如:new T()
    • 泛型方法要防止重复定义方法,例如:public boolean equals(T obj)
  • 子类可以获取父类的泛型类型

extends通配符

需求

前面提到过Pair不是Pair的子类,因此传入数据只能是指定的类型,但是如果既想传入Integer,又想传入Number的类型该如何做呢?

可以使用extend通配符定义泛型,Pair<? extends Number>表示方法可以接收泛型类型为Number或其子类类型的Pair类

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Number> p) {
      	
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

在上面add方法中,如果对p调用getFirst方法,则只能使用泛型中的父类类型Number来接收,不能使用某个子类类型如Integer接收,因为此时无法判断实际传入的类型

...
public class PairHelper {
    static int add(Pair<? extends Number> p) {
      	// 使用泛型中的父类来接收
				Number first = p.getFirst();
      	// Compile error
      	Integer fir = p.getFirst();
    }	
}
// getFirst方法实际编译为
<? extends Number> getFirst();

在调用setFirst方法时无法传入任何Number类型的变量,因为在编译时无法确定泛型的具体类型,编译时setFirst方法签名为void setFirst(? extends Number);

...
public class PairHelper {
    static int add(Pair<? extends Number> p) {
      	Number first = new Float(1.2f);
      	// Compile error
      	// 无法确定具体类型,但null除外
    		p.setFirst(first)	;
    }	
}
// setFirst方法签名
void setFirst(? extends Number)
注意事项

使用<? extends Number>的通配符时:

  • 允许调用get方法获得Number(泛型类型的父类)的饮用
  • 不允许调用set方法传入Number的饮用
  • 唯一例外:可以调用setFirst(null)
  • 在类上的泛型申明时使用通配符
    • 限定定义Pair是只能是Number或其子类
public class Pair<T extends Number>{...}
Pair<Number> p1 = new Pair<>(1, 2);
Pair<Double> p1 = new Pair<>(1.2, 2.3);
Pair<String> p1 = new Pair<>("a", "b");	// error
总结
  • 使用类似<? extends Number>通配符作为方法参数时表示:

    • 方法内部可以调用获取Number引用的方法
    Number n = objc.getXxx();
    
    • 方法内部无法调用传入Number饮用的方法(null除外)
    obj.setXxx(Number n); // error
    
  • 使用类似定义泛型类时表示:

    • 泛型类型限定为Number或Number的子类

super通配符

介绍

使用super通配符以<? super Integer>格式定义泛型,表示接收所有泛型类型为Integer或Integer超类的类

当调用setFirst方法时,可以传入Integer类型的数据,因为Integer的超类可以接收Integer类型

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Integer> p) {
      	// 实际类型为Integer或Integer的超类,可以接收Integer类型
      	p.setFirst(new Integer(2));
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

当调用getFirst方法时,方法签名变为 ?super Integer getFirst(),方法返值只能确定为Integer 的超类,无法确定具体类型,因此调用会出错

public class Pair<T>{
  	private T first;
  	private T last;
  	...
}
public class PairHelper {
		static int add(Pair<? extends Integer> p) {
      	p.getFirst();//Compile error
    }	
}
PairHelper.add(new Pair<Number>(1, 2));
PairHelper.add(new Pair<Integer>(1,2));

当在定义类的泛型时使用super通配符,表示类传入的泛型类型只能是Integer或Integer类的超类

public class Pair<T super Integer>{...}
Pair<Integer> p1 = new Pair<>(1, 2);
Pair<Number> p1 = new Pair<>(1.2, 2.3);
Pair<String> p1 = new Pair<>("a", "b");// error
extends和super

方法参数为<? extends T>和方法参数为 <? Super T>的区别:

  • <? extends T>允许调用方法获取 T 的引用
  • <? Super T>允许调用方法传入 T 的引用
无限定通配符
<?>的通配符被称为无限定通配符
  • 不允许调用set方法(null除外)
  • 只能调用get方法获取Object引用
  • Pair<?>和Pair不同
总结
  • 使用类似<? super Integer>通配符作为方法参数时表示:

    • 方法内部无法调用获取Integer引用的方法(Object除外)
    Integer n = objc.getXxx();// error
    
    • 方法内部可以调用传入Integer引用的方法
    obj.setXxx(Integer n); 
    
  • 使用类似定义泛型类时表示:

    • 泛型类型限定为Integer或Integer的超类
  • 无限定通配符<?>很少使用,可以用替换

泛型和反射

反射中的泛型
  • Class是泛型

    • T newInstance()
    // compile warning
    Class clazz = String.class;
    String str = (String)clazz.newInstance();
    // 使用泛型
    Class<String> clazz = String.class;
    String str = clazz.newInstance();
    
    • Class<? super T> getSuperclass()
    Class<? super String> sup = clazz.getSuperclass();
    
  • Constructor是泛型

Class<integer> clazz = Integer.class;
Constructor<integer> cons = clazz.getConstructor(int.class);
Integer i = cons.newInstance(123);
泛型数组

可以声明带泛型的数组,但不能用new创建带泛型的数组:

Pair<String>[] ps = null;// OK
Pair<String>[] ps = new Pair<String>[2]; // error

必须通过强制转型实现带泛型的数组:

@SuppressWarnings("unchecked")
Pair<String>[] ps = (Pair<String>[])new Pair[2];

不能直接创建T[]数组

  • 擦拭后代码编为 new Object[5]
public class Abc<T> {
  	T[] createArray() {
      	// compile error
      	return new T[5];
    }
}
  • 必须借助Class
public class Abc<T> {
  	T[] createArray(Class<T> cls) {
      	return (T[]) Array.newInstance(cls, 5);
    }
}
  • 利用可变参数创建T[]数组
    • @SafeVarargs消除编译器警告
public class ArrayHelper {
  	@SafeVarargs
  	static <T> T[] asArray(T... objs) {
      	return objs;
    }
}
String[] ss = ArrayHelper.asArray("a", "b", "c");
Integer[] ns = ArrayHelper.asArray(1, 2, 3);
总结
  • 部分反射API是泛型
    • Class
    • Constructor
  • 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型
  • 可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌影~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值