Java核心技术 卷I 第八章

泛型程序设计

思维导图

请添加图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

8.1 类型参数

构造函数中可以省略泛型类型
省略的类型可以从变量的类型推断得出,编译器会利用这个信息,不需要进行强制类型转换,编译器知道返回值的类型为String,而不是Object
类型参数的魅力在于:使得程序具有跟好的可读性和安全

ArrayList<String> files = new ArrayList<>();

8.1.2通配符类型的引出

ArrayList<Manager>中的所有元素可以添加到ArrayList<Employee>中
原因:多态,Manager是Employee的子类
反过来则不行,出于灵活性发明了新概念,“通配符类型”

8.2定义简单泛型类

可以定义Pair类,可多个不同的变量类型,其中第一个域和第二个域使用不同的类型

public class Pair<T>{
	...
}


public class Pair<T,U>{
	...
}

提示:类型变量使用大写类型,E表示集合的元素类型,K和V为键值,T、U、S较为常用——任意类型

8.3 泛型方法

class ArrayAlg{
	// 定义方法:
	// 前T为方法的泛型类型,后T为方法的返回值
	public static <T> T getMiddle(T... a){
		return a[a.length / 2];
	}
	
	public static void main(String[] args) {
		// 调用1
        String middle1 = ArrayAlg.<String>getMiddle("a","b","c");
        System.out.println(middle1);//b
        
        // 调用2
        String middle2 = ArrayAlg.getMiddle("a","b","c");
        System.out.println(middle2);//b
    }
}

1)定义格式:

修饰符列表 <泛型参数类型> 返回值类型 方法名(方法参数列表){
//方法语句
return ;
}
2)调用格式:

  • object.<泛型参数>方法名(方法参数列表);

  • object.方法名(方法参数列表);
    大多数情况下可以,但任意报错:编译器寻找参数列表的共同超类型

8.4 类型变量的限定

限定T只属于某个类或者接口下

public static <T extends Comparable> T min(T[] a){...}

限定了T是Comparable及其实现类
一个类型变量或者通配符可以有多个限定,使用 & 分隔,且至多一个类,如果用类做限定,必须在限定列表的第一个。

<T extends Comparable & Serializable>

8.5 泛型类型和虚拟机

虚拟机没有泛型类型对象,所有对象都属于普通类,所以我们定义的泛型类型和方法都会在虚拟机编译后擦除。

8.5.1 类型擦除

擦除规则:

  • 定义一个泛型时都自动提供了一个相应的原始类型(raw type)
    [删去类型参数后的泛型类型名]
  • 把 类型 替换为 限定类型 ,无 限定类型 的变量用 Object

Pair<T> 的原始类型

public class Pair{
	private Object first;
	private Object second;
	
	public Pair(Object first,Object second){
		this.first = first;
		this.second = second;
	}
	
	public Object getFirst(){
		return first;
	}
	
	public Object getSecond(){
		return second;
	}
}

编译器将没有方法的接口放在边界列表的末尾:

  • 经编译前:<T extends Serializable & Comparable>
  • 因为Serializable没有方法,所以放在后面
  • 经编译后:<T extends Comparable & Serializable>

8.5.2翻译类型表达式

当程序调用泛型方法时,由于类型擦除
如 public T getFirst();
变成 public Object getFirst();
编译器会自动插入强制类型转换,将返回值Object强制转换

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

编译器把这个方法调用翻译为两条虚拟机指令

  • 对原始方法Pair.getFirst调用
  • 返回的Object类型强制转换为Employee类型

即使是公有的域也会进行强制类型转换,如
Employee buddy = buddies.first;

8.5.3 翻译类型方法

编译器自动生成合成桥方法,不会导致泛型翻译后的方法重载多态冲突。

  • 虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用它们的限定类型替换
  • 桥方法被合成用来保持多态
  • 为保持类型安全性,必要时插入强制类型转换

8.5.4 使用注解使警告消失

@SuppressWarning(“unchecked”)注解会关闭方法中所有代码的检查

// 在警告语句前标注
@SuppressWarning("unchecked")
Dictionary<Integer,Components> labelTable = slider.getLabelTable(); // No warning

//标注整个方法
@SuppressWaring("unchecked")
public void configureSlider(){
	...
}

8.6 约束和局限性

8.6.1 不能用基本类型实例化类型参数

简而言之就是:不能用类型参数代替基本数据类型
全得是包装类

8.6.2 运行时类型查询只适用于原始类型

无法使用 instanceof 判断,都无法编译通过

public static void main(String[] args) {
        Pair<String> stringPair = new Pair<String>("aaa","bbbb");
        System.out.println(stringPair instanceof Pair<String>);//Error
        System.out.println(stringPair instanceof Pair<T>);//Error 

		ArrayList<Manager> managerList = new ArrayList<>();
		System.out.println(managerList instanceof ArrayList<?>);//always true
		System.out.println(managerList instanceof ArrayList);//always true
		

    }

最后的pair调用的方法取决于最后一次的赋值声明,
Pair<Integer> pair,所以调用的是Pair<Integer>的方法
若类型和方法不符合会在运行时报错,并不会编译不通过

public static void main(String[] args) {
        Pair<String> stringPair = new Pair<String>("aaa","bbbb");
        Pair IntPair = new Pair<>(111,222);
        @SuppressWarnings("unchecked")
        Pair<Integer> pair = (Pair<Integer>)IntPair;// Warning原生泛型--强转-->精确泛型

        System.out.println(pair.getFirst());//调用哪个方法取决于最后一次声明
    }
  • 使用 instanceof 检查会得到错误
  • getClass方法总是会返回原始类型(raw type)
    不管Pair<String>或是Pair<Integer>,其对象调用getClass,总会返回Pair.Class

8.6.3 不能创建参数化类型的数组

Pair<String>[] table = new Pair<String>[10];//Error
Pair<String>[] table = new Pair[10];//Warning -- OK

//不安全的创建泛型数组方法
Pair<String>[] table = (Pair<String>[])new Pair<?>[10];//这种方式可以的

//安全而有效的创建泛型数组方法,使用new ArrayList<Pair<T>>(),Array中存储的都是Pair<T>
ArrayList<Pair<String>> pairs = new ArrayList<Pair<String>>();
  • 只是不允许创建数组————new Pair<String>[10]
  • 声明是合法的
  • 使用原生类型是可以创建数组的
  • 最好使用 new ArrayList<Pair<T>>(); 来创建泛型数组

8.6.4 Varargs 警告

@SafeVarargs 注解标注方法可以消除创建泛型数组有关限制

public static void main(String[] args) {
        Pair<String> pair1 = new Pair<String>("aaa","bbbb");
        Pair<String> pair2 = new Pair<String>("aaaa","bbbbb");
        Collection<Pair<String>> table = new ArrayList<>();

        addAll(table,pair1,pair2);
        System.out.println(table);
    }

    @SafeVarargs
    public static <T> void addAll(Collection<T> coll,T... ts){
        for (T t:ts
             ) {coll.add(t);
        }
    }

8.6.5 不能实例化类型变量

不能使用 new T();
不能使用 T.class;

那么要怎么才能用泛型创建对象?使用Class<T>类

// 用一个静态方法来实例化Pair<T> 中的成员变量
    // 核心是 c.newInstance()
    public static <T> Pair<T> makePair(Class<T> c){
        try{
            return  new Pair<>(c.newInstance(),c.newInstance());
        }catch (Exception e){
            return null;
        }
    }
    
//调用
Pair<String> pair = makePair(String.class);

Class类本身就是泛型,所以String.class 是一个class<String>的一个实例,且是唯一实例,因此makePair方法能够推断出pair类型

8.6.7 泛型类的静态上下文中类型变量无效

不能再静态域或方法中引用类型变量

泛型禁止用于静态域
泛型禁止用于静态方法作为返回值和参数
普通方法:不带<T>

泛型可以作为静态泛型方法的参数
泛型方法:带<T>

8.6.8 不能抛出或捕获泛型类的实例

  • 泛型类不能继承Exception,Throwable
public class Problem <T> extends Exception{} //Error

public class Problem <T> extends Throwable{} //Error
  • 泛型类型参数无法用catch捕获
// 这个是
public static <T extends Throwable> void dowork(Class<T> c){
	try{
		...
	}catch(T t){//无法捕获 Error
		...
	}
}
  • 可以在异常处理器中使用泛型类型变量
public static <T extends Throwable> void doWork(T t) throws T{
        try{
            
        }catch (Throwable throwable){
            t.initCause(throwable);
            throw t;
        }
    }

8.6.10 类型擦除后的冲突

一个类型变量不能同时成为两个接口类型的子类,这个接口类型是同一接口的不同参数化
如:

class Employee implememts Comparable<Employee>{}

class Manager extends Employee implements Comparable<Manager>{} //Error 

相当于擦除类型后,子类又实现了同一接口!

8.7 泛型类型的继承规则

在这里插入图片描述
泛型和数组的重大区别:

  • 可以将Manager[] 数组赋值给 Employee[]变量
  • 但是Manager[]里的元素都是Manager,把Manager赋值给超类,但是不能把超类加入该数组
  • 这是数组的特别保护
Manager[] managers = new Manager[10];
Employee[] employees = managers;
//java.lang.ArrayStoreException
employees[0] = new Employee();

在这里插入图片描述
某一泛型类型 是 生泛型(raw) 的子类
不同的泛型类型虽然继承自一个raw type 泛型
但是它们之间没有如何关系

8.8 通配符类型

1)<?> 非受限通配
写:X 例外set(null)却可以,set(object)却不行
读:✔ 返回声明为Object

2)<? extends T> 受限通配

写:X
读:✔ 返回声明为 T的超类

3)<? super T> 下限通配
写:✔ 写入类型为 T的子类
读:✔ 返回声明为Object


问题提出:由于Pair<Employee>和Pair<Manager>是两个不同的类型,所以该方法不能接收来自Pair<Manager>的参数
方法1:单一泛型类型

public static void printBuddies1(Pair<Employee> p) {
        Employee first = p.getFirst();
        Employee second = p.getSecond();


        System.out.println("First:" + first+"\n"+"Second:"+second);

        p.setFirst(new Employee(2));
        p.setSecond(new Employee(1));
    }

方法2:使用受限通配,向下匹配(给定上限)

  • 接收泛型类型方面:能够接收更多的参数类型,向下接收
  • 读方面:可以读取get,因为返回的读取类型为(上限~其子类) 泛型类型范围的公共超类——(Object~上限)
    读取的接收声明为(Object~上限)皆可满足
  • 写方面:不能写入set
//采用通配符,Pair<Employee>和Pair<Manager>,让参数能够接收{泛型为Employee的子类}的Pair
    public static void printBuddies2(Pair<? extends Employee> p) {
        //上限通配
        //优点:可以使用get,可读取
        Employee first = p.getFirst();
        Employee second = p.getSecond();

        //缺点:无法使用set,不可写入
        //p.setFirst();

        System.out.println("First:" + first+"\n"+"Second:"+second);
    }

方法3:使用下限通配,向上匹配(给定下限)

  • 接收泛型类型方面:能够接收更多的参数类型,向上接收
  • 读方面:可以读取get,但是接收声明为泛型类型范围的公共超类,只能是Object
    所以读取的接收声明为Object
  • 写方面:可以写入,写入类型为泛型类型范围下限的子类
    所以写入的类型为 (下限~其子类)
public static void printBuddies3(Pair<? super Manager> p) {
        //下限通配,最低的就是Pair<Manager>,可传入Pair<Employee>
        //缺点:可以读取,但是丢失了类型,不知道是Manager的哪一个超类,固用Object吸收
        Object first = p.getFirst();
        Object second = p.getSecond();

        //优点:可以写入,但是写入会小小取小,只能写入比Manager更小的
        p.setFirst(new Manager(2,2));
        p.setSecond(new Manager(1,1));

        System.out.println(p);
    }

8.8.4 通配符捕获

通配符不是类型变量, 因此, 不能在编写代码中使用“ ?” 作为一种类型
所以我们要怎么明确我们接收到的通配类型是什么呢?

方法:通过增加辅助方法——泛型方法,用泛型T来捕获通配类型?

// 未捕获前:
	? t = p.getFirst();
	p.setFirst(p.getSecond());
	p.setSecond(t);
	
// 捕获后(泛型方法辅助):
public static <T> void swapHelper(Pair<T> p) { 
	T t = p.getFirst(); 
	p.setFirst(p.getSecond()); 
	p.setSecond(t); 
}

在这种情况下,swapHelper 方法的参数 T 捕获通配符。它不知道是哪种类型的通配符,
但是,这是一个明确的类型

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值