《Java编程思想》笔记15-泛型
泛型方法
如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。
对于一个 static 方法,无法访问泛型类的类型参数。
public <T> void function(T i){} //将泛型参数列表置于返回类型之前
类型参数推断
public class New {
public static <K,V> Map<K,V> map() {
return new HashMap<K,V>();
}
public static void f(Map<Person, List<? extends Pet>> petPeople) {}
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
Map<Person, List<? extends Pet>> petPeople = New.map();
f(petPeople);
//f(New.map()); // 无法编译
f(New.<Person, List<? extends Pet>>map()); // 显示指明类型
}
}
类型推断只对赋值操作有效,其他时候不一定有作用,将 New.map() 作为参数传递给另一个方法,并不会执行类型推断。
Java7 的泛型类型推断改进
在以前的版本中使用泛型类型,需要在声明并赋值的时候,两侧都加上泛型类型。例如:
Map<String, String> myMap = new HashMap<String, String>();
在Java SE 7中,这种方式得以改进,现在你可以使用如下语句进行声明并赋值:
Map<String, String> myMap = new HashMap<>(); //注意后面的 "<>",加上这个 "<>" 才表示是自动类型推断
但是:Java7 在创建泛型实例时的类型推断是有限制的:只有构造器的参数化类型在上下文中被显著的声明了,才可以使用类型推断,否则不行。例如:下面的例子在 Java7 无法正确编译(但现在在 Java8 里面可以编译,因为根据方法参数来自动推断泛型的类型):
List<String> list = new ArrayList<>();
list.add("A");// 由于addAll期望获得Collection<? extends String>类型的参数,因此下面的语句无法通过
list.addAll(new ArrayList<>());
Java8 的泛型类型推断改进
上文中 f(New.map());
可以编译通过。
static void processStringList(List<String> stringList) {
// process stringList
}
processStringList(Collections.emptyList());
processStringList(Collections.<String> emptyList()); //低版本
擦除
public class HasF {
public void f() { System.out.println("HasF.f()"); }
}
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
// Error: cannot find symbol: method f():
public void manipulate() { obj.f(); } // 编译不通过
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
manipulator.manipulate();
}
}
上面的代码没有通过编译,就是由于擦除,会将 T 替换为 Object,这样就没法调用 f() 方法了。可以使用边界来解决这个问题。
class Manipulator<T extends HasF> {
private T obj;
public Manipulator(T x) { obj = x; }
public void manipulate() { obj.f(); }
}
擦除的问题
泛型不能用于显式地引用运行时类型的操作之中,例如转型、instanceof 操作和 new 表达式(不能 new T())。因为类型信息会丢失,必须时刻提醒自己,只是看起来像拥有有关参数的类型信息而已。
在整个类的各个地方,类型 T 都在被替换,无论何时,必须时刻提醒自己“它只是个 Object”。
泛型中的所有动作都发生在边界处————对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。这有助于澄清对擦除的混淆,记住,“边界就是发生动作的地方”。
擦除的补偿
Java 泛型在 instanceof、创建类型实例,创建数组、转型时都会有问题。
public class Erased<T> {
public void f(Object arg) { // 书中
if(arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[10]; // Error
T[] array = (T)new Object[10]; // Unchecked warning
}
}
有时必须通过引入类型标签(即你的类型的 Class 对象)进行补偿。使用动态的 isInstance() 方法,而不是 instanceof。
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /* Output:
true
true
false
true
*///:~
创建类型实例
解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是 Class 对象。
class ClassAsFactory<T> {
T x;
public ClassAsFactory(Class<T> kind) {
try {
x = kind.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee { }
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(Employee.class);
print("ClassAsFactory<Employee> succeeded");
try {
ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(Integer.class);
} catch (Exception e) {
print("ClassAsFactory<Integer> failed");
}
}
}
但是对于没有默认构造器的类,上述方法不能奏效了。可以使用显示的工厂。
interface FactoryI<T> {
T create();
}
public class Foo2<T> {
private T x;
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
class IntegerFactory implements FactoryI<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {
public Widget create() {
return new Widget();
}
}
}
另一种方式是模板方法设计模式。
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() { element = create(); }
abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X> {
X create() { return new X(); }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
Creator c = new Creator();
c.f();
}
} /* Output:
X
*///:~
泛型数组
解决方案是在任何想要创建泛型数组的地方都使用 ArrayList。
如果非要用泛型数组,可以创建Object数组,然后转型。但是如果返回该泛型数组还是需要再进行一次转型。
T[] array;
public Constructor(int sz){
array = (T[]) new Object[sz];
}
使用类型标记
T[] array;
public Constructor(Class<T> type, int sz){
array = (T[]) Array.newInstance(type, sz);
}
边界
interface HasColor { }
class Colored<T extends HasColor> { }
class Dimension { }
// 编译不通过,class 必须在前
// class ColoredDimension<T extends HasColor & Dimension> {
// Multiple bounds:
class ColoredDimension<T extends Dimension & HasColor> { }
interface Weight { }
// 只能有一个类,可以有多个接口
class Solid<T extends Dimension & HasColor & Weight> { }
通配符
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10]; // 向上转型不合适这里
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
fruit[0] = new Fruit(); // ArrayStoreException
fruit[0] = new Orange(); // ArrayStoreException
List<Fruit> flist = new ArrayList<Apple>(); // 编译错误
List<? extends Fruit> flist = new ArrayList<Apple>();
// 添加任何类型,都是编译失败
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // 合法
Fruit f = flist.get(0);
List<? extends Fruit> flist2 = Arrays.asList(new Apple());
Apple a = (Apple) flist2.get(0);
flist2.contains(new Apple()); // contains() 和 indexOf() 方法接受 Object 类型的参数
flist2.indexOf(new Apple());
}
}
逆变
超类型通配符。可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 <? super MyClass>
,可以使用类型参数:<? super T>
(不能声明类型参数为<T super MyClass>
)
List<? super Apple> flist = new ArrayList<>();
flist.add(new Apple());
flist.add(new Jonathan());
//flist.add(new Fruit()); //Compile Error
Apple 是下界,这样你就知道向其中添加 Apple 或 Apple 的子类型是安全的,而添加 Fruit 是不安全的了。
无界通配符
无界通配符 <?>
看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。
List 实际上表示“持有任何 Object 类型的原生 List”,而 List<?> 表示“具有某种特定类型的非原生 List,只是我们不知道那种类型是什么。”
问题
任何基本类型都不能作为类型参数
解决之道是自动包装机制。但是自动包装机制不能作用于数组。
类泛型无法在静态方法中工作。
public class ByteSet {
Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };
Set<Byte> mySet = new HashSet<Byte>(Arrays.asList(possibles));
// 不能这样
// Set<Byte> mySet2 = new HashSet<Byte>(Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
} ///:~
专门适配基本类型的容器版本,org.apache.commons.collections.primitives
实现参数化接口
一个类不能同时实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会变成相同的接口。
interface Payable<T>{}
class Employee implements Payable<Employee> {}
class Hourly extends Employee implements Payable<Hourly> {} // 编译失败,如果把泛型都去掉,就可以了
转型和警告
泛型没有消除对转型的需要。
ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));
List<Widget> shapes = (List<Widget>)in.readObject(); // 警告
List<Widget> lw = List<Widget>.class.cast(in.readObject()); // 编译失败
List<Widget> lw = (List<Widget>)List.class.cast(in.readObject()); // 警告
List<Widget> lw = List.class.cast(in.readObject()); // 通过泛型类来转型
重载
由于擦除的原因,重载方法将产生相同的类型签名。
void f(List<T> v);
void f(List<W> v); // 编译失败
基类劫持了接口
public class ComparablePet implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) { return 0; }
} ///:~
class Cat extends ComparablePet implements Comparable<Cat>{
// 编译失败
public int compareTo(Cat arg) { return 0; }
} ///:~
自限定的类型
class SelfBounded<T extends Selfbounded<T>>{}
古怪的循环泛型(CRG):基类用导出类替代其参数。这意味着泛型基类变成了一种其所有导出类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用导出类型。
public class BasicHolder<T> {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
void f() {
System.out.println(element.getClass().getSimpleName());
}
} ///:~
class Subtype extends BasicHolder<Subtype> {}
public class CRGWithBasicHolder {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype();
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
} /* Output:
Subtype
*///:~
自限定
可以保证类型参数必须与正在被定义的类相同。
class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // Also OK
class C extends SelfBounded<C> {
C setAndGet(C arg) { set(arg); return get(); }
}
class D {}
// Can't do this:
// class E extends SelfBounded<D> {}
// Compile error: Type parameter D is not within its bound
// Alas, you can do this, so you can't force the idiom:
class F extends SelfBounded {}
自限定限制只能作用于继承关系。
动态类型安全
java.util.Collections 中提供来一组便利工具,可以解决类型检查的问题。它们是:静态方法 checkedCollection()、checkedList()、checkedMap()、checkedSet()、checkedSortedMap() 和 checkedSortedSet()。这些方法每一个都会将你希望动态检查的容器当做第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。如果向Java SE5 之前的代码传递泛型容器,可能会导致类似“将猫插入狗队列”的问题,使用这些方法可以确保不出现这种问题。
异常
由于擦除的原因,将泛型应用于异常是非常受限的。catch 语句不能捕获泛型类型的异常,泛型类也不能直接或间接继承自 Throwable。但是,类型参数可能会在一个方法的 throws 子句中用到。
interface Processor<T,E extends Exception>{
void process(List<T> resultCollector) throws E;
}
剩余部分看着有点懵...
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 bin07280@qq.com
文章标题:《Java编程思想》笔记15-泛型
文章字数:2.9k
本文作者:Bin
发布时间:2018-05-25, 21:24:26
最后更新:2019-08-06, 00:46:42
原始链接:http://coolview.github.io/2018/05/25/Java%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3/%E3%80%8AJava%E7%BC%96%E7%A8%8B%E6%80%9D%E6%83%B3%E3%80%8B%E7%AC%94%E8%AE%B015-%E6%B3%9B%E5%9E%8B/版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。