java8新特性实践

傷城~ 2022-05-31 05:57 186阅读 0赞

Lambda表达式

  • Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据
  • 最简单的形式中,一个lambda可以由用逗号分隔的参数列表、–>符号与函数体三部分表示:

    1. Arrays.asList( 1, 2, 3 ).forEach( e -> System.out.println( e ) );
  • Lambda可以引用类的成员变量与局部变量(如果这些变量不是final的话,它们会被隐含的转为final,这样效率更高):

    1. String str = ","; //等价于:final String str = ",";
    2. Arrays.asList( "a", "b", "c" ).forEach(
    3. ( String e ) -> System.out.print( e + str ) );
  • Lambda可能会返回一个值。返回值的类型也是由编译器推测出来的。如果lambda的函数体只有一行的话,那么没有必要显式使用return语句:

    1. List<Integer> list = Arrays.asList( 4, 2, 1,3);
    2. list.sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
    3. list.forEach(e->System.out.println(e));

函数式接口

  • 如何使现有的函数友好地支持lambda,java8采取了增加函数式接口的概念。
  • 函数式接口就是一个只有一个方法的普通接口,可以隐式的转换成lambda表达式,除了特殊方法:默认方法,静态方法以及继承自Object类的一些方法(toString(),equels()等)
  • 在使用中,函数式接口容易出错,如果接口中被定义了另一个方法,那么接口将不再是函数式接口,导致编译失败。为此Java8新增注解@FunctionalInterface。注意default默认方法和静态方法不会影响函数式接口。

    1. @FunctionalInterface
    2. public interface Runnable {
    3. public abstract void run();
    4. }
  • java8新增默认方法和静态方法扩展接口的声明。

    1. public interface Convert {
    2. //可以包含静态方法
    3. public static void hasStaticMethod(){
    4. System.out.println("包含静态方法");
    5. }
    6. default void hasDefaultMethod(){
    7. System.out.println("包含默认方法");
    8. }
    9. }
  • 函数式接口示例

    1. @FunctionalInterface
    2. public interface Convert<F,T> {
    3. T convert(F from);
    4. //可以包含静态方法
    5. public static void hasStaticMethod(){
    6. System.out.println("包含静态方法");
    7. }
    8. default void hasDefaultMethod(){
    9. System.out.println("包含默认方法");
    10. }
    11. }
    12. public class ConvertMain {
    13. public static void main(String[] args) {
    14. Convert<String, Integer> converter = (from) -> Integer.valueOf(from);
    15. Integer converted = converter.convert("123");
    16. System.out.println(converted);
    17. converter.hasDefaultMethod();
    18. Convert.hasStaticMethod();
  1. // Function<T, R> -T作为输入,返回的R作为输出
  2. Function<String,String> function = (x) -> {System.out.print(x+": ");return "Function";};
  3. System.out.println(function.apply("hello world"));
  4. //Predicate<T> -T作为输入,返回的boolean值作为输出
  5. Predicate<String> pre = (x) ->{System.out.print(x);return false;};
  6. System.out.println(": "+pre.test("hello World"));
  7. //Consumer<T> - T作为输入,执行某种动作但没有返回值
  8. Consumer<String> con = (x) -> {System.out.println(x);};
  9. con.accept("hello world");
  10. //Supplier<T> - 没有任何输入,返回T
  11. Supplier<String> supp = () -> {return "Supplier";};
  12. System.out.println(supp.get());
  13. //BinaryOperator<T> -两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用
  14. BinaryOperator<String> bina = (x,y) ->{System.out.print(x+" "+y);return "BinaryOperator";};
  15. System.out.println(" "+bina.apply("hello ","world"));
  16. }
  17. \}

方法引用(::)

  • 以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用
  • 第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。

    1. final Car car = Car.create( Car::new );
    2. final List< Car > cars = Arrays.asList( car );
  • 第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。

    1. cars.forEach( Car::collide );
  • 第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。

    1. cars.forEach( Car::repair );
  • 最后,第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数

    1. final Car police = Car.create( Car::new );
    2. cars.forEach( police::follow );
  1. private void sort(){
  2. List<Commodity> commodities=new ArrayList<>();
  3. //java 8之前
  4. commodities.sort(new Comparator<Commodity>() {
  5. @Override
  6. public int compare(Commodity o1, Commodity o2) {
  7. return o1.getPrice()-o2.getPrice();
  8. }
  9. });
  10. //java 8 lambda的写法
  11. commodities.sort((Commodity o1,Commodity o2)->o1.getPrice()-o2.getPrice());
  12. //java 8 方法应用的写法
  13. commodities.sort(Comparator.comparing(Commodity::getPrice));
  14. }

重复注解

  • java5开始引用注解机制,然而相同注解在同样的地方只能声明一次,java8引入重复注解。
  • 重复注解机制本身必须用@Repeatable注解。事实上是编译器技巧的改变

更好的类型推断

  • Java 8在类型推测方面有了很大的提高。在很多情况下,编译器可以推测出确定的参数类型,这样就能使代码更整洁。

    1. public class Value< T > {
    2. public static< T > T defaultValue() {
    3. return null;
    4. }
    5. public T getOrDefault( T value, T defaultValue ) {
    6. return ( value != null ) ? value : defaultValue;
    7. }
    8. }
    9. public class TypeInference {
    10. public static void main(String[] args) {
    11. final Value< String > value = new Value<>();
    12. value.getOrDefault( "22", Value.defaultValue() );
    13. }
    14. }

扩展注解的支持

  • Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,方法的异常也可以添加注解。

    1. public class Annotations {
    2. @Retention( RetentionPolicy.RUNTIME )
    3. @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    4. public @interface NonEmpty {
    5. }
    6. public static class Holder< @NonEmpty T > extends @NonEmpty Object {
    7. public void method() throws @NonEmpty Exception {
    8. }
    9. }
    10. @SuppressWarnings( "unused" )
    11. public static void main(String[] args) {
    12. final Holder< String > holder = new @NonEmpty Holder< String >();
    13. @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();
    14. }
    15. }

    ElementType.TYPE_USE和ElementType.TYPE_PARAMETER是两个新添加的用于描述适当的注解上下文的元素类型。

Optional

  • 新增类库,为解决java中常见的空指针异常导致程序无法正常运行
  • Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

    1. //允许为空值,当值为空时也不会报错
    2. Optional< String > name = Optional.ofNullable( null );
    3. System.out.println( "name是否有值 " + name.isPresent() );
    4. System.out.println( "如果名称为空: " + name.orElseGet( () -> "代替名称" ) );
    5. System.out.println( name.map( s -> "名称为: " + s ).orElse( "名称为空" ) );
    6. //不允许为空值,当值为空时会报错
    7. Optional< String > firstName = Optional.of( "name" );
    8. System.out.println( "firstName是否有值 " + firstName.isPresent() );
    9. System.out.println( "如果名称为空: " + firstName.orElseGet( () -> "代替名称" ) );
    10. System.out.println( firstName.map( s -> "名称为: " + s ).orElse( "名称为空" ) );

    Optional文档

Stream

  • 把真正的函数式编程风格引入到Java中。简化了集合框架的处理。

    1. public class Streams {
    2. private enum Status {
    3. OPEN, CLOSED
    4. };
    5. private static final class Task {
    6. private final Status status;
    7. private final Integer points;
    8. Task( final Status status, final Integer points ) {
    9. this.status = status;
    10. this.points = points;
    11. }
    12. public Integer getPoints() {
    13. return points;
    14. }
    15. public Status getStatus() {
    16. return status;
    17. }
    18. @Override
    19. public String toString() {
    20. return String.format( "[%s, %d]", status, points );
    21. }
    22. }
    23. }
    24. //task集合
    25. final Collection< Task > tasks = Arrays.asList(
    26. new Task( Status.OPEN, 5 ),
    27. new Task( Status.OPEN, 13 ),
    28. new Task( Status.CLOSED, 8 )
    29. );
  1. //所有状态为OPEN的任务一共有多少分数
  2. final long totalPointsOfOpenTasks = tasks
  3. .stream()
  4. .filter( task -> task.getStatus() == Status.OPEN )
  5. .mapToInt( Task::getPoints )
  6. .sum();
  7. System.out.println( "Total points: " + totalPointsOfOpenTasks );
  8. 首先,task集合被转换化为stream。然后,filter操作过滤掉状态为CLOSEDtask。下一步,mapToInt操作通过Task::getPoints方法调用把Taskstream转化为Integerstream。最后,用sum函数把所有的分数加起来,得到最终的结果。
  9. stream注意事项:[Ops][]
  • .stream操作被分成了中间操作与最终操作,中间操作返回一个新的stream对象。中间操作总是采用惰性求值方式,运行一个像filter这样的中间操作实际上没有进行任何过滤,相反它在遍历元素时会产生了一个新的stream对象,这个新的stream对象包含原始stream中符合给定谓词的所有元素。
  • 像forEach、sum这样的最终操作可能直接遍历stream,产生一个结果。当最终操作执行结束之后,stream管道被认为已经被消耗了,不能再使用。在大多数情况下,最终操作都是采用及早求值方式,及早完成底层数据源的遍历
  • stream另一个有价值的地方是能够原生支持并行处理

    1. final double totalPoints = tasks
    2. .stream()
    3. .parallel()
    4. .map( task -> task.getPoints() ) // or map( Task::getPoints )
    5. .reduce( 0, Integer::sum );
    6. System.out.println( "Total points (all tasks): " + totalPoints );
  1. //按照某种准则来对集合中的元素进行分组。
  2. final Map< Status, List< Task > > map = tasks
  3. .stream()
  4. .collect( Collectors.groupingBy( Task::getStatus ) );
  5. System.out.println( map );
  6. //计算整个集合中每个task分数(或权重)的平均值来结束task
  7. final Collection< String > result = tasks
  8. .stream() // Stream< String >
  9. .mapToInt( Task::getPoints ) // IntStream
  10. .asLongStream() // LongStream
  11. .mapToDouble( points -> points / totalPoints ) // DoubleStream
  12. .boxed() // Stream< Double >
  13. .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
  14. .mapToObj( percentage -> percentage + "%" ) // Stream< String>
  15. .collect( Collectors.toList() ); // List< String >
  16. System.out.println( result );
  • Stream API不仅仅处理Java集合框架。像从文本文件中逐行读取数据这样典型的I/O操作也很适合用Stream API来处理

    1. final Path path = new File( filename ).toPath();
    2. try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
    3. lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
    4. }

    对一个stream对象调用onClose方法会返回一个在原有功能基础上新增了关闭功能的stream对象,当对stream对象调用close()方法时,与关闭相关的处理器就会执行。

Date/Time API (JSR 310)

  • Joda-Time——一个可替换标准日期/时间处理且功能非常强大的Java API
  • 新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作
  • Clock类,它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()。

    1. final Clock clock = Clock.systemUTC();
    2. System.out.println( clock.instant() );
    3. System.out.println( clock.millis() );
  • LocaleDate与LocalTime。LocaleDate只持有ISO-8601格式且无时区信息的日期部分。相应的,LocaleTime只持有ISO-8601格式且无时区信息的时间部分。LocaleDate与LocalTime都可以从Clock中得到

    1. final LocalDate date = LocalDate.now();
    2. final LocalDate dateFromClock = LocalDate.now( clock );
    3. System.out.println( date );
    4. System.out.println( dateFromClock );
    5. // Get the local date and local time
    6. final LocalTime time = LocalTime.now();
    7. final LocalTime timeFromClock = LocalTime.now( clock );
    8. System.out.println( time );
    9. System.out.println( timeFromClock );
  • LocaleDateTime把LocaleDate与LocaleTime的功能合并起来,它持有的是ISO-8601格式无时区信息的日期与时间。

    1. final LocalDateTime datetime = LocalDateTime.now();
    2. final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
    3. System.out.println( datetime );
    4. System.out.println( datetimeFromClock );
  • 如果你需要特定时区的日期/时间,可以使用ZonedDateTime。它持有ISO-8601格式具具有时区信息的日期与时间

    1. final ZonedDateTime zonedDatetime = ZonedDateTime.now();
    2. final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
    3. final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
    4. System.out.println( zonedDatetime );
    5. System.out.println( zonedDatetimeFromClock );
    6. System.out.println( zonedDatetimeFromZone );
  • Duration类:Duration使计算两个日期间的差变的十分简单。

    1. final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
    2. final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
    3. final Duration duration = Duration.between( from, to );
    4. System.out.println( "Duration in days: " + duration.toDays() );
    5. System.out.println( "Duration in hours: " + duration.toHours() );

JavaScript引擎Nashorn

  • Nashorn,一个新的JavaScript引擎随着Java 8一起公诸于世,它允许在JVM上开发运行某些JavaScript应用。Nashorn就是javax.script.ScriptEngine的另一种实现,并且它们俩遵循相同的规则,允许Java与JavaScript相互调用。

    1. ScriptEngineManager manager = new ScriptEngineManager();
    2. ScriptEngine engine = manager.getEngineByName( "JavaScript" );
    3. System.out.println( engine.getClass().getName() );
    4. System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

Base64:Java 8中,Base64编码已经成为Java类库的标准。

  1. import java.nio.charset.StandardCharsets;
  2. import java.util.Base64;
  3. public class Base64Test {
  4. public static void main(String[] args) {
  5. final String text = "Base64 finally in Java 8!";
  6. final String encoded = Base64
  7. .getEncoder()
  8. .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
  9. System.out.println( encoded );
  10. final String decoded = new String(
  11. Base64.getDecoder().decode( encoded ),
  12. StandardCharsets.UTF_8 );
  13. System.out.println( decoded );
  14. }
  15. }
  16. Base64类同时还提供了对URLMIME友好的编码器与解码器(Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。

并行(parallel)数组

  • Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。

    1. import java.util.Arrays;
    2. import java.util.concurrent.ThreadLocalRandom;
    3. public class ParallelArrays {
    4. public static void main( String[] args ) {
    5. long[] arrayOfLong = new long [ 20000 ];
    6. Arrays.parallelSetAll( arrayOfLong,
    7. index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
    8. Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
    9. i -> System.out.print( i + " " ) );
    10. System.out.println();
    11. Arrays.parallelSort( arrayOfLong );
    12. Arrays.stream( arrayOfLong ).limit( 10 ).forEach(
    13. i -> System.out.print( i + " " ) );
    14. System.out.println();
    15. }
    16. }
    17. 上面的代码片段使用了parallelSetAll()方法来对一个有20000个元素的数组进行随机赋值。然后,调用parallelSort方法。这个程序首先打印出前10个元素的值,之后对整个数组排序。

并发(Concurrency)

  • 在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool类中加入了一些新方法来支持共有资源池(common pool)
  • 新增的java.util.concurrent.locks.StampedLock类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock类的替代者)。

    在java.util.concurrent.atomic包中还增加了下面这些类:

    1. DoubleAccumulator
    2. DoubleAdder
    3. LongAccumulator
    4. LongAdder

类依赖分析器jdeps

  • jdeps是一个很有用的命令行工具。它可以显示Java类的包级别或类级别的依赖。它接受一个.class文件,一个目录,或者一个jar文件作为输入。jdeps默认把结果输出到系统输出(控制台)上。

    1. jdeps org.springframework.core-3.0.5.RELEASE.jar

Java虚拟机(JVM)的新特性

  • PermGen空间被移除了,取而代之的是Metaspace(JEP 122)。JVM选项-XX:PermSize与-XX:MaxPermSize分别被-XX:MetaSpaceSize与-XX:MaxMetaspaceSize所代替。

参考:Java 8 Features Tutorial
github:示例

发表评论

表情:
评论列表 (有 0 条评论,186人围观)

还没有评论,来说两句吧...

相关阅读

    相关 java8特性实践

    Lambda表达式 Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),或者把代码看成数据 最简单的形式中,一个lambda可以由用逗号分隔

    相关 java8特性

    对于Java开发者来说,Java8的版本显然是一个具有里程碑意义的版本,蕴含了许多令人激动的新特性,如果能利用好这些新特性,能够大大提升我们的开发效率。Java8的函数式编程能