- 具有private构造器的类在该类外部不能new对象,也不能在外部被继承。
- 内部类中使用"外部类类名.this"表示外部类的this。
- 对于一个static方法而言,无法访问泛型类的类型参数:
class GenericSetter<T> {
// 编译错误,static方法无法访问泛型类的类型参数:
static void set(T arg) {
System.out.println("GenericSetter");
}
}
如果static方法想要使用泛型能力,就必须使之成为泛型方法。定义泛型方法只需将泛型参数列表置于返回值之前:
class GenericSetter<T> {
// 定义泛型方法:
static <E> void set(E arg) {
System.out.println("GenericSetter");
}
}
- 只有当你希望使用的类型参数比某个具体类型(以及它的所有子类型)更加泛化时——也就是说,当你希望代码能够跨多个类工作时,才有必要使用泛型,否则可以用多态代替。
- 泛型支持多重边界:
< T extends 类名 & 接口名 & 接口名 >
类在前,接口在后,接口可有多个,类只能一个。 - 将子类数组向上转型为父类数组,并向数组中添加父类对象,该行为在编译期不会报错,但运行时会触发异常:
public static void main(String[] args) {
Fruit[] fruits;
Apple[] apples = new Apple[10];
fruits = apples; // 向上转型
// 编译通过,但运行时引发异常:java.lang.ArrayStoreException
fruits[0] = new Fruit();
}
使用泛型容器来代替数组,可以将这种错误检测移入编译期,因为泛型容器不支持这种向上转型:
public static void test01() {
List<Fruit> fruits;
List<Apple> apples = new ArrayList<>();
// 编译不通过!
fruits = apples;
}
Apple是Fruit,但Apple的List不是Fruit的List!
但是,有时你确实想在这两个类型之间建立某种向上转型的关系,这时可以使用通配符:
public static void test01() {
List<? extends Fruit> fruits;
List<Apple> apples = new ArrayList<>();
// 编译通过!
fruits = apples;
// 但是不能往转型后的List添加任何数据,除了null(这没有任何意义)
//! fruits.add(new Object());
//! fruits.add(new Apple());
//! fruits.add(new Fruit());
fruits.add(null);
}
}
不能添加任何数据的原因是:向上转型后的fruits可能是Apple类型的List,也有可能是Apple子类的List,甚至是Apple子类的子类的List,即你不能保证<? extends Fruit>的下界在哪里,因此往其中添加任何对象都有可能造成类型不匹配,而编译器必须规避这种错误,所以。
然而,这并不意味着向上转型已经无计可施,我们还可以走另外一条路,即使用超类型通配符:
public static void test01() {
// <? super Apple>表示Apple或者Apple的父类。
List<? super Apple> supApples;
List<Fruit> fruits = new ArrayList<>();
// 编译通过!
supApples = fruits;
// 只能添加Apple或者Apple的子类!
supApples.add(new Apple());
supApples.add(new RedApple());
// 即便它实际上是一个Fruit的List,但是不能往其中添加Fruit对象
//! supApples.add(new Fruit());
}
<? super Apple>保证了List的下界,因为无论进行怎样的向上转型,supApples都会支持Apple和Apple的子类型,因此向supApples中添加Apple或Apple的子类型是安全的。
- 无界通配符<?>意味着任何事物,表示将持有某种具体类型的同构集合。它与不使用泛型时的情况不同,不使用泛型表示任何类型,而<?>表示类型是具体的,只是我现在还不知道它是什么样的类型。
- 不能使用基本类型作为类型参数,不能创建形如ArrayList< int >之类的东西。