Java8 新特性精心整理(全)

适合读者及目标

目标人群

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

目标

  • 解 java8 的函数式接口和 Lambda 表达式
  • 方法引用的使用
  • 接口的静态方法和默认方法
  • Date/Time Api 的使用
  • 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 作为输入 ,返回 boolean 值的输出
  • Consumer T 作为输入 ,没有输出
  • Supplier 没有输入 , R 作为输出
  • BinaryOperator 两个 T 作为输入 ,T 同样是输出
  • UnaryOperator 是 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.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 的新特性

  • 使用Metaspace(JEP 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 ,排序等功能,下面列出项目中经常用到的功能,会以使用频率排序。

准备一个用于下面例子测试的对象

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

准备一些车辆数据

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

// 取出所有的车架号列表
 List<String> vins = vehicles.stream().map(Vehicle::getVin).collect(Collectors.toList());
4.1.5 groupBy 按照某个属性进行分组

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


// 按照公司 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<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)

这里不多说,常用方法类似

Instant: 表示时间线上的一个点,参考点是标准的Java纪元(epoch),即1970-01-01T00:00:00Z(1970年1月1日00:00 GMT)

  • LocalDate: 日期值对象如 2019-09-22

  • LocalTime:时间值对象如 21:25:36

  • LocalDateTime:日期+时间值对象

  • ZoneId:时区

  • ZonedDateTime:日期+时间+时区值对象

  • DateTimeFormatter:用于日期时间的格式化

  • Period:用于计算日期间隔

  • Duration:用于计算时间间隔

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值