[14] 内部类
1、内部类
什么是内部类,定义在其他类中的类称之为内部类。内部类也可以有访问权限修饰符,甚至可以标记为abstract或final。内部类与外部类实例有特殊的关系,这种关系即允许内部类访问外部类的程序,也包括私有成员。
内部类分为下面四种:
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
1.1 成员内部类
正如其名,内部类声明在某个类中,如果要实例化一个内部类实例,需要一个外部类的实例作为依托,内部类的实例只能通过外部类的实例来访问。
- 不能存在static的变量和方法
- 必须先创建外部类才能创建内部类
- 普通类只有public可用修饰符,但是内部类可以使用private和protected
public class Demo {
private int value = 1;
public class InnerDemo {
public void seeOuterValue() {
System.out.println("Value is:" + value);
}
}
}
11
1
public class Demo {
2
3
private int value = 1;
4
5
public class InnerDemo {
6
public void seeOuterValue() {
7
System.out.println("Value is:" + value);
8
}
9
}
10
11
}
public class Test {
public static void main(String[] args) {
Demo.InnerDemo inner = new Demo().new InnerDemo();
inner.seeOuterValue();
}
}
6
1
public class Test {
2
public static void main(String[] args) {
3
Demo.InnerDemo inner = new Demo().new InnerDemo();
4
inner.seeOuterValue();
5
}
6
}
1.2 局部内部类
局部内部类被定义在外部类的方法中,我们说,内部类必须依托于内部类,这就意味着对于局部内部类来说:
- 如果想使用这个局部内部类,那么你必须在这个方法中实例化这个内部类
- 只有abstract和final可以修饰局部内部类
- 方法的局部变量为final或参数是effectively final,局部内部类才能使用
当变量或参数在初始化之后,值再也没改变过,则说明其为effectively final
public class Demo {
private int value = 1;
public void doSth() {
//局部内部类
class InnerDemo {
public void seeOuterValue() {
System.out.println("Value is:" + value);
}
}
//在方法体中实例化,才能使用局部内部类
InnerDemo inner = new InnerDemo();
inner.seeOuterValue();
}
}
16
1
public class Demo {
2
private int value = 1;
3
4
public void doSth() {
5
//局部内部类
6
class InnerDemo {
7
public void seeOuterValue() {
8
System.out.println("Value is:" + value);
9
}
10
}
11
//在方法体中实例化,才能使用局部内部类
12
InnerDemo inner = new InnerDemo();
13
inner.seeOuterValue();
14
}
15
16
}
public class Test {
public static void main(String[] args) {
Demo demo = new Demo();
demo.doSth();
}
}
6
1
public class Test {
2
public static void main(String[] args) {
3
Demo demo = new Demo();
4
demo.doSth();
5
}
6
}
为什么必须是final或effectively final,因为从表面上看这些变量是同一个东西,实际上在编译时外部类和内部类是两个class文件,也就是说两者是独立的。内部类并不是直接调用方法传递的参数,而是利用自身的构造函数对传入的参数进行了备份,也就是实际上是调用的自身属性,其值是从外部参数拷贝过来的。那么假如不是final,方法参数发生了变化,而内部类的属性还是原来的值,显然是不合理的,因为在我们眼里,他们俩是一个整体,为了避免出现这种情况,就使用final来让该引用的参数不可变。
1.3 匿名内部类
从学习以来不论是知识点还是面试常见的问题,总会问到诸如接口和抽象类可以被实例化吗?显然是不能。很多时候我们采用的new然后直接跟接口或抽象类的形式,这又算什么呢?这就是匿名内部类:
- 没有名字(表面上似乎有名字,但那并不是它们的名字,定义时直接创建该类的对象)
- 只能被实例化一次(无法重复使用)
- 创建匿名内部类时会立即创建一个该类的实例,该类的定义会立即消失
- 匿名内部类没有类名,无法“new 类名”来声明新的对象,所以就当前这一次
- (没有名字和只能实例化一次,我仍然有点迷糊)
- 通常被申明在方法或代码块的内部
- 没有构造函数
- 不能是静态static的,也不能包括静态变量和方法
- 匿名内部类也是一种局部内部类,所以局部内部类的显示对匿名内部类适用
匿名类相当于在定义类的同时再新建这个类的实例。一个匿名类的创建有下面几个部分:
- new:操作符
- 接口名:或抽象类/普通类
- () :这个括号表示构造函数的参数列表,接口没有构造函数所以一般填写空括号
- {...}:大括号中间代码表示类的内部结构,也可以定义变量方法等,和普通类一样
public abstract class Animal {
public abstract void run();
}
3
1
public abstract class Animal {
2
public abstract void run();
3
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal() {
@Override
public void run() {
System.out.println("run!");
}
};
animal.run();
}
}
13
1
public class Test {
2
public static void main(String[] args) {
3
4
Animal animal = new Animal() {
5
6
public void run() {
7
System.out.println("run!");
8
}
9
};
10
11
animal.run();
12
}
13
}
内部类是可以有构造方法的,但由于匿名内部类的特殊性,其没有构造函数!那么很多初始化工作我们如何完成?使用代码块。
public class Test {
public static void main(String[] args) {
Animal animal = new Animal() {
{
System.out.println("start init");
}
@Override
public void run() {
System.out.println("run!");
}
};
animal.run();
}
}
x
1
public class Test {
2
public static void main(String[] args) {
3
4
Animal animal = new Animal() {
5
{
6
System.out.println("start init");
7
8
}
9
10
11
public void run() {
12
System.out.println("run!");
13
}
14
};
15
16
animal.run();
17
}
18
}
1.4 静态内部类(嵌套内部类)
使用static修饰的内部类就是静态内部类,那么:
- 创建不再依托于外部类
- 不能使用外部类的非static成员变量和方法
(感觉和静态方法差不多)
2、小结
成员内部类:
- 必须有外部类作为依托
- 不能有static变量和方法
- 可以由private和protected修饰
局部内部类:
- 在方法中实例化后才能使用
- 方法局部变量必须是final
匿名内部类:
- 不能有static变量和方法
- 没有名字,只能使用一次
- 没有构造函数
- 方法变量必须是final
静态内部类:
- 和静态方法差不多
- 不再依托外部类
- 不能使用外部类的非静态成员变量和方法