内部类
成员内部类
在 Java 中,内部类是定义在另一个类内部的类。内部类提供了更好的封装性和代码组织方式,同时也可以访问外部类的成员。
实例化依赖
成员内部类的实例化依赖于外部类的实例。不能直接创建成员内部类的对象,必须先创建外部类的对象,再通过外部类对象来创建成员内部类对象。
成员内部类可以访问外部类的所有成员,包含私有成员。不过外部类访问成员内部类的成员时,需要先创建内部类的对象。
命名冲突
若成员内部类的成员和外部类的成员重名,在内部类中使用 this
关键字指向内部类的成员,使用 外部类名.this
指向外部类的成员。
class Outer {
int num = 10;
class Inner {
int num = 20;
public void printNums() {
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
定义和特点
成员内部类是定义在另一个类的内部,作为外部类的一个成员存在。它可以访问外部类的所有成员(包括私有成员),同时外部类也可以通过创建内部类的对象来访问内部类的成员。
示例代码
class Outer {
private int outerValue = 10;
// 成员内部类
class Inner {
public void printOuterValue() {
System.out.println("Outer value: " + outerValue);
}
}
public void useInner() {
Inner inner = new Inner();
inner.printOuterValue();
}
}
public class MemberInnerClassExample {
public static void main(String[] args) {
Outer outer = new Outer();
outer.useInner();
// 也可以直接创建内部类对象
Outer.Inner inner = outer.new Inner();
inner.printOuterValue();
}
}
代码解释
Inner
类是Outer
类的成员内部类,在Inner
类的printOuterValue
方法中可以直接访问外部类的私有成员outerValue
。- 在
Outer
类的useInner
方法中,创建了Inner
类的对象并调用其方法。 - 在
main
方法中,可以通过外部类对象创建内部类对象,语法为外部类对象.new 内部类名()
。
静态内部类
定义和特点
静态内部类是使用 static
关键字修饰的内部类。它不依赖于外部类的实例,可以直接创建对象。静态内部类只能访问外部类的静态成员,不能访问外部类的非静态成员。
示例代码
class Outer {
private static int outerStaticValue = 20;
// 静态内部类
static class StaticInner {
public void printOuterStaticValue() {
System.out.println("Outer static value: " + outerStaticValue);
}
}
}
public class StaticInnerClassExample {
public static void main(String[] args) {
// 直接创建静态内部类对象
Outer.StaticInner staticInner = new Outer.StaticInner();
staticInner.printOuterStaticValue();
}
}
代码解释
StaticInner
类是Outer
类的静态内部类,在StaticInner
类的printOuterStaticValue
方法中可以访问外部类的静态成员outerStaticValue
。- 在
main
方法中,可以直接通过外部类名.静态内部类名()
创建静态内部类的对象。
局部内部类
局部内部类是定义在方法或代码块内部的类。它的作用域仅限于定义它的方法或代码块内部。局部内部类可以访问外部类的成员,同时也可以访问所在方法的 final
局部变量(Java 8 及以上版本,局部变量隐式为 final
)在 Java 8 之前,如果要在局部内部类或匿名内部类中访问所在方法的局部变量,该局部变量必须显式地声明为 final
。而从 Java 8 开始,即使没有显式地使用 final
关键字修饰,只要该局部变量在初始化后其值不再改变,它就被视为隐式的 final
变量,局部内部类或匿名内部类就可以访问它。
原理
当局部内部类或匿名内部类访问局部变量时,实际上是通过复制该局部变量的值来实现的。这是因为局部变量的生命周期和内部类对象的生命周期可能不同,局部变量在方法执行结束后就会被销毁,而内部类对象可能还存在。为了保证内部类能够正确访问局部变量的值,Java 采用了复制的方式。如果局部变量的值可以改变,那么内部类中复制的值和原始变量的值可能会不一致,从而导致错误。因此,要求局部变量要么显式声明为 final
,要么隐式为 final
(即初始化后值不再改变)。
示例代码
class Outer {
private int outerValue = 30;
public void method() {
final int localValue = 40;
// int localValue = 40; (jdk8 可以在不对localValue 从新赋值情况下 隐式加上final)
// 局部内部类
class LocalInner {
public void printValues() {
System.out.println("Outer value: " + outerValue);
System.out.println("Local value: " + localValue);
}
}
LocalInner localInner = new LocalInner();
localInner.printValues();
}
}
public class LocalInnerClassExample {
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
代码解释
LocalInner
类是定义在Outer
类的method
方法内部的局部内部类。- 在
LocalInner
类的printValues
方法中,可以访问外部类的成员outerValue
和所在方法的final
局部变量localValue
。
匿名内部类
定义和特点
匿名内部类是一种没有显式名称的内部类,通常用于创建只需要使用一次的类的实例。它可以继承一个类或实现一个接口,并且在创建对象的同时进行定义。
示例代码
interface MyInterface {
void doSomething();
}
public class AnonymousInnerClassExample {
public static void main(String[] args) {
// 匿名内部类
MyInterface myInterface = new MyInterface() {
@Override
public void doSomething() {
System.out.println("Doing something...");
}
};
myInterface.doSomething();
}
}
代码解释
- 在
main
方法中,创建了一个实现MyInterface
接口的匿名内部类的对象。 - 匿名内部类没有显式的类名,直接在创建对象的同时实现了接口的方法。
匿名内部类的优点
- 封装性:内部类可以将相关的类组织在一起,提高代码的封装性。
- 访问权限:内部类可以访问外部类的私有成员,提供了更灵活的访问控制。
- 代码组织:内部类可以使代码更加清晰和易于维护,特别是在处理复杂的逻辑时。
匿名内部类的注意事项
- 内部类会增加代码的复杂度,使用时需要谨慎。
- 性能影响
每次创建匿名内部类的实例时,都会生成一个新的类文件,这可能会对性能和磁盘空间造成一定影响。会影响代码的性能和可维护性。
通用注意事项
- 编译生成的类文件
内部类编译后会生成独立的类文件,文件名格式为外部类名$内部类名.class
。例如,成员内部类Outer.Inner
编译后会生成Outer$Inner.class
文件。 - 序列化问题
如果内部类需要实现序列化接口Serializable
,要确保外部类也实现该接口,否则可能会出现序列化异常。
枚举
枚举的定义
枚举是一种特殊的数据类型,它用于定义一组命名的常量。使用enum
关键字来定义枚举类型。
enum Weekday {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}
上述代码定义了Weekday
枚举类型,它包含了一周七天的常量。
枚举的特点
- 常量集合:枚举类型定义了一组固定的常量值,这些值在程序运行期间不会改变。
- 类型安全:使用枚举可以避免使用字符串或整数来表示常量时可能出现的错误,编译器会进行类型检查。
- 单例模式实现:枚举类型天然支持单例模式,因为枚举的每个实例都是唯一的。
枚举的使用
可以像使用普通类一样使用枚举类型。以下是一些常见的用法:
public class EnumExample {
public static void main(String[] args) {
Weekday today = Weekday.MONDAY;
System.out.println("Today is " + today);
// 使用 switch 语句处理枚举值
switch (today) {
case MONDAY:
System.out.println("It's a busy day.");
break;
case SATURDAY:
case SUNDAY:
System.out.println("It's weekend.");
break;
default:
System.out.println("It's a normal weekday.");
}
}
}
上述代码展示了如何声明枚举类型的变量,以及如何使用switch
语句处理枚举值。
枚举的构造函数和方法
枚举类型可以有构造函数和方法。构造函数用于初始化枚举常量的属性,方法可以为枚举常量提供额外的行为。示例如下:
enum TrafficLight {
RED(30), YELLOW(5), GREEN(40);
private int duration;
TrafficLight(int duration) {
this.duration = duration;
}
public int getDuration() {
return duration;
}
}
在上述代码中,TrafficLight
枚举类型有一个构造函数,用于初始化每个信号灯的持续时间。同时,提供了getDuration
方法来获取信号灯的持续时间。
枚举既可以作为独立的类存在,也可以作为内部类存在。以下是作为内部类的示例:
package com.example.pkg3;
public class Test1 {
enum InnerEnum {
VALUE1, VALUE2, VALUE3
}
static class InnerClass {
public static final int VALUE1 = 1;
public static final int VALUE2 = 2;
public static final int VALUE3 = 3;
}
public static void main(String[] args) {
Test1.InnerEnum value = InnerEnum.VALUE1;
System.out.println(value);
System.out.println(InnerClass.VALUE1);
}
}
在上述代码中,InnerEnum
是OuterClass
的内部枚举类。枚举类也有静态的特性。Java 枚举是一种特殊的类,它可以是独立的类,也可以作为内部类存在。枚举类型为定义一组固定的常量提供了一种安全、方便的方式。
所有通过enum
定义的枚举类,在编译后会由编译器自动生成继承java.lang.Enum
的代码
由于Java的单继承限制,枚举类不能再显式声明继承其他类
非抽象的枚举类默认被final
修饰,因此无法被其他类继承(即不能有子类)
继承java.lang.Enum
的影响
- 获得通用方法
通过继承Enum
类,枚举类自动获得以下方法:name()
:返回枚举常量名称(如SPRING.name()
返回"SPRING"
)。ordinal()
:返回枚举常量的声明顺序索引(从0开始)。values()
和valueOf()
:获取所有枚举实例或通过名称查找实例。
- 构造器限制
枚举类的构造器必须为private
,确保实例只能在枚举内部定义,无法通过new
创建
抽象枚举类的特殊性
若枚举类包含抽象方法,则被视为抽象类(隐式abstract
修饰),但仍不能被继承。枚举内部的值,必须实现该抽象方法。
public enum Direction {
UP { @Override void move() { /* 向上逻辑 */ } },
DOWN { @Override void move() { /* 向下逻辑 */ } };
abstract void move();
}