对于基本类型,只能使用对应的包装类来作为类型参数。这又涉及到自动装箱和自动拆箱的问题,会对性能造成一定的影响。
Org.apache.commons.collectiions.primitives这一开源的框架中可以使用由基本类型作为类型参数的泛型。
自动装箱机制不能用于数组。
例如:
package com.mzm.chapter15;
import com.mzm.chapter16.RandomGenerator;
/**
* 自动包装机制不能解决所有问题
*
*/
public class PrimitiveGenericTest {
public static void main(String[] args) {
String[] strings = FArray.fill(new String[7], new RandomGenerator.String(10));
for(String s : strings) {
System.out.println(s);
}
Integer[] integers = FArray.fill(new Integer[7], new RandomGenerator.Integer());
for(int i : integers) {
System.out.println(i);
}
//无法使用自动包装机制
//int[] b = FArray.fill(new int[7], new RandomGenerator.Integer())
}
}
class FArray {
public static T[] fill(T[] a, Generator gen) {
for(int i = 0; i < a.length; i++) {
a[i] = gen.next();
}
return a;
}
}
一个类不能实现同一个泛型接口的两种变体,因为存在擦除。
package com.mzm.chapter15;
/**
*
*/
public class MutipleInterfaceVariants {
}
interface Payable<T> {
}
class Employee2 implements Payable<Employee2> {
}
//该类不能实现同一个泛型接口的不同类型参数版本
/*class Hourly extends Employee2 implements Payable {
}*/
对带有泛型类型参数的变量进行转型或者使用instanceof,不会有任何结果。
package com.mzm.chapter15;
/**
*
*/
public class GenericCast {
public static final int SIZE = 10;
public static void main(String[] args) {
FixedSizeStack strings = new FixedSizeStack<>(SIZE);
for(String s : "A B C D E F G H I J".split(" ")) {
strings.push(s);
}
for(int i = 0; i < SIZE; i++) {
String s = strings.pop();
System.out.print(s + " ");
}
}
}
class FixedSizeStack {
private int index = 0;
private Object[] storage;
public FixedSizeStack(int size) {
storage = new Object[size];
}
public void push(T item) {
storage[index++] = item;
}
/**
* 由于擦除的存在,类型信息缺失,转型安全性未知
* @return
*/
@SuppressWarnings("unchecked")
public T pop() {
return (T)storage[--index];
}
}
但有时泛型也确实有对转型的需要:
package com.mzm.chapter15;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
/**
*
*/
public class NeedCasting {
//@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));
List shapes = (List)in.readObject();
}
}
注释@SuppressWarnings(“unchecked”),执行编译:
注: NeedCasting.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
如果执行javac -Xlint:unchecked NeedCasting.java,又会出现以下情况:
NeedCasting.java:13: 警告: [unchecked] 未经检查的转换
List shapes = (List)in.readObject();
^
需要: List<String>
找到: Object
1 个警告
这样就造成既要求转型,又告知不应转型的情况。
解决这样的问题,必须使用JavaSE 5中引入的新的转型形式。
package com.mzm.chapter15;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.List;
/**
*
*/
public class ClassCasting {
@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(args[0]));
List lw2 = List.class.cast(in.readObject());
}
}
这种形式的重载是不允许的,因为在擦除之后,出现了两个同样的方法。这一错误,可通过编译器看出。
package com.mzm.chapter15;
import java.util.List;
/**
*
*/
public class UseList<W, T> {
void f1(List v) {
}
/*void f1(List v) {
}*/
}
类型参数中的类型是确定的,不允许是其子类。
package com.mzm.chapter15;
/**
* Created by 蒙卓明 on 2018/1/1.
*/
public class ComparablePet implements Comparable<ComparablePet> {
@Override
public int compareTo(ComparablePet o) {
return 0;
}
}
/*
class Cat extends ComparablePet implements Comparable {
}*/
package com.mzm.chapter15;
/**
*
*/
public class RestrictedComparablePets {
}
/**
* Hamster虽然是ComparablePet的子类,但当其需要实现Comparable接口时,其类型参数也必须要是ComparablePet
* 等价于Hamster是ComparablePet的子类即可
*/
class Hamster extends ComparablePet implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) {
return 0;
}
}
class Gecko extends ComparablePet {
public int compareTo(ComparablePet arg) {
return 0;
}
}
古怪的循环泛型(CRG)是指创建一个新类,其继承自一个泛型类型,接受自身类名作为泛型参数。
package com.mzm.chapter15;
/**
* 创建一个新类,其继承自一个泛型类型,接受自身类名作为泛型参数
*
*/
public class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric>{
}
class GenericType<T> {
}
package com.mzm.chapter15;
/**
*
*/
public class CRGWithBasicHolder {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype();
//新类SubType接受的参数和返回的值具有SubType类型,而不仅是基类BasicHolder类型
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
}
class Subtype extends BasicHolder<Subtype> {
}
CRG的本质:父类用子类替代其参数。
package com.mzm.chapter15;
/**
* 自限定
*
*/
public class SelfBounding {
public static void main(String[] args) {
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
}
/**
* 自限定,泛型类以自身为泛型参数的边界
* @param
*/
class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded set(T arg) {
element = arg;
return this;
}
T get() {
return element;
}
}
/**
* 自限定主要是,在继承关系中,将子类作为父类(泛型类)的泛型参数
*/
class A extends SelfBounded<A> {
}
/**
* 也可以使用其他参数
*/
class B extends SelfBounded<A> {
}
class C extends SelfBounded<C> {
C setAndGet(C arg) {
set(arg);
return get();
}
}
class D {
}
/*
* 但是这一泛型参数必须是泛型类的子类才可
class E extends SelfBounded {
}*/
class F extends SelfBounded {
}
自限定主要用于,定义一个泛型类的子类时,强制使用泛型类的子类作为其泛型参数(可以是自身或其他子类)。
package com.mzm.chapter15;
/**
* 自限定用于泛型方法
*
*/
public class SelfBoundingMethods {
static > T f(T arg) {
return arg.set(arg).get();
}
public static void main(String[] args) {
A a = f(new A());
}
}
自限定类型可以产生协变参数类型——方法参数类型会随子类而变化。
方法的返回类型随子类而变化:
package com.mzm.chapter15;
/**
* 非自限定情况
*/
public class CovariantReturnTypes {
void test(DerivedGetter d) {
Derived d2 = d.get();
}
}
class Base {
}
class Derived extends Base {
}
interface OrdinaryGetter {
Base get();
}
interface DerivedGetter extends OrdinaryGetter {
/**
* 非自限定情况通过显式重写父类的get()方法,返回子类类型
* @return 子类类型
*/
Derived get();
}
package com.mzm.chapter15;
/**
*
*/
public class GenericAndReturnTypes {
void test(Getter g) {
//子类Getter对象的返回类型为Getter
Getter result = g.get();
GenericGetter gg = g.get();
}
}
/**
* 自限定接口
* @param 泛型类自身作为泛型参数的边界
*/
interface GenericGetter> {
T get();
}
/**
* 自限定情况,无需通过显式覆盖同名方法实现返回类型随子类变化,实际上已经完成方法的重写
*/
interface Getter extends GenericGetter {
}
方法的参数类型随子类而变化:
package com.mzm.chapter15;
/**
* 非自限定情况
* Created by 蒙卓明 on 2018/1/6.
*/
public class OrdinaryArguments {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedSetter ds = new DerivedSetter();
//在DerivedSetter类中,两个set()方法共存,说明出现重载
ds.set(derived);
ds.set(base);
}
}
class OrdinarySetter {
/**
* 父类的set()方法
* @param base 以Base类自身作为参数类型
*/
void set(Base base) {
System.out.println("OrdinarySetter.set(Base)");
}
}
class DerivedSetter extends OrdinarySetter {
/**
* 非限定情况只能显式重载父类的set()方法,来实现方法参数类型随子类而变化
* @param derived 以Base子类Derived类型作为参数类型
*/
void set(Derived derived) {
System.out.println("DerivedSetter.set(Derived)");
}
}
package com.mzm.chapter15;
/**
* 自限定情况
* Created by 蒙卓明 on 2018/1/6.
*/
public class SelfBoundedSetterAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
//接受自身类型变量
s1.set(s2);
//不接受父类SelfBoundSetter类型变量作为参数,只接受子类类型
//s2.set(sbs);
}
}
interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
void set(T arg);
}
/**
* 自限定情况,则无需显式重载父类同名方法,但是实际上父类的同名方法已经被覆盖
*/
interface Setter extends SelfBoundSetter<Setter> {
}
package com.mzm.chapter15;
/**
*
*/
public class PlainGenericInheritance {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedGS dgs = new DerivedGS();
//同时接受父类对象和子类对象作为方法参数,说明父类和子类的set()共存
dgs.set(derived);
dgs.set(base);
}
}
/**
* 非自限定
* @param
*/
class GenericSetter<T> {
void set(T arg) {
System.out.println("GenericSetter.set(Base)");
}
}
class DerivedGS extends GenericSetter<Base> {
/**
* 非自限定只能显式重载父类的同名方法来实现方法参数类型随子类而变化
* @param derived
*/
void set(Derived derived) {
System.out.println("DerivedGS.set(Derived)");
}
}
在向Java SE5之前的代码传递泛型容器时,旧代码可能会破坏泛型容器。Java SE5的java.util.Collections中有一组便利工具,可以解决在这种情况下类型检查问题。它们是静态方法:
checkedCollection();
checkedList();
checkedMap();
checkedSet();
checkedSortedMap();
checkedSortSet();
这些方法接受两个参数,第一个是容器对象,第二个是检查的类型。
package com.mzm.chapter15;
import com.mzm.chapter14.Cat;
import com.mzm.chapter14.Dog;
import com.mzm.chapter14.Pet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
*
*/
public class CheckedList {
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List dogs1 = new ArrayList<>();
//针对dogs1,插入Cat类型对象没有问题
oldStyleMethod(dogs1);
List dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
try {
//针对dogs2,插入Cat类型对象抛出异常
oldStyleMethod(dogs2);
} catch (Exception e) {
System.out.println(e);
}
//以父类作为检查类型,也没有问题
List pets = Collections.checkedList(new ArrayList<>(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
}
由于擦除的存在,catch语句不能捕获泛型类型的异常,而泛型类也不能直接或间接地继承自Throwable。
但是类型参数可以应用于方法的throws子句中,基于此,可以编写出随检查类型异常的类型而发生变化的泛型代码:
package com.mzm.chapter15;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
public class ThrowGenericException {
public static void main(String[] args) {
ProcessRunner runner = new ProcessRunner<>();
for(int i = 0; i < 3; i++) {
runner.add(new Processor1());
}
try {
System.out.println(runner.processAll());
} catch (Failure1 e) {
System.out.println(e);
}
ProcessRunner runner2 = new ProcessRunner<>();
for(int i = 0; i < 3; i++) {
runner2.add(new Processor2());
}
try {
System.out.println(runner2.processAll());
} catch (Failure2 e) {
System.out.println(e);
}
}
}
/**
* 该接口执行process()方法
* @param List中持有的对象类型
* @param 抛出的异常类型
*/
interface Processor<T, E extends Exception> {
void process(List resultCollector) throws E;
}
class ProcessRunner<T, E extends Exception> extends ArrayList<Processor<T, E>> {
/**
* 执行每一个存储在该容器中的Processor对象的process()方法
* @return
* @throws E
*/
List processAll() throws E {
List resultCollector = new ArrayList<>();
for(Processor processor : this) {
processor.process(resultCollector);
}
return resultCollector;
}
}
class Failure1 extends Exception {
}
class Processor1 implements Processor<String, Failure1> {
static int count = 3;
@Override
public void process(List resultCollector) throws Failure1 {
if(count-- > 1) {
resultCollector.add("Hep!");
} else {
resultCollector.add("Ho!");
}
if(count < 0) {
throw new Failure1();
}
}
}
class Failure2 extends Exception {
}
class Processor2 implements Processor<Integer, Failure2> {
static int count = 2;
@Override
public void process(List resultCollector) throws Failure2 {
if(count-- == 0) {
resultCollector.add(47);
} else {
resultCollector.add(11);
}
if(count < 0) {
throw new Failure2();
}
}
}