泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。比如我们要写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,我们就可以使用 Java 泛型。
一、定义
1.1、泛型对象
泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。和泛型方法一样,泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。因为他们接受一个或多个参数,这些类被称为参数化的类或参数化的类型。
public class Holder3<T> {
private T a;
public Holder3(T a) {
this.a = a;
}
public void set(T a) {
this.a = a;
}
public T get() {
return a;
}
public static void main(String[] args) {
Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // No cast needed
h3.set(new Automobile());
System.out.println(h3);
// h3.set(1); // Error
}
} ///:~这里的T是随便写的,,也可以用其它的字母来代替,在运行的时候再指定具体的类型。这样在Holder类中就只能存入你指定的类型了。
////可变参数与泛型方法
class GenericVarargs {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
for (T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
1.2、泛型方法
可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
}
/*通用的容器生成器,这个工具类如果做为参数传给另一个方法最好特殊处理*/
class New {
public static <K,V> Map<K,V> map() {
return new HashMap<K,V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> lList() {
return new LinkedList<T>();
}
public static <T> Set<T> set() {
return new HashSet<T>();
}
public static <T> Queue<T> queue() {
return new LinkedList<T>();
}
public static void fun(Map map){
System.out.println(map);
}
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
List<String> ls = New.list();
LinkedList<String> lls = New.lList();
Set<String> ss = New.set();
Queue<String> qs = New.queue();
fun(New.map());
}
}
1.3、泛型接口
泛型可以用于接口,例如替代工厂模式的生成器。工厂模式一般需要参数,而用生成器生成新的对象时,它不需要任何参数。下面是一个生成器的例子。
public interface Generator<T> {
T next();
}
public class Fibonacci implements Generator<Integer> {
private int count = 0;
// 这里的参数是Integer类型的,但使用时可以使用int类型,JAVA的泛型无法把基本类型做为参数,不过它有自动打包拆包的功能。
public Integer next() {
return fib(count++);
}
private int fib(int n) {
if (n < 2) return 1;
return fib(n - 2) + fib(n - 1);
}
public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for (int i = 0; i < 18; i++)
System.out.print(gen.next() + " ");
}
}
另一个生成器例子
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();
}
}
1.4、元组
利用泛型也可以实现元组的概念,元组一般用于一次返回多个类型的参数,之前我们可以用个容器来包装,在取出时在进行类型的判断,这样很麻烦,元组正好解决了这样的问题,元组创建时默认包含了顺序。元组一般长度任意(但一个元组的长度是固定的可以通过继承来扩展长度),如果做为返回数据不允许改变元组中数据的值,他只起一个信使的作用,为了安全可以设置final,也可以设置为private,然后定义get和set方法,这两种安全方式只是编码习惯而已没有好坏之分,建议用第一种。
public class TwoTuple<A,B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) { first = a; second = b; }
public String toString() {
return "(" + first + ", " + second + ")";
}
} public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return "(" + first + ", " + second + ", " + third +")";
}
}
/*这样来调用,在运行时再指定参数,由于构造函数是有参数的所以需要初始化参数*/
new FourTuple<Vehicle,Amphibian,String,Integer>(new Vehicle(), new Amphibian(), "hi", 47);
二、通配符
2.1、super和extends
- <? extends T>:该通配符所代表的类型是 T 类型的子类;
- <? super T>:该通配符所代表的类型是T类型的父类;
class Fruit { }
class Apple extends Fruit { }
class Orange extends Fruit { }
class CovariantArrays {
public static void main(String[] args) {
//extends:面对这种情况最好使用这种方式,表示具有任何从Fruit继承的类型的列表
List<? extends Fruit> flist = new ArrayList<Apple>();
//super:上面的方式只能转型为Object或Fruit,如果想转为固定的类型,可以用super关键字
List<? super Apple> apples;
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // OK
try {
// Compiler allows, but run ArrayStoreException:
fruit[0] = new Orange();
} catch (Exception e) {
System.out.println(e);
}
}
}
2.2、?
类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List<String>、List<Integer> 等所有 List<具体类型实参>的父类。
class CaptureConversion {
static <T> void f1(Holder3<T> holder) {
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
static void f2(Holder3<?> holder) {
f1(holder); // Call with captured type
}
("unchecked")
public static void main(String[] args) {
Holder3 raw = new Holder3<Integer>(1);
f2(raw); //Integer
Holder3 rawBasic = new Holder3();
rawBasic.set(new Object());
f2(rawBasic); // Object
}
}
三、注意事项
3.1、注意事项
- 任何基本类型都不能做为泛型的类型参数,可以使用包装类型,JAVA有自动解压包的功能;
- 一个类不能实现同一个泛型接口的两种变体
interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee implements Payable<Hourly> {} ///:编译错误
- 使用带有泛型类型参数的转型或instanceof不会有任何效果。必要时可以用@SuppressWarnings(“unchecked")来消除警告信息。如果还是强制要求转型,可以用List<Widgert> l1 = List<Widgert>List.class.cast();这种方式来强制转型
- 不能直接继承一个泛型,但可以指定类型后再继承
- 尽量不要把异常泛型化
3.2、类型擦除
泛型有一个限制,就是JDK中没有提供相关的方法获得真实运行时的类型,这意味着你在使用泛型时,任何具体的类型信息都被擦除了(Java 字节代码中是不包含泛型中的类型信息的),你唯一知道的就是你在使用一个对象。即泛型的真实类型全被还原成了最原始的类型或显示继承的类型。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。下面这段代码输出结果为true。
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
如在代码中定义的 List<Object>和 List<String>等类型,在编译之后都会变成 List。JVM 看到的只是 List,而由泛型附加的类型信息对 JVM 来说是不可见的。如下面这个例子,注意第9和第16行的区别。
class HasF {
public void f() { System.out.println("HasF.f()"); }
}
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
//不能通过编译,因为边界被擦除了
public void manipulate() { obj.f(); }
}
class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) { obj = x; }
//能通过编译,因为边界被擦除到了HasF
public void manipulate() { obj.f(); }
} ///:~
class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
manipulator.manipulate();
Manipulator2<HasF> manipulator2 = new Manipulator2<HasF>(hf);
manipulator.manipulate();
}
} ///:~
四、反射补偿
4.1、方案1
class Mime {
public void walkAgainstTheWind() {
}
public void sit() {
System.out.println("Pretending to sit");
}
public void pushInvisibleWalls() {
}
public String toString() {
return "Mime";
}
}
// Does not implement Performs:
class SmartDog {
public void speak() {
System.out.println("Woof!");
}
public void sit() {
System.out.println("Sitting");
}
public void reproduce() {
}
}
class CommunicateReflectively {
public static void perform(Object speaker) {
Class<?> spkr = speaker.getClass();
try {
try {
Method speak = spkr.getMethod("speak");
speak.invoke(speaker);
} catch (NoSuchMethodException e) {
System.out.println(speaker + " cannot speak");
}
try {
Method sit = spkr.getMethod("sit");
sit.invoke(speaker);
} catch (NoSuchMethodException e) {
System.out.println(speaker + " cannot sit");
}
} catch (Exception e) {
throw new RuntimeException(speaker.toString(), e);
}
}
}
public class LatentReflection {
public static void main(String[] args) {
CommunicateReflectively.perform(new SmartDog());
CommunicateReflectively.perform(new Mime());
}
} /* Output:
Woof!
Sitting
Mime cannot speak
Pretending to sit
*///:~
4.2、方案2
public class Apply {
public static <T, S extends Iterable<? extends T>>
void apply(S seq, Method f, Object... args) {
try {
for (T t : seq)
f.invoke(t, args);
} catch (Exception e) {
// Failures are programmer errors
throw new RuntimeException(e);
}
}
}
class Shape {
public void rotate() {
print(this + " rotate");
}
public void resize(int newSize) {
print(this + " resize " + newSize);
}
}
class Square extends Shape {
}
class FilledList<T> extends ArrayList<T> {
public FilledList(Class<? extends T> type, int size) {
try {
for (int i = 0; i < size; i++)
// Assumes default constructor:
add(type.newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class ApplyTest {
public static void main(String[] args) throws Exception {
List<Shape> shapes = new ArrayList<Shape>();
for (int i = 0; i < 10; i++)
shapes.add(new Shape());
Apply.apply(shapes, Shape.class.getMethod("rotate"));
Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 5);
List<Square> squares = new ArrayList<Square>();
for (int i = 0; i < 10; i++){
squares.add(new Square());
}
Apply.apply(squares, Shape.class.getMethod("rotate"));
Apply.apply(squares, Shape.class.getMethod("resize", int.class), 5);
Apply.apply(new FilledList<Shape>(Shape.class, 10), Shape.class.getMethod("rotate"));
Apply.apply(new FilledList<Shape>(Square.class, 10), Shape.class.getMethod("rotate"));
SimpleQueue<Shape> shapeQ = new SimpleQueue<Shape>();
for (int i = 0; i < 5; i++) {
shapeQ.add(new Shape());
shapeQ.add(new Square());
}
Apply.apply(shapeQ, Shape.class.getMethod("rotate"));
}
}