内部类:一种在类内定义的类,具有很多小派别如:静态嵌套类、非静态内部类(成员类)、局部类、匿名类。
带有static修饰符的类。它不依赖于外部类,是独立的类(不持有外部类的引用)可以直接使用,注意其只能访问外部类的静态变量,故而,个人不将其判定为成员类。
示例:
外部类Outer+静态嵌套类StaticInner
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = staticFinalInt + 1;
private int var;
public static void main(String[] args) {
}
static class StaticInner extends Object {
void print() {
System.out.println(staticInt);
}
}
}
使用javac编译后得到两个class文件: Outer.class+Outer$StaticInner.class
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = 1;
private int var;
// 编译自动生成的构造函数
public Outer() {
}
public static void main(String[] var0) {
}
}
class Outer$StaticInner {
// 编译自动生成的构造函数
Outer$StaticInner() {
}
void print() {
// 直接访问外部类的staticInt变量 编译后会如下加入外部类名字
System.out.println(Outer.staticInt);
}
}
使用javac编译后得到两个class文件: Outer.class+Outer$StaticInner.class
个人理解为定义在类中(不包含块,方法),依赖于外部类,就像非静态成员变量一样,声明时需要有外部类实例(持有外部类的引用)。且可以被外部类不能有的多种修饰符(protected和private)修饰。
示例:
外部类Outer+非静态内部类(成员类)GlobalInner
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = staticFinalInt + 1;
private int var;
public static void main(String[] args) {
}
class GlobalInner extends Object {
void print() {
System.out.println(var);
System.out.println(staticInt);
}
}
}
经javac编译得到Outer$GlobalInner.class,外部类Outer的class文件如上。
class Outer$GlobalInner {
Outer$GlobalInner(Outer var1) {
this.this$0 = var1;
}
void print() {
System.out.println(this.this$0.var);
System.out.println(Outer.staticInt);
}
}
继承成员类示例:
class CC extends Outer.GlobalInner {
public CC(Outer outer) {
outer.super();
}
}
因为成员类依赖于外部类,所以继承成员类一定要声明构造函数,且传入外部类实例,调用成员类构造方法。
定义在方法或块里面的类。由于是局部作用域,所以不能使用static等修饰符修饰。
示例:
外部类Outer+局部类LocalInner
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = staticFinalInt + 1;
private int var;
public static void main(String[] args) {
int partVar = 0;
class LocalInner {
{
System.out.println(partVar);
}
}
}
}
经javac编译得到Outer$GlobalInner.class,外部类Outer的class文件如上。
class Outer$1LocalInner {
Outer$1LocalInner(int var1) {
this.val$partVar = var1;
System.out.println(this.val$partVar);
}
}
突然想到一个问题,这个类名中的数字序号是和啥有关系呢?
做个小实验,如下:
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = staticFinalInt + 1;
private int var;
void printt() {
int var = 0;
class LocalInner {
{
System.out.println(var);
System.out.println(staticFinalInt);
}
}
LocalInner localInner = new LocalInner();
}
public static void main(String[] args) {
int partVar = 0;
class LocalInner {
{
System.out.println(partVar);
}
}
}
void print() {
class LocalInner {
{
System.out.println(var);
}
}
}
void printAA() {
class AALocalInner {
{
System.out.println(var);
}
}
}
}
经javac得四个局部类class文件,分别是Outer$1AALocalInner.class,Outer$1LocalInner.class,Outer$2LocalInner.class,Outer$3LocalInner.class。根据名字我们可以发现,序号只是在类名重复时递增,如果类名不重复,序号永远是1。
那重名类之间的序号是怎样递增呢?接下来看看具体的class文件:
class Outer$1LocalInner {
Outer$1LocalInner(Outer var1, int var2) {
this.this$0 = var1;
// 这个是赋局部变量var值
this.val$var = var2;
System.out.println(this.val$var);
// final变量staticFinalInt 直接替换成0
System.out.println(0);
}
}
class Outer$2LocalInner {
Outer$2LocalInner(int var1) {
// 这个是赋局部变量partVar值
this.val$partVar = var1;
System.out.println(this.val$partVar);
}
}
class Outer$3LocalInner {
Outer$3LocalInner(Outer var1) {
this.this$0 = var1;
System.out.println(this.this$0.var);
}
}
根据上面的变量赋值过程,我们可以得知局部类的非final变量是靠构造函数传递的,final变量是直接替换成值的。而数字序号可以很明显发现,和定义局部类的顺序有关,越靠前则序号越小。
顾名思义,是一种没有名字的内部类,所以匿名类是没有构造器。同局部类一样,是不可以用static等修饰符修饰。一般直接new 类名/接口名 来定义一个匿名类,匿名类只是重写或实现一些方法,而不能自定义一些方法。
外部类Outer+匿名类
public class Outer {
private static final int staticFinalInt = 0;
private static int staticInt = staticFinalInt + 1;
private int var;
void printt() {
int var = 0;
new Runnable() {
@Override
public void run() {
System.out.println(var);
}
};
}
public static void main(String[] args) {
int partVar = 0;
new Runnable() {
@Override
public void run() {
System.out.println(partVar);
}
};
}
void print() {
new Runnable() {
@Override
public void run() {
System.out.println(var);
}
};
}
}
经javac编译得3个匿名类class文件,Outer$1.class,Outer$2.class,Outer$3.class。
class Outer$1 implements Runnable {
Outer$1(Outer var1, int var2) {
this.this$0 = var1;
// 这个是赋局部变量var值
this.val$var = var2;
}
public void run() {
System.out.println(this.val$var);
}
}
class Outer$2 implements Runnable {
Outer$2(int var1) {
// 这个是赋局部变量partVar值
this.val$partVar = var1;
}
public void run() {
System.out.println(this.val$partVar);
}
}
class Outer$3 implements Runnable {
Outer$3(Outer var1) {
this.this$0 = var1;
}
public void run() {
System.out.println(this.this$0.var);
}
}
初学java的时候,就觉得lambda表达式与匿名类长得很像,还以为他们是亲戚。其实,他们是完完全全的两个东西。匿名类,虽然没有名字,但是编译器编译的时候会给他一个名字,格式:{外部类类名$数字序号},总而言之,它依旧是一个类。而lambda表达式,它用于简化代码,还没有成为一个类。
示例:
对比匿名类实现Runnable的run方法
new Thread(()->{
System.out.println("实现runnable");
});
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("实现runnable");
}
});
可以看出来使用lambda表达式确实更简洁。
类型 | 类名格式 | 使用方式 |
---|---|---|
静态嵌套类 | 外部类类名 $ 静态类类名 | Outer.StaticInner staticInner = new Outer.StaticInner() |
非静态内部类(成员类) | 外部类类名 $ 成员类类名 | Outer.GlobalInner globalInner = new Outer().new GlobalInner() |
局部类 | 外部类类名$数字序号局部类类名 | LocalInner localInner = new LocalInner();注意只能在定义类的作用域内使用局部类。 |
匿名类 | 外部类$数字序号 | 在定义处,则使用。如:Runnable runnable = new Runnable() {@Override public void run() {System.out.println(var);} } |
1.通过改变类的修饰符或者定义内部类的位置,来控制类的访问范围。
2.解决类的单继承问题。内部类可以访问外部类的成员变量和方法。
3.匿名类使得代码更简单。
1.当访问非final变量,则以构造器的入参传递给局部类/匿名类;当使用final变量,则直接以值替换变量;
2.简单解释:由于局部类和匿名类的作用域限制,比如一个方法结束了,但是局部类和匿名类还是存在的,他们还需要正常运转,也就意味里面的变量就要能使用,如果变量可改变,就不知道什么时候改变,所以只能访问final变量。
3.java8其实已经不限定只能使用final变量了,只要不对局部变量赋值操作,也能使用。
这是因为非静态内部类始终持有外部类的引用,一旦非静态内部类被其他类使用时,相当于外部类的指针还存在,即便我们没有自己声明使用外部类,外部类的引用依旧被持有,自然可能造成内存泄漏。