#0
在上一篇文章中,我以虚拟机中.class文件的格式作为引子引出了对Class类的介绍。其中包括对类的判断、访问控制符、反射与实例化以及表示Class名称的几种方法。这一篇文章从表示Class名称的一种方法出发,介绍Class知识体系中有关内部类的那部分。本文的主要内容包括:
- 内部的介绍
- 四种内部类的表现形式和作用
- Class类中与内部类有关的方法
本文所涉及的内容可以参照思维导图。
#1
回顾上一篇文章的最后一部分内容,有一个表示Class名称的方法getCanonicalName
,它的代码长这样。
public String getCanonicalName() {
if (isArray()) {
String canonicalName = getComponentType().getCanonicalName();
if (canonicalName != null)
return canonicalName + "[]";
else
return null;
}
if (isLocalOrAnonymousClass())
return null;
Class> enclosingClass = getEnclosingClass();
if (enclosingClass == null) { // top level class
return getName();
} else {
String enclosingName = enclosingClass.getCanonicalName();
if (enclosingName == nu
return null;
return enclosingName + "." + getSimpleName();
}
}
你一定注意到了,这里面有两个陌生的方法isLocalOrAnonymousClass()
和getEnclosingClass
。这分别是用于判断一个类是不是内部类的方法,以及用于获取包含内部类的顶级类的方法。事实上,自定义的类分为顶级类和内部类。
// 顶级类示意
class upperClass {}
// 内部类示意
class upperClass {
class innerClass{}
}
如同字面意思
- 把类的定义置于顶级类内部的,就是内部类(innerClass)
- 其他的是顶级类(upperClass)
内部类一共有四种,分别是
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
看到这里让我想起有篇文章《孔乙己》,满口的之乎者也和四种"茴"字的写法。不过这里并不需要死记硬背,先允许我卖个关子,在文末我将揭晓内部类究竟有什么作用。
// 成员内部类
class upperClass {
class innerClass(
)
}
// 静态内部类
class upperClass {
static class innerClass {
}
}
// 局部内部类
interface IDestination {
}
class upperClass {
public IDestination getDestination(String s) {
class Destination implements IDestination {
private String label;
private Destination(String whereTo) {
label = whereTo;
}
}
return new Destination(s);
}
}
// 匿名内部类
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类");
}
});
当在类的作用域中定义内部类,那么就是成员内部类或静态内部类。当在一个方法里面或在任意的作用域内定义内部类,那么就是局部内部类或匿名内部类。
#10
为了更好的了解Class中是怎么判断内部类,我们先看getEnclosingClass()
这个方法。
public Class> getEnclosingClass() throws SecurityException {
// There are five kinds of classes (or interfaces):
// a) Top level classes
// b) Nested classes (static member classes)
// c) Inner classes (non-static member classes)
// d) Local classes (named classes declared within a method)
// e) Anonymous classes
EnclosingMethodInfo enclosingInfo = getEnclosingMethodInfo();
Class> enclosingCandidate;
// 通过getEnclosingMethodInfo返回的信息进行判断
if (enclosingInfo == null) {
// This is a top level or a nested class or an inner class (a, b, or c)
enclosingCandidate = getDeclaringClass();
} else {
Class> enclosingClass = enclosingInfo.getEnclosingClass();
// This is a local class or an anonymous class (d or e)
if (enclosingClass == this || enclosingClass == null)
throw new InternalError("Malformed enclosing method information");
else
enclosingCandidate = enclosingClass;
}
// a、b、c enclosingCandidate = getDeclaringClass()
// d、e enclosingCandidate = enclosingInfo.getEnclosingClass() 直接通过native方法返回的内部类的Class
// Reflection.getCallerClass是得到调用Class的类,也就是调用者
// 那么就是判断调用者是否拥有访问Class中内部类的权限
if (enclosingCandidate != null)
enclosingCandidate.checkPackageAccess(
ClassLoader.getClassLoader(Reflection.getCallerClass()), true);
return enclosingCandidate;
}
getEnclosingClass()
的目的是为了获取包含内部类的顶级类,如果是顶级类调用这个方法,那么将返回null。
在注释中很明确地表示了包含顶级类在内的五种类。为了方便理解,这里会重新解释getSimpleName()
和getSimpleBinaryName()
的作用getEnclosingClass()
的逻辑很清楚
首先通过
getEnclosingMethodInfo()
获取包含内部类方法的信息(EnclosingMethodInfo)根据返回的信息enclosingInfo进行判断
- 如果enclosingInfo为空,那么意味着这不是本地内部类或匿名内部类,调用
getDeclaringClass()
获取内部类的Class - 否则,这是本地内部类或匿名内部类,那么通过enclosingInfo获取内部类的Class
- 如果enclosingInfo为空,那么意味着这不是本地内部类或匿名内部类,调用
判断调用者(某个类)是否拥有访问内部类的权限
返回这个类的信息
getEnclosingMethodInfo()
返回的类型是EnclosingMethodInfo,这个类包含了三部分的信息
- 在方法内部的内部类的Class类
- 方法或构造函数的名称
- 方法或构造函数的修饰符
getEnclosingMethodInfo
的代码逻辑如下
private EnclosingMethodInfo getEnclosingMethodInfo() {
// getEnclosingMethod0返回一个长度为3的数组
// 0,表示内部类
// 1,方法或构造函数的名称
// 2,方法或构造函数的修饰符
Object[] enclosingInfo = getEnclosingMethod0();
if (enclosingInfo == null)
return null;
else {
return new EnclosingMethodInfo(enclosingInfo);
}
}
EnclosingMethodInfo
是Class的静态内部类,这个类当中包含的信息也正如前文所述
private final static class EnclosingMethodInfo {
private Class> enclosingClass;
private String name;
private String descriptor;
private EnclosingMethodInfo(Object[] enclosingInfo) {
if (enclosingInfo.length != 3)
throw new InternalError("Malformed enclosing method information");
try {
enclosingClass = (Class>) enclosingInfo[0];
assert(enclosingClass != null);
name = (String) enclosingInfo[1];
descriptor = (String) enclosingInfo[2];
assert((name != null && descriptor != null) || name == descriptor);
} catch (ClassCastException cce) {
throw new InternalError("Invalid type in enclosing method information", cce);
}
}
// 判断信息是否完整
boolean isPartial() {
return enclosingClass == null || name == null || descriptor == null;
}
// 判断是否是构造函数
boolean isConstructor() { return !isPartial() && "".equals(name); }
// 判断是否是函数
boolean isMethod() { return !isPartial() && !isConstructor() && !"".equals(name); }
Class> getEnclosingClass() { return enclosingClass; }
String getName() { return name; }
String getDescriptor() { return descriptor; }
}
EnclosingMethodInfo
当中存在一个判断函数isConstructor
,现在让我们来看看这个函数会起到什么作用。
内部类除了可以定义来普通函数中,也可以定义在构造函数中。getEnclosingConstructor
就是用来获取包含内部类的构造函数的方法。让我们随着代码,通过注释弄明白这个函数究竟干了什么事情。
public Constructor> getEnclosingConstructor() throws SecurityException {
// 首先获取EnclosingMethodInfo,通过native的方法获取前文描述的信息
EnclosingMethodInfo enclosingInfo = getEnclosingMethodInfo();
if (enclosingInfo == null)
return null;
else {
if (!enclosingInfo.isConstructor())
return null;
// 到了这里意味着两件事,首先存在内部类,其次这个内部类定义在构造函数中
// 通过ConstructorRepository.make获取构造函数的参数信息
ConstructorRepository typeInfo = ConstructorRepository.make(enclosingInfo.getDescriptor(),
getFactory());
Type [] parameterTypes = typeInfo.getParameterTypes();
Class>[] parameterClasses = new Class>[parameterTypes.length];
// 将参数列表从Type转换为Class
// 这一步是因为方法描述符没有使用泛型信息,因此要通过这种方法把泛型信息抹除掉
for(int i = 0; i parameterClasses[i] = toClass(parameterTypes[i]);
Class> enclosingCandidate = enclosingInfo.getEnclosingClass();
// 判断构造函数所处的类拥有该构造函数的访问权限
enclosingCandidate.checkMemberAccess(Member.DECLARED,
Reflection.getCallerClass(), true);
// 遍历该类的所有构造函数
// 比较参数列表,如果匹配上了话就返回
for(Constructor> c: enclosingCandidate.getDeclaredConstructors()) {
Class>[] candidateParamClasses = c.getParameterTypes();
if (candidateParamClasses.length == parameterClasses.length) {
boolean matches = true;
for(int i = 0; i if (!candidateParamClasses[i].equals(parameterClasses[i])) {
matches = false;
break;
}
}
if (matches)
return c;
}
}
throw new InternalError("Enclosing constructor not found");
}
}
最后在这里列举判断内部类类别的方法,这些方法都很好理解。
public boolean isAnonymousClass() {
return "".equals(getSimpleName());
}
public boolean isLocalClass() {
return isLocalOrAnonymousClass() && !isAnonymousClass();
}
public boolean isMemberClass() {
return getSimpleBinaryName() != null && !isLocalOrAnonymousClass();
}
private boolean isLocalOrAnonymousClass() {
return getEnclosingMethodInfo() != null;
}
11
为什么需要内部类
在Class中内部类只占有很少的篇幅,但是内部类这个特性还是需要每个Javaer去好好领会的。Java语言在诞生之初,为了避免菱形继承带来的歧义只允许使用单继承。
但只使用单继承使得类的表现能力受限,通过实现多个接口,子类能够具有多种行为。
但是实现多个接口,需要子类重写相应的函数。如果又希望子类具有多种行为,又希望能够复用相应函数,并且避免多继承带来的语法歧义,内部类就被设计出来了。
通过这种方式,类C就能够继承并复用类A和类B的方法。
class A {
public void make() {
System.out.println("使用A的能力");
}
}
class B {
public void make() {
System.out.println("使用B的能力");
}
}
public class C {
class innerClassA extends A {
public void make() {
super.make();
}
}
class innerClassB extends B {
public void make() {
super.make();
}
}
void make() {
innerClassA a = new innerClassA();
innerClassB b = new innerClassB();
a.make();
b.make();
}
public static void main(String[] args) {
C c = new C();
c.make();
}
}
通过普通内部类和静态内部类能够变相实现多重继承,而局部内部类和匿名内部类只是因为有些时候,我们只需要「一个」并且「比较简短」的函数作为函数的参数。但是由于Java在Java8之前是一个纯粹地面向对象的语言,它不支持将函数作为函数的参数进行传递。由于局部内部类使用的比较少,而且其提供的功能使用匿名内部类都可以实现,因此这部分就通过匿名内部类来说明情况。以创建线程为例
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("执行run");
}
}
// 这是不使用匿名内部类的方式
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
// 这是使用匿名内部类的方式
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类创建线程的方式");
}
});
当创建线程时,这个线程真正需要的是run方法当中的逻辑,而定义MyRunnable类实现Runnable接口,通过new创建MyRunnable实例都不是创建线程真正需要的步骤,只是由于Java在当时是纯粹的面向对象语言,所以必须定义类,创建实例,然后作为参数进行传递。匿名内部类的意义是为了将这些多余的步骤「省略」。
内部类与外部类的联系
内部类的另外一个特性是不需要任何特殊条件,内部类能够访问外部类的所有内容,包括通过private修饰的属性与方法。
以普通内部类为例,这是因为编译器在编译的时候会让内部类对象持有外部类对象的引用,然后所有访问外部类内容的行为都由这个引用代劳了。这部分可以参考我之前写过的一篇介绍ThreadLocal的文章
内部类与内存泄漏
内存泄漏是指内存中存在一些内存空间可以被回收的对象由于某些原因没有被回收,因此产生了内存泄漏。
想象这样的场景,依旧是之前的例子,类C存在两个内部类
//C.java
public class C {
class innerClassA extends A {
public void make() {
super.make();
}
}
class innerClassB extends B {
public void make() {
super.make();
}
}
// 省略部分方法
}
// 在另外一个类中分别创建外部类C和内部类innerClassA的实例
public class Test {
void test() {
C c = new C();
Class.C.innerClassA ainnerA = c.new innerClassA();
c = null;
}
}
从程序员的角度,c = null
的这个时候是程序员将栈中指向堆中实例对象的引用置空,这样使得堆中的对象得以通过GC释放内存空间,但是由于内部类的实例对象存在,仍旧存在指向外部类实例对象的引用,导致外部类的实例对象无法被GC,这就是所谓的「可以被回收的对象由于某些原因没有被回收」。
100
最近有点懈怠了,加上自己菜阅读Class和ClassLoader的源码进度受阻,不过很快就能调整过来。下期的内容是有关Class的反射,这部分会涉及到Class三大基本组成部分:域(Field)、方法(Method)和构造函数(Constructor),敬请期待吧。