09、JDK 23 新特性:隐式声明的类和实例主方法(JEP 477):简化单类程序开发的实践

学Java的时候,最烦的就是写个Hello World还得声明个类,main方法还得是static和public,参数还得是String[] args,一堆模板代码,看着就烦。JDK 23的JEP 477整了个新活,可以不用显式声明类,main方法也不用static了,写简单程序省事多了。

鹏磊我刚开始学Java那会儿,写个最简单的程序都得这样:

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

现在有了JEP 477,直接这样写就完事了:

void main() {
    println("Hello, World!");
}

简单多了,特别是对初学者来说,门槛降低了不少。这个特性主要是为了让写单类程序更方便,不用再写那些模板代码了。

JEP 477 的核心特性

JEP 477引入了两个主要特性:隐式声明的类和实例主方法。这两个特性配合起来,让写简单Java程序变得更容易了。

隐式声明的类

隐式声明的类就是编译器自动帮你创建的类。如果你在源文件里写了方法和字段,但是没有显式声明类,编译器就会自动创建一个隐式类,把这些方法和字段都放进去。

这个隐式类有几个特点:

  1. 自动继承Object:隐式类自动继承java.lang.Object,不用你写extends
  2. 不实现接口:隐式类不实现任何接口,就是最基础的类
  3. 不能通过名称引用:隐式类没有名字,你不能在代码里写它的名字来引用它
  4. 自动导入java.base:隐式类自动导入java.base模块的所有公共类,不用写import
// 传统方式,得显式声明类
public class Calculator {
    int add(int a, int b) {
        return a + b;
    }
    
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(1, 2));
    }
}

// 新方式,不用声明类了
int add(int a, int b) {
    return a + b;
}

void main() {
    println(add(1, 2));  // 直接调用,不用创建对象
}

编译器会自动创建一个隐式类,把add方法和main方法都放进去。main方法可以直接调用add方法,因为它们都在同一个隐式类里。

实例主方法

实例主方法就是不用static的main方法。传统的main方法必须是static的,因为JVM调用的时候还没有对象实例。但是有了隐式类,JVM可以先创建隐式类的实例,然后调用实例的main方法。

实例主方法的特点:

  1. 不需要static:main方法可以是实例方法,不用static修饰
  2. 不需要public:main方法可以是包私有的,不用public修饰
  3. 可以没有参数:main方法可以没有String[] args参数,如果不需要命令行参数的话
  4. 可以直接访问实例成员:因为是实例方法,可以直接访问类的字段和其他实例方法
// 传统方式,main必须是static
public class Counter {
    private int count = 0;
    
    void increment() {
        count++;
    }
    
    public static void main(String[] args) {
        Counter c = new Counter();  // 得先创建对象
        c.increment();
        System.out.println(c.count);
    }
}

// 新方式,main可以是实例方法
int count = 0;  // 实例字段

void increment() {
    count++;  // 直接访问实例字段
}

void main() {
    increment();  // 直接调用实例方法
    println(count);  // 直接访问实例字段
}

这样写简单多了,不用创建对象,不用static,代码更直观。

自动导入和简化的I/O

自动导入java.base模块

隐式声明的类自动导入java.base模块的所有公共类,这意味着你可以直接使用java.lang、java.util、java.io等包里的类,不用写import。

// 传统方式,得写import
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;

public class Example {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
    }
}

// 新方式,自动导入,不用写import
void main() {
    List<String> list = new ArrayList<>();  // 直接用,不用import
    Map<String, Integer> map = new HashMap<>();  // 直接用,不用import
}

这样写代码的时候,不用再写一堆import语句了,特别是用java.base模块里的类的时候。

简化的控制台I/O

JEP 477还引入了新的java.io.IO类,提供了简化的控制台I/O方法。这个类提供了静态方法,可以直接调用,不用写System.out.println这么长了。

// 传统方式
System.out.println("Hello");  // 太长了
System.out.print("World");
String line = System.console().readLine();  // 读取输入也麻烦

// 新方式,用IO类的静态方法
println("Hello");  // 简洁多了
print("World");
String line = readLine();  // 读取输入也简单了

IO类提供了这些静态方法:

  • println(Object):打印对象并换行
  • print(Object):打印对象不换行
  • readLine():读取一行输入
  • readLine(String prompt):带提示的读取一行输入
void main() {
    println("请输入你的名字:");  // 打印提示
    String name = readLine();  // 读取输入
    println("你好," + name + "!");  // 打印问候
}

这样写控制台程序就简单多了,不用再写System.out.println这么长的代码了。

实际应用场景

场景1:简单的计算器

写个简单的计算器,传统方式得声明类,main得是static:

// 传统方式
public class Calculator {
    int add(int a, int b) {
        return a + b;
    }
    
    int subtract(int a, int b) {
        return a - b;
    }
    
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println("5 + 3 = " + calc.add(5, 3));
        System.out.println("5 - 3 = " + calc.subtract(5, 3));
    }
}

用JEP 477就简单多了:

// 新方式,不用声明类,main不用static
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

void main() {
    println("5 + 3 = " + add(5, 3));  // 直接调用,不用创建对象
    println("5 - 3 = " + subtract(5, 3));
}

代码简洁多了,逻辑也更清晰。

场景2:简单的文本处理

处理文本的时候,经常要用到String、List这些类:

// 传统方式
import java.util.List;
import java.util.ArrayList;

public class TextProcessor {
    public static void main(String[] args) {
        List<String> words = new ArrayList<>();
        words.add("Hello");
        words.add("World");
        for (String word : words) {
            System.out.println(word.toUpperCase());
        }
    }
}

用JEP 477:

// 新方式,自动导入,不用写import
void main() {
    List<String> words = new ArrayList<>();  // 直接用,不用import
    words.add("Hello");
    words.add("World");
    for (String word : words) {
        println(word.toUpperCase());  // 用println,不用System.out.println
    }
}

场景3:交互式程序

写交互式程序,传统方式读取输入比较麻烦:

// 传统方式
import java.util.Scanner;

public class Interactive {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入你的名字:");
        String name = scanner.nextLine();
        System.out.println("你好," + name + "!");
        scanner.close();
    }
}

用JEP 477:

// 新方式,用IO类的readLine方法
void main() {
    print("请输入你的名字:");  // 用print,不换行
    String name = readLine();  // 用readLine,不用Scanner
    println("你好," + name + "!");
}

简单多了,不用创建Scanner对象,不用记得close。

场景4:快速原型开发

写快速原型的时候,用JEP 477能省不少事:

// 快速测试一个算法
void main() {
    int[] numbers = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // 排序
    Arrays.sort(numbers);
    
    // 打印结果
    for (int num : numbers) {
        print(num + " ");
    }
    println();
    
    // 计算平均值
    double sum = 0;
    for (int num : numbers) {
        sum += num;
    }
    double avg = sum / numbers.length;
    println("平均值: " + avg);
}

不用声明类,不用static main,不用import,直接写逻辑就行,特别适合快速测试和原型开发。

与传统方式的对比

代码量对比

看个完整的例子,对比一下代码量:

// 传统方式:Hello World程序
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
// 5行代码,还得记住public、static、String[] args这些

// JEP 477方式
void main() {
    println("Hello, World!");
}
// 3行代码,简单明了

代码量减少了,而且更容易理解。

学习曲线对比

对初学者来说,JEP 477降低了学习门槛:

// 传统方式,初学者得理解这些概念:
// - 什么是类(class)
// - 什么是public
// - 什么是static
// - 什么是String[] args
// - 什么是System.out.println
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

// JEP 477方式,初学者只需要理解:
// - 什么是方法(method)
// - 方法怎么调用
void main() {
    println("Hello, World!");
}

概念更少,更容易上手。

适用场景对比

JEP 477适合写简单程序,但是复杂程序还是用传统方式好:

// 简单程序,用JEP 477
void main() {
    println("Hello, World!");
}

// 复杂程序,还是用传统方式
public class ComplexApp {
    private Service service;
    private Repository repository;
    
    public ComplexApp(Service service, Repository repository) {
        this.service = service;
        this.repository = repository;
    }
    
    public static void main(String[] args) {
        // 依赖注入、配置加载等复杂逻辑
        Service service = new ServiceImpl();
        Repository repo = new RepositoryImpl();
        ComplexApp app = new ComplexApp(service, repo);
        app.run();
    }
    
    void run() {
        // 业务逻辑
    }
}

简单程序用JEP 477,复杂程序用传统方式,各取所长。

编译和运行

启用预览特性

JEP 477是预览特性,得先启用才能用:

# 编译时启用预览特性
javac --enable-preview --release 23 HelloWorld.java

# 运行时启用预览特性
java --enable-preview HelloWorld

Maven配置

如果用Maven,可以在pom.xml里配置:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.11.0</version>
            <configuration>
                <release>23</release>
                <compilerArgs>
                    <arg>--enable-preview</arg>
                </compilerArgs>
            </configuration>
        </plugin>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>3.1.0</version>
            <configuration>
                <mainClass>HelloWorld</mainClass>
                <jvmArgs>--enable-preview</jvmArgs>
            </configuration>
        </plugin>
    </plugins>
</build>

Gradle配置

如果用Gradle,可以在build.gradle里配置:

tasks.withType(JavaCompile) {
    options.compilerArgs += '--enable-preview'
    options.release = 23
}

tasks.withType(JavaExec) {
    jvmArgs += '--enable-preview'
}

限制和注意事项

隐式类的限制

隐式类有一些限制,得注意:

  1. 不能有显式的类声明:如果文件里有显式的类声明,就不能用隐式类了
  2. 不能有多个隐式类:一个文件只能有一个隐式类
  3. 不能继承或实现:隐式类不能显式继承其他类或实现接口
  4. 不能有包声明:隐式类必须在默认包里,不能有package声明
// 错误:不能有package声明
package com.example;  // 编译错误
void main() {
    println("Hello");
}

// 错误:不能有显式类声明
public class MyClass {  // 如果有了这个,就不能用隐式类了
}
void main() {  // 编译错误
    println("Hello");
}

实例主方法的限制

实例主方法也有一些限制:

  1. 只能有一个main方法:一个隐式类只能有一个main方法
  2. 参数类型有限制:如果有参数,必须是String[]类型
  3. 返回类型必须是void:main方法不能有返回值
// 错误:多个main方法
void main() {
    println("Hello");
}

void main(String[] args) {  // 编译错误,不能有多个main
    println("World");
}

// 错误:main方法有返回值
int main() {  // 编译错误,main必须是void
    return 0;
}

预览特性的风险

JEP 477是预览特性,可能会在未来的版本里变化或者被移除,所以:

  1. 不要在生产环境用:预览特性还不稳定,生产环境用有风险
  2. 关注版本更新:如果JDK版本升级,预览特性可能会变化
  3. 做好迁移准备:如果预览特性被移除或者语法变了,得准备好迁移方案

最佳实践

1. 适合的场景

JEP 477适合这些场景:

  • 学习Java:初学者写简单程序,降低学习门槛
  • 快速原型:快速测试想法,不用写模板代码
  • 脚本程序:写简单的脚本程序,不用复杂的类结构
  • 单文件程序:只有一个文件的简单程序
// 适合用JEP 477的场景:简单的工具脚本
void main() {
    // 读取文件
    String content = Files.readString(Path.of("input.txt"));
    
    // 处理内容
    String processed = content.toUpperCase();
    
    // 写入文件
    Files.writeString(Path.of("output.txt"), processed);
    
    println("处理完成!");
}

2. 不适合的场景

JEP 477不适合这些场景:

  • 大型项目:需要多个类、包结构复杂的项目
  • 企业应用:需要依赖注入、框架集成的应用
  • 库开发:开发给别人用的库,需要明确的类声明
  • 需要继承或实现接口:需要继承其他类或实现接口的场景
// 不适合用JEP 477的场景:需要继承的类
// 这种场景还是用传统方式
public class MyList extends ArrayList<String> {
    // 需要继承,不能用隐式类
}

3. 代码组织建议

即使用JEP 477,也要注意代码组织:

// 好的做法:方法组织清晰
// 工具方法
int add(int a, int b) {
    return a + b;
}

int multiply(int a, int b) {
    return a * b;
}

// 主方法
void main() {
    println("5 + 3 = " + add(5, 3));
    println("5 * 3 = " + multiply(5, 3));
}

// 不好的做法:所有逻辑都堆在main里
void main() {
    // 100行代码都堆在这里,不好维护
}

4. 迁移建议

如果以后JEP 477稳定了,想从预览特性迁移到正式特性,建议:

  1. 保持代码简洁:即使特性稳定了,也保持代码简洁,不要过度使用
  2. 逐步迁移:如果项目里用了JEP 477,可以逐步迁移到正式特性
  3. 测试充分:迁移后要充分测试,确保功能正常

总结

JEP 477引入的隐式类和实例主方法,确实让写简单Java程序变得更容易了。特别是对初学者来说,不用再理解public、static这些概念,直接写逻辑就行,学习门槛降低了不少。

鹏磊我觉得这个特性还是挺有用的,特别是写快速原型、简单脚本的时候,能省不少事。但是也别过度使用,复杂程序还是用传统方式好,代码结构更清晰,维护起来也方便。

总的来说,JEP 477是Java降低学习门槛、简化开发的一个很好的尝试。虽然现在还是预览特性,但是未来应该会稳定下来,成为Java开发的一个常用特性,特别是对初学者和快速开发场景来说。

本文章最后更新于 2025-11-28