还在使用 Java 8 语法?Java实用新特性来一波

自 2014 年 Java 8 发布以来,Java 语言经历了多次重大更新,引入了许多新特性和语法改进。可是最近发现很多小伙伴还不太清楚一些新特性,这本身也没啥大不了。但是新的特性能让我们更加快速、优雅的完成工作,然后有更多的时间摸鱼,岂不是妙哉。

本文列出了Java8到Java21的部分特性变更,Java21后的特性也未列出。主要是新的LTS版本并未推出。

1. Lambda 表达式和 Stream API 的增强(Java 8+)

虽然 Lambda 表达式和 Stream API 在 Java 8 中引入,但后续版本对其进行了优化和扩展,更加的锦上添花,提供了更强大的工具。

  • Java 8 的基础

    • Lambda 表达式:简化函数式编程,替代匿名内部类。例如:

      java 复制代码
      List<String> list = Arrays.asList("abc", "def", "ghi");
      list.forEach(s -> System.out.println(s));
    • Stream API:支持函数式数据处理,如过滤、映射:

      java 复制代码
      /**
      * 过滤以"a"开头的元素
      */
      long count = list.stream().filter(s -> s.startsWith("a")).count(); 
      
      //anyMatch更合适,这边只为了map示例
      boolean isThress = list.stream()
          .filter(s -> s.startsWith("g"))
          .findFirst()
          .map(s -> s.length() == 3)
          .orElse(false);
  • Java 9+ 改进

    • Stream API 增强(Java 9):新增 takeWhile 和 dropWhile 方法,用于截取流:

      java 复制代码
      Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .takeWhile(n -> n < 4) // 遇到不满足条件的就停止 
          .forEach(System.out::println); // 输出:1, 2, 3
      
      Stream.of(1, 2, 3, 4, 5, 4, 3, 2, 1) .dropWhile(n -> n < 4) // 跳过所有满足条件的元素,直到遇到不满足的才开始处理 
          .forEach(System.out::println); // 输出:4, 5, 4, 3, 2, 1
    • Optional 改进(Java 9-10):Optional 类新增 or、ifPresentOrElse 和 stream 方法,让空值的处理更加优雅便捷。

      java 复制代码
      Optional<String> opt = Optional.of("hello");
      opt.ifPresentOrElse(System.out::println, // 如果存在值,则打印
                  () -> System.out.println("Empty")); // 否则打印 Empty
      
      Optional<String> emptyOpt = Optional.empty();
      String result = emptyOpt.or(() -> Optional.of("default")).get(); // 如果为空,提供一个替代 Optional
      System.out.println(result); // 输出:default
      
      // 将 Optional 转换为 Stream,方便与 Stream API 结合
      emptyOpt.stream().forEach(System.out::println); // 不输出任何东西
      Optional.of("stream me").stream().forEach(System.out::println); // 输出:stream me
  • 优势 :这些改进使流处理更灵活,代码更简洁,尤其在处理复杂数据管道时。特别是 takeWhiledropWhile 在处理部分满足条件的数据时能显著提高效率。Optional 的增强则让空值处理更加优雅和安全。

2. 接口中的默认方法(Java 8+)

Java 8 引入了接口的 默认方法(default methods) ,允许在接口中定义带实现的方法,这在不破坏现有实现类的情况下,为接口添加新功能提供了方便。

  • 语法示例

    java 复制代码
    public interface Vehicle {
        void start(); // 抽象方法
        default void stop() { // 默认方法,带有默认实现
            System.out.println("Vehicle stopped");
        }
    }
    
    public class Car implements Vehicle {
        public void start() {
            System.out.println("Car started");
        }
        // 无需实现 stop(),使用默认实现
    }
    
    // 用法
    public static void main(String[] args) {
        Car car = new Car();
        car.start(); // 输出: Car started
        car.stop();  // 输出: Vehicle stopped
    }
  • Java 9 增强

    • 接口支持 private 方法,用于复用默认方法内部的通用逻辑,而无需暴露给外部。这使得接口内部代码更整洁。

      java 复制代码
      public interface Vehicle {
          default void stop() {
              log("Stopping");
              System.out.println("Vehicle stopped");
          }
          // 私有方法,只能在接口内部被其他默认方法调用或静态方法调用
          private void log(String message) { 
              System.out.println("Log: " + message);
          }
      }
  • 用途

    • 兼容性:向现有接口添加新方法,而无需修改所有已有实现类(向后兼容)
    • 代码复用:提供共享的默认行为,减少实现类中的重复代码。
    • 内部封装::私有方法(Java 9)进一步支持代码复用和封装,保持简洁性。
  • 优势

    • 增强接口的灵活性,特别适合框架和库的更新升级(如 Java 8 中的 Collection 接口新增 stream() 默认方法)。
    • 私有方法使接口内部逻辑更清晰,避免重复代码。
  • 注意事项

    • 默认方法可能引发"菱形继承问题"(多个接口提供存在默认方法)。你需要通过 InterfaceName.super.method() 语法显式指定调用哪个接口的默认方法来解决冲突。

      java 复制代码
      public interface A {
          default void getHello() { System.out.println("Hello from A"); }
      }
      public interface B {
          default void getHello() { System.out.println("Hello from B"); }
      }
      public class MyClass implements A, B {
          public void getHello() {
              A.super.getHello(); // 显式调用 A 的默认方法
          }
      }

3. var 关键字

Java 10 引入了局部变量类型推断,使用 var 关键字简化变量声明,减少了冗余代码。使用过Kotlin、TypeScript、Rust等语言的小伙伴应该不陌生,不再详细阐述概念。

  • 语法示例

    java 复制代码
    var list = new ArrayList<String>(); // 编译器推断为 ArrayList<String>
    var stream = list.stream(); // 推断为 Stream<String>
  • 限制

    • 仅适用于局部变量 ,且声明时必须初始化
    • 不适用于成员变量、方法参数或返回值。
    • 不能用于声明多个变量 (var x = 10, y = 20, z = 30; // 这行代码也会引起编译错误!)
    • 不能用于声明复合类型 (例如数组的匿名初始化 var arr = {1, 2, 3}; 是不允许的,但 var arr = new int[]{1, 2, 3}; 是可以的,因为 new int[]{...} 提供了明确的类型)。
  • 优势:减少样板代码,提高可读性,尤其在复杂类型声明(如泛型)中。

4. 增强的 switch 表达式

Java 12 引入了 switch 表达式的预览特性,Java 14 正式标准化,显著改进了传统的 switch 语句,使其更加简洁和安全。

  • 箭头语法(Java 12)switch 表达式可以像 Lambda 表达式一样使用箭头 ->,直接返回一个值,避免了传统的 case 块末尾忘记 break 导致的问题。是不是马大哈程序猿的福音。

    java 复制代码
    String result = switch (day) {
        case "MONDAY", "FRIDAY" -> "工作日"; // 支持多标签 case
        case "SATURDAY", "SUNDAY" -> "周末休息";
        default -> "Invalid";// 必须包含 default 或覆盖所有可能值
    };
    System.out.println(result);
  • yield 关键字(Java 13)

    java 复制代码
    int value = switch (month) {
        case "JANUARY" -> 1;
        case "FEBRUARY" -> 2;
        default -> { yield -1; }
    };
    System.out.println(value);
  • 优势

    • 代码更简洁 :消除了 break 语句的需要,减少了代码行数,减少了出错概率。
    • 支持多标签 case :一个 case 可以对应多个值,提高了代码的聚合性。
    • 作为表达式返回值 :可以直接将 switch 表达式的结果赋值给变量,使得代码流更流畅。
    • 减少错误 :强制要求处理所有可能的 case 或提供 default 分支,降低了遗漏 break 或未处理情况的风险。

5. 文本块

Java 13 引入了文本块(Text Blocks)作为预览特性,Java 15 正式化,特别是对于包含 JSON、SQL、HTML 或其他语言代码的字符串。解决了程序猿模拟Json等字符串代码的噩梦。

  • 语法示例

    java 复制代码
    String json = """
        {
            "name": "John",
            "age": 30
        }
        """;// 自动处理内部换行和大部分缩进
        
        String html = """ 
            <html> 
                <body> 
                    <h1>Hello, Text Blocks!</h1> 
                </body> 
            </html> 
            """;
            System.out.println(json); 
            System.out.println(html);
  • 优势

    • 避免了繁琐的转义字符 :不再需要手动添加 \n 进行换行或 \ 来转义内部引号。
    • 自动处理缩进:自动识别移除多余的空白,使得代码更具可读性。
    • 所见即所得:多行字符串的布局在代码中就是最终的输出布局,极大提高了代码的可读性和可维护性。
    • 提供 stripIndentformatted 等方法进一步增强灵活性。

6. Record类

Java 14 引入了 record 作为预览特性,Java 16 正式化,用于创建不可变的数据类:

  • 语法示例

    只需一行代码即可定义一个数据类。

    java 复制代码
    public record Person(String name, int age) {}
  • 自动生成

    编译器会自动为 Record 生成以下内容:

    • 构造函数(包含所有参数)
    • getter(name()age()
    • equals()hashCode()toString() 方法,基于所有的参数进行实现
  • 用法

    java 复制代码
    var person = new Person("lele", 25); 
    System.out.println(person.name()); // 通过 getter 方法访问数据:lele     
    System.out.println(person.age()); // 通过 getter 方法访问数据:25 
    System.out.println(person); // 自动生成的 toString():Person[name=Alice, age=25]
  • 优势:极大减少代码,适合 DTO(数据传输对象)或简单数据载体。

7. 模式匹配

模式匹配是 Java 近年来的重点改进,逐步简化了类型检查和转换:

  • instanceof 模式匹配(Java 14-16)

    结合了类型检查和强制类型转换,避免了重复的类型转换。

    java 复制代码
    Object obj = "Hello World";
    if (obj instanceof String s) {
        System.out.println(s.toUpperCase());
    }
    
    Object num = 123; 
    if (num instanceof Integer i && i > 100) { // 支持 && 运算符,结合条件判断 
        System.out.println("Large integer: " + i); 
       }
  • switch 模式匹配(Java 17-21)

    sql 复制代码
    Object obj = 123;
    String result = switch (obj) {
        case String s -> "String: " + s;
        case Integer i -> "Integer: " + i;
        case null -> "Null value"; // Java 17+ 支持 null case 
        case Double d -> "Double: " + d; 
        default -> "Unknown type";
    };
    System.out.println(result);
  • 优势

    • 减少显式类型转换:消除了冗余的类型转换代码。
    • 代码更简洁、更安全:将类型检查和绑定变量结合在一起,减少了类型转换错误。
    • 提高可读性:代码逻辑更加直观,易于理解。

8. 密封类(Sealed Classes,Java 15-17)

Java 15 引入了密封类,Java 17 正式化,用于限制类或接口的继承或实现,增加安全性。

  • 语法示例

    使用 sealed 关键字,并通过 permits 列出允许的实现的子类。

    java 复制代码
    // 定义一个密封接口 Shape,只允许 Circle 和 Rectangle这两个类 实现它
    public sealed interface Shape permits Circle, Rectangle {}
    public record Circle(double radius) implements Shape {}
    public record Rectangle(double width, double height) implements Shape {}
    
    // 无法继承/实现 Shape,除非在 permits 列表中 
     public class Triangle implements Shape {} // 编译错误!
  • 优势

    • 增强类型安全性 :明确指定了允许的子类,编译器可以进行更严格的检查。确保所有可能的类型都被处理(例如在模式匹配的 switch 语句中)。
    • 与模式匹配结合 :密封类与 switch 表达式的模式匹配结合使用时,可以实现穷尽性检查 。如果 switch 语句没有覆盖密封类的所有许可子类型,编译器会发出警告,确保你处理了所有可能的情况
    • Java
java 复制代码
//求取面积
double getArea(Shape shape) {
    return switch (shape) {
        case Circle c -> Math.PI * c.radius() * c.radius();
        case Rectangle r -> r.width() * r.height();
        // 编译器会检查是否覆盖了所有许可子类
    };
}

9. 其他实用改进

  • Java 11

    • HTTP Client API :支持 HTTP/2 和 WebSocket 的现代 HTTP 客户端 API,告别了老旧的 HttpURLConnection

      ini 复制代码
      var client = HttpClient.newHttpClient();
      var request = HttpRequest.newBuilder().uri(URI.create("https://example.com")).build();
      var response = client.send(request, HttpResponse.BodyHandlers.ofString());
    • String 方法 :新增了 isBlank()lines()strip()stripLeading()stripTrailing()indentrepeat() 等便捷方法:

      java 复制代码
      String s = "  Hello  ";
      System.out.println(s.strip()); // "Hello"

      注意:与 trim() 方法不同,strip() 方法使用的是 Unicode 标准来判断哪些字符是空白字符,因此它能更好地支持国际化文本处理。


      java 复制代码
        String str = "Line 1\nLine 2\r\nLine 3";
        str.lines().forEach(System.out::println);
          // 输出:Line 1 Line 2 Line 3

      lines()方法用于按行分割字符串,并返回一个流(Stream),其中每个元素都是原字符串中的一个行。行分隔符可以是 \n、\r\n 或者 \r。读取配置文件内容或日志文件,这个方法非常有用。

      java 复制代码
      String str = "   Hello, World!   ";
      //移除字符串尾部的空白字符,并返回新的字符串,原字符串不变
      String strippedStr = str.stripTrailing();
      System.out.println(strippedStr); // 输出:    Hello, World!

      与之对应的stripLeading,不再阐述用法。


      java 复制代码
      String str = "Hello";
      String repeatedStr = str.repeat(3);
      System.out.println(repeatedStr); // 输出: HelloHelloHello

      此方法用于将字符串重复指定的次数,并返回一个新的字符串。原始字符串不会被修改。


      java 复制代码
      String text = "Hello\nWorld\n";
      // 增加 4 个空格的缩进
       String indented = text.indent(4);
       System.out.println(indented);
       // 输出:
      //    Hello
      //    World
      // 移除 2 个空格的缩进(如果存在)
      String textWithSpaces = " Hello\n World\n";
      String unindented = textWithSpaces.indent(-2);
      System.out.println(unindented);
      // 输出:
      // Hello
      // World

    注意:

    • 如果输入字符串不以换行符结尾,indent() 会自动在末尾添加一个换行符。
    • 如果行首的空格数少于要移除的空格数(n < 0),则只移除实际存在的空格。
    • n > 0:在每行前面添加 n 个空格。
    • n < 0:从每行开头移除最多 n 个空格(如果存在)。
    • n = 0:不更改缩进,但会标准化换行符(移除现有行尾的换行符并在最后添加一个换行符)
  • Java 12 :
    String.transform(Function f)

    transform() 方法允许对字符串应用一个指定的转换函数(Function)。这是一个高阶方法,适用于将字符串转换为其他类型或进行自定义处理。

    java 复制代码
     String text = "hello";
    
     // 将字符串转为大写
     Function<String, String> toUpperCase = String::toUpperCase;
     String result1 = text.transform(toUpperCase);
     System.out.println(result1); // 输出: HELLO
    
     // 将字符串转为长度(整数)
     Function<String, Integer> toLength = String::length;
     Integer result2 = text.transform(toLength);
     System.out.println(result2); // 输出: 5
    
     // 自定义转换:添加前缀
     Function<String, String> addPrefix = s -> "prefix_" + s;
     String result3 = text.transform(addPrefix);
     System.out.println(result3); // 输出: prefix_hello
    • 返回值:Function 处理后的结果,类型为 T。
  • Java 17

    • 随机数生成器改进 :新的 RandomGenerator 接口及其工厂类 RandomGeneratorFactory,统一随机数生成。
    • 增强的伪随机数生成器(PRNG)。
    java 复制代码
    import java.util.random.RandomGenerator;
    import java.util.random.RandomGeneratorFactory;
    
    // 获取默认推荐的随机数生成器,生成0-100的整数
    RandomGenerator defaultGenerator = RandomGenerator.getDefault();
    System.out.println("默认随机数: " + defaultGenerator.nextInt(100));
    
    // 获取特定算法的随机数生成器 (例如:L64X128MixRandom)
    RandomGenerator l64Generator = 
    RandomGeneratorFactory.of("L64X128MixRandom").create();
    System.out.println("L64X128MixRandom随机数: " + l64Generator.nextInt(100));
    
    // 获取加密安全的随机数生成器 (例如:SecureRandom)
    RandomGenerator secureGenerator = 
    RandomGeneratorFactory.of("SecureRandom").create();
    System.out.println("SecureRandom随机数: " + secureGenerator.nextInt(100));
  • Java 21

    • 虚拟线程(Virtual Threads) :轻量级线程,极大提高并发性能:

      java 复制代码
      import java.util.concurrent.Executors; // 使用虚拟线程执行器,每个任务都运行在一个轻量级虚拟线程中 
      try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { 
              for (int i = 0; i < 10_000; i++) { // 轻松创建大量并发任务 final 
                  int taskId = i; executor.submit(() -> { // 模拟耗时操作,如网络请求或数据库查询 Thread.sleep(10); 
                  System.out.println("Task " + taskId + " running in virtual thread: " + Thread.currentThread()); 
                 }); 
            } 
      } // executor.close() 会等待所有任务完成 
      System.out.println("All tasks submitted.");
    • 序列集合(Sequenced Collections) :引入 SequencedCollectionSequencedSetSequencedMap 接口,统一了具有定义顺序的集合类型,并提供了访问首尾元素和反向视图的方法,让操作更加直观。

总结

Java 语言在不断进步,每个版本都带来了旨在提高开发者效率、改善代码质量和提升性能的新特性。

  • 代码更加简洁:更少的代码完成相同工作。
  • 提高开发效率:减少不必要的编码工作,把更多精力放在摸鱼上。
  • 提高性能:底层运行时优化和新 API 将让你的应用跑得更快。

所以,是时候使用新的 Java 特性,你的"摸鱼"时间也会增加。

相关推荐
optimistic_chen9 分钟前
【Java EE初阶 --- 网络原理】应用层---HTTP(HTTPS)协议
java·网络·http·https·java-ee
凌辰揽月26 分钟前
贴吧项目总结二
java·前端·css·css3·web
黄名富32 分钟前
Redisson 分布式锁
java·redis·分布式·缓存
转转技术团队1 小时前
游戏账号大图生成
java·后端
青云交1 小时前
Java 大视界 -- Java 大数据机器学习模型在金融市场波动预测与资产配置动态调整中的应用(355)
java·大数据·机器学习·lstm·金融市场·波动预测·资产配置
徐子童1 小时前
初识Redis---Redis的特性介绍
java·数据库·redis
Dubhehug1 小时前
6.String、StringBuffer、StringBuilder区别及使用场景
java·面试题·stringbuilder·string·stringbuffer
枣伊吕波2 小时前
第十八节:第七部分:java高级:注解的应用场景:模拟junit框架
java·数据库·junit
白鲸开源2 小时前
从批到流,Zoom 基于 DolphinScheduler 的流批统一调度系统演进
java·大数据·开源
白鲸开源2 小时前
二次开发必看!DolphinScheduler 3.1.9 开发环境搭建指南
java·大数据·开源