泛型例子:
public class Holder <T> {
private T a;
public Holder(T a){this.a = a;}
public void set(T a){this.a = a;}
public T get() { return a; }
public static void main(String[] args){
Holder <Automobile> h = new Holder<Automobile>(new Automobile());
Automobile a = h.get();
a.helloWorld();
}
}
class Automobile{
public void helloWorld(){System.out.println("HelloWorld!");
}
}
元祖,它是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中元素,但是不允许向其中存放新的对象
public class ThreeTuple<A,B,C> extends TwoTuple<A,B>
泛型接口:
public interface Generator<T> { T next(); }
Java泛型的一个局限性:基本类型无法作为类型参数。不过,Java SE5具备了自动打包和自动拆包的功能,可以很方便地在基本类型和其相应的包装器类型之间进行转换。
泛型方法:
可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系
泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所在,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
定义泛型方法:
public <T> void f(T x){}
注意:当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断。
如果调用f()时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。
类型推断只对赋值操作有效,其他时候并不起作用。如果你将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行类型推断。在这种情况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。
在泛型方法中,可以显示地指明类型,不过这种语法很少使用。要显示地指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号内。如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。
匿名内部类:
泛型还可以应用于内部类以及匿名内部类
擦除:
在泛型代码内部,无法获得任何有关泛型参数类型的信息
Java泛型是使用擦除来实现的,这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型。这两种形式都被擦除成它们的“原生”类型,即List
在基于擦除的实现中,泛型类型被当作第二类类型,即不能在某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如,诸如List<T>这样的类型注解将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object。
注意,对于在泛型中创建的数组,使用Array.newInstance()是推荐方式。
在泛型中的所有动作都发生在边界处——对传递进来的值进行额外的编译期检查,并插入对传递出去的值的转型。这有助于澄清对擦除的混淆,记住,“边界就是发生动作的地方”
擦除的补偿:
擦除丢失了在泛型代码中执行某些操作的能力。任何在运行时需要知道确切类型信息的操作都将无法工作:
public class Erased<T>
{
private final int SIZE=100;
public static void f(Object arg)
{
if(arg instanceof T) { } //Error
T var = new T(); //Error
T[] array = new T[SIZE] ; //Error
T[] array = (T) new Object[SIZE]; //Unchecked warning
}
}
偶尔可以绕过这些问题编程,但是有时必须通过引入类型标签来对擦除进行补偿。这意味着你需要显示地传递你的类型的Class对象,以便你可以在类型表达式中使用它。
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(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
}
}
/*Output:
true
true
false
true
*///
编译器将确保类型标签可以匹配泛型参数
解决new T():
1、传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以使用newInstance()来创建这个类型的新对象:
interface FactoryI<T>
{
T create();
}
class Foo2<T>
{
private T x;
public < F extends FactoryI<T>> Foo2(F factory)
{
x=factory.create();
}
}
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();
}
}
}
public class FactoryConstraint
{
public static void main(String[] args)
{
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
2、另一种方式是模板方法设计模式
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
*
解决new T[SIZE]:
不能创建泛型数组。一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList:
import java.util.*;
public class ListOfgenerics<T>
{
private List<T> array = new ArrayList<T>();
public void add(T item) { array.add(item); }
public T get(int index) { return array.get(index); }
}
这里你将获得数组的行为,以及由泛型提供的编译期的类型安全。