精 灵 王


  • 首页

  • 文章归档

  • 所有分类

  • 关于我

  • 搜索
设计模式之美 分布式 Redis 并发编程 个人成长 周志明的软件架构课 架构 单元测试 LeetCode 工具 位运算 读书笔记 操作系统 MySQL 异步编程 技术方案设计 集合 设计模式 三亚 游玩 转载 Linux 观察者模式 事件 Spring SpringCloud 实战 实战,SpringCloud 源码分析 线程池 同步 锁 线程 线程模型 动态代理 字节码 类加载 垃圾收集器 垃圾回收算法 对象创建 虚拟机内存 内存结构 Java

Java 8 新特性详细解析

发表于 2020-12-15 | 分类于 Java | 0 | 阅读次数 403

JDK 8 是一个拥有丰富特性的主要版本, Oracle 公司于 2014 年 3 月 18 日发布,在编程语言、集合IO、网络、并发性虚拟机等方面都做了升级,下面就详细介绍一下每个特性。

JAVA 编程语言

Lambda 表达式

Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。

它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

Arrays.asList( "a", "b", "d" ).forEach( ( **String** e ****) -> System.out.println( e ) );

如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

Arrays.asList( "a", "b", "d" ).forEach( e -> {
    System.out.print( e );
    System.out.print( e );
} );

Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

和

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

和

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

函数式接口

Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。

函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。

java.lang.Runnable 和 java.util.concurrent.Callable 是函数式接口的最佳例子。

在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。

为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

@FunctionalInterface
public interface Functional {
    void method();
}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();
        
    default void defaultMethod() {            
    }        
}

需要了解更多Lambda表达式的细节,可以参考官方文档。

接口的默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。

默认方法

默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要实现。

接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

private interface Defaulable {
    default String notRequired() { 
        return "Default implementation"; 
    }        
}
        
private static class DefaultableImpl implements Defaulable {
}
    
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;

OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

静态方法

Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

private interface DefaulableFactory {
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
}

默认方法和静态方法的使用场景:

public static void main( String[] args ) {
    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );
        
    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
}

这段代码的输出结果如下:

Default implementation
Overridden implementation

由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、**forEach()和removeIf()**等等。

如果你想了解更多细节,可以参考官方文档。

方法引用

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。

方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

下面的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用

public static class Car {
    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              
        
    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }
        
    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }
        
    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
}
  1. 构造器引用

    语法是Class::new,或者更一般的形式:Class<T>::new。注意:这个构造器没有参数

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

    cars.forEach( Car::collide );
    
  3. 某个类的成员方法的引用

    语法是Class::method,注意,这个方法没有定义入参

    cars.forEach( Car::repair );
    
  4. 某个实例对象的成员方法的引用

    语法是instance::method。注意:这个方法接受一个Car类型的参数:

    final Car police = Car.create( Car::new );
    cars.forEach( police::follow );
    

如果想了解和学习更详细的内容,可以参考官方文档

改进类型推断

Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。

例子代码如下:

public class Value< T > {
    public static< T > T defaultValue() { 
        return null; 
    }
    
    public T getOrDefault( T value, T defaultValue ) {
        return ( value != null ) ? value : defaultValue;
    }
}

下列代码是Value类型的应用:

public class TypeInference {
    public static void main(String[] args) {
        final Value< String > value = new Value<>();
        value.getOrDefault( "22", Value.defaultValue() );
    }
}

参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.<String>defaultValue()。

方法参数反射

为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。

Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及parameters参数)提供支持。

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class ParameterNames {
    public static void main(String[] args) throws Exception {
        Method method = ParameterNames.class.getMethod( "main", String[].class );
        for( final Parameter parameter: method.getParameters() ) {
            System.out.println( "Parameter: " + parameter.getName() );
        }
    }
}

在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:

Parameter: arg0

如果带-parameters参数,则会输出如下结果(正确的结果):

Parameter: args

如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.1</version>
    <configuration>
        <compilerArgument>-parameters</compilerArgument>
        <source>1.8</source>
        <target>1.8</target>
    </configuration>
</plugin>

允许重复注解

自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。

Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。

在Java 8中使用 @Repeatable 注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

public class RepeatingAnnotations {
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    public @interface Filters {
        Filter[] value();
    }
    
    @Target( ElementType.TYPE )
    @Retention( RetentionPolicy.RUNTIME )
    @Repeatable( Filters.class )
    public @interface Filter {
        String value();
    };
    
    @Filter( "filter1" )
    @Filter( "filter2" )
    public interface Filterable {        
    }
    
    public static void main(String[] args) {
        for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
            System.out.println( filter.value() );
        }
    }
}

这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器

这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。

另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:

filter1
filter2

如果你希望了解更多重复注解内容,可以参考官方文档。

拓宽注解应用场景

Java 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。

下面是一些例子:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Collection;

public class Annotations {
    @Retention( RetentionPolicy.RUNTIME )
    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
    public @interface NonEmpty {        
    }
        
    public static class Holder< @NonEmpty T > extends @NonEmpty Object {
        public void method() throws @NonEmpty Exception {           
        }
    }
        
    @SuppressWarnings( "unused" )
    public static void main(String[] args) {
        final Holder< String > holder = new @NonEmpty Holder< String >();       
        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();       
    }
}

@Target结合ElementType控制注解的使用范围,@Retention结合RetentionPolicy控制注解的生命阶段。

ElementType.TYPE_USER和ElementType.TYPE_PARAMETER 是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。

JDK 1.8 中ElementType枚举源码:

public enum ElementType {
    TYPE,
    FIELD,
    METHOD,
    PARAMETER,
    CONSTRUCTOR,
    LOCAL_VARIABLE,
    ANNOTATION_TYPE,
    PACKAGE,
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

集合

Stream API

Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。

一、创建流

  1. 在集合上调用stream()方法会返回一个普通的 Stream 流。如 List 和 Set 均支持 stream() 方法来创建顺序流或者是并行流

    Arrays.asList("a1", "a2", "a3")
        .stream() // 创建流
        .findFirst() // 找到第一个元素
        .ifPresent(System.out::println);  // 如果存在,即输出
    
  2. 通过 Stream.of() 从一堆对象中创建 Stream 流

    Stream.of("a1", "a2", "a3")
        .findFirst()
        .ifPresent(System.out::println);  // a1
    
  3. 特殊类型的流; 比如: IntStream , LongStream

    IntStream.range(1, 4)
        .forEach(System.out::println); // 相当于 for (int i = 1; i < 4; i++) {}
    
  4. 通过文件生成流

    使用java.nio.file.Files类中的很多静态方法都可以获取流,比如Files.lines()方法:
    Stream<String> stream = Files.lines(Paths.get("text.txt"), Charset.defaultCharset());
    

二、Stream 流常用方法

filter():对流的元素过滤
map():将流的元素映射成另一个类型
distinct():去除流中重复的元素
sorted():对流的元素排序
forEach():对流中的每个元素执行某个操作
peek():与forEach()方法效果类似,不同的是,该方法会返回一个新的流,而forEach()无返回
limit():截取流中前面几个元素
skip():跳过流中前面几个元素
toArray():将流转换为数组
reduce():对流中的元素归约操作,将每个元素合起来形成一个新的值
collect():对流的汇总操作,比如输出成List集合
anyMatch():匹配流中的元素,类似的操作还有allMatch()和noneMatch()方法
findFirst():查找第一个元素,类似的还有findAny()方法
max():求最大值
min():求最小值
count():求总数

三、过滤和排序

Stream.of(1, 8, 5, 2, 1, 0, 9, 2, 0, 4, 8)
    .filter(n -> n > 2)     // 对元素过滤,保留大于2的元素
    .distinct()             // 去重,类似于SQL语句中的DISTINCT
    .skip(1)                // 跳过前面1个元素
    .limit(2)               // 返回开头2个元素,类似于SQL语句中的SELECT TOP
    .sorted()               // 对结果排序
    .forEach(System.out::println); // 打印输出

四、查找和匹配

Stream中提供的查找方法有anyMatch()、allMatch()、noneMatch()、findFirst()、findAny(),这些方法被用来查找或匹配某些元素是否符合给定的条件:

// 检查流中的任意元素是否包含字符串"Java"
boolean hasMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .anyMatch(s -> s.equals("Java"));

// 检查流中的所有元素是否都包含字符串"#"
boolean hasAllMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .allMatch(s -> s.contains("#"));

// 检查流中的任意元素是否没有以"C"开头的字符串
boolean hasNoneMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .noneMatch(s -> s.startsWith("C"));

// 查找元素
Optional<String> element = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .filter(s -> s.contains("C"))
        // .findFirst()     // 查找第一个元素
        .findAny();         // 查找任意元素

五、归约

归约操作就是将流中的元素进行合并,形成一个新的值,常见的归约操作包括求和,求最大值或最小值。归约操作一般使用()方法,与map()方法搭配使用,可以处理一些很复杂的归约操作。

// 获取流
List<Book> books = Arrays.asList(
       new Book("Java编程思想", "Bruce Eckel", "机械工业出版社", 108.00D),
       new Book("Java 8实战", "Mario Fusco", "人民邮电出版社", 79.00D),
       new Book("MongoDB权威指南(第2版)", "Kristina Chodorow", "人民邮电出版社", 69.00D)
);

// 计算所有图书的总价
Optional<Double> totalPrice = books.stream()
       .map(Book::getPrice)
       .reduce((n, m) -> n + m);

// 价格最高的图书
Optional<Book> expensive = books.stream().max(Comparator.comparing(Book::getPrice));
// 价格最低的图书
Optional<Book> cheapest = books.stream().min(Comparator.comparing(Book::getPrice));
// 计算总数
long count = books.stream().count()

六、统计最值


List<Order> list = new ArrayList<>();
list.add(new Order("手机","10001",1999.00,10));
list.add(new Order("电脑","10002",7999.00,3));
list.add(new Order("相机","10003",12999.00,5));
list.add(new Order("投影仪","10004",6999.00,2));

// 用于收集统计数据(如计数,最小值,最大值,总和和平均值)的状态对象
DoubleSummaryStatistics dss = list.stream()
															.mapToDouble(order->order.price * order.amount).summaryStatistics();
System.out.println("订单总数:" + dss.getCount());
System.out.println("单笔最大订单金额:" + dss.getMax());
System.out.println("单笔最小订单金额:" + dss.getMin());
System.out.println("订单总金额:" + dss.getSum());
System.out.println("平均下单金额:" + dss.getAverage());

七、分组

和关系数据库一样,流也提供了类似于数据库中GROUP BY分组的特性,由Collectors.groupingBy()方法提供:

Map<String, List<Book>> booksGroup = books.stream().collect(groupingBy(Book::getPublisher));

Steam之上的操作可分为中间操作和晚期操作。中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。

如果你希望了解更多重复Stream内容,可以参考官方文档。

HashMap 性能改进

针对存在键冲突的 HashMap 的性能改进,这里推荐一篇美团技术团队关于HashMap的分析文章,点击这里查看。

IO 和 NIO

  • 减小 <JDK_HOME>/jre/lib/charsets.jar 文件的大小
  • 提高了 java.lang.String(byte[], *) 构造函数和 java.lang.String.getBytes() 方法的性能。
  • 全新的基于 Solaris 事件端口机制的面向 Solaris 的 SelectorProvider 实现。要使用它,请将系统属性java.nio.channels.spi.Selector 的值设置为 sun.nio.ch.EventPortSelectorProvider.

java.lang 和 java.util 程序包

并行数组排序

用于支持并行数组处理,最重要的方法是Arrays.parallelSort(),可以显著加快多核机器上的数组排序。

示例:

import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;

public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
        // 使用parallelSetAll**()**方法生成20000个随机数
        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();

        // 使用**parallelSort()**方法进行排序
        Arrays.parallelSort( arrayOfLong );     
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );
        System.out.println();
    }
}

上面程序会输出乱序数组和排序数组的前10个元素。

Base64 编码解码

对Base64编码的支持已经被加入到Java 8官方库java.util中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:

import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
        
        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); // 编码
        System.out.println( encoded );
        
        final String decoded = new String( 
            Base64.getDecoder().decode( encoded ), // 解码
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

这个例子的输出结果如下:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

新的Base64API也支持URL和MINE的编码解码:
Untitled

无符号算术支持

JAVA8 为 Integer 新增了如下方法:

  • public static long toUnsignedLong(int x)
    通过无符号转换将参数转换为长整型long
  • public static String toUnsignedString(int i, int radix)
    以第二个参数指定的基数为单位,返回第一个参数的无符号整数值的字符串表示形式。
  • public static String toUnsignedString(int i)
    以无符号十进制值的形式返回参数的字符串表示形式
  • private static String toUnsignedString0(int val, int shift)
    将整数转换为无符号数字

JAVA8 为 Long 新增了如下方法:

  • private static BigInteger toUnsignedBigInteger(long i)
    返回一个BigInteger,它等于参数的无符号值。
  • public static String toUnsignedString(long i, int radix)
    返回第一个参数的字符串表示形式,作为第二个参数指定的基数中的无符号整数值。
  • public static String toUnsignedString(long i)
    以无符号十进制值形式返回参数的字符串表示形式。
  • static String toUnsignedString0(long val, int shift)
    将long(格式化为无符号)格式化为String。

JAVA8 为 Byte 新增了如下方法:

  • public static int toUnsignedInt(byte x)
    通过无符号转换将参数转换为int
  • public static long toUnsignedLong(byte x)
    通过无符号转换将参数转换为long

JAVA8 为 Short 新增了如下方法:

  • public static int toUnsignedInt(short x)
    通过无符号转换将参数转换为int
  • public static long toUnsignedLong(short x)
    通过无符号转换将参数转换为long

新增 time 程序包

一组新程序包,提供全面的日期-时间模型;新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

使用示例:

首先,Clock 类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。

// Get the system clock as UTC offset 
final Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
System.out.println( clock.millis()  == System.currentTimeMillis()); // true

这个例子的输出结果是:

2020-10-27T07:00:43.488Z
1603782043489
true

第二,关注下LocalDate和LocalTime类。

LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。

// Get the local date and local time
final LocalDate date = LocalDate.now();
final LocalDate dateFromClock = LocalDate.now( clock );
        
System.out.println( date );
System.out.println( dateFromClock );
        
// Get the local date and local time
final LocalTime time = LocalTime.now();
final LocalTime timeFromClock = LocalTime.now( clock );
        
System.out.println( time );
System.out.println( timeFromClock );

上述例子的输出结果如下:

2020-10-27
2020-10-27
15:00:43.489
07:00:43.489

LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子:

// Get the local date/time
final LocalDateTime datetime = LocalDateTime.now();
final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
        
System.out.println( datetime );
System.out.println( datetimeFromClock );

上述这个例子的输出结果如下:

2020-10-27T15:02:09.028
2020-10-27T07:02:09.028

如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:

// Get the zoned date/time
final ZonedDateTime zonedDatetime = ZonedDateTime.now();
final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
        
System.out.println( zonedDatetime );
System.out.println( zonedDatetimeFromClock );
System.out.println( zonedDatetimeFromZone );

这个例子的输出结果是:

2020-10-27T15:03:28.446+08:00[Asia/Shanghai]
2020-10-27T07:03:28.446Z
2020-10-27T00:03:28.447-07:00[America/Los_Angeles]

最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:

// Get duration between two dates
final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );

final Duration duration = Duration.between( from, to );
System.out.println( "Duration in days: " + duration.toDays() );
System.out.println( "Duration in hours: " + duration.toHours() );

这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:

Duration in days: 365
Duration in hours: 8783

新增Optional类

Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查。

构造Optional:

  • empty(): 返回一个空的Optional实例
  • of(T value): 返回不包含非null值的容器对象, 如果为空, 会抛出NPE
  • ofNullable(T value): 返回描述指定值的Optional,如果非空,则返回空值的Optional
  • 常用方法:
  • isPresent(): 如果值不为null则返回true,否则返回false
  • orElse(): 返回值(如果存在),否则返回其他
Optional.ofNullable(user).ifPresent(u->{
                    dosomething(u);
                });

如果想了解更多的细节,请参考[官方文档](http://docs.oracle.com/javase/8/docs/api/java/util/Optional.html)。

并发性

  1. java.util.concurrent 包中新增了一些类和接口
    • 比如atomix包下面新增的类:
      1. DoubleAccumulator
      2. DoubleAdder
      3. LongAccumulator
      4. LongAdder
    • locks包下面新增的StampedLock类
      关于StampedLock的源码分析可以查看我之前的一篇文章,点击源码分析:升级版的读写锁 StampedLock查看。
  2. 为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法,支持基于新增流工具和 lambda 表达式的聚合操作。
  3. java.util.concurrent.ForkJoinPool 类中新增了一些方法来支持公共池实例。

HotSpot虚拟机

删除PermGen

永久代(PermGen)空间被删除,使用**Metaspace**(JEP 122)代替持久代(PermGen space)。

之前设置PermGen区域的 PermSize和MaxPermSize JVM参数将被忽略。

使用-XX:MaxMetaspaceSize标志设置最大的元空间大小,默认值是无限制的,使用-XX:MetaspaceSize调整标志定义元空间的初始大小。如果不指定此标志,则元空间将根据运行时的应用程序需求动态调整大小。

工具

Javac 工具

  • javac 命令的-parameters 选项可用于存储正式参数名称,并启用反射 API 来检索正式参数名称。
  • javac工具现在支持检查 javadoc 注释的内容,从而避免在运行javadoc 时生成的文件中产生各种问题,例如无效的 HTML 或可访问性问题。可通过新的-Xdoclint 选项来启用此特性。
  • javac 工具现在支持根据需要生成原生标头。这样便无需在构建管道中单独运行 javah 工具。可以使用新的 -h 选项在 javac 中启用此特性,该选项用于指定写入头文件的目录。

Javadoc 工具

  • javadoc 工具支持新的 DocTree API,让您可以将 Javadoc 注释作为抽象语法树来进行遍历。
  • javadoc 工具支持新的 Javadoc Access API,可以直接从 Java 应用中调用 Javadoc 工具,而无需执行新的进程。有关更多信息,请参阅 javadoc 新特性 页面。
  • javadoc工具现在支持检查javadoc 注释的内容,从而避免在运行 javadoc 时生成的文件中产生各种问题,例如无效的 HTML 或可访问性问题。此特性默认为启用状态,可以通过新的-Xdoclint 选项加以控制。有关更多详细信息,请参阅运行 "javadoc -X" 时的输出。

通过 jjs 命令来调用 Nashorn 引擎

jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。

例如,我们创建一个具有如下内容的sample.js文件:

print('Hello World!');

打开控制台,输入以下命令:

$ jjs sample.js

以上程序输出结果为:

Hello World!

Java 中调用 JavaScript

使用 ScriptEngineManager, JavaScript 代码可以在 Java 中执行,实例如下:

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

代码的输出结果如下:

jdk.nashorn.api.scripting.NashornScriptEngine
Result:2.0

jdeps 命令分析类文件

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。

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

这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".

org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.ref                                      
      -> java.lang.reflect                                  
      -> java.util                                          
      -> java.util.concurrent                               
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.reflect                                  
      -> java.util

更多的细节可以参考官方文档。

更多的 JAVA8 新特性可以参阅官网:What's New in JDK 8

精 灵 王 wechat
👆🏼欢迎扫码关注微信公众号👆🏼
  • 本文作者: 精 灵 王
  • 本文链接: https://jinglingwang.cn/archives/newinjava8
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 设计模式之美 # 分布式 # Redis # 并发编程 # 个人成长 # 周志明的软件架构课 # 架构 # 单元测试 # LeetCode # 工具 # 位运算 # 读书笔记 # 操作系统 # MySQL # 异步编程 # 技术方案设计 # 集合 # 设计模式 # 三亚 # 游玩 # 转载 # Linux # 观察者模式 # 事件 # Spring # SpringCloud # 实战 # 实战,SpringCloud # 源码分析 # 线程池 # 同步 # 锁 # 线程 # 线程模型 # 动态代理 # 字节码 # 类加载 # 垃圾收集器 # 垃圾回收算法 # 对象创建 # 虚拟机内存 # 内存结构 # Java
⑥SpringCloud 实战:引入gateway组件,开启网关路由功能
⑦SpringCloud 实战:引入Sleuth组件,完善服务链路跟踪
  • 文章目录
  • 站点概览
精 灵 王

精 灵 王

青春岁月,以此为伴

85 日志
14 分类
43 标签
RSS
E-mail
Creative Commons
Links
  • 添加友链说明
© 2022 精 灵 王
渝ICP备2020013371号
0%