内部类
概述
- 把类定义在其他类的内部,这个类就被称为内部类。
- 举例:在类A中定义了一个类B,类B就是内部类。
- 内部类访问特点
- 内部类可以直接访问外部类的成员,包括私有。
- 外部类要访问内部类的成员,必须创建对象。
- 内部类作用
- 部类作用主要实现功能的隐藏、减少内存开销,提高程序的运行速度
- 内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。
- 对于一个名为outer的外部类和其内部定义的名为inner的内部类。
- 编译完成后出现outer.class和outer$inner.class两类。
内部类分类
- Java中的内部类共分为四种:
- 静态内部类static inner class (also called nested class)
- 成员内部类member inner class
- 局部内部类local inner class
- 匿名内部类anonymous inner class
- 按照内部类位置分类
- 成员位置:在成员位置定义的类,被称为成员内部类。
- 局部位置:在局部位置定义的类,被称为局部内部类。
成员内部类
作为外部类的一个成员存在,与外部类的属性、方法并列。可通过外部类对象进行访问。
定义格式
1
2
3
4
5
6
7
8// 外部类
修饰符 class 类名称{
// 内部类
修饰符 class 类名称{
...
}
...
}外部类静态方法中不能直接访问实例内部类数据,要用对象调用
访问内部类的成员:
- 格式:
外部类名.内部类名 对象名 = 外部类对象.内部类对象;
- 格式:
特点
成员内部类可以访问外围类的所有成员,包括私有成员;
只有内部类可以是私有类,而常规类只可以是protected或public
成员内部类是不可以声明静态成员(包括静态变量、静态方法、静态成员类、嵌套接口)
- 但有个例外—可以声明
static final
的变量, 这是因为编译器对final
类型的特殊处理,是直接将值写入字节码; static final int N=22
;编译期能识别,编译后所有N用22替代了
- 但有个例外—可以声明
成员内部类对象都隐式地保存了一个引用,指向创建它的外部类对象;或者说,成员内部类的入口是由外围类的对象保持着(静态内部类的入口,则直接由外围类保持着),通过这个引用,内部类就可以无限制的访问外部类的成员了。
成员内部类可以继续包含成员内部类,而且不管一个内部类被嵌套了多少层,它都能透明地访问它的所有外部类所有成员;
成员内部可以继续嵌套多层的成员内部类,但无法嵌套静态内部类;静态内部类则都可以继续嵌套这两种内部类。
成员内部类中的 this,new关键字:
- 获取外部类对象:
OuterClass.this
- 获取外部类对象:
明确指定使用外部类的成员(当内部类与外部类的名字冲突时):
OuterClass.this.成员名
创建内部类对象的new:
外围类对象.new
1
2
3
4//先创建外围类对象
OuterClass outer=new OuterClass();
//创建成员内部类对象
OuterClass.InnerClass inner=outer.new InnerClass();
使用
定义类
1
2
3
4
5
6
7
8
9class Body {//外部类,身体
private boolean life= true; //生命状态
public class Heart { //内部类,心脏
public void jump() {
System.out.println("心脏噗通噗通的跳");
System.out.println("生命状态" + life); //访问外部类成员变量
}
}
}
创建内部类对象
创建对象:
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
1
2
3
4
5
6public static void main(String[] args) {
//创建内部类对象
Body.Heart bh = new Body().new Heart();
//调用内部类中的方法
bh.jump();
}
外部内中创建内部类对象
创建内部类的对象时,必须由此外围类的对象来创建其内部类的一个对象
- 内部类的对象被创建的时候,内部类的对象会悄悄地链接到创建它地外部类地对象,从而获得它地一个隐式引用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class OutterClass {
private String name;
private int age;
//外部类的非静态方法访问成员内部类
public void getInnerClass(){
InnerClass innerClass = new InnerClass();
System.out.println(innerClass);
}
//外部类的静态方法访问成员内部类,和外部类的外部访问内部类的方法一样
public static void get(){
OutterClass outterClass = new OutterClass();
InnerClass innerClass = outterClass.new InnerClass();
}
//一个内部类
class InnerClass{
private String nickName;
private String name;
}
}
内部类生成对外部类对象的引用
使用外部类.this,这样就能够产生一个正确引用外部类的引用了
非静态内部类持有外部类的引用 使用不慎会造成内存溢出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class App {
private String name;
public int step = 3;
public class InnerClass {
private String nickName;
private String name;
public App getOuterClass() {
//在内部类中,访问自己的变量时直接使用变量名,也可以使用this.变量名
System.out.println(nickName);
System.out.println(this.nickName);
//在内部类中访问外部类中与内部类同名的实例变量:外部类名.this.变量名
System.out.println(OutterClass.this.name);
//如果内部类中没有与外部类同名的变量,则可以直接用变量名访问外部类变量
System.out.println(step);
return App.this;
}
}
public static void main(String[] args) {
App. InnerClass innerClass = new App().new InnerClass();
App outerClass = innerClass.getOuterClass();
System.out.println(outerClass.step);
}
}
继承成员内部类
在内部类的访问权限允许的情况下,成员内部类也是可以被继承的
由于成员内部类的对象依赖于外围类的对象
成员内部类的构造器入口由外围类的对象把持着。
继承了成员内部类的子类必须要与一个外围类对象关联起来。
子类的构造器是必须要调用父类的构造器方法,所以也只能通过父类的外围类对象来调用父类构造器。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31class ClassA{
class ClassB{ //内部类
private String name;//内部类的私有属性
public ClassB(String name){//内部类的构造方法
this.name = name;
};
public String info(){//内部类的成员方法
return name;
}
} //内部类定义完毕
}
public class OutputInnerClass extends ClassA.ClassB{
public OutputInnerClass(ClassA a, String s){
//new ClassA().super();
// 第一行,必须是调用父类的构造方法
// a 是 类 A (class A) 的一个实体(对象)。
a.super(s); // 首先调用父类(ClassA 的内部类)的构造方法
// 执行 a.super(s), 将使用字符串 s 作为参数来调用类A (class A) 的内部类 B (class B) 的要求一个字符串参数的构造方法,
// 也就是调用类OutputInnerClass的超类(class B)的、要求一个字符串参数的构造方法,以便首先创建父类对象。
}
public static void main(String[] args){
// 创建子类对象时一定要先创建父类对象吗
ClassA a = new ClassA();
OutputInnerClass oi = new OutputInnerClass(a,"顺应天意了却凡尘" );
System.out.println(oi.info());
}
}
外部类不可以直接访问非静态内部类的成员
非静态内部类可以直接访问外部类的成员,
而外部类不可以直接访问非静态内部类的成员
- 非静态内部类没有被static修饰,所以这个内部类就不是类相关的,也就说不是类的,是实例的
- 但是我们非静态内部类要创建实例,外部类一定会先创建一个外部类的实例,非静态内部类的实例就是寄生在外部类的实例上的。
- 所以,非静态内部类的实例可以直接访问外部类的成员,因为,外部类已经创建一个实例的,内部类保留了外部类创建的实例的引用
静态内部类是被static修饰的,所以是类的一员。
根据静态成员不能访问非静态成员的原则,静态内部类是不能访问外部类的非静态成员的
示例代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29class Outer {
public String name = "MAH";
private static int num = 100;
//非静态内部类
public class Inner {
//非静态内部类的Feild
public int Num = 4;
//例外:这里的变量a相当于是一个常量
static final int a=1;
public void acc() {
System.out.println(num);
System.out.println(name);//非静态内部类可以直接访问外部类的成员
}
}
public void accessInner() {
// 不可以直接访问非静态内部类的成员
System.out.println("内部类的Num值是:" + new Inner().Num);
System.out.println("内部类的静态变量的值是"+ Inner.a);
new Inner().acc();
}
public static void main(String[] args) {
Outer b = new Outer();
//外部类通过调用accessInner方法产生一个非静态内部类的实例
b.accessInner();
}
}
局部内部类
- 在方法中定义的内部类称为局部内部类。
- 局部类不能用访问修饰符进行声明,它的作用域被限定在声明这个局部类的块(方法)中。
- 局部类有一个优势,即是对外部世界可以完全地隐藏起来。即是是OutterClass类中的其他代码也不能访问它。
访问限制
对于局部变量,局部内部类只能访问
final
的局部变量。- 在JDK8中如果我们在匿名内部类中需要访问局部变量,那么这个局部变量不需要用final修饰符修饰。因为在生成的匿名内部类中维护着外部类实例的引用, 在内部类调用该变量时, 实际上是通过外部类的实例来调用的, 也就是两个实例引用着同一个内存地址, 保证了数据一致性
局部类不仅能够访问包含它们的外部类,还可以访问局部变量。但是局部变量必须事实上为final。用final修饰实际上就是为了保护数据的一致性(对引用变量来说是引用地址的一致性,对基本类型来说就是值的一致性)。在内部类中,局部变量是被当成了参数传递给匿名对象的构造器,作为成员变量在堆上存在。只需要保证这个变量代表的值或地址不变就行了。
对于类的全局成员,局部内部类定义在实例环境中(构造器、对象成员方法、实例初始化块),则可以访问外围类的所有成员
但如果内部类定义在静态环境中(静态初始化块、静态方法),则只能访问外围类的静态成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35public class OuterClass {
private int a = 21;
static {//静态域中的局部内部类
class LocalClass1{
// int z = a; //错误,在静态的作用域中无法访问对象成员
}
}
{//实例初始化块中的局部内部类
class localClass2{
}
}
public OuterClass(){
int x = 2;
final int y = 3;
// x = 3;//若放开此行注释,编译无法通过,因为局部变量x已经是final类型
//构造器中的局部内部类
class localClass3{
int z = y; //可以访问final的局部变量
int b = a;//可以访问类的所有成员
//1.8后可以访问没有用final修饰的局部变量
int c = x;
}
}
public void createRunnable() {
final int x = 4;
//方法中的局部内部类
class LocalClass4 implements Runnable {//
public void run() {
System.out.println("局部final变量:"+x);
System.out.println("对象成员变量:"+a);
}
}
}
}
访问
外部类方法中,创建内部类对象,进行访问
1
2
3
4
5
6
7
8
9
10
11class Party {//外部类,聚会
public void puffBall(){// 吹气球方法
class Ball {// 内部类,气球
public void puff(){
System.out.println("气球膨胀了");
}
}
//创建内部类对象,调用puff方法
new Ball().puff();
}
}方法外:创建外部类对象访问方法,方法中调用内部类的对象
1
2
3
4
5
6public static void main(String[] args) {
//创建外部类对象
Party p = new Party();
//调用外部类中的puffBall方法
p.puffBall();
}
匿名内部类
匿名内部类(anonymous inner class),简单地说:匿名内部类就是没有名字的内部类。什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:
匿名内部类就是实现接口的类 或 继承了该类 的 对象
类在定义后马上用到。
类非常小(SUN推荐是在4行代码以下)
给类命名并不会导致你的代码更容易被理解。
格式
1
2
3new 父类(参数列表) | 实现接口(){
//匿名内部类的类体部分
}
使用原则
匿名内部类不能有构造方法。
- 构造器和类名一样
- 匿名就是没有名字
不能定义任何静态成员,方法和类。
- 只有静态内部类才能定义静态
- static final 除外
匿名内部类不能是public,protected,private,static。
必须实现一个类或一个接口。
- 返回的是子类 或 实现类 的子类对象
因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。
1
2
3
4
5
6
7
8
9
10
11
12
13//已经存在的父类:
public abstract class Person{
public abstract void eat();
}
//定义并创建该父类的子类对象,并用多态的方式赋值给父类引用变量
Person p = new Person(){
public void eat() {
System.out.println("我吃了");
}
};
//调用eat方法
p.eat();匿名内部类如果不定义变量引用,则也是匿名对象
1
2
3
4
5
6// 将定义子类与创建子类对象两个步骤由一个格式一次完成
new Person(){
public void eat() {
System.out.println(“我吃了”);
}
}.eat();传递参数
- 匿名内部类不能有构造器,但是我们需要一个有参构造器的时候应该怎么办?
- 只需简单地传递合适的参数给基类的构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class Test01 {
//1.8之前编译器会要求其参数引用是final型的。
public Person fun1(final String str) {
return new Person() {
public void eat() {
System.out.println("我吃了"+str);
}
};
}
public static void main(String[] args) {
Person person = new Test01().fun1("西瓜");
person.eat();
}
}- 匿名内部类不能有构造器,但是我们需要一个有参构造器的时候应该怎么办?
匿名内部类实现抽象类
抽象类定义
1
2
3
4
5
6
7
8abstract class Base1 {
Base1(int n) {
System.out.println(n);
}
public void f() {
}
}匿名内部类实现抽象类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// 匿名内部类
Base1 base1 = new Base1(22) {
{
System.out.println("构造块");
}
public void f() {
System.out.println("sub_f");
}
public void method() {
}// 调用不到,Base1没有,多态
};
//匿名内部类调用方法
//匿名内部类是对象,所以能调用
new Base1(44) {
public void f() {
System.out.println("sub_f");
}
public void m() {
System.out.println("自己添加的m()");
}
}.m();
示例
按照要求,补齐代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14//要求在控制台输出”HelloWorld”
interface Inter { void show(); }
class Outer {
//补齐代码
}
class OuterDemo {
public static void main(String[] args) {
// 1.类能直接调用method方法,有静态方法method()
// 2.能有show()方法,说明method返回的是Inter
// 3.使用匿名内部类重写show()方法
Outer.method().show();
}
}答案:
1
2
3
4
5
6
7
8
9//补齐代码
public static Inter method() {
//匿名内部类
return new Inter() {
public void show(){
System.out.println("HelloWorld") ;
} ;
}
}
静态内部类
如果你不需要内部类对象与其外围类对象之间有联系,那你可以将内部类声明为static。这通常称为嵌套类(nested class)。
想要理解static应用于内部类时的含义,你就必须记住,普通的内部类对象隐含地保存了一个引用,指向创建它的外围类对象。
当内部类是static的时,就不依赖于外围类对象实例而独立存在。嵌套类意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象。
- 静态内部类的可以访问外围类中的所有静态成员,包括
private
的静态成员,但是它不能使用任何外围类的非static成员变量和方法。
其创建对象、继承(实现接口)、扩展子类等使用方式与外围类并没有多大的区别。
访问
创建静态内部类对象
外部类名.静态内部类名 = new 外部类名.静态内部类名();
外部类访问内部类
- 静态成员/方法:
内部类.静态成员
,内部类名.静态方法名();
- 非静态成员: 实例化内部类调用
- 非静态方法:通过创建对象使用
- 静态成员/方法:
内部类访问外部类
- 所有静态成员,包括
private
的静态成员 - 不能使用任何外围类的非static成员变量和方法
- 所有静态成员,包括
静态内部类的静态方法只能访问静态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37public class Outer {
private static int i = 1;
private int j = 10;
public static void outerF1() {
}
public void outerF3() {
// 外部类访问内部类的静态成员:内部类.静态成员
System.out.println(Inner.inner_i);
Inner.innerF1();
// 外部类访问内部类的非静态成员:实例化内部类即可
Inner inner = new Inner();
inner.innerF2();
}
/**
* 静态内部类可以用public,protected,private修饰
* 静态内部类中可以定义静态或者非静态的成员
*/
static class Inner {
static int inner_i = 100;
int innerJ = 200;
static void innerF1() {
// 静态内部类只能访问外部类的静态成员(包括静态变量和静态方法)
System.out.println("Outer.i" + i);
outerF1();
}
void innerF2() {
// 静态内部类不能访问外部类的非静态成员(包括非静态变量和非静态方法)
// System.out.println("Outer.i"+j);
// outerF2();
}
}
}
接口写嵌套类
正常情况下,你不能在接口内部放置任何代码
但嵌套类可以作为接口的一部分,因为它是static 的。
只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。
声明在接口中的内部类自动成为
static
和public
类- 与常规内部类不同,静态内部类可以有静态域和方法。
- 此外,静态内部类只能访问外部类的静态成员。
定义接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31interface Outer2 {
void car();
//因为接口中的类,方法自动有public&static
//所以不用声明也是静态内部类,还可以继承本接口
class staticinner implements Outer2 {
public void car() {
System.out.println("This is a car");
}
}
//---静态内部类------------------------
class A{
int x = 11;
public void f() {}
public static void sf() {}
}
//---静态内部接口------------------------
interface IA{
int N = 45;
void af();
default void df() {}
static void sf1() {}
}
}
class Test02{
public static void main(String[] args) {
Outer2.staticinner staticinner = new Outer2.staticinner();
staticinner.car();
}
}实现接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33class Outer2Impl implements Outer2{
public void car() {
}
//class A是实现类的内部类,属于实现类的对象
public void method() {
// 访问内部类中的变量需要用对象
System.out.println(new A().x);
// 访问内部类中的方法需要用对象
new A().f();
// 静态方法属于类,可以直接调用
A.sf();
}
//访问接口的内容
public void method2() {
// 接口变量:public static final 修饰 ,直接访问
System.out.println(IA.N);
IA.sf1();
// 使用接口对象(匿名内部类)调用默认方法和抽象方法
IA ia = new IA() {
public void af() {
// TODO Auto-generated method stub
}
};
ia.df();
ia.af();
}
}实现接口中的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class IAImpl implements Outer2.IA {
public void af() {
}
public void df() {
}
public static void main(String[] args) {
//默认方法用对象调用
new IAImpl().df();
new IAImpl().af();
//静态的用接口名限定
System.out.println(Outer2.IA.N);
Outer2.IA.sf1();
}
}
总结
成员内部类和局部内部类,在编译以后,都会生成字节码文件
- 成员内部类: 外部类$内部类名.class. 如: Cat$CatBody.class
- 局部内部类: 外部类$数字内部类名
访问控制修饰符
- 外部类: public (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类: 什么都不能写
成员内部类
归该类的对象,内部类对象中都隐藏了一个外部类对象
不允许 定义静态内容,static final 除外
如果外部类成员变量,内部类成员变量,内部类方法中的变量同名
- 内部类方法中的变量> 内部类成员变量> 外部类成员变量
- 内部类的方法访问同名变量
- num:内部类方法中的变量
- this.num:内部类成员变量
- Other.this.num:内部类使用外部类成员变量
- Other.this.fun():内部类调用外部类的成员方法