本篇是对学习【Java编程思想 第 15 章 泛型】的学习总结。
一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制的束缚就会很大。
在面向对象编程语言中,多态算是一种泛化机制。例如,你可以将方法的参数设为一个基类,那么该方法就可以接受从这个基类导出的任意类型作为参数。
自Java SE5以来,提出一个重大的变化:泛型的概念。泛型实现了参数化类型的概念。
就是在类名后加上泛型参数,语法是<>
里放上我们不确定的类型,通常用大写字母表示,常用的有A T E R
,,,
public class Holder {
private T a;
public Holder(T a) {
this.a = a;
}
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
}
泛型可以应用于接口。例如生成器(generator),这是一种专门负责创建对象的类。实际上,这是工厂模式的一种应用。
// 泛型接口
public interface Generator {
T next();
}
//咖啡工厂
public class CoffeeGenerator implements Generator {
private Random rand = new Random(47);
//咖啡的子类
private Class[] types = {
Amerciano.class,
Breve.class,
Cappuccino.class,
Latte.class,
Mocha.class};
private int size = 0 ;
public CoffeeGenerator() {
}
public CoffeeGenerator(int size) {
this.size = size;
}
@Override
public Coffee next() {
try {
return (Coffee) types[rand.nextInt(types.length)].newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
当然我们也可以用适配器模式
来创建泛型接口。这里就不贴代码了。
要定义泛型方法,只需要将泛型参数置于返回值之前即可。另外,是否拥有泛型方法,与其是否是泛型类没有关系。
// 泛型方法
public void f(T t) {
System.out.println(t.getClass().getName());
}
<>
里不止放一个或者两个“大写字母”,可以放很多的。这被称为元组,可以存放不同类型的对象。
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
public C c;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.c = c;
}
@Override
public String toString() {
return "ThreeTuple{" +
"a=" + first +
", c=" + c +
", second=" + second +
'}';
}
}
泛型方法与可变参数列表能够很好地共存。
public static List makeList(T...args) {
List list = new ArrayList<>();
for ( T t: args ) {
list.add(t);
}
return list;
}
泛型还可应用于内部类和匿名内部类。
class Customer {
private static long counter = 0;
private long id = counter++;
private Customer(){}
@Override
public String toString() {
return "Customer{ id=" + id + "}";
}
public static Generator generator() {
//lambda表达式
return () -> new Customer();
}
}
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
ArrayList
与ArrayList
被看作是相同的类型。
Class.getTypeParameters()将返回一个TypeVariable对象数组,表示有泛型声明中所声明的参数类型。
所以说,ArrayList
与ArrayList
在运行时实际上是相同的类型。这两种类型都被擦除为它们的原生类型,即ArrayList
。
class Forb {}
class Fnork {}
class Quark<Q extends Forb> {}
class Particle<POSITION, MOMENTUM> {}
源代码 | 擦除后的Type对象 |
---|---|
Forb |
[] |
List |
[E] |
List |
[E] |
Map |
[K,V] |
Quark extends Forb> |
[Q] |
Particle |
[POSITION, MOMENTUM] |
泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。
List ==> List
Person==>Object//普通的类型变量未指定泛型边界时
泛型擦除意味着在运行时失去了参数的类型信息。
有趣的是,非泛型代码和泛型代码的字节码文件是一样的。
由于擦除丢失了参数的类型信息,任何在运行时需要知道确切类型信息的操作都将无法工作。
if( o instanceof T) {}
T var = new T();
T[] array = new T[size];
T[] arr = (T[])new Object[size];
为此,引入了类型标签,就可以转而使用动态的isInstance。
public boolean f(Object obj) {
return c.isInstance(obj);
}
Java
中的解决方案是传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象。
try {
//使用该语法的类必须有一个无参的构造函数
return c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
对上述这个不好的地方的解决办法就是使用显示的工厂模式。
interface Factory<T>{
T create();
}
class Foo<T> {
private T t;
//F代表不确定的类型,>表示Factory或者是其未知的子类型
public > Foo(F factory) {
t = factory.create();
}
}
class IntegerFactory implements Factory<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
Foo foo = new Foo<>(new IntegerFactory());
ArrayList
创建擦除后的数组,然后对其转型
?????
携带类型标记创建数组(推荐)
public GenericArrayWithTypeToken(Class type, int size) {
array = (T[]) Array.newInstance(type, size);
}
边界使得你可以用于泛型的参数上设置限制条件。
class ColoredDimension<T extends Dimension & HasColor> { }
泛型重用了extends
关键字,这里它区别于继承,表示某个类以及该类的子类。
类在前,接口在后。
List extends Fruit> fruits = new ArrayList<>();
//编译错误
//fruits.add(new Fruit());
//fruits.add(new Apple());
//fruits.add(new Orange());
//fruits.add(new Object());
fruits.add(null);
再看一个例子
public class Holder {
private T t;
public Holder(){}
public Holder(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Holder apples = new Holder<>(new Apple());
Apple a = apples.getT();
apples.setT(a);
//不能装换
//Holder fruits = apples;
//? extends Fruit意味着它可以是任意从Fruit继承的类,编译器无法验证
//故添加的操作都将失败
Holder extends Fruit> fruits = apples;
Fruit fruit = fruits.getT();
Apple aa = (Apple) fruits.getT();
try {
//会报错
Orange orange = (Orange) fruits.getT();
} catch (Exception e) {
e.printStackTrace();
}
// 编译错误
// fruits.setT(new Fruit());
// fruits.setT(new Apple());
// fruits.setT(new Orange());
// fruits.setT(new Object());
}
}
//能读不能写
List
List super Fruit> flist = new ArrayList();
List
无界通配符>
看起来意味着“任何事物”,因此使用无界通配符好像等价使用原生类型。
class GenericType<T>{ }
public class CuriouslyRecurringGeneric
extends GenericType<CuriouslyRecurringGeneric> {
}
我创建了一个新类,它继承自一个泛型类型,这个泛型类型接受一个我的类的名字作为参数
自限定将采用额外的步骤,强制泛型当做其自己的边界参数来使用。
自限定限制只能强制作用于继承关系。
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 {}
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());
}
}
SelfBounded
要求只接受从该泛型继承的类型作为泛型参数。
自限定类型的价值在于它们可以产生协办类型参数——方法参数类型会随子类而变化。
class Base{}
class Derived extends Base{}
interface GenericsGetter<T extends GenericsGetter<T>> {
T get() ;
}
interface Getter extends GenericsGetter<Getter> {}
public class GenericAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericsGetter gg = g.get();
}
}
我们看到继承自自限定类型的类,返回类型是导出类的类型。同样的,方法中参数也是接受导出类的类型。
interface GenericsGetter<T extends GenericsGetter<T>> {
T get() ;
}
interface Getter extends GenericsGetter<Getter> {}
public class GenericAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericsGetter gg = g.get();
}
}
checkedCollection
checkedList
checkedMap
checkedSet
checkedSortedMap
checkedSortedSet()
这些方法每一个都会将你希望动态检查的容器当做第一个参数接受,并希望你强制要求的类型作为第二个参数接受。
public class CheckedList {
static void oldStyleMethod(List pro) {
pro.add(new Cat());
}
public static void main(String[] args) {
List dogs1 = new ArrayList<>();
//这句不合法的语句居然逃脱了编译器的检查
oldStyleMethod(dogs1);
List dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
try{
oldStyleMethod(dogs2);
} catch (ClassCastException cce) {
cce.printStackTrace();
}
List pets = Collections.checkedList(new ArrayList<>(), Pet.class);
pets.add(new Dog());
pets.add(new Dog());
}
}
最基本的概念就是混合多个类的能力。(根本不理解)
相信看代码就懂了。
class Dog{
public:
void speak(){}
void sit(){}
void reproduce(){}
};
class Robot{
public:
void speak(){}
void sit(){}
void reproduce(){}
}
template<class T> void perform(T anything) {
anything.speak();
anything.sit();
}
int main() {
Dog d = new Dog();
Robot r = new Robot();
perform(d);
perform(r);
return 0;
}
# coding:UTF-8
class Dog:
def speak(self):
print "Arf"
def sit(self):
print "Sitting"
def reproduce(self):
pass
class Robot:
def speak(self):
print "Click"
def sit(self):
print "Clank"
def reproduce(self):
pass
def perform(anything):
anything.speak();
anything.sit();
dog = Dog();
robot = Robot();
perform(dog);
perform(robot);
可惜的是Java的语法不允许这样写。你可能想到用多态或者简单的泛型来写,可以到达相同的效果,是的。
interface Performs {
void speak();
void sit();
}
static void perform(Performs p) {
p.speak();
p.sit();
}
static extends Performs>void perform(P p) {
p.speak();
p.sit();
}
public static void perform(Object speaker) {
Class> cl = speaker.getClass();
try {
//指定公共成员方法
Method method1 = cl.getMethod("speak");
method1.invoke(speaker);
} catch (Exception e) {
e.printStackTrace();
}
try {
Method method2 = cl.getMethod("sit");
method2.invoke(speaker);
} catch (Exception e) {
e.printStackTrace();
}
}