Java核心技术卷一 6. java泛型程序设计
Posted lovezyu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java核心技术卷一 6. java泛型程序设计相关的知识,希望对你有一定的参考价值。
泛型程序设计
泛型程序设计:编写的代码可以被很多不同类型的对象所重用。
类型参数:使用<String>
,后者可以省略,因为可以从变量的类型推断得出。类型参数让程序更具更好的可读性和安全性。
通配符类型:很抽象,让库的构建者编写出尽可能灵活的方法。
定义简单泛型类
泛型类就是具有一个或多个类型变量的类。
//引用类型变量 T ,可以有多个类型变量,public class Pair<T, U>{...}
public class Pair<T> {
//类定义的类型变量制定方法的返回类型以及域和局部变量的类型
private T first;
private T second;
public Pair(){
first = null;
second = null;
}
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst(){
return first;
}
public T getSecond(){
return second;
}
public void setFirst(T newValue){
first = newValue;
}
public void setSecond(T newValue){
second = newValue;
}
}
在实例化类时,加上具体的类型替换类型变量就可以实例化类型。
Pair<String> //用具体的类型替换类型变量就可以实例化泛型类型
//此时,构造器和方法的类型都会赋予
Pair<String>()
Pair<String>(String, String)
String getFirst()
String getSecond()
void setFirst(String)
void setSecond(String)
多个类型变量只要这样即可:
public class Pair<T, U>{...}
调用时的结构:
Pair pair = new Pair<String>();
泛型可以看作普通类的工厂。
泛型方法
定义带有类型参数的简单方法
class Arraylg{
public static <T> getMiddle(T... a){
return a[a.length / 2];
}
}
调用泛型方法:
String middle = Arraylg.<String>getMiddle("John","Q","Public");
类型变量的限定
有时类或方法需要对变量进行约束实现某种功能,对类型变量 T 设置限定实现:
<T extends BoundingType>
多个限定
<T extends Comparable & Serializable>
此时,就可以让类型 T 限制为实现了这些接口。
泛型代码和虚拟机
虚拟机没有泛型类型对象,所有对象都属于普通类。
类型擦除
泛型类型,自动提供了一个相应的原始类型:删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定名的变量用 Object)。
经过编译器之后,泛型在虚拟机不存在。
翻译泛型表达式
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
编译器吧这个方法调用翻译为两条虚拟机指令:
- 对原始方法 Pair.getFirst 的调用。
- 将返回的 Object 类型强制装换为 Employee 类型。
翻译泛型方法
public static <T extends Comparable> T min(T[] a)
public static Comparable min(Comparable[] a)
类型参数 T 擦除,留下限定类型 Compatable 。
类型擦除与多态发生冲突,要在类中生成一个桥方法:
public void setSecong(Object second) { setSecond((Date) second); }
java 泛型转换的事实:
- 虚拟机中没有泛型,只有普通的类和方法。
- 所有的类型参数都用它们的限定类型替换。
- 桥方法被合成来保持多态。
- 为保持类型安全性,必要时插入强制类型转换。
约束与局限性
不能用基本类型实例化类型参数
可以用包装器类型,如果包装器类型不能接受替换时,可以使用独立的类和方法处理他们
运行时类型查询只适合于原始类型
无论何时对泛型使用 instanceof 会得到一个编译器错误;
泛型类型的强制转换表达式都会看到一个编译警告。
getClass 方法总是返回原始类型。
Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
stringPair.getClass() == employeePair.getClass();
if (a instanceof Pair<String>) //Error
Pair<String> p = (Pair<String>) a; //Warning
结果为 true 都将返回 Pair.class
不能创建参数化类型的数组
Pair<String>[] table = new Pair<String>[10];//error
可以声明通配符类型的数组,然后进行类型转换:
Pair<String>[] table = (Pair<String>[]) new Pair<?>[10];
但是结果不安全
安全方法:
ArrayList<Pair<String>>
Varargs 警告
向参数个数可变的方法传递一个泛型类型的实例:
public static <T> void addAll(Collection<T> coll, T... ts){
for (t : ts) coll.add(t);
}
此时需要 ts 数组,但是他违反了规则,会得到一个警告,可以通过注释消除警告:
@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts)
此时可以提供泛型类型来调用这个方法了。
不能实例化类型变量
非法:public Pair() { first = new T(); second = new T(); } // Error
同样反射调用也非法:fisrt = T.class.newInstance(); // Error
不能构造泛型数组
非法:T[] mm = new T[2];
泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。
private static T s; // Error
public static T getS(){...}; // Error
不能抛出或捕获泛型类的实例
非法:public class Problem<T> extends Exception { /*...*/ } // Error
catch 子句中不能使用类型变量。
在异常规范中使用类型变量是允许的:public static <T extends Throwable> void doWork(T t) throws T // OK
可以消除对受检查异常的检查
异常处理规定,必须为所有受查异常提供一个处理器。不过可以利用泛型消除这个限制。
注意擦除后的冲突
当泛型类型被擦除时,无法创建引发冲突的条件。
要想支持擦除的转换,就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这两个接口是同一接口的不同参数化。
非法:
class Employee implements Comparable { ... }
class Manager extends Employee implements Comparable<Manager> { ... } //Error
Manager
会实现Comparable<Employee>
和Comparable<Manager>
,这是同一接口的不同参数化。
泛型类型的继承规则
Pair<Manager>
不是 Pair<Employee>
的子类。可以将参数类型转换为一个原始类型,但是会产生类型错误。
无论 S 与 T 有什么关系,Pair<Manager>
和 Pair<Employee>
基本没什么关系。
通配符类型
通配符概念
通配符类型中,允许类型参数变化。
Pair<? extends Employee>
便是任意泛型 Pair 类型,它的类型参数是 Employee 子类。
Pair<Manager>
和 Pair<Employee>
是Pair<? extends Employee>
的子类型。
通配符的超类型限定
超类型限定:
? super Manager
这个通配符限制为 Manager 的所有超类型。
public static <T extends Comparable<T>> T min(T[] a)
public static <T extends Comparable<? super T>> T min(T[] a) //用于处理需要泛型类型超类型的情况
无限定通配合
无限定通配符:Pair<?>
这个类有如下方法:
? getFirst()
void setFirst(?)
getFirst 能赋予一个 Object;
setFirst 方法不能被调用,也不能用 Object 调用。但是可以调用setFirst(null)
。
通配符捕获
通配符不是类型变量,不能在编写代码中使用?
作为一种类型。
解决方案:
public static <T> void swapHelper(Pair<T> p){
T t = p.getFirst();
p.setFirst(p.getSecond();
p.setSecond(t);
}
public static void swap(Pair<?> p){
swapHelper(p);
}
swapHelper 方法的参数 T 捕获通配符。他不知道是哪种类型的通配符,但是这是一个明确的类型。
反射和泛型
泛型 Class 类
Class 类是泛型的,String.class
是一个Class<String>
类的对象(唯一的对象)。
如果给定的类型确实是 T 的一个子类型,cast 方法就会返回一个现在声明为类型 T 的对象,否则,抛出一个 BadCastException 异常。
如果这个类不是 enum 类或类型 T 的枚举值的数组,getEnumConstants
方法将返回 null。
getConstructor
与 getdeclaredConstructor
方法返回一个 Constructor<T>
对象。Constructor
类也已经变成泛型,以便newInstance
方法有一个正确的返回类型。
API:
T newInstance()
T cast(Object obj)
T[] getEnumConstants()
Class<? super T> getSuperclass()
Constructor<T> getConstructor<Class... parameterTypes)
Constructor<T> getDeclaredConstructor<Class... parameterTypes)
//java.lang.reflect.Constructor<T>
T newInstance(Object... parameters)
使用 Class<T>
参数进行类型匹配
public static <T> Pair<T> makePair(class<T> c) throws InstantiationException, IllegalAccessException {
return new Pair<>(c.newInstance(), c.newInstance());
}
调用
makePair(Employee.class);
类型参数 T 同 Employee 匹配。
虚拟机中的泛型类型信息
可以使用反射 API 确定:
- 这个泛型方法有一个叫做 T 的类型参数。
- 这个类型参数有一个子类限定,其自身又是一个泛型类型。
- 这个限定类型有一个通配符参数。
- 这个通配符参数有一个超类限定。
- 这个泛型方法有一个泛型数组参数。
以上是关于Java核心技术卷一 6. java泛型程序设计的主要内容,如果未能解决你的问题,请参考以下文章
关于JAVA核心技术(卷一)读后的思考(泛型数组列表的讨论)
java 核心技术卷一笔记 6 .2接口 lambda 表达式 内部类
java 核心技术卷一笔记 6 .1.接口 lambda 表达式 内部类