泛型类
1 2 3 4 5 6 7 8
| public class Test<T> { private T testname; private T testfunction(){ } }
|
泛型接口
1 2 3 4 5
| public interface Test<T> { public T test(){};
}
|
需注意点:
- 在声明类实现泛型接口时,若泛型参数未传入实参,则实现类也需要声明泛型
1 2 3
| class MyTest<T> implements Test<T> { public T one(){}; }
|
- 在声明类实现泛型接口时,若泛型参数有传入实参,则实现类里所有使用泛型的地方都需要声明对应的类型
1 2 3 4
| class MyTest implements Test<String> { @override public String one(){}; }
|
泛型方法
1 2 3 4
| public <T> T test(T name){ System.out.print(name); }
|
需注意点:
- 泛型方法也可以定义在泛型类中,泛型方法里的泛型参数 T 不受泛型类的泛型参数 T 的影响,是独立的。
- \<T>用来声明该方法为泛型方法,\只是一个代表符号,也可以用\ 或其他( K,V 等)表达。
- 有了 才可以在泛型方法的参数里声明参数泛型 T ,符号需保持一致,如 对应 E。
泛型通配符
上界通配符
需注意点:
- 上界通配符只支持从通配类型里 get,而不支持将多种类型的 set 进去, 所以上界描述符Extends适合频繁读取的场景。
原因是:
一个Plate<? extends Fruit>的引用,指向的可能是一个Plate类型的盘子,要往这个盘子里放Banana当然是不被允许的。一个理解是:Plate<? extends Fruit>代表某个只能放某种类型水果的盘子,而不是什么水果都能往里放的盘子
下界通配符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| <? super T>
```
需注意点:
* 下界通配符<? super T>不影响往里面存储,但是读取出来的数据只能是Object类型。
原因是:
下界通配符规定了元素最小的粒度,必须是T及其基类,那么我往里面存储T及其派生类都是可以的,因为它都可以隐式的转化为T类型。但是往外读就不好控制了,里面存储的都是T及其基类,无法转型为任何一种类型,只有Object基类才能装下。
### PECS原则
Effective Java书里的PECS原则。
* 上界<? extends T>不能往里存,只能往外取,适合频繁往外面读取内容的场景。 * 下界<? super T>不影响往里存,但往外取只能放在Object对象里,适合经常往里面插入数据的场景。
### 无线通配符
```java public class test<?> {
}
|
泛型的擦除
泛型参数将会被擦除到它的第一个边界(边界可以有多个,重用 extends 关键字,通过它能给与参数类型添加一个边界)。编译器事实上会把类型参数替换为它的第一个边界的类型。如果没有指明边界,那么类型参数将被擦除到Object。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface Test { void f(); }
public class Manipulator<T extends Test> { T obj; public T getObj() { return obj; } public void setObj(T obj) { this.obj = obj; } }
|
extend关键字后后面的类型信息决定了泛型参数能保留的信息。Java类型擦除只会擦除到 Test 类型。
泛型擦除的缺陷
泛型类型不能显式地运用在运行时类型的操作当中,例如:转型、instanceof 和 new。因为在运行时,所有参数的类型信息都丢失了。类似下面的代码都是无法通过编译的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Erased<T> { private final int SIZE = 100; public static void f(Object arg) { if (arg instanceof T) { } T var = new T(); T[] array = new T[SIZE]; T[] array = (T) new Object[SIZE]; } }
|