在 Java 中,类变量(Class Variables) 是指用 static
关键字修饰的成员变量。它们属于类本身,而不是类的实例对象。因此,无论创建多少个对象,类变量在整个程序中只有一份副本。任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象取修改它时,修改的也是同一个变量。
static
关键字声明。// 定义语法:
访问修饰符 static 数据类型 变量名; // 推荐使用
static 访问修饰符 数据类型 变量名;
// 举例:
public static int count = 0;
// 访问语法:
类名.静态变量名 // 推荐使用
对象名.静态变量名
// 举例:
int childCount = Child.count;
public class MyClass {
// 类变量(静态变量)
public static int count = 0;
// 实例变量
public String name;
}
特性 | 描述 |
---|---|
所属对象 | 属于类本身,不属于类的实例 |
内存分配 | 在类加载时分配内存,且只分配一次 |
生命周期 | 从类加载开始到类卸载结束 |
访问方式 | 通过类名访问:类名.变量名 ,也可以通过对象访问(不推荐) |
初始化时机 | 类首次主动使用时(如创建实例、调用静态方法等) |
对比项 | 类变量(static) | 实例变量(非 static) |
---|---|---|
声明方式 | static 修饰 |
没有 static |
所属对象 | 类本身 | 类的每个实例 |
存储位置 | 方法区(JVM 规范) | 堆中每个对象内部 |
生命周期 | 随类加载而存在,类卸载而销毁 | 随对象创建而存在,垃圾回收而销毁 |
访问方式 | 推荐通过类名访问 | 必须通过对象访问 |
示例 | MyClass.count |
myObj.name |
用途 | 共享数据、计数器、常量等 | 描述对象状态 |
多个对象之间共享一个值,例如用户登录计数器。
class User {
public static int loginCount = 0;
public void login() {
loginCount++;
}
}
User u1 = new User();
u1.login();
User u2 = new User();
u2.login();
System.out.println(User.loginCount); // 输出:2
将不变的数据作为类变量,通常与 final
一起使用。
class Constants {
public static final double PI = 3.14159;
public static final String APP_NAME = "MyApp";
}
System.out.println(Constants.PI);
保存一些全局可用的配置或缓存数据。
class Config {
public static String ENV = "production";
}
if (Config.ENV.equals("development")) {
// 开发环境配置
}
在 Java 中,类方法(Class Method) 是指使用 static
关键字修饰的方法。它与对象无关,而是属于类本身的成员,可以通过类名直接调用。
成员方法可以直接访问类变量或类方法
静态方法只能访问静态成员,非静态的方法可以访问静态成员和非静态成员。(必须遵守访问权限)
public class MyClass {
// 类方法(静态方法)
public static void sayHello() {
System.out.println("Hello, static method!");
}
}
调用方式:
MyClass.sayHello(); // 不需要创建对象
特点 | 说明 |
---|---|
属于类本身 | 而不是类的实例 |
通过类名调用 | 也可以通过对象调用,但不推荐 |
不能访问非静态成员 | 不能直接访问实例变量或实例方法 |
可以重载 | 支持方法重载(Overload) |
不能被重写 | 子类不能重写父类的静态方法(静态绑定) |
独立于对象存在 | 即使没有实例,也能调用 |
this
:类方法不属于某个对象。super
:不能访问父类的实例成员。static
方法)。4.类方法不能被重写
静态方法不能被重写(Override),但可以通过相同的方法签名在子类中隐藏(Hide)。具体规则如下:
绑定机制不同
静态方法在编译时通过静态绑定确定调用哪个类的方法,而非运行时动态绑定[。例如:
Parent cp = new Child();
cp.getCName(); // 调用Parent的静态方法,而非Child的
隐藏而非重写
子类定义与父类相同的静态方法时,父类方法会被隐藏,而非被覆盖。调用时根据引用类型决定,而非实际对象类型。
无法实现多态
重写的目的是实现多态(运行时根据对象类型调用对应方法),但静态方法的隐藏行为无法满足这一条件。例如:
class Father {
public static void staticMethod() {
System.out.println("Father");
}
}
class Son extends Father {
public static void staticMethod() {
System.out.println("Son");
}
}
Father mBySon = new Son();
mBySon.staticMethod(); // 输出"Father",非"Son"
3.语法限制
在子类中定义与父类相同的静态方法时,若使用@Override
注解会编译报错,因为逻辑上不构成重写
在Java中,main
方法必须声明为static
,这是由JVM(Java虚拟机)的调用机制决定的
在 Java 中,程序的入口方法是:
public static void main(String[] args)
main
方法必须是 static
的?Java 程序的执行是从 main
方法开始的,而 JVM(Java 虚拟机)在启动时并不会自动创建该类的对象。
1.main方法是虚拟机调用;
2.java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public;
3.java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static;
你可以通过命令行向 main
方法传递参数:
public class ArgsDemo {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}
}
}
编译并运行:
javac ArgsDemo.java java ArgsDemo hello world 123
输出结果:
hello world 123
public static void main(String[] args) { // 程序入口代码 }
程序启动入口
java ClassName
命令运行程序时,JVM 会查找并调用该类的 main
方法。java HelloWorld
接收命令行参数
java Calculator add 10 20
main
方法中,args
数组内容为:["add", "10", "20"]
特征 | 是否允许 |
---|---|
必须是 public |
是 |
必须是 static |
是 |
返回类型必须是 void |
是 |
方法名必须是 main |
是 |
参数必须是 String[] 类型 |
是 |
只要满足语法要求,main
方法可以写在任何类中。
示例:
public class A {
public static void main(String[] args) {
System.out.println("Class A main");
}
}
class B {
public static void main(String[] args) {
System.out.println("Class B main");
}
}
你可以通过命令行运行不同的类:
java A # 输出: Class A main
java B # 输出: Class B main
Java 允许你在多个类中定义 main
方法,但每次只能运行一个类的 main 方法,由你指定要执行的类名决定。
场景 | 是否允许 |
---|---|
多个类都有 main 方法 | 允许 |
main 方法写在非 public 类中 | 允许 |
main 方法写在接口中 | 允许(Java 8+) |
main 方法写在内部类中 | 允许 |
main 方法参数不是 String[] | ❌ 不允许 |
main 方法不是 static | ❌ 不允许 |
一个类中定义了 main
方法后,它可以自己调用自己。这种“自己调用自己的类”在 Java 中是非常常见的做法,尤其是在封装逻辑、复用代码或进行递归调用时。
类自己调用自己的方法
public class SelfCallingClass {
public static void main(String[] args) {
System.out.println("开始执行 main 方法");
doSomething();
}
public static void doSomething() {
System.out.println("doSomething 方法被调用");
}
}
/*
SelfCallingClass 类有 main 方法。
在 main 方法中调用了本类的静态方法 doSomething()。
这就是类自己调用自己的方法的一种常见形式。*/
类自己调用自己的 main 方法(递归)
你甚至可以递归地调用 main
方法(虽然不推荐):
public class SelfCallingClass {
public static void main(String[] args) {
System.out.println("main 方法被调用");
main(null); // 自己调用自己(递归)
}
}
注意:这会导致无限递归,最终抛出 StackOverflowError
。
类内部调用自己的构造函数(this 和 super)
除了调用方法,还可以在构造函数中调用其他构造函数:
public class Person {
public Person() {
this("未知姓名"); // 调用另一个构造函数
System.out.println("无参构造函数");
}
public Person(String name) {
System.out.println("带参构造函数: " + name);
}
public static void main(String[] args) {
new Person(); // 触发构造函数的自调用
}
}
/*
输出
带参构造函数: 未知姓名
无参构造函数
*/
静态方法调用非静态方法
如果想在静态方法中调用非静态方法,需要先创建当前类的对象:
public class MyClass {
public void instanceMethod() {
System.out.println("实例方法");
}
public static void staticMethod() {
MyClass obj = new MyClass();
obj.instanceMethod(); // 通过对象调用非静态方法
}
public static void main(String[] args) {
staticMethod(); // main 调用静态方法,间接调用实例方法
}
}
代码块:又称为初始化块,属于类中的成员【即是类的一部分】,类似于方法,将逻辑语句封装在方法体中,用 {} 包围起来。
语法:
static {
// 静态代码块内容
}
/*
修饰符可以选,要写的话,也只能写static;
代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的叫做普通代码块/非静态代码块;
逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
;号可以写上,也可以省略。
*/
根据所处的位置和修饰符的不同,Java 的代码块可以分为以下几类
类型 | 关键字 | 所属对象 | 执行时机 |
---|---|---|---|
局部代码块(Local Block) | 无 | 方法内部或语句块中 | 随方法调用执行 |
构造代码块(Instance Block) | 无 | 类中、方法外 | 每次创建对象时执行 |
静态代码块(Static Block) | static |
类中、方法外 | 类加载时执行一次 |
同步代码块(Synchronized Block) | synchronized |
方法内部或语句块中 | 多线程访问时控制同步 |
写在方法内部或任意语句块中的 {}
。
作用:限制变量作用域,提高代码可读性和安全性。
public class Test {
public void method() {
{
int x = 10;
System.out.println("x = " + x);
}
// System.out.println(x); // 编译错误:x 不可见
}
}
定义在类中、方法外部的代码块,没有 static
修饰。
作用:多个构造函数共享的初始化逻辑。
class Person {
{
System.out.println("构造代码块执行");
}
public Person() {
System.out.println("无参构造函数");
}
public Person(String name) {
System.out.println("带参构造函数:" + name);
}
}
// 测试
new Person();
new Person("Alice");
// 输出:
// 构造代码块执行
// 无参构造函数
// 构造代码块执行
// 带参构造函数:Alice
使用 static {}
定义的代码块。
作用: 类级别的初始化操作。
class MyClass {
static {
System.out.println("静态代码块执行");
}
public MyClass() {
System.out.println("构造函数执行");
}
}
// 测试
new MyClass();
new MyClass();
// 输出:
// 静态代码块执行
// 构造函数执行
// 构造函数执行
1.首次主动使用时加载
类在首次被主动使用时加载,例如:
new Class()
)。Class.forName("ClassName")
)。2.创建子类对象实例,父类也会被加载;
3.使用子类的静态成员时,父类也会被加载。
当一个类包含多种代码块时,其执行顺序如下:
首先是在类加载时,调用静态代码块和静态属性的初始化。
其次是在堆内存中创建对象空间,调用普通代码块和普通属性的初始化,调用构造方法。
class Parent {
static { System.out.println("父类静态代码块"); }
{ System.out.println("父类构造代码块"); }
public Parent() { System.out.println("父类构造函数"); }
}
class Child extends Parent {
static { System.out.println("子类静态代码块"); }
{ System.out.println("子类构造代码块"); }
public Child() { System.out.println("子类构造函数"); }
}
// 测试
new Child();
// 输出:
// 父类静态代码块
// 子类静态代码块
// 父类构造代码块
// 父类构造函数
// 子类构造代码块
// 子类构造函数
单例模式(Singleton Pattern)是 Java 中最常用的设计模式之一,属于创建型设计模式。它确保一个类只有一个实例,并提供一个全局访问点。
饿汉式在类加载时就创立了对象,可能造成资源浪费。
/*
构造器私有化 =>防止直接new;
类的内部创建对象;
向外暴露一个静态的公共方法。
*/
public class Singleton {
private Singleton() {}
private static final Singleton INSTANCE = new Singleton();
public static Singleton getInstance() {
return INSTANCE;
}
}
首次调用时初始化实例,需处理线程安全。
/*
构造器私有化 =>防止直接new;
类的内部创建对象,但不new;
向外暴露一个静态的公共方法,调用该方法时候再new,再次调用时候,会返回上一次创建的对象,保证单例。
*/
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
new
创建对象。Serializable
接口,需重写 readResolve()
方法。final
关键字的作用Java 中的 final
关键字用于限制用户对类、方法和变量的操作。它有以下几种主要用途:
final int MAX_VALUE = 100;
MAX_VALUE = 200; // 编译错误:无法修改 final 变量
class Parent {
final void display() {
System.out.println("This is a final method.");
}
}
class Child extends Parent {
// 无法重写 display() 方法
}
String
类,确保其不变性。final class MyClass { // 此类不能被继承 }
final
变量必须在声明时或构造函数中初始化。final
变量是引用类型,则引用地址不可变,但对象内部状态可以修改如果final修饰的属性是静态的,则初始化的位置只能是:定义时; 在静态代码块中。不能在构造器中赋值,因为static属性在类加载时候就有了,但是构造器是在对象创建时才调用的。
final类不能继承,但是可以实例化对象;
如果类不是final类,但是含有final方法, 则该方法虽然不能重写,但是可以被继承;
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法;
final不能修饰构造方法(即构造器);
包装类(Integer,Double,Float,Boolean等都是final),String也是final类。
当父类的某些方法,需要声明,但是又不确定如何实现时,可以将其声明为抽象到底,那么这个类就是抽象类。
抽象类的价值更多在于设计,是设计者设计好之后,让子类继承并且实现的类。
在 Java 中,抽象类(Abstract Class) 是一种不能被实例化的类,主要用于作为其他类的基类。它允许定义抽象方法和具体方法,并且可以包含字段、构造函数、静态方法等。
abstract
关键字声明。abstract class Animal {
// 抽象方法 不能包含方法体
public abstract void makeSound();
// 具体方法
public void breathe() {
System.out.println("Breathing...");
}
}
特性 | 描述 |
---|---|
不能实例化 | new Animal() 会编译错误 |
抽象类不一定要包含abstract方法。 | 也就是说,抽象类可以没有abstract方法。旦类包含了abstract方法,则这个类必须声明为abstract |
可以有构造函数 | 被子类调用 |
可以有具体方法 | 支持普通方法实现 |
支持访问控制 | 成员可以是 private , protected , public |
可以继承其他类 | 支持单继承 |
可以实现接口 | 可以实现多个接口 |
比较项 | 抽象类 | 普通类 |
---|---|---|
是否能实例化 | ❌ 不能 | ✅ 可以 |
是否必须有抽象方法 | ❌ 不一定 | ❌ 不能有抽象方法 |
构造函数 | ✅ 可以有 | ✅ 可以有 |
成员变量 | ✅ 支持 | ✅ 支持 |
继承关系 | ✅ 支持 | ✅ 支持 |
接口实现 | ✅ 可以实现接口 | ✅ 可以实现接口 |
//基础抽象类
abstract class Shape {
abstract double getArea(); // 抽象方法
void printType() { // 具体方法
System.out.println("This is a shape.");
}
}
class Circle extends Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double getArea() {
return Math.PI * radius * radius;
}
}
模板方法模式(Template Method Pattern):在一个抽象类中定义一个或多个抽象方法,以及一个模板方法,该模板方法调用这些抽象方法,并定义了它们的执行顺序。子类通过实现抽象方法来完成具体的业务逻辑。
抽象类(Abstract Class)
具体子类(Concrete Class)
实现抽象类中的抽象方法,覆盖钩子方法,但不改变模板方法的结构。
final
,防止子类重写算法结构。private
、final
、static
修饰。角色 | 描述 |
---|---|
AbstractClass |
定义了一个或多个抽象方法供子类实现,同时定义并实现模板方法,该方法调用这些基本方法来完成整个流程。 |
ConcreteClass |
实现抽象类中定义的抽象方法,完成与特定上下文相关的逻辑。 |
//模板方法模式
abstract class Game {
// 抽象方法,由子类具体实现
abstract void initialize();
abstract void startPlay();
abstract void endPlay();
// 模板方法 定义算法骨架
public final void play() {
initialize();
startPlay();
endPlay();
}
}
class Cricket extends Game {
@Override
void initialize() {
System.out.println("Cricket initialized");
}
@Override
void startPlay() {
System.out.println("Cricket started");
}
@Override
void endPlay() {
System.out.println("Cricket ended");
}
}
//使用示例
const game: Game = new Cricket ();
game.play();
abstract
修饰私有方法:因为私有方法无法被继承,也无法被重写。在 Java 中,接口(interface
)是一种抽象类型,它是对行为的抽象。接口中可以定义常量和抽象方法(隐式 public abstract
),但不能包含方法的具体实现(Java 8 之前)。从 Java 8 开始,接口可以包含默认方法和静态方法。
[访问修饰符] interface 接口名 [extends 父接口列表] {
// 常量定义
// 抽象方法声明
// 默认方法
// 静态方法
// 私有方法
}
//Animal 抽象类
public interface Animal {
void speak() {}
}
public class Dog implements Animal {
@Override
public void speak() {
System.out.println("汪汪!");
}
}
//多个接口实现
public class Robot implements Animal, Machine {
@Override
public void speak() {
System.out.println("Robot speaking");
}
@Override
public void start() {
System.out.println("Machine started");
}
}
特性 | 描述 |
---|---|
不可实例化 | 接口不能被 new 实例化 |
可多继承 | 一个类可以实现多个接口 |
方法默认 public abstract | 所有方法默认为 public abstract |
常量默认 public static final | 接口中定义的变量默认是 public static final 类型 |
默认方法(Default Method)
允许接口提供默认实现:
public interface Animal {
void speak();
default void breathe() { System.out.println("Breathing...");
}
}
interface C{
default void fun2(){//如果用default修饰,那么就可以有具体的实现,可以被重写
System.out.println("fun2 ");
}
static void fun3(){//如果用static修饰,那么就可以有具体的实现,可以不用被重写
System.out.println("static ");
}
}
子类可以选择是否重写默认方法。
静态方法(Static Method)
接口也可以拥有静态方法:
public interface Animal {
static void info() {
System.out.println("This is an animal interface.");
}
}
Animal.info(); // 直接通过接口名调用
对比项 | 接口 | 抽象类 |
---|---|---|
方法实现 | Java 8+ 支持默认/静态方法 | 可以有具体方法 |
成员变量 | 默认 public static final |
普通变量可用 |
构造函数 | 无 | 有 |
多继承 | 支持 | 不支持 |
使用目的 | 行为规范 | 共享代码逻辑 |
口的多态特性是指通过接口引用调用不同实现类的方法,实现“一个接口多种行为”的能力。
接口多态性的实现机制
此处,Animal
接口引用 a1
和 a2
分别指向 Cat
和 Cow
对象,调用 sound()
方法时会根据实际对象类型动态绑定到具体实现
interface Animal {
void sound();
}
class Cat implements Animal {
public void sound() { System.out.println("Cat meows"); }
}
class Cow implements Animal {
public void sound() { System.out.println("Cow moos"); }
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Cat(); // 接口引用指向Cat对象
Animal a2 = new Cow(); // 接口引用指向Cow对象
a1.sound(); // 输出 "Cat meows"
a2.sound(); // 输出 "Cow moos"
}
}
Shape s = new Circle()
。List list = new ArrayList()
特性 | 接口(Interface) | 继承(Class Inheritance) |
---|---|---|
定义方式 | 使用 interface 关键字定义 |
使用 class 定义,通过 extends 实现继承 |
表达关系 | 表示“能做什么”(行为规范) | 表示“是什么”(父子类关系) |
方法实现 | 默认是抽象方法(Java 8+ 支持 default/static) | 可以有具体实现 |
成员变量 | 默认 public static final 常量 |
普通成员变量 |
构造函数 | 不可以有构造函数 | 可以有构造函数 |
多继承 | ✅ 支持多接口实现 | ❌ 不支持多继承(只能有一个父类) |
如果你正在设计一个系统,建议:
接口的继承是一个非常重要的特性,它允许一个接口从另一个或多个接口中继承方法定义和默认实现。通过接口继承,可以构建出更加灵活、可扩展的系统架构。
default
)和静态方法(static
)。interface A {
void methodA();
}
interface B extends A {
void methodB();
}
下面我们详细解释这两者的含义以及它们在接口继承中的表现。
子接口或实现类可以覆盖父接口中定义的方法(包括默认方法),这称为“方法重写”。
// 父接口
interface Animal {
default void speak() {
System.out.println("Animal speaks");
}
}
// 子接口重写父接口的默认方法
interface Dog extends Animal {
@Override
default void speak() {
System.out.println("Dog barks");
}
}
// 实现类
class MyDog implements Dog {
public static void main(String[] args) {
MyDog dog = new MyDog();
dog.speak(); // 输出: Dog barks
}
}
@Override
来解决冲突。重载(Overloading) 是指在同一作用域内定义多个同名但参数列表不同的方法。
interface Calculator {
int add(int a, int b);
double add(double a, double b); // 不是重载,而是两个不同方法
}
interface BadInterface {
void doSomething();
void doSomething(int x); // 编译错误?不是,这是合法的!
}
注意:上面这个例子其实是合法的,因为接口可以声明多个签名不同的方法,这并不违反 Java 规范。它只是声明了两个方法,并不是“重载”的行为本身发生在接口内部。
对比项 | 接口是否支持 |
---|---|
方法重写 | ✅ 支持(通过子接口或实现类) |
方法重载 | ❌ 不支持(接口本身不能实现方法,无法执行重载逻辑) |
默认方法 | ✅ 支持(Java 8+) |
静态方法 | ✅ 支持(Java 8+) |
私有方法 | ✅ 支持(Java 9+,用于辅助默认方法) |
当一个类实现多个接口,且这些接口有相同的默认方法时,需要显式 @Override
并指定使用哪个接口的默认方法:
interface A {
default void show() {
System.out.println("A");
}
}
interface B {
default void show() {
System.out.println("B");
}
}
class MyClass implements A, B {
@Override public void show() {
A.super.show(); // 显式调用 A 的默认方法
}
}
abstract
default
方法时,实现类必须显式覆盖该方法以解决冲突接口中的静态方法只能通过接口名调用
接口不能有构造函数
able
, ible
, tion
, er
ava 的内部类(Inner Class)是指定义在另一个类内部的类。它是 Java 中面向对象编程的一个重要特性,常用于封装、逻辑组织和访问外部类成员等场景。
类型 | 描述 |
---|---|
成员内部类 | 定义在外部类的成员位置上,与方法、属性并列。 |
静态内部类 | 使用 static 修饰的成员内部类,不能直接访问外部类的非静态成员。 |
局部内部类 | 定义在方法或作用域内的类,只能在该方法内使用。 |
匿名内部类 | 没有类名的内部类,通常用于实现接口或继承类并立即实例化。 |
public class Outer {
private String outerMsg = "Outer Message";
// 成员内部类
class Inner {
void print() {
System.out.println(outerMsg); // 可以访问外部类的私有成员
}
}
void testInner() {
// // 在外部类中使用内部类
Inner inner = new Inner();
inner.print();
}
}
//在其他类中使用成员内部类
Outer outer = new Outer(); Outer.Inner inner = outer.new Inner();
特点:
可以访问外部类的所有成员(包括私有)。
静态内部类是定义在外部类的成员位置,并且有static修饰
public class Outer {
// 静态内部类
public static class StaticNested {
// 可以包含静态和非静态成员
}
}
//创建静态内部类对象的格式:
外部类名.内部类名 对象名 = new 外部类名.内部类名();
//调用静态内部类的静态方法:外部类名.内部类名.方法名();
//调用静态内部类的非静态方法:先创建静态内部类对象,再用对象调用方法
public class Outer {
private static String staticMsg = "Static Message";
private String instanceMsg = "Instance Message";
// 静态内部类
public static class StaticNested {
void print() {
System.out.println(staticMsg); // 合法:访问外部类的静态字段
// System.out.println(instanceMsg); // 编译错误:不能访问非静态字段
}
}
}
// 使用
public class Main {
public static void main(String[] args) {
Outer.StaticNested nested = new Outer.StaticNested();
nested.print(); // 输出: Static Message
}
}
public class Outer {
// 静态内部类
static class Inner {
// 静态方法
public static void staticMethod() {
System.out.println("静态内部类的静态方法");
}
// 非静态方法
public void normalMethod() {
System.out.println("静态内部类的非静态方法");
}
}
}
// 调用方法
public class Test {
public static void main(String[] args) {
// 调用静态方法
Outer.Inner.staticMethod();
// 调用非静态方法
Outer.Inner inner = new Outer.Inner();
inner.normalMethod();
}
}
特点:
是一种没有名字的类,定义并实例化的同时会创建一个类的对象。它是 在类定义的地方直接实例化 的,通常用于实现接口或继承类,并覆盖或实现必要的方法。
匿名内部类是定义在方法或代码块中的类,同时直接创建其实例。它本质上是接口或抽象类的子类对象
new 父类构造器(参数列表) 或 接口名() {
// 匿名内部类的类体部分
// 可以重写方法、定义新字段、初始化块等
}
//new 关键字:用于创建匿名内部类的实例。
//父类构造器(参数列表) 或 接口名 指的是匿名内部类继承的类或实现的接口。匿名内部类可以继承一个类或实现一个接口。
//实现接口的匿名内部类 这里我们创建了一个 Runnable 接口的匿名实现,并将其传给 Thread 构造器。
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Running in anonymous class");
}
};
new Thread(r).start();
//继承类的匿名内部类(包括抽象类)
abstract class Animal {
abstract void speak();
}
Animal a = new Animal() {
@Override
public void speak() {
System.out.println("Anonymous animal speaks");
}
};
a.speak();
特点:
Main$1.class
匿名内部类的参数传递是一个非常关键且容易出错的部分。由于匿名内部类没有显式的类名,它通常定义在方法或表达式内部,因此对外部变量的访问有严格的限制。
匿名内部类可以访问:
effectively final
指的是虽然没有显式使用final
修饰,但实际未被修改的局部变量。
不允许修改的变量
匿名内部类不能修改外部方法中传入的局部变量(即使是非 final 的),否则编译报错。
示例 1:访问 final 局部变量
public class Test {
public static void main(String[] args) {
final String name = "Alice";
Runnable r = new Runnable() {
@Override public void run() {
System.out.println("Hello, " + name); // 正确:访问 final 变量
}
};
new Thread(r).start();
}
}
示例 2:访问 effectively final 变量 (Java 8+)
public class Test {
public static void main(String[] args) {
String message = "Welcome"; // 虽然没加 final,但未被修改
Runnable r = new Runnable() {
@Override public void run() {
System.out.println(message); // 正确:effectively final
}
};
}
}
示例 3:尝试修改外部变量
public class Test {
public static void main(String[] args) {
String msg = "Hello";
Runnable r = new Runnable() {
@Override
public void run() {
msg = "Hi"; // 编译错误:Cannot assign a value to final variable 'msg'
}
};
}
}
即使你没有用 final
修饰,只要你在匿名类中试图修改该变量,Java 就会将其视为 final
。
如果你想在匿名内部类中“修改”外部变量,可以使用以下方式:
方法一:使用数组包装变量(不推荐)
public class Test {
public static void main(String[] args) {
final String[] data = { "Initial" };
Runnable r = new Runnable() {
@Override
public void run() {
data[0] = "Modified"; // 修改数组内容是允许的 System.out.println(data[0]); } };
new Thread(r).start();
}
}
注意:数组本身是 final 的,但数组元素是可以变的。
方法二:使用可变对象(如 AtomicReference
, StringBuilder
)
import java.util.concurrent.atomic.AtomicReference;
public class Test { public static void main(String[] args) {
AtomicReference ref = new AtomicReference<>("Start");
Runnable r = new Runnable() {
@Override
public void run() {
ref.set("Updated");
System.out.println(ref.get()); } };
new Thread(r).start();
}
}
除了访问外部变量,你还可以通过构造器传参的方式将数据传递给匿名内部类。
示例:通过构造器传参
public class Test {
public static void main(String[] args) {
String user = "Bob"; // 通过构造器传参方式创建一个匿名子类
Animal a = new Animal(user) {
private String name;
{ // 初始化块 this.name = user; }
@Override
void speak() {
System.out.println("Hello, " + name); } };
a.speak();
}
}
abstract class Animal {
Animal(String name) { System.out.println("Animal constructor called with: " + name);
}
abstract void speak();
}
注意事项 | 说明 |
---|---|
访问外部变量必须是 final 或 effectively final | 否则编译失败 |
不能直接修改外部局部变量 | 即使不是 final,也不允许赋值 |
可以使用数组/可变对象间接修改 | 如 String[] , AtomicReference , List 等 |
构造器传参是一种替代方案 | 更清晰,适合复杂逻辑 |
Lambda 表达式也遵循相同规则 | Java 8+ 的 Lambda 和匿名内部类一样受限制 |
在 Java 中,局部内部类(Local Inner Class) 是定义在方法、构造函数或代码块中的类。它仅在定义它的那个方法或代码块内可见和使用。
public class Outer {
void method() {
// 局部内部类定义在方法中
class LocalInner {
void print() {
System.out.println("Inside LocalInner");
}
}
LocalInner inner = new LocalInner();
inner.print();
}
}
public class Outer {
private String outerMsg = "Outer Message";
void method() {
String localMsg = "Local Message"; // JDK 8+ 可以不写 final
// 局部内部类
class LocalInner {
void print() {
System.out.println(outerMsg); // 访问外部类成员
System.out.println(localMsg); // 访问方法内的局部变量(Effectively Final)
}
}
LocalInner inner = new LocalInner();
inner.print();
}
public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}
特点: