Java8函数式编程深入理解

什么是匿名内部类?

       无需知道类实现名,在内部实现接口方法,做类的定义。

Lambda表达式与匿名内部类的区别?

  1. 匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。
  2. Lambda表达式通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。
  3. 简化匿名内部类的书写,取代部分匿名内部类,只能用来取代函数接口(Functional Interface)的简写。

Lambda表达式

       Lambda表达式:参数 箭头(->)表达式

       好处:简化匿名内部类写法,去掉样板代码。

原本设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码。若使用Lambda写法,就能直接用一行代码替代4行,去掉样板代码,只留下具体逻辑代码。

       正常写法Runable

Runnable runnable = new Runnable() {
     @Override
     public void run() {
         System.out.println("run");
     }
 };

       Lambda写法

Runnable runnable = ()->System.out.println("run");

Lambda的几种写法

     简写的依据

       能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。

     无参函数写法 

       使用空括号()表示无参数,使用Lambda表达式实现Runable接口,该接口只有一个run方法,没有参数,返回类型为void。

/*Runnable runnable = new Runnable() {

    @Override

    public void run() {

        System.out.println("run");

    }

};*/
Runnable runnable = ()->System.out.println("run");

带参函数写法

       使用括号(参数,参数)表示有多个参数,当只有一个参数时可省略括号。

/*BinaryOperator<Long> add = new BinaryOperator<Long>() {

    @Override

    public Long apply(Long x, Long y) {

        return x + y;

    }

};*/
BinaryOperator<Long> add = (x, y) -> x + y;
/*ActionListener listener = new ActionListener() {

    @Override

    public void actionPerformed(ActionEvent e) {

        System.out.println("button clicked");

    }

};*/
ActionListener listener = event -> System.out.println("button clicked");

复杂逻辑写法

       若不止一行代码,需要加上大括号。(参数)->{代码逻辑}

/*ActionListener actionListener = new ActionListener() {

    @Override

    public void actionPerformed(ActionEvent e) {

        System.out.println("button clicked");

        System.out.println("notice other");

    }

};*/

  ActionListener actionListener = event -> {

    System.out.println("button clicked");

    System.out.println("notice other");

};

注:错误写法

       在只有一行代码逻辑的时候,若有程序逻辑有返回时,编译器会自动识别返回类型,无需加上return关键字。加上大括号时需要加上return关键字。

//正确写法
BinaryOperator<Long> add = (x, y) -> x + y;
// 错误写法
BinaryOperator<Long> add = (x, y) -> return x + y;
//正确写法
BinaryOperator<Long> add = (x, y) -> {

    return x + y;

};

自定义函数接口

       只需要编写一个抽象方法的接口即可。@FunctionalInterface注释是可选的,其目的是为了标注编译器帮你检查接口是否符合函数接口规范,就像@Override标注会检查是否重载了函数一样。注意:函数接口中只允许一个抽象方法的接口,若出现多个的情况下,编译器无法根据上下文进行推算当前接口。

@FunctionalInterface

  public interface Consumer<T> {
void accept(T t);
}

错误定义:

报错:Operator  cannot be applied to lambda parameter

public interface TestComsumer<T> {

    void accept(T t);

    void apply(T x,T y);

}


类型推断

       程序员可省略Lambda表达式中所有的参数类型,javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断

       使用 Lambda 表达式示例1检测一个 Integer 是否大于 5,示例2检测一个String是否等于”x” 。这实际上是一个 Predicate ——用来判断真假的函数接口。

       类型推断

//示例1
Predicate<Integer> predicate = x -> x > 5;
//示例2
Predicate<String> predicate = x -> x.equals("x");



       Predicate源码:接受一个对象,返回一个布尔值

@FunctionalInterface

  public interface Predicate<T> {
boolean test(T t);
}

Predicate 只有一个泛型类型的参数, Integer 或String用于其中。Lambda表达式实现了 Predicate 接口,因此它的单一参数被推断为 Integer或String 类型。 javac 还可检查Lambda 表达式的返回值是不是 boolean ,这正是 Predicate 方法的返回类型。

没有泛型,代码不通过编译。当代码没有给与任何泛型时,编译器会将参数识别成java.lang.Object,Object是无法直接进行大小比较。


//当将x进行toString后,再转成Integer就能进行大小比较,这样就不报错。
Predicate predicate = x -> Integer.parseInt(x.toString()) > 5;
//当将x进行toString后,就能跟字符”x”进行比较
Predicate predicate = x -> x.toString().equals("x");

Stream流

       Stream流部分常见接口方法

操作类型

接口方法

惰性求值

concat() distinct() filter() flatMap() limit() map() peek()
skip() sorted() parallel() sequential() unordered()

及早求值

allMatch() anyMatch() collect() count() findAny() findFirst()
forEach() forEachOrdered() max() min() noneMatch() reduce() toArray()

       惰性求值方法:只描述stream流,不产生新的集合。

       及早求值方法:从stream流中产生值。

是否是惰性求值还是及早求值有一个简单的判断依据,当返回的值是stream流,那么就是惰性求值,若返回值是具体的值或空,那么就是及早求值。

注:只执行惰性求值方法,而不使用及早求值方法是不会真正的执行stream流。惰性求值可以比作“我要做“,及早求值比作”我要得到“。当”我要做“的时候,代码只是将代码执行流程都准备好,只有执行”我要得到“指令时,才会去执行”我要做“指令。

惰性求值

concat

distinct

       distinct可以进行去重操作。

//Stream中distinct方法签名
Stream<T> distinct();




//代码示例
Stream<String> stream= Stream.of("I", "love", "you", "too", "too");

stream.distinct()

        .forEach(str -> System.out.println(str));


filter

filter是将一种Stream流中过滤中需要的新Stream流。

// Stream中filter方法签名
Stream<T> filter(Predicate<? super T> predicate);
//代码示例 将oldList中大于3的元素转换成newList
List<Integer> oldList  = Arrays.asList(1,2,3,4,5);

  /*List<Integer> newList = new ArrayList<Integer>();

for(Integer integer:oldList){

    if(integer>3){

        newList.add(integer);

    }

}*/
List<Integer> newList = oldList.stream()
.filter(x -> x > 3).collect(toList());


flatMap

       flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。

// Stream中flatMap方法签名
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);




//将多个list中的值转换成一个list
List<Integer> list1 = asList(1, 2);

List<Integer> list2 = asList(3, 4);

Stream<List<Integer>> listStream = Stream.of(list1, list2);

List<Integer> together = listStream

        .flatMap(numbers -> numbers.stream())

        .collect(toList());


limit

map

       map是将一种Stream流转换成另外一种Stream流。

//Stream中map方法签名
<R> Stream<R> map(Function<? super T, ? extends R> mapper);




//示例代码将Integer集合转换成String集合
List<String> list = Stream.of(1, 2, 3)
.map(x -> String.valueOf(x)).collect(toList());


peek

skip

sorted

       sorted有两种排序,一种是自然顺序排序,一种是自定义比较器排序。

//Stream中sorted方法签名
Stream<T> sorted();
Stream<T> sorted(Comparator<? super T> comparator);




//代码示例
Stream<String> stream= Stream.of("I", "love", "you", "too");
//自然顺序排序
stream.sorted().forEach(str -> System.out.println(str));
//自定义排序

stream.sorted((str1, str2) -> str1.length()-str2.length())

        .forEach(str -> System.out.println(str));


parallel

sequential

unordered     

及早求值

collect

     collect(toList()) 方法由 Stream 里的值生成一个列表。不单单是toList(),还可以toMap(),toSet(),toConcurrentMap(),toCollection().

//Stream中collect方法签名
<R, A> R collect(Collector<? super T, A, R> collector);




//toList()代码示例
List<String> list = Stream.of("a", "b" , "c")
.collect(Collectors.toList());




//toMap()代码示例
Map<String, String> map = Stream.of("a", "b", "c")
.collect(Collectors.toMap(str -> str, Function.identity()));




// Function.identity()表示返回自身,等同str -> str à源码
static <T> Function<T, T> identity() {

    return t -> t;

}




//toSet()代码示例
Set<String> set = Stream.of("a", "b", "c").collect(Collectors.toSet());


forEach

       forEach是一个遍历集合的接口方法,作用是对容器中每个元素执行action指定动作,也就是对元素进行遍历。

// Stream中forEach方法签名。
void forEach(Consumer<? super T> action)
//代码示例
Stream<String> stream = Stream.of("I", "love", "you", "too");
/*stream.forEach(new Consumer<String>() {

    @Override

    public void accept(String str) {

        System.out.println(str);

    }

});*/

stream.forEach(str -> System.out.println(str));



      

max和min

       max和min是查找Stream流中按照Comparator对象对比出来的max与min对象值。

//Stream中max方法签名
Optional<T> max(Comparator<? super T> comparator);




//查找字符长度最长的元素
String maxStr = Stream.of("I", "love", "you", "too")
.max(Comparator.comparing(x -> x.length())).get();





reduce

       reduce 可以实现从一组值中生成一个值。reduce可以做count , max , min等操作。

//Stream中reduce方法签名
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,

             BiFunction<U, ? super T, U> accumulator,

             BinaryOperator<U> combiner);




//代码示例
//Optional可能为空
Optional<Integer> reduce = Stream.of(1, 2, 3)

        .reduce((acc, element) -> acc + element);
//默认给了起始值,所以返回integer

Integer reduce = Stream.of(1, 2, 3)

        .reduce(0, (acc, element) -> acc + element);





Optional

       Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们常常使用 null 值表示值不存在, Optional 对象能更好地表达这个概念。使用 null 代表值不存在的最大问题在于 NullPointerException 。一旦引用一个存储 null 值的变量,程序会立即崩溃。使用 Optional 对象有两个目的:

首先, Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;

其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。

//创建一个字符”a”的optional
Optional<String> optional = Optional.of("a");




//创建一个空的 Optional 对象,并检查其是否有值
Optional emptyOptional = Optional.empty();

Optional alsoEmpty = Optional.ofNullable(null);




//使用 orElse 和 orElseGet 方法
emptyOptional.orElse("b");

emptyOptional.orElseGet(() -> "c");





方法引用

数据并行化

参考文献