欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

java8 新特性精心整理

程序员文章站 2022-07-02 12:49:07
前言 越来越多的项目已经使用 "Java 8" 了,毫无疑问, "Java 8" 是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和 JVM 等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。 引用: 本文参 ......

前言

越来越多的项目已经使用 java 8 了,毫无疑问,java 8 是java自java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和 jvm 等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

引用:本文参考了这两篇文章,加以自己的理解,整理成一份最容易理解的 java8 新特性文章,有少部分章节可能内容一致,但绝对不是抄袭,只是为了文章的完整性,大部分常用的地方加了我自己的理解和示例。

适合读者及目标

目标人群

  • 适合有用过 lambda 表达式的同学,想彻底了解清楚
  • 学习 java8 的新特定

目标

  • 了解 java8 的函数式接口和 lambda 表达式,可以自定义函数式接口
  • 方法引用的使用
  • 接口的静态方法和默认方法
  • 相比于以前的版本新加的类及期使用
  • stream api 的使用

1. java 语言的新特性

java8 的 lambda 的使用确实方便了许多,但也使初次了解的人感觉到难以阅读,其实是你不习惯的原因。很多语言从一开始就支持了 lambda 表达式,像 groovy,scala 等。

1.1 lambda 表达式和函数式接口

在 java8 以前,我们想要让一个方法可以与用户进行交互,比如说使用方法内的局部变量;这时候就只能使用接口做为参数,让用户实现这个接口或使用匿名内部类的形式,把局部变量通过接口方法传给用户。

传统匿名内部类缺点:代码臃肿,难以阅读

lambda 表达式

lambda 表达式将函数当成参数传递给某个方法,或者把代码本身当作数据处理;

语法格式:

  • 用逗号分隔的参数列表
  • -> 符号
  • 和 语句块 组成
arrays.aslist( "a", "b", "d" ).foreach( e -> system.out.println( e ) );

等价于

list<string> list = arrays.aslist( "a", "b", "d" );
for(string e:list){
    system.out.println(e);
}

如果语句块比较复杂,使用 {} 包起来

arrays.aslist( "a", "b", "d" ).foreach( e -> {
    string m = "9420 "+e;
    system.out.print( m );
});

lambda 本质上是匿名内部类的改装,所以它使用到的变量都会隐式的转成 final

string separator = ",";
arrays.aslist( "a", "b", "d" ).foreach( 
    e -> system.out.print( e + separator ) );

等价于

final string separator = ",";
arrays.aslist( "a", "b", "d" ).foreach( 
    e -> system.out.print( e + separator ) );

lambda 的返回值和参数类型由编译器推理得出,不需要显示定义,如果只有一行代码可以不写 return 语句

arrays.aslist( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareto( e2 ) );

等价于

list<string> list = arrays.aslist("a", "b", "c");
collections.sort(list, new comparator<string>() {
    @override
    public int compare(string o1, string o2) {
        return o1.compareto(o2);
    }
});

函数式接口

  • 接口中只能有一个接口方法
  • 可以有静态方法和默认方法
  • 使用 @functionalinterface 标记
  • 默认方法可以被覆写
@functionalinterface
public interface functionaldefaultmethods {
    void method();
 
    default void defaultmethod() {            
    }
    
    static void staticmethod(){
    }
}
private interface defaulable {
    // interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    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";
    }
}

// 也可以由接口覆盖 
public interface overridableinterface extends defaulable{
    @override
    public string notrequired() {
        return "interface overridden implementation";
    }
}

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

已经存在的 java8 定义的函数式接口

我们基本不需要定义自己的函数式接口,java8 已经给我们提供了大量的默认函数式接口,基本够用,在 rt.jar 包的 java.util.function 目录下可以看到所有默认的函数式接口,大致分为几类

  • function<t,r> t 作为输入,返回的 r 作为输出
  • predicate<t> t 作为输入 ,返回 boolean 值的输出
  • consumer<t> t 作为输入 ,没有输出
  • supplier<r> 没有输入 , r 作为输出
  • binaryoperator<t> 两个 t 作为输入 ,t 同样是输出
  • unaryoperator<t>function 的变种 ,输入输出者是 t

其它的都是上面几种的各种扩展,只为更方便的使用,下面演示示例,你可以把其当成正常的接口使用,由用户使用 lambda 传入。

// hello world 示例
function<string,string> function = (x) -> {return x+"function";};
system.out.println(function.apply("hello world"));  // hello world function

unaryoperator<string> unaryoperator = x -> x + 2;
system.out.println(unaryoperator.apply("9420-"));   // 9420-2

// 判断输入值是否为偶数示例
predicate<integer> predicate = (x) ->{return x % 2 == 0 ;};
system.out.println(predicate.test(1));              // false

// 这个没有返回值
consumer<string> consumer = (x) -> {system.out.println(x);};
consumer.accept("hello world ");                    // hello world

// 这个没有输入 
supplier<string> supplier = () -> {return "supplier";};
system.out.println(supplier.get());                 // supplier

// 找出大数
binaryoperator<integer> bina = (x, y) ->{return x > y ? x : y;};
bina.apply(1,2);                                    // 2 

1.2 方法引用

方法引用使得开发者可以直接引用现存的方法、java类的构造方法或者实例对象。方法引用和lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

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() );
    }
}

第一种方法引用的类型是构造器引用,语法是class::new,或者更一般的形式:class::new。注意:这个构造器没有参数。

final car car = car.create( car::new );

等价于

car car = car.create(() -> new car());

第二种方法引用的类型是静态方法引用,语法是class::static_method。注意:这个方法接受一个car类型的参数。

cars.foreach( car::collide );

foreach 原型为 foreach(consumer<? super t> action) 使用的是 consumer 只有参数,没有返回值;这个参数 t 就是 car 类型,因为是 cars.foreach 嘛,所以上面的方法引用等价于

cars.foreach(car -> car.collide(car));

第三种方法引用的类型是某个类的成员方法的引用,语法是class::method,注意,这个方法没有定义入参:

cars.foreach( car::repair );

它等价于

cars.foreach(car -> car.repair());

1.3 重复注解

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

在java 8中使用 @repeatable 注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:

@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实例。

1.4 更好的类型推断

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;
    }
}
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()

1.5 拓宽注解的应用场景

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

package com.javacodegeeks.java8.annotations;
 
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<>();        
    }
}

elementtype.type_user elementtype.type_parameter 是java 8新增的两个注解,用于描述注解的使用场景。java 语言也做了对应的改变,以识别这些新增的注解。

2. java 编译器的新特性

java 8 开始正式支持参数名称,终于不需要读 class 字节码来获取参数名称了,这对于经常使用反射的人特别有用。

在 java8 这个特性默认是关闭的,需要开启参数才能获取参数名称:

<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>

3. jvm 的新特性

使用metaspacejep 122)代替持久代(permgen space)。在jvm参数方面,使用-xx:metaspacesize-xx:maxmetaspacesize代替原来的-xx:permsize-xx:maxpermsize

4. java 官方库的新特性

java 8增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持现代的并发编程、函数式编程等,本章节参考原文,并提取出常用功能。

4.1 streams

streams 操作分为中间操作和晚期操作,中间操作会返回一个新的 stream ,只是把要做的操作记录起来而已,并不会真的执行,晚期操作才会真的遍历列表并执行所有操作

stream 的另一个价值就是支持了并行处理 parallel 方法。

stream api 简化了集合的操作,并扩展了集合的分组,求和,mapreduce,flatmap ,排序等功能,下面列出项目中经常用到的功能,会以使用频率排序。

  1. 准备一个用于下面例子测试的对象
import lombok.allargsconstructor;
import lombok.data;
import lombok.noargsconstructor;

@data
@noargsconstructor
@allargsconstructor
public class vehicle {
    //车架号
    private string vin;
    // 车主手机号
    private string phone;
    // 车主姓名
    private string name;
    // 所属车租车公司
    private integer companyid;
    // 个人评分
    private double score;
    //安装的设备列表imei,使用逗号分隔
    private string devicenos;
}
  1. 准备一些车辆数据
static list<vehicle> vehicles = new arraylist<>();

@before
public void init(){
    list<string> imeis = new arraylist<>();
    for (int i = 0; i <5 ; i++) {
        list<string> singlevehicledevices = new arraylist<>();
        for (int j = 0; j < 3; j++) {
            string imei = randomstringutils.randomalphanumeric(15);
            singlevehicledevices.add(imei);
        }
        imeis.add(stringutils.join(singlevehicledevices,','));
    }
    vehicles.add(new vehicle("kptsoa1k67p081452","17620411498","9420",1,4.5,imeis.get(0)));
    vehicles.add(new vehicle("kptcob1k18p057071","15073030945","张玲",2,1.4,imeis.get(1)));
    vehicles.add(new vehicle("kpts0a1k87p080237","19645871598","sanri1993",1,3.0,imeis.get(2)));
    vehicles.add(new vehicle("knajc526975740490","15879146974","李种",1,3.9,imeis.get(3)));
    vehicles.add(new vehicle("knajc521395884849","13520184976","袁绍",2,4.9,imeis.get(4)));
}

4.1.1 foreach 遍历collection 数据

vehicles.foreach(vehicle -> system.out.println(vehicle));

//这样就可以遍历打印
vehicles.foreach(system.out::println);

4.1.2 foreach 遍历 map 数据

map<string,integer> map = new hashmap<>();
map.put("a",1);map.put("b",2);map.put("c",3);

map.foreach((k,v) -> system.out.println("key:"+k+",value:"+v));

4.1.3 filter 数据过滤

// 去掉评分为 3 分以下的车
list<vehicle> collect = vehicles.stream().filter(vehicle -> vehicle.getscore() >= 3).collect(collectors.tolist());

4.1.4 map 对象映射

对一个 list<object> 大部分情况下,我们只需要列表中的某一列,或者需要把里面的每一个对象转换成其它的对象,这时候可以使用 map 映射,示例:

// 取出所有的车架号列表
 list<string> vins = vehicles.stream().map(vehicle::getvin).collect(collectors.tolist());

4.1.5 groupby 按照某个属性进行分组

// 按照公司 id 进行分组
map<integer, list<vehicle>> companyvehicles = vehicles.stream().collect(collectors.groupingby(vehicle::getcompanyid));

// 按照公司分组求司机打分和
map<integer, double> collect = vehicles.stream().collect(collectors.groupingby(vehicle::getcompanyid, collectors.summingdouble(vehicle::getscore)));

4.1.6 sort 按照某个属性排序 ,及多列排序

// 单列排序 
vehicles.sort((v1,v2) -> v2.getscore().compareto(v1.getscore()));

// 或使用 comparator 类来构建比较器,流处理不会改变原列表,需要接收返回值才能得到预期结果
 list<vehicle> collect = vehicles.stream().sorted(comparator.comparing(vehicle::getscore).reversed()).collect(collectors.tolist());

// 多列排序,score 降序,companyid 升序排列
list<vehicle> collect = vehicles.stream().sorted(comparator.comparing(vehicle::getscore).reversed()
                .thencomparing(comparator.comparing(vehicle::getcompanyid)))
                .collect(collectors.tolist());

4.1.7 flatmap 扁平化数据处理

// 查出所有车绑定的所有设备
list<string> collect = vehicles.stream().map(vehicle -> {
    string devicenos = vehicle.getdevicenos();
    return stringutils.split(devicenos,',');
}).flatmap(arrays::stream).collect(collectors.tolist());

flatmap 很适合 list<list>list<object []> 这种结构,可以当成一个列表来处理;像上面的设备列表,在数据库中存储的结构就是以逗号分隔的数据,而车辆列表又是一个列表数据。

4.1.8 mapreduce 数据处理

// 对所有司机的总分求和
double reduce = vehicles.stream().parallel().map(vehicle::getscore).reduce(0d, double::sum);

4.1.9 综合处理示例

// 总的分值
double totalscore = vehicles.stream().parallel().map(vehicle::getscore).reduce(0d, double::sum);

// 查看每一个司机占的分值比重
list<string> collect = vehicles.stream()
    .maptodouble(vehicle -> vehicle.getscore() / totalscore)
    .maptolong(weight -> (long) (weight * 100))
    .maptoobj(percentage -> percentage + "%")
    .collect(collectors.tolist());

原文的 boxed 不知道是什么意思,希望有大神能帮忙解答下,不用 boxed 也是可以的

4.2 optional

optional 用来解决 java 中经常出现的 nullpointerexception ,从而避免源码被各种空检查污染,使源码更加简洁和更加容易阅读

// 假设有一个对象 obj ,你不知道它是不是为空的,但是你想用它的方法,可以这么玩
optional<t> canuseobj = optional.ofnullable(obj);
canuseobj.ifpresent(system.out::println);       //如果 obj 不为空,则可以使用 obj 的方法,这里做个简单输出 

4.3 date/time api(jsr 310)

4.4 base64

对于 base64 终于不用引用第三方包了,使用 java 库就可以完成

// 编码
final string encoded = base64.getencoder().encodetostring( text.getbytes( standardcharsets.utf_8 ) );
// 解码
final string decoded = new string( base64.getdecoder().decode( encoded ),standardcharsets.utf_8 );

4.5 juc 工具包扩充

基于新增的lambda表达式和steam特性,为java 8中为java.util.concurrent.concurrenthashmap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentforkjoinpool类添加了新的方法来支持通用线程池操作(更多内容可以参考)。

java 8还添加了新的java.util.concurrent.locks.stampedlock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.readwritelock的替代者)。

java.util.concurrent.atomic包中也新增了不少工具类,列举如下:

  • doubleaccumulator
  • doubleadder
  • longaccumulator
  • longadder

5. 新的工具

java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具。

5.1 类依赖分析器:jdeps

deps是一个相当棒的命令行工具,它可以展示包层级和类层级的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

一点小推广

创作不易,希望可以支持下我的开源软件,及我的小工具,欢迎来 gitee 点星,fork ,提 bug 。

excel 通用导入导出,支持 excel 公式
博客地址:
gitee:

使用模板代码 ,从数据库生成代码 ,及一些项目中经常可以用到的小工具
博客地址:
gitee: