内部类的定义
成员内部类是定义在另一个类的内部,但在方法外部的类。它可以访问外部类的所有成员(包括私有成员),并且每个成员内部类的实例都与一个外部类的实例相关联
内部类又称为嵌套类,外部类又称为封闭类
Java中内部类分为四类
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
一:成员内部类
地位类似一般成员属性可看为外部类的一个成员属性,无static修饰,可以使用任何访问修饰符
例
成员内部类与外部类成员的访问
1)内部类访问外部类成员
- 内部类可以直接通过变量名字访问外部类成员包括私有成员private
class Outer {
private int A = 1;
private int B = 2;
// 成员内部类
class Inner {
public void lookOuter() {
// 可以直接访问外部类的私有成员
System.out.println(A);
}
}
// 类似成员变量之间的互相影响
private int C = A+B;
}
- 内部类利用外部类的this引用访问外部类成员
常用于内部类与外部类中的变量重名时,如果内部与外部类成员重名,直接通过名字访问成员将访问内部类中成员
class Outer {
private int A = 1;
private int B = 2;
// 成员内部类
class Inner {
public void lookOuter() {
// 可以直接访问外部类的私有成员
System.out.println(Outer.this.A);
}
}
}
2)外部类访问内部类成员
这将涉及到内部类的实例化,实例化内部类后通过名字直接访问内部类成员(包括私有成员)
class Outer {
private int A = 1;
// 成员内部类
class Inner {
private int B = 2;
}
// 在外部类中进行了内部类的实例化
Inner inner =new Inner();
public void lookInner() {
// 可以直接访问内部类的私有成员
System.out.println(inner.B);
}
}
二:静态内部类
静态内部类是使用 static 关键字修饰的内部类。地位类似静态成员属性,它只能访问外部类的静态成员,并且不依赖于外部类的实例,可以直接通过外部类名来创建实例。可以使用任何访问修饰符
静态内部类与外部类成员的访问
1)静态内部类中访问外部类成员
- 静态内部类通过变量名字直接访问外部类的静态成员
class Outer {
private static int outerStaticField = 1;
// 静态内部类
static class StaticInner {
public void lookOuter() {
System.out.println(outerStaticField);
}
}
}
- 通过外部类名 . 变量名来访问外部类静态成原员(与成员内部类类似,用于区分变量重名时的情况)
class Outer {
private static int outerStaticField = 1;
// 静态内部类
static class StaticInner {
public void display() {
System.out.println(Outer.outerStaticField);
}
}
}
- 注意静态内部类无法访问外部类非静态成员
2)外部类访问静态内部类中成员
依然类似于成员内部类,需通过实例化内部类,外部类可以访问静态内部类所有成员
class Outer {
private static int outerStaticField = 1;
// 静态内部类
static class StaticInner {
private int InnerField = 1;
}
public void lookInner() {
StaticInner a = new StaticInner();
System.out.println(a.InnerField);
}
}
三:局部内部类
局部内部类是定义在方法、构造器、代码块等局部作用域内的类。其定义位置决定了它的可见范围和生命周期。
class Outer {
// 定义局部内部类在构造方法内
public Outer() {
class LocalInner {
}
}
// 定义局部内部类在方法内部
public void methodWithLocalInner() {
class LocalInner {
}
}
// 代码块中定义内部类
{
class LocalInner {
}
}
}
局部内部类与外部类成员的访问
1)局部内部类中访问外部类成员
- 内部类可以直接通过变量名字访问外部类成员包括私有成员private
class Outer {
private int A = 1;
{
class Inner {
public void lookOuter() {
// 可以直接访问外部类的私有成员
System.out.println(A);
}
}
}
}
- 内部类利用外部类的this引用访问外部类成员
常用于内部类与外部类中的变量重名时,如果内部与外部类成员重名,直接通过名字访问成员将访问内部类中成员
class Outer {
private int A = 1;
{
class Inner {
public void lookOuter() {
// 可以直接访问外部类的私有成员
System.out.println(Outer.this.A);
}
}
}
}
2)外部类中访问局部内部类成员
- 在作用域内通过实例化直接访问
注意:以 MyInterface 接口的形式返回,这样调用者只需要关心接口定义的行为,而不需要了解具体的实现类,可以不用到接口
class Outer {
public void outerMethod() {
// 定义局部内部类
class LocalInner {
void printMessage() {
System.out.println("This is a message from the local inner class.");
}
}
// 在方法内创建局部内部类的实例并调用方法
LocalInner inner = new LocalInner();
inner.printMessage();
}
}
- 如果需要在定义局部内部类的作用域之外访问局部内部类,可以通过返回局部内部类的实例或者将局部内部类的实例传递给其他方法等间接方式来实现
interface MyInterface {
void doSomething();
}
class Outer {
//返回类型为MyInterface的方法
public MyInterface getLocalInnerInstance() {
// 定义局部内部类实现接口()
class LocalInner implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something in local inner class.");
}
}
// 返回局部内部类的实例
return new LocalInner();
}
}
public class Main {
public static void main(String[] args) {
Outer outer = new Outer();
MyInterface obj = outer.getLocalInnerInstance();
obj.doSomething();
}
}
注意
特别地,如果局部内部类定义在静态局部中,则在局部内部类中无法访问外部类非静态成员,并且不能有访问修饰限定符,不能有静态成员
因为静态局部属于类本身,外部成员依赖于实例,所以定义在静态局部中不能访问外部类的非静态成员
并且局部内部类的作用域已经决定了作用范围故不能用访问修饰限定符
局部内部类是与特定的方法调用相关联的,没有独立的类生命周期因此不能有静态成员
四:匿名内部类
匿名内部类是没有名字的内部类,通常在创建对象的同时定义类,通常用于接口或抽象类的实例化
并且匿名内部类必须通过继承父类和实现接口来实现
只能继承一个父类或实现一个接口
不能定义构造方法,但可通过代码块初始化
匿名内部类的定义语法为:
new 父类构造器() { 类体 } ;
或 new 接口() { 类体 };
注意结尾;
// 继承普通类
class Animal {}
Animal dog = new Animal() {
// 匿名内部类隐式继承 Animal
};
// 实现接口
Runnable task = new Runnable() {
@Override
public void run() {}
};
// 继承抽象类
abstract class Shape {
abstract void draw();
}
Shape circle = new Shape() {
@Override
void draw() { System.out.println("画圆"); }
};
匿名内部类与外部类成员之间的访问
- 匿名内部类可以通过变量名或者外部类引用访问外部类变量(包括private)
- 外部类无法直接访问匿名内部类特有变量
原因:类比为子类继承父类后父类无法访问子类特有成员
多态约束,父类/接口类型的引用无法访问子类特有成员
匿名性,无显式类名导致无法类型转换或直接引用
作用域隔离,变量作用域仅限于匿名内部类内部
封装性设计,鼓励通过方法而非直接访问字段
3.外部类中借由动态绑定可以实现匿名内部类中的方法
class Outer {
private String outerField = "外部类变量"; // 外部类实例变量
public void demo() {
// 创建匿名内部类(继承 Animal 类)
Animal cat = new Animal() {
// 匿名内部类自定义的成员(外部无法访问)
private String innerField = "内部类变量";
@Override
public void speak() {
// 1. 直接访问外部类实例变量
System.out.println(outerField);
// 2. 通过外部类的引用访问外部类实例变量
System.out.println(Outer.this.outerField);
}
};
// 3.外部类通过动态绑定调用匿名内部类的方法
cat.speak();
// 以下代码会报错:
//4.外部类无法访问匿名内部类特有成员
// System.out.println(cat.innerField); // 错误!无法访问内部类成员
}
public static void main(String[] args) {
new Outer().demo();
}
}
// 普通父类(非抽象类)
class Animal {
public void speak() {
System.out.println("动物叫声");
}
}
注意
- 匿名内部类无法有构造方法但能通过实例初始化块实现初始化
interface Animal {
void speak();
}
public class Demo {
public static void main(String[] args) {
Animal dog = new Animal() {
private String name;
// 实例初始化块(类似构造方法)
{
name = "旺财";
}
@Override
public void speak() {
System.out.println(name + ": 汪汪");
}
};
dog.speak(); // 输出:旺财: 汪汪
}
}
- 匿名内部类没有显式类名,通常用于实现接口或继承类,其访问权限取决于父类/接口的修饰符
五:内部类中访问外部类的局部变量
内部类都可以访问外部类的局部变量但是有所限制
-
在 Java 8 之前,访问的变量必须由final显式声明
-
在 Java 8 及以后的版本中,可以有隐式声明为final的情况(effectively final)
如果一个局部变量在初始化后没有被重新赋值,那么它就是 effectively final 的,即使没有显式地声明为 final,局部内部类也可以访问它。
为什么需要 final 或等效 final?
- 变量生命周期问题
局部变量:存储在栈内存中,生命周期与所在方法同步(方法结束即销毁)
内部类实例:可能存活更久(例如,传递给异步线程或存储在集合中)
因此为了保证内部类访问的局部变量在方法结束后依然有效,Java 要求变量必须为 final 或等效 final,从而确保其值不可变,并通过值拷贝(而非直接引用)的方式传递给内部类
- 数据一致性
若允许修改局部变量,内部类中看到的变量值可能与外部方法中的实际值不一致。强制不可变可避免这种不一致性。
六:在其他类中创建内部类的实例
在 Java 中,在其他类中创建内部类的实例需要根据内部类的类型(成员内部类、静态嵌套类)和访问修饰符(public、protected、private、默认包级)来决定具体方式
并且局部内部类和匿名内部类无法实现在其他类中创建内部类实例,因此仅讨论成员内部类与静态内部类
1)对于成员内部类
- 成员内部类为 public 或默认包级
允许其他类访问内部类,但必须通过外部类实例创建。
语法:
1.外部类实例 . new 内部类构造方法()
2.创建外部类实例同时创建内部类实例(本质与1相同)
class Outer {
public class PublicInner {}
class DefaultInner {}
}
// 其他类中创建成员内部类实例
public class OtherClass {
public static void main(String[] args) {
//创建外部类实例同时实例化内部类例子
//Outer.PublicInner publicInner =new Outer().new PublicInner();
Outer outer = new Outer();
// 创建 PublicInner 实例
Outer.PublicInner publicInner = outer.new PublicInner();
// 创建 DefaultInner 实例(需同包)
Outer.DefaultInner defaultInner = outer.new DefaultInner();
}
}
- 成员内部类为 protected
仅允许同包类或子类访问。
语法与 public 相同,但需满足包或继承关系
class Outer {
protected class ProtectedInner {}
}
// 子类中创建实例
class SubClass extends Outer {
void demo() {
Outer outer = new Outer();
Outer.ProtectedInner inner = outer.new ProtectedInner(); // 合法(子类)
}
}
// 同包其他类中创建实例
class SamePackageClass {
void demo() {
Outer outer = new Outer();
Outer.ProtectedInner inner = outer.new ProtectedInner(); // 合法(同包)
}
}
- 成员内部类为 private
无法在其他类中直接访问或实例化,仅限外部类内部使用
2)对于静态内部类
- 静态嵌套类为 public 或默认包级
直接通过外部类名实例化。
语法:new 外部类名.静态嵌套类构造方法()
class Outer {
public static class PublicStaticNested {} // public 静态嵌套类
static class DefaultStaticNested {} // 默认包级静态嵌套类
}
// 其他类中创建实例
public class OtherClass {
public static void main(String[] args) {
// 创建 PublicStaticNested 实例
Outer.PublicStaticNested publicNested = new Outer.PublicStaticNested();
// 创建 DefaultStaticNested 实例(需同包)
Outer.DefaultStaticNested defaultNested = new Outer.DefaultStaticNested();
}
}
- 静态嵌套类为 protected
仅允许同包类或子类访问。
class Outer {
protected static class ProtectedStaticNested {}
}
// 子类中创建实例
class SubClass extends Outer {
void demo() {
Outer.ProtectedStaticNested nested = new Outer.ProtectedStaticNested(); // 合法(子类)
}
}
// 同包其他类中创建实例
class SamePackageClass {
void demo() {
Outer.ProtectedStaticNested nested = new Outer.ProtectedStaticNested(); // 合法(同包)
}
}
- 静态嵌套类为 private
无法在其他类中直接访问或实例化。
七:编译器为内部类生成的文件名
都可以通过getClass()方法来查看
- 对于静态内部类与成员内部类:
外部类名+$+成员内部类名 . class
class Outer {
class Inner {} // 成员内部类
}
class Outer {
static class StaticNested {} // 静态嵌套类
}
//生成文件:
//Outer.class Outer.class
//Outer$Inner.class Outer$StaticNested.class
- 对于局部内部类:
外部类名+$+数字编号+局部类名.class
class Outer {
void demo() {
class LocalInner {} // 局部内部类
}
}
//生成文件:
//Outer.class
//Outer$1LocalInner.class(不同编译器可能不同,如 Outer$demo$1LocalInner.class)
- 对于匿名内部类:
外部类名+$+数字编号.class
class Outer {
void demo() {
Runnable task1 = new Runnable() { // 第一个匿名内部类
@Override public void run() {}
};
Runnable task2 = new Runnable() { // 第二个匿名内部类
@Override public void run() {}
};
}
}
//生成文件:
//Outer.class
//Outer$1.class(对应 task1)
//Outer$2.class(对应 task2)
- 对于多层嵌套内部类
命名规则:外部类名+ $ +内部类名+ $ +更深层内部类名.class
class Outer {
class Inner {
class DeeperInner {} // 多层嵌套内部类
}
}
//生成文件:
//Outer.class
//Outer$Inner.class
//Outer$Inner$DeeperInner.class