IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Functional Programming in Java venkat(6) Strings Comparators and Filters part1 -> 正文阅读

[Java知识库]Functional Programming in Java venkat(6) Strings Comparators and Filters part1

Functional Programming in Java venkat(6): Strings Comparators and Filters part1

Introduction

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

image-20220501161559008

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),

Strings Comparators and Filters

Java JDK现在进化到很容易就能支持函数式风格。

The Java Development Kit (JDK) has evolved to include convenience methods
that promote the functional style. When using familiar classes and interfaces
from the library—String, for example—we need to look for opportunities to use
these newer functions in place of the old style. Also, anywhere we used an
anonymous inner class with just one method, we can now use lambda
expressions to reduce clutter and ceremony.

这一章我们学习使用lambda表达式和方法引用来遍历String, 实现Comparators,操作文件和目录等。

In this chapter we’ll use lambda expressions and method references to iterate
over a String, to implement Comparators, to list files in a directory, and to observe
file and directory changes. Quite a few methods introduced in the previous
chapter will appear here again to help with the tasks at hand. Techniques
you pick up along the way will help turn long, mundane tasks into concise
code snippets you can quickly write and easily maintain.

Iterating a String

chars方法来自CharSequence 接口,可以流畅地遍历String的characters。这是一个内部迭代器

The chars() method is a new one in the String class from the CharSequence interface.
It’s useful for fluently iterating over the String’s characters. We can use this
convenient internal iterator to apply an operation on the individual characters
that make up the string. Let’s use it in an example to process a string. Along
the way we’ll discuss a few more handy ways to use method references.

    final String str = "w00t";

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

chars返回一个Stream,然后使用forEach来处理每个元素。

The chars() method returns a Stream over which we can iterate, using the forEach()
internal iterator. We get direct read access to the characters in the String
within the iterator. Here’s the result when we iterate and print each character.

119
48
48
116

结果是字母对应的ASCII码,不是我们希望的字母。这是因为chars方法返回的是整数流。

The result is not quite what we’d expect. Instead of seeing characters we’re
seeing numbers. That’s because the chars() method returns a stream of Integers,
representing the characters instead of a stream of Characters. Let’s explore the
application programming interface a bit further before we fix the output.

这里先把上面的println优化一下,使用方法调用

In the previous code we created a lambda expression in the argument list for
the forEach() method. The implementation was a simple call where we routed
the parameter directly as an argument to the println() method. Since this is a
trivial operation, we can eliminate this mundane code with the help of the
Java compiler. We can rely on it to do this parameter routing for us, using a
method reference like we did in Using Method References, on page 25.

方法调用的使用:这里我们调用静态引用System.out,然后后面跟着双冒号

We already saw how to create a method reference for an instance method.
For example, for the call name.toUpperCase(), the method reference is String::toUpperCase.
In this example, however, we have a call on a static reference System.out.
We can use either a class name or an expression to the left of the double
colon in method references. Using this flexibility, it’s quite easy to provide a
reference to the println() method, as we see next.

    str.chars()
       .forEach(System.out::println);

这里的对实例方法的方法引用,是基于一个表达式:通过静态引用System.out接触到的PrintStream的实例。

具体的过程是,forEach里面的每个参数(比如parameter),会被当作是引用方法的参数:System.out.println(parameter);

这面这一段原文写得非常通透!!!建议读者仔细阅读。

In this example we see the smarts of the Java compiler for parameter routing.
Recall that lambda expressions and method references may stand in where
implementations of functional interfaces are expected, and the Java compiler
synthesizes the appropriate method in place (see A Little Sugar to Sweeten,
on page 15). In the earlier method reference we used, String::toUppercase, the
parameter to the synthesized method turned into the target of the method
call, like so: parameter.toUppercase();. That’s because the method reference is
based on a class name (String). In this example, the method reference, again
to an instance method, is based on an expression—an instance of PrintStream
accessed through the static reference System.out. Since we already provided a
target for the method, the Java compiler decided to use the parameter of the
synthesized method as an argument to the referenced method, like so: System.
out.println(parameter);. Sweet.

再深挖一点方法引用。

The code with the method reference is quite concise, but we have to dig into
it a bit more to understand what’s going on. Once we get used to method
references, our brains will know to autoparse these.

修复上面输出数字的bug,通过写a convenience method

In this example, although the code is concise, the output is not satisfactory.
We want to see characters and not numbers in their place. To fix that, let’s
write a convenience method that takes an int and prints it as a character.

  private static void printChar(int aChar) {
    System.out.println((char)(aChar));
  }

然后调用这个自己写的static方法来输出合适的值,下面IterateString是我们的类,是上面printChar静态方法所在的类,所以我们可以使用方法引用的方式来写。

We can use a reference to this convenience method to fix the output.

str.chars().forEach(IterateString::printChar);

我们可以继续使用chars的int结果,只是在forEach输出的时候,输出为char类型。

We can continue to use the result of chars() as an int, and when it’s time to
print we can convert it to a character. The output of this version will display
characters.

w
0
0
t

另一种方法:我们直接把int转化为char。

如果我们想从一开始就处理字符而不是 int,我们可以在调用 chars() 方法之后立即将 int 转换为字符

If we want to process characters and not int from the start, we can convert
the ints to characters right after the call to the chars() method, like so:

str.chars()
    .mapToObj(ch -> Character.valueOf((char)ch))
    .forEach(System.out::println);

其实在chars返回一个流之后,我们可以使用之前学到的任何方法,比如map,filter和reduce等等。

笔者去查看了以下chars()方法的源码,大概的实现如下

可以看到chars方法返回的是IntStream, 具体实现细节需要实现一个类CharIterator, 用到hasNext等方法的实现

/**
     * Returns a stream of {@code int} zero-extending the {@code char} values
     * from this sequence.  Any char which maps to a <a
     * href="{@docRoot}/java.base/java/lang/Character.html#unicode">surrogate code
     * point</a> is passed through uninterpreted.
     *
     * <p>The stream binds to this sequence when the terminal stream operation
     * commences (specifically, for mutable sequences the spliterator for the
     * stream is <a href="../util/Spliterator.html#binding"><em>late-binding</em></a>).
     * If the sequence is modified during that operation then the result is
     * undefined.
     *
     * @return an IntStream of char values from this sequence
     * @since 1.8
     */
    public default IntStream chars() {
        class CharIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                if (hasNext()) {
                    return charAt(cur++);
                } else {
                    throw new NoSuchElementException();
                }
            }

            @Override
            public void forEachRemaining(IntConsumer block) {
                for (; cur < length(); cur++) {
                    block.accept(charAt(cur));
                }
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliterator(
                        new CharIterator(),
                        length(),
                        Spliterator.ORDERED),
                Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
                false);
    }

下面使用filter试一下

We used the internal iterator on the Stream that the chars() method returned,
but we’re not limited to that method. Once we get a Stream we can use any
methods available on it, like map(), filter(), reduce(), and so on, to process the
characters in the string. For example, we can filter out only digits from the
string, like so:

str.chars()
    .filter(ch -> Character.isDigit(ch))
    .forEach(ch -> printChar(ch));

因为我们的str是

final String str = "w00t";

所以过滤出来的数字是00

然后对上面的filter和forEach重构,使用方法引用

Once again, instead of the lambda expressions we passed to the filter() method
and the forEach() method, we can use references to the respective methods.

str.chars()
    .filter(Character::isDigit)
    .forEach(IterateString::printChar);

方法引用帮助我们拿走了无聊的参数路由。

The method references here helped remove the mundane parameter routing.
In addition, in this example we see yet another variation of method references
compared to the previous two instances where we used them. When we first
saw method references, we created one for an instance method. Later we
created one for a call on a static reference. Now we’re creating a method reference
for a static method—method references seem to keep on giving.

方法引用的两种方式

实例方法:是参数调用这个方法,比如 parameter.toUppercase()

静态方法(类方法):是方法调用这个参数,比如 Character.isDigit(parameter)

The one for an instance method and a static method look the same structurally:
for example, String::toUppercase and Character::isDigit. To decide how to route the
parameter, the Java compiler will check whether the method is an instance
method or a static method. If it’s an instance method, then synthesized
method’s parameter becomes the call’s target, like in parameter.toUppercase(); (the
exception to this rule is if the target is already specified like in System.out::println).
On the other hand, if the method is static, then the parameter to the synthesized
method is routed as an argument to this method, like in Character.isDigit(
parameter);. See Appendix 2, Syntax Overview, on page 159, for a listing of
method-reference variations and their syntax.

尽管参数路由很方便,但是也有意外情况,比如方法冲突和结果不确定。

这种情况下,就返回去使用lambda表达式。

While this parameter routing is quite convenient, there is one caveat—method
collisions and the resulting ambiguity. If there’s both a matching instance
method and a static method, we’ll get a compilation error due to the reference’s
ambiguity. For example, if we write Double::toString to convert an instance of
Double to a String, the compiler would get confused whether to use the public
String toString() instance method or the static method public static String toString(double
value), both from the Double class. If we run into this, no sweat; we simply
switch back to using the appropriate lambda-expression version to move on.

我们慢慢地就熟悉函数式风格了。

Once we get used to the functional style, we can switch between the lambda
expressions and the more concise method references, based on our comfort
level.

下一节学the enhancements to the Comparator interface

We used a new method in Java 8 to easily iterate over characters. Next we’ll
explore the enhancements to the Comparator interface.

Implementing the Comparator Interface

Comparator接口在java8中变成了一个函数式接口,我们可以使用流畅的语法来实现它。

The Comparator interface is used in hundreds of places in the JDK library, from
searching operations to sorting, reversing, and so on. In Java 8 this has
turned into a functional interface; the benefit is that we can use charmingly
fluent syntax to implement comparators.

我们再也不用创建匿名函数类了,少写很多代码。

Let’s create a few different implementations of the Comparator to understand
the influence of the new style. Our fingers will thank us for all the keystrokes
saved by not having to create anonymous inner classes.

Sorting with a Comparator

例子:创建一些人,然后进行一个比较操作。

We’ll build an example to sort a list of people using a few different points of
comparisons. Let’s first create the Person JavaBean.

package fpij;

public class Person {
  private final String name;
  private final int age;
  
  public Person(final String theName, final int theAge) {
    name = theName;
    age = theAge;
  } 
  
  public String getName() { return name; }
  public int getAge() { return age; }
  
  public int ageDifference(final Person other) {
    return age - other.age;
  }
  
  public String toString() {
    return String.format("%s - %d", name, age);
  }
}

这里补充一下什么是JavaBean: https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680

简而言之,就是通过getter和setter来获取和设置字段的类。

在Java中,有很多class的定义都符合这样的规范:

  • 若干private实例字段;
  • 通过public方法来读写实例字段。

如果读写方法符合以下这种命名规范:

// 读方法:
public Type getXyz()
// 写方法:
public void setXyz(Type value)

那么这种class被称为JavaBean

JavaBean是一种符合命名规范的class,它通过gettersetter来定义属性;

属性是一种通用的叫法,并非Java语法规定;

可以利用IDE快速生成gettersetter

使用Introspector.getBeanInfo()可以获取属性列表。

为了可扩展,我们不在Person类中实现Comparable interface

We could implement the Comparable interface on the Person class, but that’d
limit us to one particular comparison. We would want to compare on different
things—on name, age, or a combination of fields, for example. To get this
flexibility, we’ll create the code for different comparisons just when we need
them, with the help of the Comparator interface.

创建一个people的列表

Let’s create a list of people to work with, folks with different names and ages.

final List<Person> people = Arrays.asList(
    new Person("John", 20),
    new Person("Sara", 21),
    new Person("Jane", 21),
    new Person("Greg", 35));

使用lambda表达式来做

We could sort the people by their names or the ages and in ascending or
descending order. In the habitual way to achieve this we would implement
the Comparator interface using anonymous inner classes. But the essence here

is the code for the comparison logic, and anything else we write would be
pure ceremony. We can boil this down to its essence using lambda expressions.

sort方法不好,这是一个void类型的方法,会修改源List的内容,labor intensive,我们使用Stream。

Let’s first sort the people in the list in ascending order by age.
Since we have a List, the obvious choice is the sort() method on the List. There
are downsides to using this method, however. That’s a void method, which
means the list will be mutated when we call it. To preserve the original list,
we’d have to make a copy and then invoke the sort() method on the copy; that’s
quite labor intensive. Instead we’ll seek the help of the Stream.

使用Stream,很方便地调用sorted方法,它会返回一个新的List。

We can get a Stream from the List and conveniently call the sorted() method on
it. Rather than messing with the given collection, it will return a sorted collection.
We can nicely configure the Comparator parameter when calling this
method.

List<Person> ascendingAge = 
  people.stream()
    .sorted((person1, person2) -> person1.ageDifference(person2))
    .collect(toList());
printPeople("Sorted in ascending order by age: ", ascendingAge);

这里再放一遍ageDifference方法

public int ageDifference(final Person other) {
    return age - other.age;
  }

使用stream方法把List转变为Stream,然后在流上调用sorted方法。sorted把Comparator 作为参数。排完序之后,使用collect方法将其变成List。collect方法是一个reducer,可以把结果变成想要的数据类型。 toList方法是Collectors类中的一个静态方法。

We first transformed the given List of people to a Stream using the stream() method.
We then invoked the sorted() method on it. This method takes a Comparator as
its parameter. Since Comparator is a functional interface, we conveniently passed
in a lambda expression. Finally we invoked the collect() method and asked it
to put the result into a List. Recall that the collect() method is a reducer that
will help to target the members of the transformed iteration into a desirable
type or format. The toList() is a static method on the Collectors convenience class.

上面笔者有个疑问,就是比较器是怎么工作的?下面就是Venkat的回答

Comparator’s compareTo()抽象方法接受两个参数(需要比较的对象),然后返回一个int值。

Comparator’s compareTo() abstract method takes two parameters, the objects to be
compared, and returns an int result. To comply with this, our lambda
expression takes two parameters, two instances of Person, with their types
inferred by the Java compiler. We return an int indicating whether the objects
are equal.

这里使用的是年龄,如果两个年龄相等,lambda表达式返回0。否则的话,如果返回的是负数,表示第一个人比较年轻;返回的是正数,表示第一个人年龄更大。

Since we want to sort by the age property, we compare the two given people’s
ages and return the difference. If they’re the same age, our lambda expression
will return a 0 to indicate they’re equal. Otherwise, it will indicate the first
person is younger by returning a negative number or older by returning a
positive number for the age difference.

看了一下Stream接口源码,里面的sorted方法,有序的streams,排序是稳定的;未有序的streams,不保证稳定。

 /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the provided {@code Comparator}.
     *
     * <p>For ordered streams, the sort is stable.  For unordered streams, no
     * stability guarantees are made.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to be used to compare stream elements
     * @return the new stream
     */
    Stream<T> sorted(Comparator<? super T> comparator);

其实sorted比较器,笔者查了一下:https://stackoverflow.com/questions/2839137/how-to-use-comparator-in-java-to-sort

如果使用升序排列:第一个参数减去第二个参数

image-20220505231303854

sorted方法也会遍历列表中的每个元素

The sorted() method will iterate over each element in the target collection (people
in this example) and apply the given Comparator (a lambda expression in this

case) to decide the logical ordering of the elements. The execution mechanism
of sorted() is much like the reduce() method we saw earlier. The reduce() method
trickles the list down to one value. The sorted() method, on the other hand,
uses the result of the comparison to perform the ordering.

现在写打印结果的方法

Once we sort the instances we want to print the values, so we invoke a convenience
method printPeople(); let’s write that method next.

  public static void printPeople(
    final String message, final List<Person> people) {
      
    System.out.println(message);
    people.forEach(System.out::println);
  }

打印结果

In this method we print a message and iterate over the given collection,
printing each of the instances.
Let’s call the sorted() method, and the people in the list will be printed in
ascending order by age.

Sorted in ascending order by age and name: 
John - 20
Jane - 21
Sara - 21
Greg - 35

重新看sorted方法

Let’s revisit the call to the sorted() method and make one more improvement
to it.

.sorted((person1, person2) -> person1.ageDifference(person2))

lambda表达式route两个参数,第一个参数作为ageDifference方法的target,第二个参数作为它的参数。

In the lambda expression we’re passing to the sorted() method, we’re simply
routing the two parameters—the first parameter as the target to the ageDifference()
method and the second as its argument. Rather than writing this code,
we can ask the Java compiler to do the routing again, using a method reference.

我们想让java编译器来做自动参数routing。

The parameter routing we want here is a bit different from the ones we saw
earlier. So far we’ve seen a parameter being used as a target in one case and
as an argument in another case. In the current situation, however, we have
two parameters and we want those to be split, the first to be used as a target
to the method and the second as an argument. No worries. The Java compiler
gives us a friendly nod: “I can take care of that for you.”

我们使用方法引用这里。

Let’s replace the lambda expression in the previous call to the sorted() method
with a short and sweet reference to the ageDifference() method.

    List<Person> ascendingAge = 
      people.stream()
            .sorted(Person::ageDifference)
            .collect(toList());

    printPeople("Sorted in ascending order by age: ", ascendingAge);

有两个参数的方法引用,一定要确保第一个参数是,被引用的方法的对象,然后后面的参数们是这个方法的参数。

The code is fantastically concise, thanks to the method-reference convenience
the Java compiler offers. The compiler took the parameters, the two person
instances being compared, and made the first the ageDifference() method’s target
and the second the parameter. Rather than explicitly connecting these, we
let the compiler work a little extra for us. When using this conciseness, we
must be careful to ensure that the first parameter is really the intended target
of the method referenced and the remaining parameters are its arguments

Reusing a Comparator

刚才上面是按照年龄升序排序,现在来降序排列。

We got the people sorted in ascending order by age quite easily, and sorting
them in descending order is just as easy. Let’s give that a shot.

这里的区别是我们调换了lambda函数体中的参数1和参数2,这样就是降序。

We called the sorted() method and passed a lambda expression that conforms
to the Comparator interface, much like the previous time. The only difference is
the implementation of the lambda expression—we switched the people in the
age comparison. The result should be a sort by descending order of their ages.

    printPeople("Sorted in descending order by age: ",
      people.stream()
            .sorted((person1, person2) -> person2.ageDifference(person1))
            .collect(toList()));
    System.out.println("//" + "END:AGE_DESCEND_OUTPUT");

上面的lambda表达式可以产生正确的结果,但是我们不能把它转化为方法引用,为什么?

因为它不遵从第一个参数是调用方法的目标,第二个参数是方法的参数。

下面我们修复它。

Changing the logic for our comparison was effortless. We can’t quite refactor
this version to use the method reference, though, because the parameter
order here does not follow the parameter-routing conventions for method
reference; the first parameter is not used as a target to the method, but rather
as its argument. There’s a way to fix that, and in the process remove a
duplication of effort that crept in. Let’s see how.

其实,这里升序排列和降序排列我们写了两份代码,重复了,不符合DRY原则。

我们如果想要逆序排列,只需要对Comparator调用reversed方法。

Comparator<Person> compareAscending = 
  (person1, person2) -> person1.ageDifference(person2);
Comparator<Person> compareDescending = compareAscending.reversed();

Earlier we created two lambda expressions: one to order the ages of two people
in ascending order and the other to do it in descending order. In so doing, we
duplicated the logic and the effort, and violated the DRY principle.1 If all we
want is a reverse of the comparison, the JDK has us covered with a reversed()

method on the Comparator, available as a special method called default. We’ll
discuss default methods later in the book, but here we’ll use the new reversed()
method to remove the duplication and use method references.

reversed方法干了什么呢? Under the hood the reversed() creates a comparator that swaps its parameters’ order of comparison.

We first created a Comparator, compareAscending, to compare the age of the people
in ascending order using the lambda expression syntax. To reverse the order
of comparison instead of duplicating the effort, we can simply call reversed()
on the first Comparator to get another Comparator with the comparison orders in
reverse. Under the hood the reversed() creates a comparator that swaps its
parameters’ order of comparison. This makes the reversed() method a higherorder
method—this function creates and returns another functional expression
with no side effect. Let’s use these two comparators in the code, but we’ll use
method references to them.

    Comparator<Person> compareAscending = 
      (person1, person2) -> person1.ageDifference(person2);
    Comparator<Person> compareDescending = compareAscending.reversed();

    printPeople("Sorted in ascending order by age: ",
      people.stream()
            .sorted(compareAscending)
            .collect(toList())
    );
    printPeople("Sorted in descending order by age: ",
      people.stream()
            .sorted(compareDescending)
            .collect(toList())
    );

java 8 提供的特性

It’s becoming clear how the new features in Java 8 can greatly reduce code
complexity and duplication of effort, but to get all the benefits we have to
explore the seemingly endless possibilities the JDK offers.

我们也可以对姓名排序

We’ve been sorting by age, but we could sort by name quite easily, as well.
Let’s sort in ascending alphabetical order by name; again, only the logic
within the lambda expression needs to change.

    printPeople("Sorted in ascending order by name: ",
      people.stream()
            .sorted((person1, person2) -> 
               person1.getName().compareTo(person2.getName()))
            .collect(toList()));

而且,我们可以同时基于姓名和年龄排序。

So far our comparisons have worked on either the age or the name property.
We can make the logic in the lambda expression more intelligent. For example,
we could sort based on both name and age.

选出最年轻的人,使用min方法。

Let’s pick the youngest person in the list. We could find the first person after
we’ve sorted by age in ascending order. But we don’t need to go that far; the
Stream has us covered with a min() method. This method also accepts a Comparator
but returns the smallest object in the order. Let’s use that method.

    people.stream()
          .min(Person::ageDifference)
          .ifPresent(youngest -> System.out.println("Youngest: " + youngest));

我们在min方法中调用方法引用,从低到高排序(ageDifference内部返回 第一个参数 减去 第二个参数)

We use the reference for the ageDifference() method in the call to the min() method.
The min() method returns an Optional because the list may be empty and
therefore there may not be a youngest person. We then print the details of
the youngest person that we get access to from the Optional using its ifPresent()
method. Let’s look at the output.

Youngest: John - 20

We can as easily find the oldest person in the list. Simply pass that method
reference to a max() method.

    System.out.println("//" + "START:ELDEST_OUTPUT");
    people.stream()
          .max(Person::ageDifference)
          .ifPresent(eldest -> System.out.println("Eldest: " + eldest));

比较很fluent,lambda表达式和方法引用的功劳。

We saw how lambda expressions and method references make implementing
comparators concise and easy. For its part, the JDK has evolved with a few
convenience methods added to the Comparator interface to make comparisons
more fluent, as we’ll see next.

Multiple and Fluent Comparisons

看一看Comparator 中的新convenience methods

Let’s look at the new convenience methods added to the Comparator interface
and use them to compare with ease based on multiple properties.

例子:还是通过名字排序

We’ll continue with the example from the previous section. To sort people by
their name we used this:

    people.stream()
          .sorted((person1, person2) -> 
             person1.getName().compareTo(person2.getName()));

The syntax is quite concise compared to the inner-classes syntax from
yesteryear. But we can do better thanks to convenience functions in the
Comparator interface. We can more fluently express our objectives using them.
For example, to sort people by comparing their names, we can write this:

    final Function<Person, String> byName = person -> person.getName();
    people.stream()
          .sorted(comparing(byName));

我们静态导入Comparator中的comparing方法,这个方法使用调用的lambda表达式来确定比较逻辑,这里是byName 这个lambda表达式。

In the code we statically imported the comparing() method in the Comparator
interface. The comparing() method uses the logic embedded in the provided
lambda expression to create a Comparator. In other words, it’s a higher-order
function that takes in one function (Function) and returns another (Comparator).
In addition to making the syntax more concise, the code now reads fluently
to express the problem being solved.

可以实现多比较

We can take this fluency further to make multiple comparisons. For example,
here is some cogent syntax to sort people in ascending order by both age and
name:

    final Function<Person, Integer> byAge = person -> person.getAge();
    final Function<Person, String> byTheirName = person -> person.getName();
    
    printPeople("Sorted in ascending order by age and name: ",
      people.stream()
            .sorted(comparing(byAge).thenComparing(byTheirName))
            .collect(toList()));

首先创建两个lambda表达式,一个返回年龄,另一个返回姓名。然后在sorted方法中结合使用这两个表达式。

具体步骤:comparing方法计算年龄,返回一个Comparator,然后基于这个返回的结果,我们调用thenComparing方法,创建一个复合comparator,再比较姓名。

We first created two lambda expressions, one to return the age of a given
person and the other to return that person’s name. We then combined these
two lambda expressions in the call to the sorted() method to compare on both
properties. The comparing() method created and returned a Comparator to compare
based on age. On the returned Comparator we invoked the thenComparing() method
to create a composite comparator that compares based on both age and name.

输出结果

The output from this code shows the net result of sorting by age first and
then by name.

Sorted in ascending order by age and name: 
John - 20
Jane - 21
Sara - 21
Greg - 35

第一部分完结。

As we can see, it’s easy to combine the Comparator implementations using the
convenience of lambda expressions and the new utility classes in the JDK.
Next we’ll examine Collectors.

英文

clutter: 杂乱无章

crowd (something) untidily; fill with clutter.

his apartment was cluttered with paintings and antiques

trivial: 微不足道的,不重要的

Since this is a trivial operation, we can eliminate this mundane code with the help of the
Java compiler.

双冒号:double colon

这个英文表达写得不错: 好处是我们可以使用迷人流畅的语法来实现比较器

the benefit is that we can use charmingly fluent syntax to implement comparators

treadmill: 跑步机

boil xxx down = condense, reduce, distill, compress,这里取压缩(这个方向)的意思

我们可以使用 lambda 表达式将其归结为本质。

We can boil this down to its essence using lambda expressions.

yesteryear:去年;过去不久

学习与总结

学习了对String操作的chars方法,它将其转化为Stream,方便我们后续filter,map等方法的使用。

了解了lambda 表达式转化为方法引用的手法。需要注意的是多个参数的情形,第一个参数必须是target,后续的参数是arguments。

使用lambda表达式和方法引用对collection进行比较排序,这是通过实现Comparator接口完成的。

同时,还可以写出多个lambda表达式,进行综合比较,多变量比较排序等。

2022年5月5日23点59分, time to rest。今天把这篇blog发出来就去睡觉,5月份还有很多工作要做。

今天重温了Venkat的一篇演讲,

Keynote: Pleasure and Peril of Being a Polyglot Programmer by Dr. Venkat Subramaniam

With so many languages on the Java platform, there are real benefits to learning and using them. However, these languages bring along some challenges as well. Attend this keynote to learn, from a world renowned polyglot programmer and author of books on multiple languages, the pleasures and perils of being a polyglot programmer.

Conference: http://2013.agileindia.org

Slide and Other details: http://betterconf.com/agileindia2013/…

其中一个洞见是,多学习几门编程语言,走出舒适区。不能总是写Java,每种语言有其独有的文化和优势。善于利用之,更好地完成业务,交付成果。

在这里插入图片描述

Venkat是一位真正的多语言programmer,polyglot programmer,给了我很多启发。

目前,笔者感兴趣的语言有Java,Groovy,Scala,Kotlin,Ruby, Haskell等等,可以按照Venkat的意见,一年学习一门新的编程语言,当然不仅仅会语法,要掌握idioms,要用这门语言处理tasks,做业务,遇到问题,解决问题。

当然,作为一名NLPer,Python必不可少,最近看到Fluent Python出第二版了,喜上眉梢!

参考

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/

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-07 11:02:36  更:2022-05-07 11:03:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 0:21:26-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码