【Java】Java核心知识点与相应面试技巧(八)——类与对象(三)
Java 内部类
上期面试题解析
上文链接:https://blog.csdn.net/weixin_73492487/article/details/146690712
- 以下代码输出什么?
class A { static { System.out.print("1"); } { System.out.print("2"); } public A() { System.out.print("3"); } } class B extends A { static { System.out.print("4"); } { System.out.print("5"); } public B() { System.out.print("6"); } } public class Test { public static void main(String[] args) { new B(); } }
输出:142536
- 静态方法能否调用非静态方法?为什么?
答:不能,静态方法调用时可能还没有实例存在
- 以下代码是否合法?
class Test { static int a = b; static int b = 10; }
非法(向前引用
- 何时选择抽象类?何时选择接口?
需要共享代码 → 抽象类
需要定义类型规范 → 接口
需要多继承 → 接口
- 以下代码是否合法?为什么?
abstract class A { abstract void method1(); final void method2() {} } interface B { default void method3() {} static void method4() {} }
合法。抽象类可包含final方法,接口支持默认和静态方法
1. 内部类的定义以及分类
定义:
内部类是定义在另一个类内部的类。内部类可以访问外部类的成员(包括私有成员),内部类为外部类服务,封装或组织逻辑。
分类和基本特性:
类型 | 定义位置 | 访问权限 | 内存依赖 | 典型应用场景 |
---|---|---|---|---|
成员内部类 | 类内部(非static) | 可访问外部类所有成员 | 依赖外部类实例,创建时需要外部类实例 | 迭代器实现/深度封装 |
静态内部类 | 类内部,static修饰 | 只能访问外部类静态成员 | 与外部类实例无关,静态成员 | 工具类/关联紧密的辅助类 |
局部内部类 | 方法/代码块内部 | 访问外部类成员、方法的 final 局部变量 | 仅在方法内有效,依赖外部类实例 | 方法内部专用逻辑 |
匿名内部类 | 即时实现,无类名 | 同局部内部类 | 依赖外部类实例,通常用于即时创建 | 事件监听/一次性实现 |
2. 各种内部类的详解
2.1 成员内部类/普通内部类(Member Inner Class)
定义:
成员内部类是定义在外部类的成员位置(即类体内,但不在方法、构造器或代码块中)的一种类。成员内部类可以访问外部类的所有成员(包括私有成员)。成员内部类的实例通常依赖于外部类的实例来创建。
示例:
class Outer {
private int outerField = 20;
//成员内部类
class MemberInner {
void access() {
System.out.println(outerField); // 可以访问外部类(包括私有)实例成员
}
}
}
public class Main {
public static void main(String[] args) {
// 实例化
Outer outer = new Outer();
Outer.MemberInner inner = outer.new MemberInner();
}
}
应用场景:
(1)处理复杂的逻辑或业务:
在一些复杂的业务逻辑中,成员内部类可以作为外部类的一个补充,来封装特定的功能,而不需要暴露给外部类之外的代码。这样可以减少外部类的复杂度,同时保持类的封装性。
(2)事件监听器:
在 GUI 编程中,尤其是 Swing 或 AWT 中,成员内部类常用于处理按钮点击、窗口关闭等事件,因为它可以访问外部类的成员(比如 GUI 控件)。
(3)迭代器模式:
在实现集合类的迭代器模式时,成员内部类常常用来定义迭代器。这样迭代器就能够直接访问外部类的私有数据。
(4)简化多层嵌套:
在某些情况下,成员内部类可以帮助简化多层嵌套逻辑,使代码结构更清晰,特别是当内部类的功能非常依赖于外部类的状态时。
特点:
- 内部类能直接访问外部类的私有成员,可以实现更高效的封装和代码分离。
- 在需要频繁访问外部类成员的情况下,成员内部类可以减少外部类与内部类之间的交互复杂度。
- 内部类无需显式传递外部类的成员,可以直接访问外部类的私有字段和方法。
2.2 静态内部类(Static Nested Class)
定义:
静态内部类是定义在另一个类内部,并且声明为static的类。与普通的内部类不同,静态内部类没有隐式地持有外部类的实例,它不需要通过外部类的实例来创建。静态内部类只能访问外部类的静态成员(字段、方法)
示例:
class Outer {
private static int staticField = 10;
//静态内部类,由static修饰
static class StaticInner {
void access() {
System.out.println(staticField); // 只能访问外部类静态成员
}
}
}
// 使用:不依赖于外部类的实例,可以通过外部类的类名直接访问
Outer.StaticInner inner = new Outer.StaticInner();
特点:
- 独立于外部类实例存在,静态内部类不依赖于外部类的实例,因此可以通过外部类的类名直接访问
- 可声明静态成员,只能访问外部类的静态成员,不能访问外部类的实例成员
- 常用于工具类(如Map.Entry)
2.3 局部内部类(Local Inner Class)
定义:
局部内部类是定义在方法内部的类,它的作用范围仅限于该方法内。局部内部类通常用于在特定的方法中定义一个类,而不需要让这个类在外部类的其他部分可见。
示例:
class OuterClass {
private String outerField = "Outer Field";
// 方法内定义局部内部类
public void Method() {
//局部变量
String localVar = "Local Variable";
// 局部内部类定义在方法内
class LocalInnerClass {
void display() {
System.out.println("Accessing outer class field: " + outerField);
System.out.println("Accessing local variable: " + localVar);
}
}
// 创建局部内部类的实例
LocalInnerClass inner = new LocalInnerClass();
inner.display();
}
}
public class Main {
public static void main(String[] args) {
OuterClass outer = new OuterClass();
//通过调用外部类的方法来使用局部内部类
outer.someMethod();
// 输出:Accessing outer class field: Outer Field
// 输出:Accessing local variable: Local Variable
}
}
特点:
- 局部作用域: 局部内部类只能在它定义的那一个方法中使用,方法结束后该类的实例就不能再使用。
- 访问方法中的局部变量: 局部内部类可以访问方法的局部变量,但这些局部变量必须是 final 或隐式 final(即不会被修改,但 Java 8+ 已经隐式地带有final属性)。
- 不能有静态成员: 局部内部类不能定义静态成员,因为它是与外部方法的执行过程关联的,不能和方法的生命周期脱离。
- 访问外部类成员: 局部内部类可以访问外部类的成员(如字段、方法等),包括私有成员。
2.4 匿名内部类(Anonymous Inner Class)
定义:
匿名内部类是Java中一种特殊的内部类,它没有名字,并且通常在创建对象的同时定义类的实现。它是一个类的实例化表达式,通常用于实现接口或继承某个类。
语法:
new 接口或类名() {
// 重写接口或类中的方法
};
示例:
//实现接口
interface ClickListener {
void onClick();
}
//外部类
class Button {
void setListener(ClickListener listener) {
/*...*/
}
}
// 使用:匿名内部类实现接口
new Button().setListener(new ClickListener() {
@Override
public void onClick() {
System.out.println("Button clicked!");
}
});
特性:
- 没有显式类名,不能有构造函数,通常用于只需要一次性使用的类定义
- 只能实现一个接口或继承一个类,并且必须在构造时直接实现这些方法
- 作用域仅限于创建它的代码块,所以只能在定义时使用
- Java 8+可用Lambda替代(函数式接口时)
3. 核心机制
3.1 访问外部类成员原理
- 成员内部类通过
Outer.this
访问外部类实例 - 编译器自动生成合成访问方法(synthetic accessor)
3.2 内存泄漏风险
class Outer {
class Inner { /*...*/ }
}
// 长期持有内部类实例会导致外部类无法回收
解决方案:
- 使用静态内部类
- 使用弱引用(WeakReference)
3.3 字节码分析
- 内部类编译为独立.class文件(如
Outer$Inner.class
) - 自动生成桥接方法访问外部类私有成员
⚡ 高频面试题
-
为什么局部内部类访问的局部变量必须final?
-
如何避免内部类导致的内存泄漏?
-
以下代码是否正确?
class Outer { int val = 10; class Inner { int val = 20; void print() { System.out.println(Outer.this.val); } } }
-
匿名内部类能否实现多接口?