Functional Programming in Java venkat

Functional Programming in Java venkat,第1张

文章目录
  • Functional Programming in Java venkat(3): Using Collections part1
    • Introduction
    • Using Collections
      • Iterating through a List
      • Transforming a List
    • 英文
    • 感想
    • 参考

Functional Programming in Java venkat(3): Using Collections part1 Introduction

这里是记录学习这本书 Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions 的读书笔记,如有侵权,请联系删除。

About the author

Venkat Subramaniam

Dr. Venkat Subramaniam, founder of Agile Developer, Inc., has trained and mentored thousands of software developers in the US, Canada, Europe, and Asia. Venkat helps his clients effectively apply and succeed with agile practices on their software projects. He is a frequent invited speaker at international software conferences and user groups. He’s author of .NET Gotchas (O’Reilly), coauthor of the 2007 Jolt Productivity award-winning book Practices of an Agile Developer (Pragmatic Bookshelf),

Using Collections

使用lambda 表达式来处理集合。

We often use collections of numbers, strings, and objects. They are so commonplace
that removing even a small amount of ceremony from coding collections
can reduce code clutter greatly. In this chapter we explore the use of
lambda expressions to manipulate collections. We use them to iterate collections,
transform them into new collections, extract elements from them, and
easily concatenate their elements.

学完这一章,我们处理collection的方式会变得很不一样。

After this chapter, our Java code to manipulate collections will never be the
same—it’ll be concise, expressive, elegant, and more extensible than ever
before.

Iterating through a List

It is a basic operation to iterate through a list

Iterating through a list is a basic operation on a collection, but over the years
it’s gone through a few significant changes. We’ll begin with the old and evolve
an example—enumerating a list of names—to the elegant style

创建一个immutable collection

We can easily create an immutable collection of a list of names with the following
code:

import java.util.List;
import java.util.Arrays;

public class Folks {
  public static final List<String> friends = 
    Arrays.asList("Brian", "Nate", "Neal", "Raju", "Sara", "Scott");

  public static final List<String> editors = 
    Arrays.asList("Brian", "Jackie", "John", "Mike");

public static final List<String> comrades =                      
    Arrays.asList("Kate", "Ken", "Nick", "Paula", "Zach");
}

下面是verbose的方式遍历

Here’s the habitual, but not so desirable, way to iterate and print each of the
elements.

for(int i = 0; i < friends.size(); i++) {
     System.out.println(friends.get(i));
}

这种external的遍历很容易出错,读代码的时候需要手动判断到底是i小于还是i小于等于,不易维护。

This style is verbose and error prone—we have to stop and wonder, is it i < or
i <=? This is useful only if we need to manipulate elements at a particular index in the collection, but even then, we can opt to use a functional style that favors immutability, as we’ll discuss soon.

Java同样提供for each的外部迭代器

Java also offers a construct that is a bit more civilized than the good old for
loop.

for(String name : friends) {
     System.out.println(name);
}

Under the hood this form of iteration uses the Iterator interface and calls into
its hasNext() and next() methods.

两种方法都是external iterators:混合了how to do和我们想要达到的目标。

Both these versions are external iterators, which mix how we do it with what
we’d like to achieve. We explicitly control the iteration with them, indicating
where to start and where to end; the second version does that under the hood
using the Iterator methods. With explicit control, the break and continue statements
can also help manage the iteration-control flow.

两种都是imperative。

The second construct has less ceremony than the first. Its style is better than
the first if we don’t intend to modify the collection at a particular index. Both
of these styles, however, are imperative and we can dispense with them in
modern Java.

我们更喜欢functional style的理由:for loop很难并行;这样的loops不是多态的;在设计层面,破坏了"Tell, don’t ask” principle

There are quite a few reasons to favor the change to the functional style:

• The for loops are inherently sequential and are quite difficult to parallelize.

• Such loops are non-polymorphic; we get exactly what we ask for. We
passed the collection to for instead of invoking a method (a polymorphic
operation) on the collection to perform the task.

• At the design level, the code fails the "Tell, don’t ask” principle. We ask
for a specific iteration to be performed instead of leaving the details of the
iteration to underlying libraries.

是时候转向函数式风格啦,只需要focus 在whats,而不用关心how: internal iterator

It’s time to trade in the old imperative style for the more elegant functional-style
version of internal iteration. With an internal iteration we willfully turn
over most of the hows to the underlying library so we can focus on the
essential whats. The underlying function will take care of managing the iteration.
Let’s use an internal iterator to enumerate the names.

Iterable接口:froEach方法接受一个类型为Consumer的参数。

The Iterable interface has been enhanced in Java Development Kit (JDK) 8 with
a special method named forEach(), which accepts a parameter of type Consumer.
As the name indicates, an instance of Consumer will consume, through its accept() method, what’s given to it. Let’s use the forEach() method using the all-too-familiar anonymous inner class syntax.

friends.forEach(new Consumer<String>() {
	public void accept(final String name) {
		System.out.println(name);
	}
});

对代码的解释:传一个Consumer(这个函数式接口,主要是接收参数,没有返回值)的匿名实例,然后forEach方法会对每一个元素调用accept方法

We invoked the forEach() on the friends collection and passed an anonymous
instance of Consumer to it. The forEach() method will invoke the accept() method
of the given Consumer for each element in the collection and let it do whatever
it wants with it. In this example we merely print the given value, which is the
name.

使用lambda表达式代替匿名内部类。

We changed just one thing: we traded in the old for loop for the new internal
iterator forEach(). As for the benefit, we went from telling it how to iterate to
focusing on what we want to do for each element. The bad news is the code
looks a lot more verbose—so much that it can drain away any excitement
about the new style of programming. Thankfully, we can fix that quickly; this
is where lambda expressions and the new compiler magic come in. Let’s make
one change again, replacing the anonymous inner class with a lambda
expression.

friends.forEach((final String name) -> System.out.println(name));

对代码进行解释:forEach是高阶函数,它接收一个lambda表达式或者函数block来对每一个元素进行 *** 作。

所有的执行细节由underlying library控制,可以惰性求值,可以按照任意顺序来做,也可以利用并行加速等,只要可以使用。

That’s a lot better. We look at less code, but watch closely to see what’s in
there. The forEach() is a higher-order function that accepts a lambda expression
or block of code to execute in the context of each element in the list. The
variable name is bound to each element of the collection during the call. The
underlying library takes control of how the lambda expressions are evaluated.
It can decide to perform them lazily, in any order, and exploit parallelism as
it sees fit.

This version produces the same output as the previous versions.

内部迭代器很declarative

The internal-iterator version is more concise than the other ones. However,
when we use it we’re able to focus our attention on what we want to achieve
for each element rather than how to sequence through the iteration—it’s
declarative.

这个内部迭代器版本也有弊端:就是不能把迭代break掉。

This version has a limitation, however. Once the forEach method starts, unlike
in the other two versions, we can’t break out of the iteration. (There are
facilities to handle this limitation.) As a consequence, this style is useful in
the common case where we want to process each element in a collection.
Later we’ll see alternate functions that give us control over the path of iteration.

语法可以更加简略

The standard syntax for lambda expressions expects the parameters to be
enclosed in parentheses, with the type information provided and comma
separated. The Java compiler also offers some lenience and can infer the
types. Leaving out the type is convenient, requires less effort, and is less
noisy. Here’s the previous code without the type information.

friends.forEach((name) -> System.out.println(name));

编译器有很大功劳,下面是编译器做的事情:Java 编译器根据上下文确定 name 参数是 String 类型。 它查找被调用方法的签名,在本例中为 forEach(),并分析它参数的功能接口。

In this case, the Java compiler determines the name parameter’s a String type,
based on the context. It looks up the signature of the called method, forEach()
in this example, and analyzes the functional interface it takes as a parameter.
It then looks at that interface’s abstract method to determine the expected
number of parameters and their types. We can also use type inference if a
lambda expression takes multiple parameters, but in that case we must leave
out the type information for all the parameters; we have to specify the type
for none or for all of the parameters in a lambda expression.

java编译器对单变量的lambda表达式特殊处理:可以不用加参数的括号

The Java compiler treats single-parameter lambda expressions as special: we
can leave off the parentheses around the parameter if the parameter’s type
is inferred.

friends.forEach(name -> System.out.println(name));

可使用final来方式modifying,否则更改参数就有点in poor taste

There’s one caveat: inferred parameters are non-final. In the previous example,
where we explicitly specified the type, we also marked the parameter as final.
This prevents us from modifying the parameter within the lambda expression.
In general, modifying parameters is in poor taste and leads to errors, so
marking them final is a good practice. Unfortunately, when we favor type
inference we have to practice extra discipline not to modify the parameter, as
the compiler is not there to protect us.

还可以再精简一点

We have come a long way with this example and reduced the code quite a bit.
But there’s more. Let’s take one last step to tease out another ounce of conciseness.

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

这里使用方法引用,把函数体用方法名替换掉了。

In the preceding code we used a method reference. Java lets us simply replace
the body of code with the method name of our choice. We will dig into this
further in the next section, but for now let’s reflect on the wise words of
Antoine de Saint-Exupéry: “Perfection is achieved not when there is nothing
more to add, but when there is nothing left to take away.”

到这里就学习完成了遍历。

Lambda expressions helped us concisely iterate over a collection. Next we’ll
cover how they help remove mutability and make the code even more concise
when transforming collections.

全部的代码在此,我们拆开来看

import java.util.List;
import java.util.Arrays;
import static fpij.Folks.friends;
import java.util.function.Consumer;

public class Iteration {
  public static void main(final String[] args) {
    for(int i = 0; i < friends.size(); i++) {
      System.out.println(friends.get(i));
    }

    for(String name : friends) {
      System.out.println(name);
    }

    System.out.println("//" + "START:INTERNAL_FOR_EACH_OUTPUT");

    friends.forEach(new Consumer<String>() {
      public void accept(final String name) {
        System.out.println(name);
      }
    });

    System.out.println("//" + "END:INTERNAL_FOR_EACH_OUTPUT");

    System.out.println("//" + "START:INTERNAL_OUTPUT");
    friends.forEach((final String name) -> System.out.println(name));
    System.out.println("//" + "END:INTERNAL_OUTPUT");

    friends.forEach((name) -> System.out.println(name));

    friends.forEach(name -> System.out.println(name));

    friends.forEach(System.out::println);
  }
}

下面是vs code截图

Transforming a List

举例:把名字全部转换为大写

Manipulating a collection to produce another result is as easy as iterating
through the elements of a collection. Suppose we’re asked to convert a list of
names to all capital letters. Let’s explore some options to achieve this.

java 的String是immutable的,所以它的实例不能改变。

Java’s String is immutable, so instances can’t be changed. We could create
new strings in all caps and replace the appropriate elements in the collection.
However, the original collection would be lost; also, if the original list is
immutable, like it is when created with Arrays.asList(), then the list can’t change.
Another downside is it would be hard to parallelize the computations.

还是需要创建一个新的list,里面的字母全是大写。

Creating a new list that has the elements in all caps is a better option.

函数式编程在这里提供了不错的性能。

That suggestion may seem quite naïve at first; performance is an obvious
concern we all share. Surprisingly, the functional approach often yields better
performance than the imperative approach, as we’ll see in Performance Concerns,
on page 153.

先用一个imperative的方式来做

Let’s start by creating a new collection of uppercase names from the given
collection.

final List<String> uppercaseNames = new ArrayList<String>();
for(String name : friends) {
	uppercaseNames.add(name.toUpperCase());
}

下面是用forEach这个高阶函数来做

In this imperative style, we created an empty list then populated it with all-uppercase
names, one element at a time, while iterating through the original list. As a first step to move toward a functional style, we could use the internal
iterator forEach() method from Iterating through a List, on page 19, to replace
the for loop, as we see next.

final List<String> uppercaseNames = new ArrayList<String>();
friends.forEach(name -> uppercaseNames.add(name.toUpperCase()));

System.out.println(uppercaseNames);

使用了内部迭代器。

We used the internal iterator, but that still required the empty list and the
effort to add elements to it. We can do a lot better.

Using Lambda Expressions

Stream接口中的map方法闪亮登场,可以避免mutability而且让代码更concise。

The map() method of a new Stream interface can help us avoid mutability and
make the code concise. A Stream is much like an iterator on a collection of
objects and provides some nice fluent functions. Using the methods of this
interface, we can compose a sequence of calls so that the code reads and
flows in the same way we’d state problems, making it easier to read.

map方法可以把输入序列转化为输出序列。

The Stream’s map() method can map or transform a sequence of input to a
sequence of output—that fits quite well for the task at hand.

 friends.stream()
     .map(name -> name.toUpperCase())
     .forEach(name -> System.out.print(name + " "));     

stream方法把collection封装为Stream的实例。map方法对Stream中的每个元素调用labmda表达式,来进行处理。

map方法收集执行lambda表达式的结果,并且返回the result collection。最后一部是使用forEach方法来打印出来结果中的每个元素。

The method stream() is available on all collections in JDK 8 and it wraps the
collection into an instance of Stream. The map() method applies the given
lambda expression or block of code, within the parenthesis, on each element
in the Stream. The map() method is quite unlike the forEach() method, which
simply runs the block in the context of each element in the collection. In
addition, the map() method collects the result of running the lambda expression
and returns the result collection. Finally, we print the elements in this result
using the forEach() method.

map方法把输入序列转变为一个新的输出集合,两者的元素数量是一样的。但是呢,输入序列的数据类型不一定和输出序列相同。

The map() method is quite useful to map or transform an input collection into
a new output collection. This method will ensure that the same number of
elements exists in the input and the output sequence. However, element types
in the input don’t have to match the element types in the output collection.
In this example, both the input and the output are a collection of strings. We could have passed to the map() method a block of code that returned, for example, the number of characters in a given name. In this case, the input
would still be a sequence of strings, but the output would be a sequence of
numbers, as in the next example.

这个例子输入序列是string名字序列,map得到的是int名字长度序列

friends.stream()
    .map(name -> name.length())
    .forEach(count -> System.out.print(count + " "));

使用lambda表达式没有explicit mutation;不需要初始化空的collection或者垃圾变量。这些东西完全被库函数cover掉了,更安全了。(它悄悄地退到了底层实现的阴影中)

The versions using the lambda expressions have no explicit mutation; they’re
concise. These versions also didn’t need any initial empty collection or garbage
variable; it quietly receded into the shadows of the underlying implementation.

Using Method References

可以使用方法引用来让代码更加concise。java编译器期待一个函数式接口的实现要么是lambda表达式,要么是方法的引用。

We can nudge the code to be just a bit more concise by using a feature called
method reference. The Java compiler will take either a lambda expression or
a reference to a method where an implementation of a functional interface is
expected. With this feature, a short String::toUpperCase can replace name ->
name.toUpperCase(), like so:

 friends.stream()
     .map(String::toUpperCase)
     .forEach(name -> System.out.println(name));

java编译器知道去调用String中的toUpperCase方法,用在综合方法传进来的参数上。这里参数是隐式的看不到。

Java knows to invoke the the String class’s given method toUpperCase() on the
parameter passed in to the synthesized method—the implementation of the
functional interface’s abstract method. That parameter reference is implicit
here. In simple situations like the previous example, we can substitute method
references for lambda expressions; see When should we use method references?,
on page 26.

方法引用还有其他用法。

In the preceding example, the method reference was for an instance method.
Method references can also refer to static methods and methods that take
parameters. We’ll see examples of these later.

java的函数式编程中,我们使用lambda表达式比方法引用要多。方法引用在lambda表达式很短的时候很好用,只要直接调用实例方法或者类方法。可以直接使用方法名。

但是有一种情况不能使用method reference: However, we can’t use this convenience if we have to manipulate parameters before sending them as arguments or tinker with the call’s results before returning them.


来源:Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions 1st Edition

后面学习从集合中选择元素 pick an element from a collection.

Lambda expressions helped us enumerate a collection and transform it into
a new collection. They can also help us concisely pick an element from a
collection, as we’ll see next

完整代码如下:

package fpij;

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import static fpij.Folks.friends;

public class Transform {
  public static void main(final String[] args) {
  {
    final List<String> uppercaseNames = new ArrayList<String>();
    
    for(String name : friends) {
      uppercaseNames.add(name.toUpperCase());
    }

    System.out.println(uppercaseNames);
  }
  {
    final List<String> uppercaseNames = new ArrayList<String>();
    friends.forEach(name -> uppercaseNames.add(name.toUpperCase()));
    System.out.println(uppercaseNames);
  }

/*
      friends.stream()
             .map(name -> name.toUpperCase());
*/

System.out.println("//" + "START:TRANSFORM_OUTPUT");

    friends.stream()
           .map(name -> name.toUpperCase())
           .forEach(name -> System.out.print(name + " "));     
    System.out.println();

System.out.println("//" + "END:TRANSFORM_OUTPUT");

System.out.println("//" + "START:NUMBER_OUTPUT");

    friends.stream()
           .map(name -> name.length())
           .forEach(count -> System.out.print(count + " "));

System.out.println();
System.out.println("//" + "END:NUMBER_OUTPUT");

/*
    friends.stream()
           .map(String::toUpperCase);
*/

    friends.stream()
           .map(String::toUpperCase)
           .forEach(name -> System.out.println(name));
  }
}

运行结果

 [BRIAN, NATE, NEAL, RAJU, SARA, SCOTT]
[BRIAN, NATE, NEAL, RAJU, SARA, SCOTT]
//START:TRANSFORM_OUTPUT
BRIAN NATE NEAL RAJU SARA SCOTT 
//END:TRANSFORM_OUTPUT
//START:NUMBER_OUTPUT
5 4 4 4 4 5 
//END:NUMBER_OUTPUT
BRIAN
NATE
NEAL
RAJU
SARA
SCOTT
英文

caveat:发音 开via, 警告 = warning

nudge: 轻推

我们可以通过使用称为方法引用的功能使代码更加简洁。

We can nudge the code to be just a bit more concise by using a feature called
method reference.

感想

这章,对map()函数的用法有了更深的了解,原来map也是对输入序列的每个元素都进行处理。只不过,map()不要求输出序列和输出序列的元素类型相同。

参考

Functional Programming in Java: Harnessing the Power Of Java 8 Lambda Expressions 1st Edition:https://www.amazon.com/Functional-Programming-Java-Harnessing-Expressions/dp/1937785467

source code: https://pragprog.com/titles/vsjava8/functional-programming-in-java/

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/794643.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-06
下一篇 2022-05-06

发表评论

登录后才能评论

评论列表(0条)

保存