JAVA8新特性

  • Post author:
  • Post category:java




1. Lambda表达式

Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。



语法:

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符 或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表 (其实就是接口中的抽象方法的形参列表)

右侧:指定了 Lambda 体,是抽象方法的实现逻辑,(其实就是重写的抽象方法的方法体)

问题:在Runnable中打印一串文字

标准写法(不使用Lambda):

@Test
public void standard(){
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println("标准模式");
        }
    };
    runnable.run();
}

Lambda:
@Test
public void lambda1(){
   Runnable runnable = ()-> system.out.println("Lambda1");
   runnable.run();
}

问题:设置View的点击事件

标准写法(不使用Lambda):

@Test
public void clickStandard(Context context){
    View view = new View(context);
    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            System.out.println("点击事件");
        }
    });
}
Lambda:
@Test
public void clickLambda(Context context){
    View view = new View(context);
    view.setOnClickListener(v -> System.out.println("点击事件"));
}

问题:匿名内部类的输出两数相加

标准写法(不使用Lambda):

@Test
public void addStandard(){
    Consumer<Integer> consumer = new Consumer<Integer>() {
        @Override
        public void accept(Integer integer) {
            System.out.println(integer + 30);
        }
    };
    consumer.accept(20);
}
Lambda:
@Test
public void addLambda(){
    Consumer<Integer> consumer = s -> System.out.println(s + 30);
    consumer.accept(20);
}

问题:匿名内部类比较两数并返回结果

标准写法(不使用Lambda):

@Test
public void compaResultStandard(){
    Comparator<Integer> comparator = new Comparator<Integer>() {
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1.compareTo(o2);
        }
    };
    System.out.println(comparator.compare(80, 50));
}
Lambda:
@Test
public void compaResultLambda(){
    Comparator<Integer> comparator = (s1, s2) -> {
        return s1.compareTo(s2);
    };
    System.out.println(comparator.compare(80, 50));
}

总结:

->左边:lambda形参列表的参数类型可以省略(类型推断);如果lambda形参列表只有一个参数,其一对()也可以省略

->右边:lambda体应该使用一对{}包裹;如果lambda体只有一条执行语句(可能是return语句),省略这一对{}和return关键字



函数式接口(Functional Interface)

Lambda表达式的本质:作为函数式接口的实例

如果一个接口中,只声明了一个抽象方法,则此接口就称为函数式接口。我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。

这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。

以前用匿名实现类表示的现在都可以用Lambda表达式来写。

1、该注解只能标记在”有且仅有一个抽象方法”的接口上。

2、JDK8接口中的静态方法和默认方法,都不算是抽象方法。

3、接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么 也不算抽象方法。

注:该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。



类型推断

在Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。



接口Api

Java 内置四大核心函数式接口

java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:

1	BiConsumer<T,U>
代表了一个接受两个输入参数的操作,并且不返回任何结果

2	BiFunction<T,U,R>
代表了一个接受两个输入参数的方法,并且返回一个结果

3	BinaryOperator<T>
代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果

4	BiPredicate<T,U>
代表了一个两个参数的boolean值方法

5	BooleanSupplier
代表了boolean值结果的提供方

6	Consumer<T>
代表了接受一个输入参数并且无返回的操作

7	DoubleBinaryOperator
代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。

8	DoubleConsumer
代表一个接受double值参数的操作,并且不返回结果。

9	DoubleFunction<R>
代表接受一个double值参数的方法,并且返回结果

10	DoublePredicate
代表一个拥有double值参数的boolean值方法

11	DoubleSupplier
代表一个double值结构的提供方

12	DoubleToIntFunction
接受一个double类型输入,返回一个int类型结果。

13	DoubleToLongFunction
接受一个double类型输入,返回一个long类型结果

14	DoubleUnaryOperator
接受一个参数同为类型double,返回值类型也为double 。

15	Function<T,R>
接受一个输入参数,返回一个结果。

16	IntBinaryOperator
接受两个参数同为类型int,返回值类型也为int 。

17	IntConsumer
接受一个int类型的输入参数,无返回值 。

18	IntFunction<R>
接受一个int类型输入参数,返回一个结果 。

19	IntPredicate
:接受一个int输入参数,返回一个布尔值的结果。

20	IntSupplier
无参数,返回一个int类型结果。

21	IntToDoubleFunction
接受一个int类型输入,返回一个double类型结果 。

22	IntToLongFunction
接受一个int类型输入,返回一个long类型结果。

23	IntUnaryOperator
接受一个参数同为类型int,返回值类型也为int 。

24	LongBinaryOperator
接受两个参数同为类型long,返回值类型也为long。

25	LongConsumer
接受一个long类型的输入参数,无返回值。

26	LongFunction<R>
接受一个long类型输入参数,返回一个结果。

27	LongPredicate
R接受一个long输入参数,返回一个布尔值类型结果。

28	LongSupplier
无参数,返回一个结果long类型的值。

29	LongToDoubleFunction
接受一个long类型输入,返回一个double类型结果。

30	LongToIntFunction
接受一个long类型输入,返回一个int类型结果。

31	LongUnaryOperator
接受一个参数同为类型long,返回值类型也为long。

32	ObjDoubleConsumer<T>
接受一个object类型和一个double类型的输入参数,无返回值。

33	ObjIntConsumer<T>
接受一个object类型和一个int类型的输入参数,无返回值。

34	ObjLongConsumer<T>
接受一个object类型和一个long类型的输入参数,无返回值。

35	Predicate<T>
接受一个输入参数,返回一个布尔值结果。

36	Supplier<T>
无参数,返回一个结果。

37	ToDoubleBiFunction<T,U>
接受两个输入参数,返回一个double类型结果

38	ToDoubleFunction<T>
接受一个输入参数,返回一个double类型结果

39	ToIntBiFunction<T,U>
接受两个输入参数,返回一个int类型结果。

40	ToIntFunction<T>
接受一个输入参数,返回一个int类型结果。

41	ToLongBiFunction<T,U>
接受两个输入参数,返回一个long类型结果。

42	ToLongFunction<T>
接受一个输入参数,返回一个long类型结果。

43	UnaryOperator<T>
接受一个参数为类型T,返回值类型也为T。


方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!

方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。

要求:实现接口的抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!

格式:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。

如下三种主要使用情况:

对象::实例方法名

类::静态方法名

类::实例方法名
@Test
public void methodQuote(){
    Supplier<String> supplier = new Supplier<String>() {
        @Override
        public String get() {
            return "FUCK";
        }
    };
    System.out.println(supplier.get());

    User user = new User("川普",80,150);

    //对象::实例方法名
    Supplier<String> supplierL = user::getName;
    System.out.println(supplierL.get());

    //类::静态方法名
    Comparator<String> comparator = String::compareTo;
    System.out.println(comparator.compare("aa","bb"));

    //类::实例方法名
    Function<User,String> function = User::getName;
    System.out.println(function.apply(user));

    Function<User,String> fun = new Function<User, String>() {
        @Override
        public String apply(User user) {
            return user.name;
        }
    };

}


构造器引用

ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

可以把构造器引用赋值给定义的方法,要求构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象

@Test
public void MethodConstructor(){
    //构造器
    Function<String, User> function = User::new;
    System.out.println(function.apply("老毕登"));

    Function<String,User> fun = new Function<String, User>() {
        @Override
        public User apply(String s) {
            return new User(s);
        }
    };
} 


数组引用

可以把数组看做是一个特殊的类,则写法与构造器引用一致。

@Test
public void MethodArray(){
    //数组引用
    Function<Integer, Integer[]> function = Integer[]::new;
    System.out.println(Arrays.toString(function.apply(20)));

}



2. Stream API

什么是 Stream

Stream到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,Stream讲的是计算!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他	们会等到需要结果的时候才执行

流程:

创建Stream -> 中间操作 -> 终止操作



创建Stream:


1、Collection方式:

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

default Stream stream() : 返回一个顺序流

default Stream parallelStream() : 返回一个并行流

@Test
public void ColletcionsStream(){
    List<User> users = new ArrayList<User>();
    users.stream();
    users.parallelStream();
}



2、Arrays方式:

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流

@Test
public void arraysStream(){
    User[] users = new User[20];
    Stream<User> userStream = Arrays.stream(users);
}


3、Stream.of()方式:

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

@Test
public void streamOfStream(){
    User user1 = new User("普京");
    User user2 = new User("梅德韦杰夫");
    Stream<User> userStream = Stream.of(user1,user2);
}


4、无限流方式

可以使用静态方法 Stream.iterate() 和 Stream.generate(),创建无限流。

@Test
public void infiniteStreamIterate(){
    //迭代创建
    Stream.iterate(1, t -> t *2).limit(10).forEach(System.out::println);
    Stream s = Stream.iterate(1, new UnaryOperator<Integer>() {
        @Override
        public Integer apply(Integer integer) {
            return integer * 2;
        }
    });
    s = s.limit(20);

    s.forEach(System.out::println);

}
@Test
public void infiniteStreamGenerate(){
    //生成创建
    Stream.generate(Math::random).limit(20).forEach(System.out::println);
}


5、BufferedReader.lines() 每行内容转成流方式
@Test
public void bufferStream() throws FileNotFoundException {
    BufferedReader reader = new BufferedReader(new FileReader("F:\\test\\test.txt"));
    reader.lines().forEach(System.out::println);
}



6、Pattern.splitAsStream() 将字符串分隔成流
@Test
public void patternStream(){
    Pattern pattern = Pattern.compile(",");
    pattern.splitAsStream("adfadfa,bbbb,cccccc").forEach(System.out::println);
}


流的中间操作

Stream是惰性流,中间操作只是将lambda表达式记录下来,返回一个新的Stream,只有终止操作被调用时,才会触发计算。这样可以保证数据被尽量少的遍历,这也是Stream高效的原因之一。中间操作分为无状态操作和有状态操作,无状态操作不会存储元素的中间状态,有状态操作会保存元素中间状态。



无状态操作: filter() map() mapToInt() mapToLong() mapToDouble() flatMap() flatMapToInt() flatMapToLong() flatMapToDouble() peek() unordered()



有状态操作: distinct() sorted() limit() skip()

无状态操作是指元素的处理不受之前元素的影响;有状态操作是指该操作只有拿到所有元素之后才能继续下去.



1、筛选与切片
//	1 filter:过滤流中的某些元素
public void filter(List<User> list){
    list.stream().filter(item -> item.age >= 60).forEach(item -> System.out.println(item.name));
}

//	2 limit(n):获取n个元素,限制获取元素的个数
public void limit(List<User> list){
    list.stream().limit(5).forEach(item -> System.out.println(item.name));
}

//	3 skip(n):跳过n元素,配合limit(n)可实现分页
public void skip(List<User> list){
    list.stream().skip(1).forEach(aa -> System.out.println(aa.name));
}

//	4 distinct:通过流中元素的 hashCode() 和 equals() 去除重复元素
public void distinct(List<User> list){
    list.stream().distinct().forEach(bb -> System.out.println(bb.name));
}


2、映射
//  map:接收一个函数作为参数,(常为lambda表达式)该函数会被应用到每个元素上,并将其映射成一个新的元素。
public void map(List<String> list){
    list.stream().map(cc -> cc.split(",")).forEach( dd -> Arrays.stream(dd).forEach(System.out::println));
}



//  flatMap:各个数组并不是分别映射一个流,而是映射成流的内容。
public void flatmap(List<String> list){
    list.stream().flatMap(cc -> {
        String[] res = cc.split(",");
        return Arrays.stream(res);
    }).forEach(System.out::println);
}


3、排序
//排序
public void sort(List<User> list){
    list.stream().sorted((aa, bb) -> aa.age - bb.age).forEach(item -> System.out.println(item.name));
}


4、消费
// peek:如同于map,能得到流中的每一个元素。但map接收的是一个Function表达式,有返回值;而peek接收的是Consumer表达式,没有返回值。
public void peek(List<User> list) {
    list.stream().peek(aa -> aa.setName("大哥大")).forEach( item -> System.out.println(item.name));
}



流的终止操作



匹配、聚合操作

allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false

noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false

anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false

findFirst:返回流中第一个元素

findAny:返回流中的任意元素

count:返回流中元素的总个数

max:返回流中元素最大值

min:返回流中元素最小值

public void over(List<User> list){

    //allMatch
    Boolean b = list.stream().allMatch(aa -> aa.name.length() > 10);
    System.out.println(b);

    //noneMatch
    Boolean c = list.stream().noneMatch(aa -> aa.name.length() > 10);
    System.out.println(c);

//findFirst
    User user = list.stream().findFirst().get();
    System.out.println(user.name);

}


规约操作

Stream中的Reduce方法:根据一定的规则将Stream中的元素进行计算后返回一个唯一的值,它有三个变种,输入参数分别是一个参数、二个参数以及三个参数。

/**
 * 一个参数
 */
@Test
public void reduce(){
    Stream<Integer> s = Stream.of(1, 2, 3, 4, 5, 6);
    Integer sum = s.reduce(Integer::sum).get();
    System.out.println(sum);
}

/**
 * 两个参数
 */
@Test
public void reduce2(){
    Stream<String> s = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
    System.out.println(s.reduce("丢你螺母", String::concat));
}

/**
 * 三个参数
 * identity: 一个初始化的值;这个初始化的值其类型是泛型U,与Reduce方法返回的类型一致;
 *      注意此时Stream中元素的类型是T,与U可以不一样也可以一样,这样的话操作空间就大了;
 *      不管Stream中存储的元素是什么类型,U都可以是任何类型,如U可以是一些基本数据类型的包装类型Integer、Long等;
 *      或者是String,又或者是一些集合类型ArrayList等(这个和之前的两参数和单参数就不一样了)
 * accumulator: 其类型是BiFunction,输入是U与T两个类型的数据,
 *      而返回的是U类型;也就是说返回的类型与输入的第一个参数类型是一样的,
 *      而输入的第二个参数类型与Stream中元素类型是一样的。
 * combiner: 其类型是BinaryOperator,支持的是对U类型的对象进行操作;
 */
@Test
public void reduce3(){
    Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
    System.out.println(s1.reduce(new ArrayList<String>(), (r, t) -> {r.add(t); return r; }, (r1, r2) -> r1));
}



注:当Stream是并行时,第三个参数就有意义了,它会将不同线程计算的结果调用combiner做汇总后返回。



收集操作


collect:接收一个Collector实例,将流中元素收集成另外一个数据结构。
User p1 = new User("aaaa",26,180);
User p2 = new User("bbbb",22,182);
User p3 = new User("cccc",23,183);
User p4 = new User("ddddd",23,183);
User p5 = new User("eeee",26,183);
List<User> list = Arrays.asList(p1,p2,p3,p4,p5);
    //装成list
List<Integer> ageList = list.stream().map(User::getAge).collect(Collectors.toList());//[26,22,22]
    //转成set
Set<Integer> ageSet = list.stream().map(User::getAge).collect(Collectors.toSet());//[26,22]

    //转成map,注:key不能相同,否则报错
Map<String, Integer> studentMap = list.stream().collect(Collectors.toMap(User::getName, User::getAge));

    //字符串分隔符连接
String joinName = list.stream().map(User::getName).collect(Collectors.joining(",", "(", ")"));

    //聚合操作
    //1.总数
Long count = list.stream().collect(Collectors.counting());
    //2.最大年龄 (最小的minBy同理)
Integer maxAge = list.stream().map(User::getAge).collect(Collectors.maxBy(Integer::compare)).get();
    //3.所有人的年龄求和
Integer sumAge = list.stream().collect(Collectors.summingInt(User::getAge));
    //4.平均年龄
Double averageAge = list.stream().collect(Collectors.averagingDouble(User::getAge));

    // 带上以上所有方法
DoubleSummaryStatistics statistics = list.stream().collect(Collectors.summarizingDouble(User::getAge));
System.out.println("count:" + statistics.getCount() + ",max:" + statistics.getMax() + ",sum:" + statistics.getSum() + ",average:" + statistics.getAverage());

    //分组 按年龄分组
Map<Integer, List<User>> ageMap = list.stream().collect(Collectors.groupingBy(User::getAge));
    //分区
    //分成两部分,一部分大于10岁,一部分小于等于10岁
Map<Boolean, List<User>> partMap = list.stream().collect(Collectors.partitioningBy(v -> v.getAge() > 10));
    //规约
Integer allAge = list.stream().map(User::getAge).collect(Collectors.reducing(Integer::sum)).get();

System.out.println();
System.out.println();
System.out.println(allAge);



3.接口增强

在java8以前的版本中,定义一个接口时,所有的方法必须是抽象方法,不能有具体实现,这是java语法规定的。但是在java8中定义一个接口时,在满足特定的前提下,可以有方法的具体实现。这样一个接口中可以有属性,可以有抽象方法,也可以有具体的方法,这跟java8以前的接口比,明显接口的功能变得强大了。

  • 1.新增 : 默认方法 default 关键字修饰

    default 关键字修饰方法,必须有方法体

    实现类可以重写该方法

    调用方式 : 通过实现类的对象进行调用
  • 2.新增 :静态方法

    static 关键字修饰方法,必须有方法体

    实现类不可以重写该方法

    调用方式 : 只能通过 【接口名.静态方法名】 进行调用



4.Optional

传统的写代码方式经常会遇到NullPointerException,这就需要我们在代码中经常判空。而判空的写法又会显得很累赘,这里就可以用到Optional来简化代码.

Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。

Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional 类的引入很好的解决空指针异常

构建Optional

构建一个Optional对象;方法有:empty( )、of( )、ofNullable( )

相关API

  1. isPresent(): 持有非空值,返回true;否则false;
  2. ifPresent(): 如果 Optional 中有值,则对该值调用consumer.accept,否则什么也不做。
  3. orElse: 参数是一个值,如果 Optional 中有值则将其返回,否则返回 orElse 方法传入的参数。
  4. orElseGet: 传入的参数为一个 Supplier 接口的实现。
  5. orElseThrow: 没有值的时候会抛出异常,抛出的异常由传入的 exceptionSupplier 提供。
  6. map:如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional。
  7. flatMap: map 方法参数中的函数 mapper 输出的是值,然后 map 方法会使用 Optional.ofNullable 将其包装为 Optional;而 flatMap 要求参数中的函数 mapper 输出的就是 Optional。
  8. filter: 接受一个 Predicate 来对 Optional 中包含的值进行过滤,如果包含的值满足条件,那么还是返回这个 Optional;否则返回 Optional.empty。



版权声明:本文为qq_36651718原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。