六、Java高级-泛型(二)

本文详细介绍了Java中的泛型概念,包括泛型的使用场景、类型变量的上下限、泛型擦除、继承关系以及通配符的运用。重点讲解了PECS原则,以及在实际编程中的注意事项。
摘要由CSDN通过智能技术生成

2、泛型的实现

2.1 泛型的使用

1、类或接口后面→泛型类/泛型接口(参数化的类型)

  • 当类中的某个成员变量不确定时,可以考虑在类的上面声明泛型;当类/接口中某个非静态方法的形参或返回值不确定时。
  • 当类或接口中的某个/某些非静态方法的形参返回值类型不确定时,可以考虑在类/接口上声明泛型来表示这个未知的类型。如果多个方法都使用了类/接口上面声明的泛型,它们的类型是一致的。
//java.lang.comparable接口:
public interface Compareble<T> {
    public int compare(T o);//不是泛型方法
}
//java.util.Comparator:
	int compare(T t1,T t2)

2、方法的返回值类型前面→泛型方法

  • 当某个方法在设计时,它的形参、返回值不确定时,需要调用才能确定时,可以使用泛型方法。eg:复制任意类型的数组的方法。
//java.util.Arrays类
public static <T> List<T> asList(T... a){
    ....
}
  • asList方法返回的是一个只读的集合,不能删除、添加元素(报错)
List<String> list = Arrays.asList("hello","world","zhang","san");
list.add("wangwu");//UnsupportedOprationException
list.removw("san");//UnsupportedOprationException
  • list不是ArrayList类型,而是ArrayList里面的一个私有内部类( java.util.Array&ArrayList),并没有add()\remove()方法
//复制一个数组
public static <T> T[] copyOf(T[] original,int length);
  • 每一个泛型方法声明的<T>都是独立的,与其他方法无关。方法在调用时,会根据实参类型自动推断<T>的具体类型。
  • 在类/接口上声明的泛型类型<T>不能用于静态方法不能用于静态成员/静态方法,因为T类型和对象有关。在static修饰符后面加一个<T>
class MyGenrics<T>{
	public void method(T t){...}
    public void fun(T t){...}
    //上面两个方法的T是一致的
    public static void test1(T t){...}//报错,T类型与MyGenrics对象有关
     public static <T> void test2(T t){...}//test2方法的T类型与MyGrics对象无关,与上面的T类型是相互独立的,相当于:
    // public static <K> void test(K k){...}
}
2.2 类型变量的上限
  • <T extends 上限>:T的类型必须是<=上限,要么是上限本身,要么继承上限类型。
  • <T extends 类 & 接口1 &接口2& …>:一个类型变量的上限可以是一个也可以是多个,但只能有一个类上限,且类在左边,其他类型上限在右边
Class Student<T extends Number & Comparable & ...>{...}
  • JVM在运行是不支持泛型类型的识别
2.3 泛型的擦除(eraser)
  • 如果用户在使用泛型类或泛型接口时,没有使用指定泛型的类型,就会发生泛型的擦除,将泛型转为非泛型。
  • 泛型擦除后,自动按照类型变量声明时的第一个上限进行处理,如果没有指定参数类型的上限,上限为Object。eg:List<Integer>和ListString<String>在运行时都会转为List类型。

泛型并不是由虚拟机执行的,而是在编译时实行的。JVM并不能识别泛型,如果没有指定<T>的类型,编译器会将<T>视为Object类型进行处理,并可以根据<T>的类型自动实现安全的强制转换。

  • <T>不能是基本数据类型(Object类型无法拥有基本类型)
  • 不能取得带泛型的Class,也不能判断泛型的类型
  • 不能实例化T类型→借助Class<T>参数并通过反射来实例化T类型
Student<String> stu1 = new Student<>(String.class);

3、泛型的继承关系

  • 泛型类、泛型接口和泛型类型之间的关系

1、泛型类、泛型接口的继承关系

在泛型类和泛型接口继承和实现的过程中,子类可以获取父类的泛型类型<T>

子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时:

  • 指定类型变量对应的实际类型参数,此时子类或实现类不再是泛型类
class Student<T>{
    private String name;
    private T score;
    ...
}
//Primary不是泛型类
class Primary extends Student<String>{
    ...
}
  • 指定类型变量,此时子类、子接口、实现类仍然是泛型类或泛型接口
class Senior extends<E> extends Student<E>{
	...
}

2、泛型类型之间的继承关系

在Java中,泛型是不可变的,即泛型类型之间没有继承关系,即使它们的类型参数之间存在父子关系。所以Student<Integer>Student<Number>之间不是继承关系,而是不同类型的泛型类型。

Student<Integer> stu1 = new Student<>(100);
Student<Number> stu2 = inStu;//发生类型转换,但是编译报错classCastException

虽然Integer是Number的子类,但是Studeny<Integer>无法赋值给Student<Number>,stu1和stu2都指向同一个对象,即stu1所指向的对象。Java中的泛型是不可变的,stu2只能访问该对象的Number类型的方法和属性,而不能访问Integer类型的方法和属性。

并且,泛型通过类型擦除实现的,在编译后,Student<Integer>Student<Number>都被擦除为Student类型,编译器不能保证类型的一致性。

4、通配符

什么情况下使用?

当声明了一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,但还是不能确定这个泛型类/泛型接口的类型变量<T>的具体类型→ 类型通配符 ?

4.1 三种形式

1、<?>:?代表任意类型

2、<? extends 上限> :?代表<=上限类型

  • 该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

3、<? supur 下限>:?代表>=下限类型

  • 该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。
<? extends Number>
Collection<?> coll = new ArrayList<>();
coll.add("hello");//报错
coll.add(1);//报错
coll.add(1.0);//报错
<?>:泛型类型未知,不能确定集合的元素`void add(E t)`方法中E由?表示,直到add方法被调用时,也不能确定E的类型。
Collection<? extends Number> coll = new ArrayList<Double>();
coll.add(1.0);//报错

2、<? extends 上限> :?代表<=上限类型

  • 该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。

3、<? supur 下限>:?代表>=下限类型

  • 该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。
<? extends Number>
Collection<?> coll = new ArrayList<>();
coll.add("hello");//报错
coll.add(1);//报错
coll.add(1.0);//报错

1、<?>:泛型类型未知,不能确定集合的元素void add(E t)方法中E由?表示,直到add方法被调用时,也不能确定E的类型。

Collection<? extends Number> coll = new ArrayList<Double>();
coll.add(1.0);//报错

2、<? extends Number>:泛型类型限定为Number及其子类。void add(E t)方法中<E><? extends Number>表示,但是直到add方法被调用时,也不能确定E的类型,可以是<=Number的任意一种类型。

Collection<? super Number> coll = new ArrayList<>();
coll.add(1);
coll.add(1.0);
coll.add("hello");//报错

3、<? super Number>:可以添加Number对象或Number子类对象。

PECS原则

PECS原则:Producer Extends Consumer Super。如果需要返回T,它是生产者(Producer),使用extends通配符;如果需要写入T,它是消费者(Consumer),使用super通配符。

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); // src是producer
        dest.add(t); // dest是consumer
    }
}
4.2 注意的问题

1、<?>

如果某个泛型类/接口<T>在使用时,泛型指定为<?>,里面所有使用T类型声明的方法都不能正常使用(不能读也不能写,可以判断null类型)

2、<? extends 上限>

如果某个泛型类/接口<T>在使用时,泛型指定为<? extends 上限>,里面所有使用T类型声明的方法都不能正常使用(只能读也不能写,传入null除外[异常])

2、<? supur 下限>

如果某个泛型类/接口<T>在使用时,泛型指定为<? supur 上限>,里面所有使用T类型声明的方法都可以正常使用,但不能超过下限的类型(只能写也不能读,获取Object除外)

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);
        }
    }
}
  • copy()方法内部不会读取dest,因为不能调用dest.get()来获取T的引用;
  • copy()方法内部也不会修改src,因为不能调用src.add(T)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值