Java8新特性概览——Stream特性,Lambda表达式,函数式接口Function、Predicate、Consumer,方法引用等概述

概述:
Java 8 新特性概述:https://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
JAVA8 十大新特性详解:https://www.cnblogs.com/xingzc/p/6002873.html

关于lambda表达式:
State of the Lambda:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
Translation of Lambda Expressions:http://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html

关于Stream:
Java的Stream流式处理:https://blog.csdn.net/qq_20989105/article/details/81234175
Java 8 中的 Streams API 详解:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/


Java 8 新特性

结构安排与大部分内容转自:https://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html
关于Stream的大部分内容转自:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

内容较多,建议通过目录浏览

函数式接口(Functional Interfaces)

在接口里面添加抽象方法,这些方法可以直接从接口中运行。

如果一个接口定义唯一一个抽象方法,那么这个接口就成为函数式接口

新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。

这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断。不过最好在接口上使用注解 @FunctionalInterface 进行声明,编译器会判断接口中是否只有一个方法,否则编译器会报错。

//java.lang.Runnable 就是一个函数式接口。
@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

通用函数式接口

Function、Predicate、Consumer

java.util.function

  • Function<T, R>:将 T 作为输入,返回 R 作为输出,还包含了和其他函数组合的默认方法。

    public class Test {  
        public static void main(String[] args) throws InterruptedException {  
            String name = "";  
            String name1 = "12345";  
            System.out.println(validInput(name, inputStr -> inputStr.isEmpty() ? "名字不能为空":inputStr));  
            System.out.println(validInput(name1, inputStr -> inputStr.length() > 3 ? "名字过长":inputStr));  
        }  
          
        public static String validInput(String name,Function<String,String> function) {  
            return function.apply(name);  
        }  
    }  
    
  • Predicate<T> :将 T 作为输入,返回一个布尔值作为输出,该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)。

    public class Test {  
        public static void main(String[] args) throws InterruptedException {  
            String name = "";  
            String name1 = "12345";  
              
            validInput(name, inputStr ->    
                    System.out.println(inputStr.isEmpty() ? "名字不能为空":"名字正常"));  
            validInput(name1, inputStr ->  
                    System.out.println(inputStr.isEmpty() ? "名字不能为空":"名字正常"));  
              
        }  
        public static void validInput(String name,Consumer<String> function) {  
            function.accept(name);  
        }  
    }  
    
  • Consumer<T> :将 T 作为输入,不返回任何内容,表示在单个参数上的操作。

    public class Test {  
        public static void main(String[] args) throws InterruptedException {  
            String name = "";  
            String name1 = "12";  
            String name2 = "12345";  
              
            System.out.println(validInput(name,inputStr ->  !inputStr.isEmpty() &&  inputStr.length() <= 3 ));  
            System.out.println(validInput(name1,inputStr ->  !inputStr.isEmpty() &&  inputStr.length() <= 3 ));  
            System.out.println(validInput(name2,inputStr ->  !inputStr.isEmpty() &&  inputStr.length() <= 3 ));  
              
        }  
        public static boolean validInput(String name,Predicate<String> function) {  
            return function.test(name);  
        }  
    }
    

函数式接口与通用函数式接口比较

//例如,People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
interface PersonInterface {
	public boolean test(Person person);
}
public class People {
	private List<Person> persons= new ArrayList<Person>();
	public List<Person> getMaleList(PersonInterface filter) {//自定义函数式接口
		List<Person> res = new ArrayList<Person>();
		persons.forEach((Person person) -> {
			if (filter.test(person)) {
				//调用 PersonInterface 的方法
				res.add(person);
			}
		});
		return res;
	}
}

//为了去除 PersonInterface 这个函数式接口,可以用通用函数式接口 Predicate 替代如下:
class People{
	private List<Person> persons= new ArrayList<Person>();
	public List<Person> getMaleList(Predicate<Person> predicate) {//通用函数式接口
		List<Person> res = new ArrayList<Person>();
		persons.forEach(person -> {
			if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
				res.add(person);
			}
		});
		return res;
	}
}

Lambda 表达式

函数式接口的重要属性是:我们能够使用 Lambda 实例化它们。

Lambda 表达式让我们能够将函数作为方法参数,或者将代码作为数据对待。

Lambda 表达式由三个部分组成:
第一部分为一个括号内用逗号分隔的形式参数(参数列表),参数是函数式接口里面方法的参数;
第二部分为一个箭头符号->
第三部分为方法体,可以是表达式和代码块。

形式参数的类型可以由Java的类型推断机制进行推断,也可以显示声明
涉及知识点:类型参数推断(Java8的泛型目标类型推断)

为了进一步简化 Lambda 表达式,可以使用方法引用。
例如,下面三种分别是使用内部类、Lambda 表示式和方法引用的比较:

//1. 使用匿名内部类
Function<Integer, String> f = new Function<Integer,String>(){
	@Override
	public String apply(Integer t) {
	return null;
	}
};

//2. 使用 Lambda 表达式
Function<Integer, String> f2 = (t)->String.valueOf(t); 


//3. 使用方法引用的方式
Function<Integer, String> f1 = String::valueOf;

方法引用、构造器引用

方法引用和构造器引用:https://www.cnblogs.com/aoeiuv/p/5911692.html

objectName::instanceMethod

ClassName::staticMethod

ClassName::instanceMethod

ClassName::new

接口的增强

默认方法 ——default关键字

Java 8 允许我们给接口添加一个非抽象的方法实现,只需要使用default关键字即可,这个特征又叫做扩展方法。

在实现该接口时,该默认方法在子类上可以直接使用,它的使用方式类似于抽象类中非抽象成员方法。

默认方法不能够重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。

静态方法 ——static关键字

使用static关键字

流式操作 Stream

概述

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

通过Stream操作可以实现对集合(Collection)的并行处理和函数式操作。

根据操作返回的结果不同,流式操作分为中间操作最终操作两种:

  • 最终操作返回一特定类型的结果
  • 中间操作返回流本身,这样就可以将多个操作依次串联起来。

根据流的并发性,流又可以分为串行并行两种。流式操作实现了集合的过滤、排序、映射等功能。

当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。

Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。

Stream 的另外一大特点是,数据源本身可以是无限的。

Stream 和 Collection 集合的区别:

  • Collection是一种静态的内存数据结构,主要面向内存,存储在内存中,
  • Stream是有关计算的。主要是面向 CPU,通过 CPU 实现计算。

流的构成

在这里插入图片描述

流的生成方式

有多种方式生成 Stream Source:

  • 从 Collection 和数组
    Collection.stream()
    Collection.parallelStream()
    Arrays.stream(T array)
    Stream.of()
    
  • 从 BufferedReader
    java.io.BufferedReader.lines()
    
  • 静态工厂
    java.util.stream.IntStream.range()
    java.nio.file.Files.walk()
    
  • 自己构建
    java.util.Spliterator
    
  • 其它
    Random.ints()
    BitSet.stream()
    Pattern.splitAsStream(java.lang.CharSequence)
    JarFile.stream()
    

流的操作类型

  • Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都做掉的总和吗?

其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成

我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数

还有一种操作被称为 short-circuiting,它表示:

  • 对于一个 intermediate 操作,如果它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
  • 对于一个 terminal 操作,如果它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。

Intermediate 中间操作

该操作会保持 stream 处于中间状态,允许做进一步的操作。它返回的还是的 Stream,允许更多的链式操作。常见的中间操作有:

filter():对元素进行过滤;

sorted():对元素排序;

map():元素的映射;

distinct():去除重复元素;

subStream():获取子 Stream 等。

list.stream()
.filter((s) -> s.startsWith("s"))
.forEach(System.out::println);

Terminal 终止操作

该操作必须是流的最后一个操作,一旦被调用,Stream 就到了一个终止状态,而且不能再使用了。常见的终止操作有:

forEach():对每个元素做处理;

toArray():把元素导出到数组;

findFirst():返回第一个匹配的元素;

anyMatch():是否有匹配的元素等。

list.stream() //获取列表的 stream 操作对象
.filter((s) -> s.startsWith("s"))//对这个流做过滤操作
.forEach(System.out::println);

short-circuiting

当操作一个无限大的 Stream,而又希望在有限时间内完成操作,则在管道内拥有一个 short-circuiting 操作是必要非充分条件。

示例:

int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
.mapToInt(w -> w.getWeight())
.sum();

stream() 获取当前widgets的 source。

filter 和 mapToInt 是 intermediate 操作,进行数据筛选和转换。

最后一个 sum() 为 terminal 操作,对符合条件的全部小物件作重量求和。

(这里没太明白,哪里体现了short-circuiting 操作?)

流的使用详解

简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

具体内容:https://blog.csdn.net/u011848397/article/details/89108648

注解的更新

对于注解,Java 8 主要有两点改进:类型注解和重复注解。

类型注解

现在几乎可以为任何东西添加注解:局部变量、类与接口,就连方法的异常也能添加注解。

Java 8 本身虽然没有自带类型检测的框架,但可以通过使用 Checker Framework 这样的第三方工具,自动检查和确认软件的缺陷,提高生产效率。

import org.checkerframework.checker.nullness.qual.NonNull;
public class TestAnno {
	public static void main(String[] args) {
		@NonNull Object obj = null;
		obj.toString();
	}
}

console:
C:\workspace\TestJava8\src\TestAnno.java:4: Warning:
  (assignment.type.incompatible) $$ 2 $$ null $$ @UnknownInitialization @NonNull Object $$ ( 152, 156 )
  $$ incompatible types in assignment.
@NonNull Object obj = null;
 ^
 found : null
 required: @UnknownInitialization @NonNull Object

重复注解

Java 8 引入了重复注解机制,这样相同的注解可以在同一地方声明多次。重复注解机制本身必须用 @Repeatable 注解。

安全性

IO/NIO 的改进

全球化功能

新的 java.time 中包含了所有关于:

时钟(Clock),
本地日期(LocalDate)、
本地时间(LocalTime)、
本地日期时间(LocalDateTime)、
时区(ZonedDateTime)
持续时间(Duration)的类。

历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值