Java 内部类和泛型的理解
Java 内部类
Java非静态内部类持有对外部类的引用,可以访问外部类的状态
使用方法 OuterClass.this.variable
编译结果 OuterClass$InnerClass
内部类访问修饰符可以为private
,而普通类的修饰符不能是private,只可以具有包可见性或者公有可见性
实例化一个公有内部类:
Outer_Class outer = new Outer_Class();
Outer_Class.Inner_Class inner = outer.new Inner_Class();
- 内部静态类不需要有指向外部类的引用;但非静态内部类需要持有对外部类的引用
- 非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员,只能访问外部类的静态成员。
- 一个非静态内部类不能脱离外部类实体被创建,一个非静态内部类可以访问外部类的数据和方法,因为他就在外部类里面。
注意
- 内部类的静态域必须是final
- 内部类不能有static方法
内部类似C++ 中的嵌套类,方便进行命名控制和访问控制
局部内部类
定义在方法内部,不能用private或public访问说明符修饰,作用域被限定到这个局部类的块中
局部类不仅可以访问外部类,还可以访问final局部变量
原理:
局部内部类有一个实例域(this$0
),是外部类的引用
局部内部类对要访问的局部变量对应的备份
final表明初始化后不能再修改数据,保证了局部变量与局部类内部建立的拷贝是一致的
匿名内部类
如果只创建这个类的一个对象,可以不必命名,称为匿名内部类(anonymous inner class)
匿名内部类在声明的同时,进行实例化。用来重载类或者接口的方法
new AnonymousInner(){
inner class methods and data
}
因为匿名类没有类名,所以也没有构造器,若有需要应该把参数传递给超类构造器
在构造参数的闭小括号后面跟一个开大括号,正在定义的就是匿名内部类
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
//处理对应的消息
}
};
静态内部类
只是为了把一个类隐藏在另一个类的内部,不需要引用外围类的对象
静态内部类可以有静态域和方法
声明在接口中的内部类,自动成为public和static类
接口
接口中可以包含常量,不能包含实例域,
可以包含静态方法,可以包含默认方法(用default修饰符标记)
泛型
泛型编程(generic programming)可以方便代码被不同的类型重用
泛型的本质是参数化类型(Parametersized Type),操作的数据类型被指定为一个参数,这种类型参数可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法
泛型方法的定义:类型变量放在修饰符的后面,返回类型的前面
public <T> T getMiddle(T...a)
调用时可以省略类型参数,编译器可以自行推断
Java泛型实现是通过类型擦除实现的,类型参数被替换为原生类型(Raw Type)(或者说是限定类型),代码相应地方被插入类型强制转换,这实际是一种伪泛型
C++的泛型是真实的,例如List<int>
和List<String>
是两种不同的类型,编译生成不同的代码,成为这种泛型会导致类型膨胀
由于类型擦除,可能会影响多态性,引入桥方法(编译器实现的方法,实现对泛型擦除后的方法的覆盖)
注意:
- 运行时进行泛型类型查询只产生原始类型 ,例如
ArrayList<int> a ;
ArrayList<String> b;
a.getClass()==b.getClass()//类型都是ArrayList.class
- 支持可变长度的泛型类型的参数,使用
@SafeVarargs
抑制警告
@SafeVarargs static <E> E[] array(E... array) {return array; }
- 不能在静态字段或方法中引用类型变量
private static T field; //错误
- 不能创建泛型数组,数组会记住元素的类型,如果存入类型不正确应该抛出
ArrayStoreException
异常,然而类型擦除使得泛型的参数类型消失,在运行中无法进行监测
但是允许创建通配符类型的数组
// Not really allowed.
List<String>[] lsa = new List<String>[10];
// OK, array of unbounded wildcard type.
List<?>[] lsa = new List<?>[10];
声明参数化类型的数组是可行的,所以可以实现如下的泛型数组
//HashMap中的泛型数组
@SuppressWarnings({"rawtypes","unchecked"})
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
//ConcurrentHashMap中的泛型数组
@SuppressWarnings("unchecked")
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
通配符类型
通配符类型允许类型参数变化,通配符?
,分为子类型限定、超类型限定和无限定。通配符不是类型变量,因此不能在代码中使用"?"作为一种类型
子类型限定
表示类型的上界,格式? extends A
作用:主要用来读取数据,可以访问A及其子类型
超类型限定
表示类型的下界,限定为A和A的超类型,格式是? super A
特点:
作用:主要用来写入数据,可以写入A及其子类型
public static void addNumbers(List<? super Number> list) {
for (int i = 1; i <= 10; i++) {
list.add(i); //可以添加Integer以及它的子类
}
}
举例:
Collections
类中的public static <T extends Comparable<? super T>> void sort(List<T> list)
方法的类型声明,可以处理T没有实现Comparable
接口,而T的父类实现了的情况,类似于外部提供比较器的方法:
public static <T> void sort(List<T> list, Comparator<? super T> c)
无限定通配符
用法 ?
,例如List<?>
,表示未知类型的list
- 如果方法可以通过
Object
类提供的函数实现功能 - 使用泛型类的方法,不依赖类型参数的情况,例如
List.size List.clear
使用场景:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
List<Object>
和List<?>
是不同的 ,List<?>
只能插入null
综合理解
//定义 Level3 extends Level2,Level2 extends Level1
public static void main(String[] args) {
List<? extends Level2> a = new ArrayList<>();
List<? super Level2> b = new ArrayList<>();
// a.add(new Level1(0));
// a.add(new Level2(0)); // 错误
// a.add(new Level3(0));
a.add(null); // works
Level1 a1 = a.get(0);
Level2 a2 = a.get(0);
// Level3 a3 = a.get(0); //错误
// b.add(new Level1(0)); //错误
b.add(new Level2(0));
b.add(new Level3(0));
Object o = b.get(0);
// Level1 b1 = b.get(0);
// Level2 b2 = b.get(0); // 错误
}
虚拟机中的泛型类型信息
Class文件实际保留了泛型信息,位于在Signature
属性中,类型擦除只是对Code
属性中的字节码
可以通过反射读取相关信息,其核心是Type
类型,根据其不同的子类型,获取泛型信息,具体方法可以查看API文档