JDK — 我以为有用的新特性

很兴奋,我们的项目升级到了JDK17,接下来就介绍一些关于JDK中个人觉得有用的功能,毕竟升级了要有升级的样子,不要一层不变吃老本,会被diss的。当然也会穿插一些个人感觉很鸡肋的功能,会阐述其鸡肋的理由。

1.Optional类的增强

1.1 Stream — Optional流化 (JDK9)

//1.单个对象的流化
Optional.ofNullable("testString").stream().toList();

//2.对象中的属性流化
LoginUser loginUser = new LoginUser();
        loginUser.setName("Test");
        List<String> list = Arrays.asList("eat", "sleep", "dadoudou");
        loginUser.setHobbies(list);
        //方式一:使用orElse
        Optional.ofNullable(loginUser)
                .map(LoginUser::getHobbies)
                .orElse(Collections.emptyList())
                .stream().filter(value -> value.equals("eat"))
                .collect(Collectors.toList());
        //方式二:使用stream
        Optional.ofNullable(loginUser)
                .map(LoginUser::getHobbies)
                .stream()
                .flatMap(Collection::stream)
                .filter(value -> value.equals("eat"))
                .collect(Collectors.toList());

个人认为该方法实在是显得鸡肋,我们日常开发中Optional主要用于对象的判空,并且对于集合对象要求不返回null而是空集合,这时基本用不到Optional;再者stream一般都用于集合的,如果只有一个对象没有必要做流化操作;

1.2 ifPresentOrElse — Optional的if-else (JDK9)

 public static void optionalMethod_ifPresentOrElse() {
        Person person = new Person();
        person.setId("test");

        //if-else
        if (null != person) {
            System.out.println("Hello " + person.getId());
        } else {
            System.out.println("World");
        }
        Optional.ofNullable(person)
                .ifPresentOrElse(value -> System.out.println("Hello " + value.getId()),
                        () -> System.out.println("World"));

        //only if
        if (null != person) {
            System.out.println("Hello " + person.getId());
        }
        Optional.ofNullable(person)
                .ifPresent(value -> System.out.println("Hello " + value.getId()));
    }

该方法有助于解决if-else的判断,但是不带有返回结果。如果你想要带有结果返回,该方式不合适。

1.3 or方法 (JDK9)

     Person nullPerson = null;
        //普通的方式:
        if(null == nullPerson){
            Person person1 = new Person();
            person1.setId("test1");
            if(null == person1){
                Person person2 = new Person();
                person2.setId("test2");
                return person2.getId();
            }
            return person1.getId();
        }

     //使用or的方式:
     String result = Optional.ofNullable(nullPerson)
                .or(() -> {
                    Person person1 = new Person();
                    person1.setId("test1");
                    return Optional.ofNullable(person1);
                })
                .or(() -> {
                    Person person2 = new Person();
                    person2.setId("test2");
                    return Optional.ofNullable(person2);
                })
                .map(Person::getId)
                .orElse("id");

总的来看该方法可以有效解决如下的if结构
         /**
         *      Xxxx xxxx= xxxxService.getXxxxById();
         *      if( null == xxxx ){
         *        xxxx = bbbbService.getXxxxById();
         *        if( null == xxxx ){
         *        .....
         *        }
         *      }
         */

该方法可以解决多重if嵌套的情况,但是对于多种if嵌套的情况我还是建议大家提前return,这样显得逻辑更加清晰。

1.4 orElseThrow方法 (JDK10)

Optional.ofNullable(null).orElseThrow();

该方法会抛出Optional的内置异常NoSuchElementException

2 集合的增强

2.1 集合的静态工厂(JDK9)

 List<String> list = List.of("test1", "test2", "test3");
        //list.set(0, "test1_update");  报UOE错

        //set/map不能保证输出的顺序和定义元素/键值对的顺序一致
        Set<String> set = Set.of("test1", "test2", "test3");
        Map<String, String> map = Map.of("1", "test1", "2", "test2", "3", "test3");
        Map<String, String> map2 = Map.ofEntries(Map.entry("1", "test1"), Map.entry("2", "test2"));
        System.out.println(map);
Note:
  • 注意创建的集合对象都不允许任何写操作, 所有的都不可以为null;
  • set/map不能保证输出的顺序和定义元素/键值对的顺序一致;
  • 和Arrays.asList产生的List不同,静态工厂是完全不能写,Arrays产生的list可以update不能add/remove;

2.2 takeWhile方法(JDK9)

List<Integer> list = List.of(1, 2, 33, 44, 4);
List<Integer> takeWhileResult = list.stream().takeWhile(value -> value < 33).collect(Collectors.toList());
System.out.println(takeWhileResult);  //[1, 2]
Note:
  • takeWhile 从头遍历 遇到不满足就结束流 返回从头到不满足的截断结果 作用类似break
  • 如果使用不可修改的Set或者Map 在使用这个方法是要注意 会存在每次输出的结果都是不一样的

2.3 dropWhile方法(JDK9)

List<Integer> list = List.of(1, 2, 33, 44, 4);
List<Integer> dropWhileResult = list.stream().dropWhile(value -> value < 33).collect(Collectors.toList());
System.out.println(dropWhileResult);  //[33, 44, 4]
Note:
  • dropWhile 从头开始删除 遇到不满足的就结束流 返回原始流剩下的结果;
  • 如果使用不可修改的Set或者Map 在使用这个方法和takeWhile一样是要注意;
  • dropwhile可以理解成是takeWhile的补集;

2.4 copyOf的方法(JDK10)

List<String> list = new ArrayList<>();
        list.add("aaa");
        list.add("bbb");
        List<String> copyList = List.copyOf(list);
        list.add("ccc");
        System.out.println(copyList);  //[aaa, bbb]

集合提供了copyOf的方法 返回的不可以修改的集合 另外副本集合不会因为原集合的改变而改变

2.5 toList方法(JDK16)

List<String> list = List.of("hhh", "www", "ttt");
List<String> result1 = list.stream().toList();
List<String> result2 = list.stream().collect(Collectors.toUnmodifiableList());
List<String> result3 = list.stream().collect(Collectors.toList());
Note:
  • stream().toListCollectors.toUnmodifiableList()都是不可修改的集合,Collectors.toList()是生成的是普通的list,可写。
  • 性能比较:toList(优) –> Collectors.toList() –> Collectors.toUnmodifiableList(劣)

3. 类型推断(JDK10)


String value = "Hello World";
List<Integer> list1 = List.of(1, 2, 3);
List<String> list2 = List.of("1", "2", "3");


var value = "Hello World";
var list1 = List.of(1, 2, 3);
var list2 = List.of("1", "2", "3");

类型推断是JDK使用var的关键字来代替各种类型,实际上在编译后的class文件还是会把这些类型给加上。虽然在IDEA里面使用var后会有类型提示,但是在git的merge request里面简直就是灾难,到时候全是var恐怕连你自己都不知道返回的对象类型是什么了。

我个人认为很大程度影响了代码的可读性性,不推荐大家在日常开发中使用。

4. 支持文本块(JDK14)

//before
  String text = "  {\n" +
                "                \"value\":\"Hello World\",\n" +
                "                \"result\":\"data\";\n" +
                "                }";

//after
  String text = """
                {
                "value":"Hello World",
                "result":"data";
                }
                """;

文本代码块让整个String看上去都是比较整洁的,我第一能想到的就是可以帮助我们解决mock的json数据。因为在单元测试的过程中需要mock一些对象,主要是通过json的数据反序列化到这个对象。如果是之前的String格式需要加一些转义符,后期比如要增加个字段或者修改数据很麻烦,不能快速定位修改。

5. instance of 类型转换(JDK14)

//before
Object value = "abc";
if (value instanceof String) {
 String newValue = (String) value;
 System.out.println(newValue.length());
}

//after
if (value instanceof String newValue) {
  System.out.println(newValue.length());
}

该特性很大程度上帮助了将代码化繁为简,推荐在日常中使用

6. NPE精准定位(JDK14)

//Before:

Exception in thread "main" java.lang.NullPointerException
	at com.dev.wizard.feature.JDK17Demo.optionalMethod_orElseThrow(JDK17Demo.java:99)
	at com.dev.wizard.feature.JDK17Demo.main(JDK17Demo.java:23)

//After:

Exception in thread "main" java.lang.NullPointerException: Cannot read field "student" because "persons" is null
	at com.dev.wizard.feature.JDK17Demo.optionalMethod_orElseThrow(JDK17Demo.java:99)
	at com.dev.wizard.feature.JDK17Demo.main(JDK17Demo.java:23)

7. 接口支持新特性 可以定义私有方法(JDK9)

这个理解很简单,就是在接口种可以定义一些私有的方法给default方法调用,仅此而已。

8. switch的增强

String value = "def";

================================对字符串的判断===============================================

//Before
switch (value){
   case "abc":
        System.out.println("Hello"); break;
   case "def":
        System.out.println("World"); break;
   default:
        System.out.println("Java"); break;
}

//After
switch (value){
   case "abc" -> System.out.println("Hello");
   case "def" -> System.out.println("World");
   default -> System.out.println("Java");
}


================================对类型的判断===============================================
//Before
Object obj = "value";
if(obj instanceof String){
    String newValue = obj + "TTT";
    System.out.println(newValue);
}else if(obj instanceof Integer){
    Integer number = (Integer) obj + 3;
    System.out.println(number);
}else {
    System.out.println(obj);
}

//After
switch (obj){
    case String str -> {
        String newStr = str + "TTT";
        System.out.println(newStr);
    }
    case Integer number -> {
        Integer newNumber = number + 3;
        System.out.println(newNumber);
    }
    default -> System.out.println(obj);
}

================================对null值的处理===============================================

String value = null;

switch (value){
    case null -> {return;}
    case "abc" -> System.out.println("Hello");
    case "def" -> System.out.println("World");
    default -> System.out.println("Java");
}

================================对条件判断的处理===============================================
Object obj = 5;
switch (obj){
    case String str -> {
        String newStr = str + "TTT";
        System.out.println(newStr);
    }
    case Integer number && number >= 5 -> {
        Integer newNumber = number + 3;
        System.out.println(newNumber);
    }
    default -> System.out.println(obj);
}

从上述的写法来看的话,主要使用了-> 替代了break语句,看上去更简洁。但是我有种感觉:switch是想最贱代替if-else吗。

9 总结

上述主要讲述了JDK的一些特性以及各种用法,同时描述特性中的一些注意点避免大家踩坑,当然JDK提供了很多新特性,我这里只是提供了一些有助于我们日常开发的一些特性,最后希望大家都能写出简约不简单的代码。