Java 8(一)函数式编程

函数式编程

一、Lambda表达式

一)如何辨别Lambda表达式

 1 Runnable noArguments = () -> System.out.println("Hello World");
 2
 3 ActionListener oneArgument = event -> System.out.println("button clicked");
 4 Runnable multiStatement = () -> {
 5 System.out.print("Hello");
 6 System.out.println(" World");
 7 };
 8
 9 BinaryOperator<Long> add = (x, y) -> x + y;
10
11 BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y; 

1)没有参数用空“()”表示。

2)只有一个参数,括号可以省略,只写参数名。

3)Lambda 表达式的主体不仅可以是一个表达式, 而且也可以是一段代码块, 使用大括号
( {}) 将代码块括起来。 该代码块和普通方法遵循的规则别无二致, 可以用返
回或抛出异常来退出。 只有一行代码的 Lambda 表达式也可使用大括号, 用以明确 Lambda
表达式从何处开始、 到哪里结束。

4)Lambda 表达式也可以表示包含多个参数的方法。 这时就有必要思考怎样去阅
读该 Lambda 表达式。 这行代码并不是将两个数字相加, 而是创建了一个函数, 用来计算
两个数字相加的结果。 变量 add 的类型是 BinaryOperator<Long>, 它不是两个数字的和,
而是将两个数字相加的那行代码。

5)到目前为止, 所有 Lambda 表达式中的参数类型都是由编译器推断得出的。 这当然不错,
但有时最好也可以显式声明参数类型, 此时就需要使用小括号将参数括起来, 多个参数的
情况也是如此。

6)方法引用:Lambda表达式调用参数的一种简便写法。

如:

artist -> artist.getName()
//可以写成
Artist :: getName

构造函数有同样的写法:

(name,nationality)-> new Artist(name,nationlity)
//可以写为
Artist :: new  

目标类型是指 Lambda 表达式所在上下文环境的类型。 比如, 将 Lambda 表
达式赋值给一个局部变量, 或传递给一个方法作为参数, 局部变量或方法参
数的类型就是 Lambda 表达式的目标类型。

二)引用值,而不是变量

匿名内部类需要引用它所在方法里的变量时,需要将变量声明为final。

Java 8中放松了这一限制,可以引用非final变量,但该变量在既成事实上必须是final(只能给该变量赋值一次,

如果试图给该变量多次赋值,然后在Lambda表达式中引用它,编译器就会报错)。

这种行为也解释了为什么lamdba表达式为闭包-------含有自由变量(不是传入参数,且没有在方法块中定义的变量)的代码块。

三)函数接口

函数接口是只有一个抽象方法的接口,用作lambda表达式的类型。

四)类型推断

可省略Lambda表达式中所有参数的类型。

无法推断出类型的报错信息:

1 Operator ‘& #x002B;‘ cannot be applied to java.lang.Object, java.lang.Object.

二、流(Stream)

流使得程序员在更高的层次上对集合进行操作。

一)从外部迭代到内部迭代

使用 for 循环计算来自伦敦的艺术家人数 :

1 int count = 0;
2 for (Artist artist : allArtists) {
3 if (artist.isFrom("London")) {
4 count++;
5 }
6 }

此类代码的问题:

1.每次迭代集合类时, 都需要写很多样板代码

2.for 循环改造成并行方式运行也很麻烦, 需要修改每个 for 循环才能实现。

3.for 循环的样板代码模糊了代码的本意, 程
序员必须阅读整个循环体才能理解。 若是单一的 for 循环, 倒也问题不大, 但面对一个满
是循环( 尤其是嵌套循环) 的庞大代码库时, 负担就重了。

就其背后的原理来看, for 循环其实是一个封装了迭代的语法糖, 我们在这里多花点时间,
看看它的工作原理。 首先调用 iterator 方法, 产生一个新的 Iterator 对象, 进而控制整
个迭代过程, 这就是外部迭代。 迭代过程通过显式调用 Iterator 对象的 hasNext 和 next
方法完成迭代。 展开后的代码如例 3-2 所示, 图 3-1 展示了迭代过程中的方法调用。

1 int count = 0;
2 Iterator<Artist> iterator = allArtists.iterator();
3 while(iterator.hasNext()) {
4 Artist artist = iterator.next();
5 if (artist.isFrom("London")) {
6 count++;
7 }
8 }

内部迭代:

1 long count = allArtists.stream()
2 .filter(artist -> artist.isFrom("London"))
3 .count();

Stream 是用函数式编程方式在集合类上进行复杂操作的工具。

二)常用的流操作

惰性求值方法:只描述Steam,最终不产生新集合的方法。

及早求值方法:最终会从Steam产生值的方法。

1.collect(toList())

该方法由Steam的值生成一个列表,是一个及早求值的操作。

List<String> collected = Stream.of("a", "b", "c") //Stream.of方法使用一组初始值生成Stream。
.collect(Collectors.toList()); assertEquals(Arrays.asList("a", "b", "c"), collected); 

2.map

map可以将一个流中的值转换成一个新的流。

1 List<String> collected = Stream.of("a", "b", "hello")
2 .map(string -> string.toUpperCase()) //Lambda 表达式必须是 Function 接口的一实例
3 .collect(toList());
4 assertEquals(asList("A", "B", "HELLO"), collected);

3.filter

遍历数据并检查其中的元素。

1 List<String> beginningWithNumbers
2 = Stream.of("a", "1abc", "abc1")
3 .filter(value -> isDigit(value.charAt(0))) //必须是Predicate
4 .collect(toList());

4.flatmap

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

1 List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
2 .flatMap(numbers -> numbers.stream()) //必须是Function
3 .collect(toList());
4 assertEquals(asList(1, 2, 3, 4), together);

5.max和min方法

1 List<Track> tracks = asList(new Track("Bakai", 524),
2 new Track("Violets for Your Furs", 378),
3 new Track("Time Was", 451));
4 Track shortestTrack = tracks.stream()
5 .min(Comparator.comparing(track -> track.getLength()))
6 .get();
7 assertEquals(tracks.get(1), shortestTrack);

6.调用List或者Set的stream方法就可以得到Steam对象。

三)元素顺序

1)出现顺序

测试永远通过:

1 List<Integer> numbers = asList(1, 2, 3, 4);
2 List<Integer> sameOrder = numbers.stream()
3 .collect(toList());
4 assertEquals(numbers, sameOrder);

无法保证每次都通过:

1 Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
2 List<Integer> sameOrder = numbers.stream()
3 .collect(toList());
4 // 该断言有时会失败
5 assertEquals(asList(4, 3, 2, 1), sameOrder);

这会带来一些意想不到的结果, 比如使用并行流时, forEach 方法不能保证元素是
按顺序处理的。 如果需要保证按顺序处理, 应该使用
forEachOrdered 方法

四)收集器

一种通用的、 从流生成复杂值的结构。 只要将它传给 collect 方法, 所有
的流就都可以使用它了 。

1)转换为其他集合

如:toList, toSet, toCollection

2)转换成值

利用收集器让流生成一个值 ,如:

1 public Optional<Artist> biggestGroup(Stream<Artist> artists) {
2 Function<Artist,Long> getCount = artist -> artist.getMembers().count();
3 return artists.collect(maxBy(comparing(getCount)));
4 }

3)数据分块

分解成两个集合 ,收集器 partitioningBy, 它接受一个流, 并将其分成两部分 ,它使用 Predicate 对象判断一个元素应该属于哪个部分, 并根据布尔值返回一
个 Map 到列表。

1 public Map<Boolean, List<Artist>> bandsAndSolo(Stream<Artist> artists) {
2 return artists.collect(partitioningBy(artist -> artist.isSolo()));
3 }

4)数据分组

1 public Map<Artist, List<Album>> albumsByArtist(Stream<Album> albums) {
2 return albums.collect(groupingBy(album -> album.getMainMusician()));
3 }

5)字符串

以前的写法:

1 StringBuilder builder = new StringBuilder("[");
2 for (Artist artist : artists) {
3 if (builder.length() > 1)
4 builder.append(", ");
5 String name = artist.getName();
6 builder.append(name);
7 } b
8 uilder.append("]");
9 String result = builder.toString();

现在的写法:

1 String result =
2 artists.stream()
3 .map(Artist::getName)
4 .collect(Collectors.joining(", ", "[", "]"));

6)定制收集器

未完待续

三、重载和继承

一)重载

1)Lambda 表达式作为参数时, 其类型由它的目标类型推导得出, 推导过程遵循
如下规则:
1.如果只有一个可能的目标类型, 由相应函数接口里的参数类型推导得出;
2.如果有多个可能的目标类型, 由最具体的类型推导得出;
3.如果有多个可能的目标类型且最具体的类型不明确, 则需人为指定类型。

2)每个用作函数接口的接口都应该添加@FunctionalInterface注释,该注释会检查被注释接口是否符合函数接口的标准。

三)继承

1)默认方法

引入默认方法目的:接口的向后兼容(如果没有默认方法,我们在接口中定义一个新方法时......)。

1.无论函数接口,还是非函数接口都可以使用默认方法。

2.任何时候,默认方法与类中的方法产生冲突,优先选择类中的方法。

3.多重继承,类实现的多个接口中有方法签名相同的默认方法,此时编译器会报错:

1 class Musical Carriage
2 inherits unrelated defaults for rock() from types Carriage and Jukebox。

解决方法:重写:

1 public class MusicalCarriage
2 implements Carriage, Jukebox {
3 @Override
4 public String rock() {
5 return Carriage.super.rock();
6 }
7 }

三)与抽象类的区别

接口和抽象类之间还是存在明显的区别。 接口允许多重继承, 却没有成员变量; 抽象类可
以继承成员变量, 却不能多重继承。

四、接口的静态方法

如果一个方法有充分的语义原因和某个概念相关, 那么就应该将该方法和相关的类
或接口放在一起, 而不是放到另一个工具类中。 这有助于更好地组织代码, 阅读代码的人
也更容易找到相关方法。

时间: 2024-10-22 19:23:32

Java 8(一)函数式编程的相关文章

001 java为什么需要函数式编程

一 .概述 集合是我们java程序员每天都需要的工具,没有了集合,java程序员几乎不能干任何的事情,我们每天的工作也是在对集合进行不同的操作. 尽管集合的功能已经足够强大,但是当我们面对复杂的业务问题的时候,利用原始的集合操作就会变得让人恶心. 于是在java8之中出现了lambda和stream的API,为我们以一种更加优雅的方式使用集合. 没错,就是集合,当我们现在使用NOSQL,各种日志分析,等等的大数据操作的时候,我们不可能使用原始的数据库的sql操作帮助我们完成如排序,求和,分组等操

java自制简易函数式编程库初出炉

程序处理过程中的绝大多数运算,理想状况下都可以用简单优雅的数学语言加以描述. 而通常的代码多关注于这些逻辑的实现细节,臃肿而丑陋,所以产生了将抽象逻辑分离出来的想法. 是时候展现真正的技术了...一个简易的函数式编程库就这么诞生了... 作为函数式编程库,体现了以下特征: 1.以函数为基本复用单元的编程风格 2.用懒惰序列(对应于java中的Iterator)进行简单的数据交换 3.以函数.懒惰序列为核心的工具集 作为函数式编程库,未体现以下重要特征: 1.不可变数据结构(目前没有这种需求) 2

Scala:用于Java的轻量级函数式编程

Scala为Java开发提供了轻量级的代码选项,但是学习过程可能会很艰难.了解有关Scala的知识以及是否值得采用. 基于Java的语言通常涉及冗长的语法和特定于领域的语言,用于测试,解析和数值计算过程.这些事情可能是开发人员的祸根,因为成堆的重复代码需要开发人员花费更多的时间进行梳理才能发现错误. 作为一种通用的编程语言,Scala可以通过结合面向对象的样式和功能样式来帮助减轻这些问题.为了减轻语法复杂性,Scala还将命令性编程与功能性编程相融合,并且可以方便地使用其对庞大的Java库生态系

为什么函数式编程在Java中很危险?

摘要:函数式编程这个不温不火的语言由来已久.有人说,这一年它会很火,尽管它很难,这也正是你需要学习的理由.那么,为什么函数式编程在Java中很危险呢?也许这个疑问普遍存在于很多程序员的脑中,作者Elliotte对此发表了一些见解,我们一起来看看他是怎么说的. 在我的日常工作中,我身边的开发者大多是毕业于CS编程顶级院校比如MIT.CMU以及Chicago,他们初次涉及的语言是Haskell.Scheme及Lisp.他们认为函数式编程是一种自然的.直观的.美丽的且高效的编程样式.但奇怪的是,我和我

[Java 8] 函数式编程简介

思维方式的转变 以从一个城市集合中寻找是否存在Chicago为例: 习惯的方式 boolean found = false; for(String city : cities) { if(city.equals("Chicago")) { found = true; break; } } System.out.println("Found chicago?:" + found); 以上代码就是绝大多数开发人员在面对这个问题时的第一反应.它通过命令式风格(Impera

[连载]Swift开发入门(06)--- 函数式编程

??面向对象编程和函数式编程是目前最主流的两种编程范式,而关于这两种范式孰优孰劣的讨论一直都没有停止过.事实上,真正理解两种编程范式的程序员不会武断的说这二者孰优孰劣,因为任何编程语言都没有什么灵丹妙药让其使用者成为优秀的程序员.其实,像Java这样很经典的面向对象的编程语言,也能够看到函数式编程的影子,如果你使用过访问者模式.命令模式,如果你使用过接口回调,你实际上已经使用了函数式编程的理念,而且在新版本的Java中,已经开始支持Lambda表达式和函数式接口,这些都是Java为了支持函数式编

Java 中的函数式接口

概念 函数式接口在Java中是指:有且仅有一个抽象方法的接口. 函数式接口,即适用于函数式编程场景的接口.而Java中的函数式编程体现就是Lambda,所以函数式接口就是可 以适用于Lambda使用的接口.只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导. 备注:“语法糖”是指使用更加方便,但是原理不变的代码语法.例如在遍历集合时使用的for-each语法,其实 底层的实现原理仍然是迭代器,这便是“语法糖”.从应用层面来讲,Java中的Lambda可以被当做是匿名内

[Java] 函数式编程相关概念 - 笔记1

Java 8 引入了 lambda 表达式,以及函数式编程风格.在了解函数式编程过程中,做了些笔记,摘录于本文. 嵌套函数( Nested Function ) 1. 嵌套函数,是指在另一个函数里面定义的一个函数.外层的函数,这里简称为外层函数. 2. 函数的嵌套可以是多层嵌套.嵌套函数可以看到其全部的外层函数的非局部变量.在实际程序中,嵌套的层数一般很少.下面是一个三层嵌套的例子, innerOfInner 也可以访问在 outer 函数体重定义的变量 x . function outer()

[Java] 函数式编程相关概念 - 笔记2

匿名函数 ( Anonymous function ) 1. 匿名函数,是指没有被绑定到标识符的函数.根据字面意思,也可以理解为没有名字的函数. 2. 匿名函数通常: 1). 作为参数,被传递给高阶函数. 2). 作为高阶函数的返回值. 3. 在函数式编程语言,以及其他拥有头等函数的语言中,匿名函数比较普遍. 4. 在一些编程语言中,匿名函数被关键字 lambda 所标记.所以,匿名函数有也被称为Lambda 表达式 ( Lambda express ).例如,Python 就使用 lambda

paip.提升效率---filter map reduce 的java 函数式编程实现

#paip.提升效率---filter map reduce 的java 函数式编程实现 ======================================================== #----------------------index索引------------------++函数式编程的优点儿以及缺点++actual code 实际代码例如以下---filter 实现...--- map 实现..---reduce---调用 ##函数式编程的优点儿以及缺点------