Administrator
Published on 2025-04-14 / 25 Visits
0
0

Java泛型

1.泛型

1.1.概述

泛型是一种“代码模板”,可以用一套代码套用各种类型,只需要编写一次代码模板就能实现万能的类型匹配和保障编译器编译时的类型安全。

img

1.2.泛型基本使用

1.2.1.泛型类

使用以List<T>集合使用为例

当指定泛型类型为Number时,只能接收Number类型和Number类型的子类

前面泛型类型可以推导出后面泛型类型时可以省略后面泛型类型的编写

List<Number> listArray1=new ArrayList<>(); //ArrayList<>泛型类型可以不写
listArray1.add(3.1415926);
//listArray1.add("abcdefg");//报错

不定义泛型类型时,泛型类型实际上就是Object

List listArray=new ArrayList();
listArray.add(new String("abcdefg"));
System.out.println(listArray);

1.2.2.泛型接口

不仅是普通类可以是泛型类,像接口和抽象类也可以定义为泛型

例如Arrays.sort();就是利用了泛型接口,它可以排序任意类型数组,例如它既可以排序string又可以排序Number

但是使用Arrays.sort()的前提条件是排序的元素它必须要先实现 interface Comparable<T> 泛型接口

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

String能直接使用Arrays.sort()排序内部就实现了Comparable<T>泛型接口

import java.util.Arrays;
public class Main {
    public static void main(String[] args) {
        Person[] ps = new Person[] {
            new Person("Bob", 61),
            new Person("Alice", 88),
            new Person("Lily", 75),
        };
        Arrays.sort(ps);
        System.out.println(Arrays.toString(ps));
    }
}
//自定义普通类必须实现Comparable<Person>接口,才能用排序方法
class Person implements Comparable<Person> {
    String name;
    int score;
    Person(String name, int score) {
        this.name = name;
        this.score = score;
    }
    //实现Comparable<Person>接口方法
    public int compareTo(Person other) {
        return this.name.compareTo(other.name);
    }
    public String toString() {
        return this.name + "," + this.score;
    }
}

1.3.编写泛型类

泛型类主要用于通用类型中,例如集合这种需要实用于多种不同类型的类

1.3.1改造普通类为泛型类

普通类(String类型)

public class Generics_2 {
    public static void main(String[] args) {
        System.out.println(new Pair("one",1));
        System.out.println(Pair.staticCreate("two",2));
    }
}
class Pair { //类名不带泛型<T>
    private String first; //成员变量为String类型
    private Integer last;
    public Pair(){};
    public Pair(String first, Integer last) {
        this.first = first;
        this.last = last;
    }
    public String getFirst() {
        return first;
    }
    public Integer getLast() {
        return last;
    }
    public Pair create(String first, Integer last) {
        return new Pair(first, last);
    }
    //静态方法
    public static Pair staticCreate(String first, Integer last) {
        return new Pair(first, last);
    }
    @Override
    public String toString() {
        return "Pair{" +
                "first='" + first + '\'' +
                ", last=" + last +
                '}';
    }
}
​

泛型类(T类型)

T代表任意类型,也就是泛型类型,我们将上面创建的普通类改造为泛型类,

泛型类的泛型类型可以是多个

泛型类中的静态泛型方法参数类型必须要与泛型类的泛型类型进行区分,注意静态泛型返回类型返回值和普通泛型泛型方法返回值的语法区别

package generics;
​
public class Generics_2 {
    public static void main(String[] args) {
        System.out.println(new Pair<String,Integer>("one",1));
        System.out.println(new Pair<>().create("one",1));
        System.out.println(Pair.staticCreate("two",2));//静态泛型方法
    }
}
class Pair<T,H> { //类名带<T>
    private T first;  //成员类型为泛型T
    private H last;
    public Pair(){};
    public Pair(T first, H last) {
        this.first = first;
        this.last = last;
    }
    public T getFirst() { //方法返回的也是泛型T
        return first;
    }
    public H getLast() {
        return last;
    }
    public Pair<T,H> create(T first, H last) {
        return new Pair<T,H>(first, last);
    }
    // 静态泛型方法应该使用其他类型区分,因为静态方法是属于整个类的,它并不属于任何实例化的实例
    // 返回类型为泛型类<K,L> Pair<K,L>
    public static <K,L> Pair<K,L> staticCreate(K first, L last) {
        return new Pair<K,L>(first, last);
    }
    @Override
    public String toString() {
        return "Pair{" +
                "first=" + first +
                ", last=" + last +
                '}';
    }
}

1.3.2.泛型类的擦拭法(泛型原理)

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

所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。

例如,我们编写了一个泛型类Pair<T>,这是编译器看到的代码:

public class Generics_2{
    public static void main(String[] args) {
        Pair<String> p = new Pair<>("Hello", "world");
        String first = p.getFirst();
        String last = p.getLast();
    }
}
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;
    }
}

虚拟机根本不知道泛型,编译器会将泛型进行编译成为下面这种虚拟机执行的代码

Java使用擦拭法实现泛型,导致了:

  • 编译器把类型<T>视为Object

  • 编译器根据<T>实现安全的强制转型。

编译之后的代码没有泛型了

public class Generics_2{
    public static void main(String[] args) {
        Pair p = new Pair("Hello", "world");
        String first = (String) p.getFirst(); //根据泛型类型T进行安全转换
        String last = (String) p.getLast();
    }
}
class Pair {
    private Object first; //将泛型类型替换为了Object类型
    private Object last;
    public Pair(Object first, Object last) {
        this.first = first;
        this.last = last;
    }
    public Object getFirst() {
        return first;
    }
    public Object getLast() {
        return last;
    }
}

1.3.3.泛型类擦拭法的局限性

局限一:<T>不能是基本类型,例如int,因为实际类型是ObjectObject类型无法持有基本类型:

Pair<int> p = new Pair<>(1, 2); // compile error!,因为Object无法向下安全转换为int基本类型

局限二:无法取得带泛型的Class,因为获取到的都是编译后的Pair<Object>字节码文件

public class Main {
    public static void main(String[] args) {
        Pair<String> p1 = new Pair<>("Hello", "world");
        Pair<Integer> p2 = new Pair<>(123, 456);
        Class c1 = p1.getClass();
        Class c2 = p2.getClass();
        System.out.println(c1==c2); // true,因为获取到的都是编译后的`Pair<Object>`字节码文件
        System.out.println(c1==Pair.class); // true,因为获取到的都是编译后的`Pair<Object>`字节码文件
    }
}
​
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;
    }
}

局限三:无法判断带泛型的类型:因为获取到的都是编译后的Pair<Object>字节码文件,没有Pair<String>.class这么一说,只有Pair.class

Pair<Integer> p = new Pair<>(123, 456);
// Compile error:语法错误,没有Pair<String>.class这么一说,只有Pair.class
if (p instanceof Pair<String>) { 
}
// 正确写法
if (p instanceof Pair) { 
}

局限四:不能直接实例化T类型,因为T会全部编译成为Object然后执行

public class Main {
    public static void main(String[] args) {
    	Pair<String> pair = new Pair<>(String.class);
        pair.first="实例化T的正确写法"
        pair.first="实例化T的正确写法"
	}
}
class Pair<T> {
    T first;
    T last;
    public Pair() {
        // Compile error:错误写法
        first = new T();
        last = new T();
    }
    //Class<T>为泛型反射类型,它是不同类型的字节码
    public Pair(Class<T> clazz) {
        // 正确写法,通过反射实例化T
        first = clazz.newInstance(); //必须有一个空参数的构造方法
        last = clazz.newInstance();
    }
}

局限五:不恰当的方法覆盖写法

因为equals是Object的方法,而编译器会将T编译为Object,所以会造成Object的equals方法被覆盖,编译器会阻止该行为。

public class Pair<T> {
    //报错编译器不通过
    public boolean equals(T t) {
        return this == t;
    }
    //正确写法,改个不重名的方法名,避免方法覆盖
    public boolean same(T t) {
        return this == t;
    }
}

1.4.泛型继承

1.4.1普通类继承

//泛型继承
public class Generics_3 {
   public static void main(String[] args) {
      System.out.println(new IntPair(18,18));
      System.out.println(new IntPair_A<String,Integer>("泛型继承","泛型类继承泛型类",18));
   }
}
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;
	}
	@Override
	public String toString() {
		return "Pair{" +
				"first=" + first +
				", last=" + last +
				'}';
	}
}
//普通类继承泛型类,注意普通类继承泛型类是要写明泛型类的类型
class IntPair extends Pair<Integer> {
	public IntPair(Integer first, Integer last) {
		super(first, last);
	}
	@Override
	public String toString() {
		return super.toString();
	}
}

1.4.2.泛型类继承泛型类

//泛型继承
public class Generics_3 {
   public static void main(String[] args) {
      System.out.println(new IntPair(18,18));
      System.out.println(new IntPair_A<String,Integer>("泛型继承","泛型类继承泛型类",18));
   }
}
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;
	}
	@Override
	public String toString() {
		return "Pair{" +
				"first=" + first +
				", last=" + last +
				'}';
	}
}
//泛型类继承泛型类,注意泛型类的类型和继承泛型类的类型一致但不是绝对的
class IntPair_A<T,K> extends Pair<T> {
	K age;
	public IntPair_A(T first, T last,K name) {
		super(first, last);
		this.age=name;
	}
	@Override
	public String toString() {
		String s = "IntPair_A{" +
				"name=" + this.age +
				'}';
		return super.toString()+" "+s;
	}
}

1.4.3.获取继承泛型的类型((普通类继承泛型类))

正常情况下,我们无法直接获取到Pair<T>中的类型,因为编译器的擦拭问题,但是普通类继承Pair<T>时,我们就可以获取到到Pair<T>中的T类型了,可以通过以下方式获取

public class Main {
    public static void main(String[] args) {
        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
        }
    }
}

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;
    }
}

class IntPair extends Pair<Integer> {
    public IntPair(Integer first, Integer last) {
        super(first, last);
    }
}

因为Java引入了泛型,所以,只用Class来标识类型已经不够了。实际上,Java的类型系统结构如下:

                      ┌────┐
                      │Type│
                      └────┘
                         ▲
                         │
   ┌────────────┬────────┴─────────┬───────────────┐
   │            │                  │               │
┌─────┐┌─────────────────┐┌────────────────┐┌────────────┐
│Class││ParameterizedType││GenericArrayType││WildcardType│
└─────┘└─────────────────┘└────────────────┘└────────────┘

1.5.泛型类型限定(extends和super)

1.5.1.子类限定拓展通配符extends

当我们有一个具体的泛型参数类方法时,传入不对等的泛型参数时就会产生错误

//泛型通配符extends、super
public class Generics_4 {
	public static void main(String[] args) {
		//正常调用
		System.out.println("---对等泛型类型参数,编译通过----");
		Pair_4<Number> numberPair = new Pair_4<Number>(10,10);
		add(numberPair);
		System.out.println("---不对等泛型类型参数,编译报错----");
		Pair_4<Integer> numberPair_4 = new Pair_4<>(10,10);
		//add(numberPair_4); //报错,因为Pair_4<Integer>并不是Pair_4<Number>的子类,也不对等

	}
	public static void add(Pair_4<Number> p){
		Number first = p.getFirst();
		Number last = p.getLast();
		System.out.println(first.intValue() + last.intValue());
	}
}

class Pair_4<T> {
	private T first;
	private T last;
	public Pair_4(T first, T last) {
		this.first = first;
		this.last = last;
	}
	public T getFirst() {
		return first;
	}
	public T getLast() {
		return last;
	}
}

为了能解决上面的问题,可以实用通配符限定泛型类型

add(Pair_4<Number> p)方法改成add(Pair_4<? exdends Number> p)通过exdends 对Number进行子类限定拓展,允许Number的子类传入。

当然泛型类的泛型也可以被子类限定拓展通配符extends修饰,例如Pair_4<T extends Number>,意思是T只能为Number的子类,传入其他非Number或者Number的子类(例如String类)将编译错误、

通过以下例子可以看出,被通过子类限定拓展符extends 修饰的方法,只能获取限定的父类类型值不能设置值(null可以传入,但是没意义会编译出错)

//泛型通配符extends、super
public class Generics_4 {
	public static void main(String[] args) {
         System.out.println("-----使用extends拓展泛型参数------");
        Pair_4<Double> numberPair_4_e = new Pair_4<>(10,10);
		add(numberPair_4);//允许Number的任意子类泛型传入
	}
    public static void add(Pair_4<? extends Number> p){
		Number first = p.getFirst();
		Number last = p.getLast();//只能转换为NUmber类型值
  		System.out.println(first.intValue() + last.intValue());
        //报错,因为转换类型可能不是Integer,有可能是Double,无法确定转换类型
        //Integer first_1=p.getFirst(); //转换为非Number类型的值
        //报错,因为传唤类型不确定,因为add_1的类型可能是Double,可能是Integer,所以传入的setFirst方法中的类型不确定
        //p.setFirst(new Integer(18));
	}
}
class Pair_4<T extends Number> { //限定T为Number的子类
	private T first;
	private T last;
	public Pair_4(T first, T last) {
		this.first = first;
		this.last = last;
	}
	public T getFirst() {
		return first;
	}
	public T getLast() {
		return last;
	}
    public void setFirst(T first) {
		this.first=first;
	}
	public void setLast(T last) {
		this.last= last;
	}
}

1.5.2.父类限定拓展通配符super

和子类限定拓展通配符extends功能刚好相反,super只允许限定类和限定类的父类传入参数

通过以下例子可以看出,被通过子类限定拓展符 super 修饰的方法,只能设置限定的父类类型值不能设置值(null可以传入,但是没意义会编译出错)

结合super和extends特点总结来说,Java向上转型安全(super,设置值父类接收值安全),而向下转型不安全(extends,子类接收父类会出现类型问题,不安全)

//泛型通配符extends、super
public class Generics_4 {
	public static void main(String[] args) {
		System.out.println("---------使用父类限定拓展通配符super------");
		Pair_super<Integer> numberPair_s = new Pair_super<>(10,10);
		add(numberPair_s);
		Pair_super<Number> numberPair_s1 = new Pair_super<>(10,10);
		add(numberPair_s1);
	}
    public static void add(Pair_4<? super Integer> p){
		p.setFirst(18);//正常调用
		p.setLast(18);
   		System.out.println(p);
		//报错,不能确定add_super(Pair_super<? super Integer> p)传入的是Number还是Integer,所以不能安全转换为Number
		//Number first = p.getFirst(); //类型不确定
        Object first = (Object) p.getFirst(); //唯一可以接收的是Object类型
        System.out.println(first);
	}
}
class Pair_4<T> {
	private T first;
	private T last;
	public Pair_4(T first, T last) {
		this.first = first;
		this.last = last;
	}
	public T getFirst() {
		return first;
	}
	public T getLast() {
		return last;
	}
    public void setFirst(T first) {
		this.first=first;
	}
	public void setLast(T last) {
		this.last= last;
	}
    @Override
	public String toString() {
		return "Pair_super{" +
				"first=" + first +
				", last=" + last +
				'}';
	}
}



Comment