💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ***** # 17.1 内部类概述 Java语言中允许在一个类(或方法、代码块)的内部定义另一个类,后者称为“内部类”,封装它的类称为“外部类”。内部类与外部类之间存在逻辑上的隶属关系,内部类一般只用在封装它的外部类或代码块中使用。 ## 17.1.1 内部类的作用 内部类的作用如下: 1. 封装。将不想公开的实现细节封装到一个内部类中,内部类可以声明为私有的,只能在所在外部类中访问。 2. 提供命名空间。静态内部类和外部类能够提供有别于包的命名空间。 3. 便于访问外部类成员。内部类能够很方便访问所在外部类的成员,包括私有成员也能访问。 ## 17.1.2 内部类的分类 内部类的分类如图17-1所示,按照内部类在定义的时候是否给它一个类名,可以分为:有名内部类和匿名内部类。有名内部类又按照作用域不同可以分为:局部内部类和成员内部类,成员内部类又分为:实例内部类和静态内部类。 ![](https://box.kancloud.cn/6700bb0988a36f93e8cc4f2df9be78b3_809x628.jpg) # 17.2 成员内部类 成员内部类类似于外部类的成员变量,在外边类的内部,且方法体和代码块之外定义的内部类。 ## 17.2.1 实例内部类 实例内部类与实例成员类似,可以声明为公有级别、私有级别、默认级别或保护级别,即4种访问级别都可以,而外部类只能声明为公有或默认级别。 **实例内部类示例代码如下:** ``` //Outer.java文件 package no_17; //外部类 public class Outer { // 外部类成员变量 private int x = 10; // 外部类方法 private void print() { System.out.println("调用外部方法..."); } // 测试调用内部类 public void test() { Inner inner = new Inner(); inner.display(); } // 内部类 class Inner { // 内部类成员变量 private int x = 5; // 内部类方法 void display() { // 访问外部类的成员变量x System.out.println("外部类成员变量 x = " + Outer.this.x); // 访问内部类的成员变量x System.out.println("内部类成员变量 x = " + this.x); System.out.println("内部类成员变量 x = " + x); // 调用外部类的成员方法 Outer.this.print(); print(); //8 } } } ``` 如果内部类和外部类它们的成员命名没有冲突情况下,在引用外部类成员时可以不用加“外部类名.this”,如代码第8行的print()方法只有外部类中定义,所以可以省略Outer.this。 **测试内部HelloWorld代码如下:** ``` //Outer.java文件 package no_17; public class HelloWorld { public static void main(String[] args) { // 通过外部类访问内部类 Outer outer = new Outer(); outer.test(); System.out.println("-------直接访问内部类------"); // 直接访问内部类 Outer.Inner inner = outer.new Inner(); //2 inner.display(); //3 } } ``` 通常情况下,使用实例成员内部类不是给外部类之外调用使用的,而是给外部类自己使用的。但是一定要在外部类的之外访问内部类,Java语言也是支持的,见代码第②行内部类的类型表示“外部类.内部类”,实例化过程是先实例化外部类,再实例化内部类,outer对象是外部类实例,outer.new Inner()表达式实例化内部类对象。 另外,HelloWorld与内部类Inner在同一个包中,内部类Inner和它的方法display()访问级别都是默认的,它们对于在同一包中HelloWorld是可见的。 > **提示** 内部类编译成功后生成的字节码文件是“外部类$内部类.class”。 ## 17.2.2 静态内部类 静态内部类与静态成员类似,在声明的时候使用关键字static修饰。静态内部类只能访问外部类静态成员,所以静态内部类使用的场景不多。但可以提供有别于包的命名空间。 **示例代码如下:** ``` //Outer.java文件 package no_17; //外部类 public class View { // 外部类实例变量 private int x = 20; // 外部类静态变量 private static int staticX = 10; // 静态内部类 static class Button { // 内部类方法 void onClick() { //访问外部类的静态成员 System.out.println(staticX); //不能访问外部类的非静态成员 // System.out.println(x); //编译错误 } } } ``` 上述代码第③行定义了静态内部类Button,在静态内部类中可以访问外部类的静态成员,见代码第⑤行。但是不能访问非静态成员,见代码第⑥行试图访问外部类的x实例变量,会发生编译错**误。 测试内部HelloWorld代码如下:** ``` //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { // 直接访问内部类 View.Button button = new View.Button(); button.onClick(); } } ``` 从代码View.Button button = new View.Button()可见,在声明静态内部时采用“内部类.静态内部类”形式,实例化也是如此形式。 > **提示** 如果不看代码或文档,View.Button形式看起来像是View包中的Button类,事实上它是View类中静态内部类Button。View.Button形式客观上能够提供有别于包的命名空间,View相关的类集中管理起来,View.Button可以防止命名冲突。 # 17.3 局部内部类 局部内部类就是在方法体或代码块中定义的内部类,局部内部类的作用域仅限于方法体或代码块中。 局部内部类访问级别只能是默认的,不能使用public、private和protected修饰。局部内部类也不能是静态,即不能使用static修饰。局部内部类可以访问外部类所有成员。 **示例代码如下:** ``` ~~~ //Outer.java文件 package com.a51work6; //外部类 public class Outer { // 外部类成员变量 private int value = 10; // 外部类方法 public void add(final int x, int y) { //1 //局部变量 int z = 100; // 定义内部类 class Inner { //2 // 内部类方法 void display() { int sum = x + z + value; //3 System.out.println("sum = " + sum); } } // Inner inner = new Inner(); // inner.display(); //声明匿名对象 new Inner().display(); //4 } } ~~~ ``` 上述代码在add方法中定义了局部内部类,见代码第②行,在内部类中代码第③行访问了外部类成员变量value、方法参数x和方法局部变量z,其中方法参数应该声明为final的,见代码第①行x参数是final的。 > **提示** 代码第④行new Inner().display()是实例化Inner对象后马上调用它的方法,没有为Inner对象分配一个引用变量名,这种写法称为“匿名对象”。匿名对象适合只运行一次情况下。匿名对象写法使代码变得简洁,但是给初学者阅读代码带来了难度。 **测试内部HelloWorld代码如下:** ``` //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { Outer outer = new Outer(); outer.add(100, 300); } } ~~~ ``` # 17.4 匿名内部类 匿名内部类是没有名字的内部类,本质上是没有名的局部内部类,具有局部内部类所有特征。例如:可以访问外部类所有成员。如果匿名内部类在方法中定义,它所访问的参数需要声明为final的。 **下面通过示例介绍一下匿名内部类。有如下一个View类:** ~~~ //View.java文件 package com.a51work6; //外部类 public class View { public void handler(OnClickListener listener) { ① listener.onClick(); } } ~~~ 代码第①行中handler方法需要一个实现OnClickListener接口的参数。OnClickListener接口代码如下: ~~~ //OnClickListener.java文件 package com.a51work6; public interface OnClickListener { void onClick(); } ~~~ 接口中只有一个onClick()方法。使用匿名内部类的示例代码如下: ~~~ //HelloWorld.java文件 package com.a51work6; public class HelloWorld { public static void main(String[] args) { View v = new View(); //1 // 方法参数是匿名内部类 v.handler(new OnClickListener() { //2 @Override public void onClick() { System.out.println("实现接口的匿名内部类..."); } }); //继承类的匿名内部类 Figure f = new Figure() { //3 @Override public void onDraw() { System.out.println("继承类的匿名内部类..."); } }; //具体类作为内部类 Person person = new Person("Tony", 18) { //4 @Override public String toString() { return "匿名内部类.实现 " + " Person[name=" + name + ", age=" + age + "]"; } }; //打印过程自动调用person的 toString()方法 System.out.println(person); } } ~~~ 在HelloWorld的main方法中,代码第①行是实例化View对象,代码第②行是调用它的handler方法,该方法需要一个实现OnClickListener接口的参数,new OnClickListener() {… }表达式是实际参数,它就是匿名内部类。表达式中OnClickListener是要实现的接口或要继承的类,new是为匿名内部类创建对象,()是调用构造方法,{… }是类体部分。 代码第③行是在赋值时候使用匿名内部类,其中Figure是14.1.2节使用过的抽象类。匿名内部类实现了它的抽象方法onDraw()。 代码第④行也是在赋值时候使用匿名内部类,其中Person是13.4.2节使用过的具体类,匿名内部类覆盖了toString()方法。它有个两个参数的构造方法,匿名内部类使用了这个构造方法。 匿名内部类通常用来实现接口或抽象类的,很少覆盖具体类。