zoukankan      html  css  js  c++  java
  • Lambdas in Java 8--reference

    Part 1

    reference:http://jaxenter.com/lambdas-in-java-8-part-1-49700.html

    Get to know lambda expressions in Java 8.

    Few things excite a community of software developers more than a new release of their chosen programming language or platform. Java developers are no exception. In fact, we’re probably even more excited about new releases, partly because there was a time not too long ago when we thought that Java’s fortunes—like those of Java’s creator, Sun—were on the wane. A brush with death tends to make one cherish renewed life all the more. But in this case, our enthusiasm also stems from the fact that unlike the prior release, Java 8 will finally get a new “modern” language feature that many of us have been requesting for years—if not decades.

    Of course, the major Java 8 buzz is around lambdas (also called closures), and that’s where this two-part series will focus. But a language feature, on its own, will often appear anything but useful or interesting unless there’s a certain amount of support behind it. Several features in Java 7 fit that description: enhanced numeric literals, for example, really couldn’t get most people’s eyes to light up.

    In this case, however, not only do Java 8 function literals change a core part of the language, but they come alongside some additional language features designed to make them easier to use, as well as some library revamping that makes use of those features directly. These will make our lives as Java developers easier.

    Java Magazine has run articles on lambdas before, but given that syntax and semantics might have changed since then and not all readers will have the time or inclination to read those articles, I will assume that readers have never seen any of this syntax before.

    Note: This article is based on a prerelease version of Java SE 8 and, as such, it might not be entirely accurate when the final release ships. Syntax and semantics are always subject to change until the final release.

    For those who desire a deeper, more official explanation of this material, Brian Goetz’ papers, for example his “State of the Lambda: Libraries Edition” paper and others available at the Project Lambda home page, are priceless references.

    Background: Functors

    Java has always had a need for functional objects (sometimes called functors), though we in the community struggled mightily to downplay their usefulness and need. In Java’s early days, when building GUIs, we needed blocks of code to respond to user events such as windows opening and closing, button presses, and scrollbar movement.

    In Java 1.0, Abstract Window Toolkit (AWT) applications were expected, like their C++ predecessors, to extend window classes and override the event method of choice; this was deemed unwieldy and unworkable. So in Java 1.1, Sun gave us a set of “listener” interfaces, each with one or more methods corresponding to an event within the GUI.

    CODE = OBJECT
    As Java grew and matured, we found more places where treating blocks of code as objects (data, really) was not only useful but necessary.

    But in order to make it easier to write the classes that must implement these interfaces and their corresponding methods, Sun gave us inner classes, including the ability to write such a class within the body of an existing class without having to specify a name—the ubiquitous anonymous inner class. (By the way, the listeners were hardly the only example of these that appeared during Java’s history. As we’ll see later, other, more “core” interfaces just like them appeared, for example, Runnable andComparator.)

    Inner classes had some strangeness to them, both in terms of syntax and semantics. For example, an inner class was either a static inner class or an instance inner class, depending not on any particular keyword (though static inner classes could be explicitly stated as such using the static keyword) but on the lexical context in which the instance was created. What that meant, in practical terms, is that Java developers often got questions such as those in Listing 1 wrong on programming interviews.

    Listing 1 
    
    class InstanceOuter {
      public InstanceOuter(int xx) { x = xx; }
    
      private int x;
    
      class InstanceInner {
        public void printSomething() {
          System.out.println("The value of x in my outer is " + x);
        }
      }
    }
    
    class StaticOuter {
      private static int x = 24;
    
      static class StaticInner {
        public void printSomething() {
          System.out.println("The value of x in my outer is " + x);
        }
      }
    }
    
    public class InnerClassExamples {
      public static void main(String... args) {
        InstanceOuter io = new InstanceOuter(12);
    
        // Is this a compile error?
        InstanceOuter.InstanceInner ii = io.new InstanceInner();
    
        // What does this print?
        ii.printSomething(); // prints 12
    
        // What about this?
        StaticOuter.StaticInner si = new StaticOuter.StaticInner();
        si.printSomething(); // prints 24
      }
    } 
    

    “Features” such as inner classes often convinced Java developers that such functionality was best relegated to the corner cases of the language, suitable for a programming interview and not much else—except when they needed them. Even then, most of the time, they were used purely for event-handling reasons.

    Above and Beyond

    As clunky as the syntax and semantics were, however, the system worked. As Java grew and matured, we found more places where treating blocks of code as objects (data, really) was not only useful but necessary. The revamped security system in Java SE 1.2 found it useful to pass in a block of code to execute under a different security context. The revamped Collection classes that came with that same release found it useful to pass in a block of code in order to know how to impose a sort order on a sorted collection. Swing found it useful to pass in a block of code in order to decide which files to display to the user in a File Open or File Save dialog box. And so on. It worked—though often through syntax that only a mother could love.

    But when concepts of functional programming began to enter mainstream programming, even Mom gave up. Though possible (see this remarkably complete example), functional programming in Java was, by any account, obtuse and awkward. Java needed to grow up and join the host of mainstream programming languages that offer first-class language support for defining, passing, and storing blocks of code for later execution.

    Java 8: Lambdas, Target Typing, and Lexical Scoping

    Java 8 introduces several new language features designed to make it easier to write such blocks of code—the key feature being lambda expressions, also colloquially referred to as closures (for reasons we’ll discuss later) or anonymous methods. Let’s take these one at a time.

    Lambda expressions. Funda-men-tally, a lambda expression is just a shorter way of writing an implementation of a method for later execution. Thus, while we used to define a Runnable as shown in Listing 2, which uses the anonymous inner class syntax and clearly suffers from a “vertical problem” (meaning that the code takes too many lines to express the basic concept), the Java 8 lambda syntax allows us to write the code as shown in Listing 3.

    Listing 2 
    
    public class Lambdas {
      public static void main(String... args) {
        Runnable r = new Runnable() {
          public void run() {
            System.out.println("Howdy, world!");
          }
        };
        r.run();
      }
    } 
    
    Listing 3 
    
    public static void main(String... args) {
        Runnable r2 = () -> System.out.println("Howdy, world!");
        r2.run();
      } 
    

    Both approaches have the same effect: a Runnable-implementing object whose run() method is being invoked to print something to the console. Under the hood, however, the Java 8 version is doing a little more than just generating an anonymous class that implements the Runnable interface—some of which has to do with the invoke dynamic bytecode that was introduced in Java 7. We won’t get to that level of detail here, but know that this is more than “just” an anonymous class instance.

    Functional interfaces. The Runnable interface—like the  interface, the Comparator<T> interface, and a whole host of other interfaces already defined within Java—is what Java 8 calls a functional interface: it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface. This is how the syntax achieves its brevity, because there is no ambiguity around which method of the interface the lambda is trying to define.

    The designers of Java 8 have chosen to give us an annotation, @FunctionalInterface, to serve as a documentation hint that an interface is designed to be used with lambdas, but the compiler does not require this—it determines “functional interfaceness” from the structure of the interface, not from the annotation.

    Throughout the rest of this article, we’ll continue to use the Runnable and Comparator<T> interfaces as working examples, but there is nothing particularly special about them, except that they adhere to this functional interface single-method restriction. Any developer can, at any time, define a new functional interface—such as the following one—that will be the interface target type for a lambda.

    interface Something {
      public String doit(Integer i);
    }
    

    The Something interface is every bit as legal and legitimate a functional interface as Runnable or Comparator<T>; we’ll look at it again after getting some lambda syntax under our belt.

    Syntax. A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body, which can either be a single expression or a block of Java code. In the case of the example shown in Listing 2, run takes no parameters and returns void, so there are no parameters and no return value. A Comparator<T>-based example, however, highlights this syntax a little more obviously, as shown in Listing 4. Remember that Comparator takes two strings and returns an integer whose value is negative (for “less than”), positive (for “greater than”), and zero (for “equal”). 

    Listing 4 
    
    public static void main(String... args) {
        Comparator<String> c = 
          (String lhs, String rhs) -> lhs.compareTo(rhs);
        int result = c.compare("Hello", "World");
      }
    

    If the body of the lambda requires more than one expression, the value returned from the expression can be handed back via the return keyword, just as with any block of Java code (see Listing 5).

    Listing 5 
    
    public static void main(String... args) {
        Comparator<String> c =
          (String lhs, String rhs) ->
            {
              System.out.println("I am comparing" +
                                 lhs + " to " + rhs);
              return lhs.compareTo(rhs);
            };
        int result = c.compare("Hello", "World");
      } 
    

    (Where we put the curly braces in code such as Listing 5 will likely dominate Java message boards and blogs for years to come.) There are a few restrictions on what can be done in the body of the lambda, most of which are pretty intuitive—a lambda body can’t “break” or “continue” out of the lambda, and if the lambda returns a value, every code path must return a value or throw an exception, and so on. These are much the same rules as for a standard Java method, so they shouldn’t be too surprising.

    Type inference. One of the features that some other languages have been touting is the idea of type inference: that the compiler should be smart enough to figure out what the type parameters should be, rather than forcing the developer to retype the parameters.

    Such is the case with the Comparator example in Listing 5. If the target type is a Comparator<String>, the objects passed in to the lambda must be strings (or some subtype); otherwise, the code wouldn’t compile in the first place. (This isn’t new, by the way—this is “Inheritance 101.”)

    In this case, then, the String declarations in front of the lhs and rhs parameters are entirely redundant and, thanks to Java 8’s enhanced type inference features, they are entirely optional (see Listing 6).

    Listing 6 
    
    public static void main(String... args) {
        Comparator<String> c =
          (lhs, rhs) ->
            {
              System.out.println("I am comparing" +
                                 lhs + " to " + rhs);
              return lhs.compareTo(rhs);
            };
        int result = c.compare("Hello", "World");
      } 
    

    The language specification will have precise rules as to when explicit lambda formal type declarations are needed, but for the most part, it’s proving to be the default, rather than the exception, that the parameter type declarations for a lambda expression can be left out completely.

    One interesting side effect of Java’s lambda syntax is that for the first time in Java’s history, we find something that cannot be assigned to a reference of type Object (see Listing 7)—at least not without some help.

    Listing 7 
    
    public static void main4(String... args) {
        Object o = () -> System.out.println("Howdy, world!");
          // will not compile
      }
    

    The compiler will complain that Object is not a functional interface, though the real problem is that the compiler can’t quite figure out which functional interface this lambda should implement: Runnable or something else? We can help the compiler with, as always, a cast, as shown in Listing 8.

    Listing 8 
    
     public static void main4(String... args) {
        Object o = (Runnable) () -> System.out.println("Howdy, world!");
          // now we're all good
      } 
    

    Recall from earlier that lambda syntax works with any interface, so a lambda that is inferred to a custom interface will also be inferred just as easily, as shown in Listing 9. Primitive types are equally as viable as their wrapper types in a lambda type signature, by the way.

    Listing 9 
    
     Something s = (Integer i) -> { return i.toString(); };
        System.out.println(s.doit(4)); 
    

    Again, none of this is really new; Java 8 is just applying Java’s long-standing principles, patterns, and syntax to a new feature. Spending a few minutes exploring type inference in code will make that clearer, if it’s not clear already.

    Lexical scoping. One thing that is new, however, is how the compiler treats names (identifiers) within the body of a lambda compared to how it treated them in an inner class. Consider the inner class example shown in Listing 10 for a moment.

    Listing 10 
    
    class Hello {
      public Runnable r = new Runnable() {
          public void run() {
            System.out.println(this);
            System.out.println(toString());
          }
        };
    
      public String toString() {
        return "Hello's custom toString()";
      }
    }
    
    public class InnerClassExamples {
      public static void main(String... args) {
        Hello h = new Hello();
        h.r.run();
      }
    } 
    

    When run, the code in Listing 10 counterintuitively produces “Hello$1@f7ce53” on my machine. The reason for this is simple to understand: both the keyword this and the call to toString in the implementation of the anonymous Runnable are bound to the anonymous inner class implementation, because that is the innermost scope that satisfies the expression.

    If we wanted (as the example seems to imply) to print out Hello’s version of toString, we have to explicitly qualify it using the “outer this” syntax from the inner classes portion of the Java spec, as shown in Listing 11. How’s that for intuitive?

    Listing 11 
    
    class Hello {
      public Runnable r = new Runnable() {
          public void run() {
            System.out.println(Hello.this);
            System.out.println(Hello.this.toString());
          }
        };
    
      public String toString() {
        return "Hello's custom toString()";
      }
    } 
    

    Frankly, this is one area where inner classes simply created more confusion than they solved. Granted, as soon as the reason for the this keyword showing up in this rather unintuitive syntax was explained, it sort of made sense, but it made sense in the same way that politicians’ perks make sense.

    Lambdas, however, are lexically scoped, meaning that a lambda recognizes the immediate environment around its definition as the next outermost scope. So the lambda example in Listing 12 produces the same results as the second Hello nested class example in Listing 11, but with a much more intuitive syntax.

    Listing 12 
    
    class Hello {
      public Runnable r = () -> {
          System.out.println(this);
          System.out.println(toString());
        };
    
      public String toString() {
        return "Hello's custom toString()";
      }
    } 
    

    This means, by the way, that this no longer refers to the lambda itself, which might be important in certain cases—but those cases are few and far between. What’s more, if such a case does arise (for example, perhaps the lambda needs to return a lambda and it wants to return itself), there’s a relatively easy workaround, which we’ll get to in a second.

    Variable capture. Part of the reason that lambdas are called closures is that a function literal (such as what we’ve been writing) can “close over” variable references that are outside the body of the function literal in the enclosing scope (which, in the case of Java, would typically be the method in which the lambda is being defined). Inner classes could do this, too, but of all the subjects that frustrated Java developers the most about inner classes, the fact that inner classes could reference only “final” variables from the enclosing scope was near the top.

    Lambdas relax this restriction, but only by a little: as long as the variable reference is “effectively final,” meaning that it’s final in all but name, a lambda can reference it (see Listing 13). Because message is never modified within the scope of the mainmethod enclosing the lambda being defined, it is effectively final and, therefore, eligible to be referenced from within the Runnable lambda stored in r.

    Listing 13 
    
    public static void main(String... args) {
        String message = "Howdy, world!";
        Runnable r = () -> System.out.println(message);
        r.run();
      } 
    

    While on the surface this might sound like it’s not much of anything, remember that the lambda semantics rules don’t change the nature of Java as a whole—objects on the other side of a reference are still accessible and modifiable long after the lambda’s definition, as shown in Listing 14.

    Listing 14 
    
    public static void main(String... args) {
        StringBuilder message = new StringBuilder();
        Runnable r = () -> System.out.println(message);
        message.append("Howdy, ");
        message.append("world!");
        r.run();
      } 
    

    Astute developers familiar with the syntax and semantics of older inner classes will remember that this was also true of references declared “final” that were referenced within an inner class—the final modifier applied only to the reference, not to the object on the other side of the reference. Whether this is a bug or a feature in the eyes of the Java community remains to be seen, but it is what it is, and developers would be wise to understand how lambda variable capture works, lest a surprise bug appear. (In truth, this behavior isn’t new—it’s just recasting existing functionality of Java in fewer keystrokes and with more support from the compiler.)

    Method references. Thus far, all the lambdas we’ve examined have been anonymous literals—essentially, defining the lambda right at the point of use. This is great for one-off kinds of behavior, but it doesn’t really help much when that behavior is needed or wanted in several places. Consider, for example, the following Person class. (Ignore the lack of proper encapsulation for the moment.)

    class Person {
      public String firstName;
      public String lastName;
      public int age;
    }); 
    

    When a Person is put into a SortedSet or needs to be sorted in a list of some form, we want to have different mechanisms by which Person instances can be sorted—for example, sometimes by first name and sometimes by last name. This is whatComparator<T> is for: to allow us to define an imposed ordering by passing in the Comparator<T> instance. 

    SCOPE IT OUT
    Lambdas are lexically scoped, meaning that a lambda recognizes the immediate environment around its definition as the next outermost scope.

    Lambdas certainly make it easier to write the sort code, as shown in Listing 15. But sorting Person instances by first name is something that might need to be done many times in the codebase, and writing that sort of algorithm multiple times is clearly a violation of the Don’t Repeat Yourself (DRY) principle.

    Listing 15 
    
     public static void main(String... args) {
        Person[] people = new Person[] {
          new Person("Ted", "Neward", 41),
          new Person("Charlotte", "Neward", 41),
          new Person("Michael", "Neward", 19),
          new Person("Matthew", "Neward", 13)
        };
        // Sort by first name
        Arrays.sort(people, 
          (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName));
        for (Person p : people)
          System.out.println(p);
      } 
    

    The Comparator can certainly be captured as a member of Person itself, as shown in Listing 16. The Comparator<T> could then just be referenced as any other static field could be referenced, as shown in Listing 17. And, truthfully, functional programming zealots will prefer this style, because it allows for the functionality to be combined in various ways.

    Listing 16 
    
    class Person {
      public String firstName;
      public String lastName;
      public int age;
    
      public final static Comparator<Person> compareFirstName =
        (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
    
      public final static Comparator<Person> compareLastName =
        (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
    
      public Person(String f, String l, int a) {
        firstName = f; lastName = l; age = a;
      }
    
      public String toString() {
        return "[Person: firstName:" + firstName + " " +
          "lastName:" + lastName + " " +
          "age:" + age + "]";
      }
    } 
    
    Listing 17 
    
     public static void main(String... args) {
        Person[] people = . . .;
    
        // Sort by first name
        Arrays.sort(people, Person.compareFirstName);
        for (Person p : people)
          System.out.println(p);
      } 
    

    But it feels strange to the traditional Java developer, as opposed to simply creating a method that fits the signature of Comparator<T> and then using that directly—which is exactly what a method reference allows (see Listing 18). Notice the double-colon method-naming style, which tells the compiler that the method compareFirstNames, defined on Person, should be used here, rather than a method literal.

    Listing 18 
    
    class Person {
      public String firstName;
      public String lastName;
      public int age;
    
      public static int compareFirstNames(Person lhs, Person rhs) {
        return lhs.firstName.compareTo(rhs.firstName);
      }
    
      // ...
    }
    
      public static void main(String... args) {
        Person[] people = . . .;
        // Sort by first name
        Arrays.sort(people, Person::compareFirstNames);
        for (Person p : people)
          System.out.println(p);
      } 
    

    Another way to do this, for those who are curious, would be to use the compareFirstNamest method to create a Comparator<Person> instance, like this:

    Comparator cf =    Person::compareFirstNames; 
    

    And, just to be even more succinct, we could avoid some of the syntactic overhead entirely by making use of some of the new library features to write the following, which makes use of a higher-order function (meaning, roughly, a function that passes around functions) to essentially avoid all of the previous code in favor of a one-line in-place usage:

    Arrays.sort(people, comparing(
      Person::getFirstName));
    

    This, in part, is why lambdas, and the functional programming techniques that come with them, are so powerful.

    Virtual extension methods. One of the drawbacks frequently cited about interfaces, however, is that they have no default implementation, even when that implementation is ridiculously obvious. Consider, for example, a fictitious Relationalinterface, which defines a series of methods to mimic relational methods (greater than, less than, greater than or equal to, and so on). As soon as any one of those methods is defined, it’s easy to see how the others could all be defined in terms of the first one. In fact, all of them could be defined in terms of a Comparable<T>’s compare method, if the definition of the compare method were known ahead of time. But interfaces cannot have default behavior, and an abstract class is still a class and occupies any potential subclass’ one implementation-inheritance slot.

    With Java 8, however, as these function literals become more widespread, it becomes more important to be able to specify default behavior without losing the “interfaceness” of the interface. Thus, Java 8 now introduces virtual extension methods(which used to be known in a previous draft as defender methods), essentially allowing an interface to specify a default behavior for a method, if none is provided in a derived implementation.

    Consider, for a moment, the Iterator interface. Currently, it has three methods (hasNextnext, and remove), and each must be defined. But an ability to “skip” the next object in the iteration stream might be helpful. And because the Iterator’s implementation is easily defined in terms of the other three, we can provide it, as shown in Listing 19.

    Listing 19  
    
     interface Iterator<T> {
      boolean hasNext();
      T next();
      void remove();
    
      void skip(int i) default {
        for (; i > 0 && hasNext(); i--) next();
      }
    } 
    

    Some within the Java community will scream, claiming that this is just a mechanism to weaken the declarative power of interfaces and create a scheme that allows for multiple inheritance in Java. To a degree, this is the case, particularly because the rules around precedence of default implementations (in the event that a class implements more than one interface with different default implementations of the same method) will require significant study.

    But as the name implies, virtual extension methods provide a powerful mechanism for extending existing interfaces, without relegating the extensions to some kind of second-class status. Using this mechanism, Oracle can provide additional, powerful behaviour for existing libraries without requiring developers to track different kinds of classes. There’s no SkippingIterator class that developers now have to downcast to for those collections that support it. In fact, no code anywhere has to change, and all Iterator<T>s, no matter when they were written, will automatically have this skipping behaviour.

    It is through virtual extension methods that the vast majority of the changes that are happening in the Collection classes are coming. The good news is that your Collection classes are getting new behavior, and the even better news is that your code won’t have to change an iota in the meantime. The bad news is that we have to defer that discussion to the next article in this series.

    Conclusion

    Lambdas will bring a lot of change to Java, both in terms of how Java code will be written and how it will be designed. Some of these changes, inspired by functional programming languages, will change the way Java programmers think about writing code—which is both an opportunity and a hassle.

    We’ll talk more about the impact these changes will have on the Java libraries in the next article in this series, and we’ll spend a little bit of time talking about how these new APIs, interfaces, and classes open up some new design approaches that previously wouldn’t have been practical due to the awkwardness of the inner classes syntax.

    Java 8 is going to be a very interesting release. Strap in, it’s going to be a rocket-ship ride.

    Part 2

    reference:http://jaxenter.com/lambdas-in-java-8-part-2-49708.html

    Learn how to use lambda expressions to your advantage

    The release of Java SE 8 swiftly approaches. With it come not only the new linguistic lambda expressions (also called closures or anonymous methods)—along with some supporting language features—but also API and library enhancements that will make parts of the traditional Java core libraries easier to use. Many of these enhancements and additions are on the Collections API, and because the Collections API is pretty ubiquitous across applications, it makes the most sense to spend the majority of this article on it.

    However, it’s likely that most Java developers will be unfamiliar with the concepts behind lambdas and with how designs incorporating lambdas look and behave. So, it’s best to examine why these designs look the way they do before showing off the final stage. Thus, we’ll look at some before and after approaches to see how to approach a problem pre-lambda and post-lambda.

    Note: This article was written against the b92 (May 30, 2013) build of Java SE 8, and the APIs, syntax, or semantics might have changed by the time you read this or by the time Java SE 8 is released. However, the concepts behind these APIs, and the approach taken by the Oracle engineers, should be close to what we see here.

    Collections and Algorithms

    BE ATTENTIVE

    Algorithms, a more functional-centric way of interacting with collections, have been a part of the Collections API since its initial release, but they often get little attention, despite their usefulness.

    The Collections API has been with us since JDK 1.2, but not all parts of it have received equal attention or love from the developer community. Algorithms, a more functional-centric way of interacting with collections, have been a part of the Collections API since its initial release, but they often get little attention, despite their usefulness. For example, the Collections class sports a dozen or so methods all designed to take a collection as a parameter and perform some operation against the collection or its contents. 

    Consider, for example, the Person class shown in Listing 1, which in turn is used by a List that holds a dozen or so Person objects, as shown in Listing 2.

    Listing 1

    public class Person {
      public Person(String fn, String ln, int a) {
        this.firstName = fn; this.lastName = ln; this.age = a;
      }
    
      public String getFirstName() { return firstName; }
      public String getLastName() { return lastName; }
            public int getAge() { return age; }
    } 
    

    Listing 2 

    List<Person> people = Arrays.asList(
          new Person("Ted", "Neward", 42),
          new Person("Charlotte", "Neward", 39),
          new Person("Michael", "Neward", 19),
          new Person("Matthew", "Neward", 13),
          new Person("Neal", "Ford", 45),
          new Person("Candy", "Ford", 39),
          new Person("Jeff", "Brown", 43),
          new Person("Betsy", "Brown", 39)
        );
    } 
    

    Now, assuming we want to examine or sort this list by last name and then by age, a naive approach is to write a for loop (in other words, implement the sort by hand each time we need to sort). The problem with this, of course, is that this violates DRY (the Don’t Repeat Yourself principle) and, worse, we have to reimplement it each time, because for loops are not reusable.

    The Collections API has a better approach: the Collections class sports a sort method that will sort the contents of the List. However, using this requires the Person class to implement the Comparable method (which is called a natural ordering, and defines a default ordering for all Person types) or you have to pass in a Comparator instance to define how Person objects should be sorted.

    So, if we want to sort first by last name and then by age (in the event the last names are the same), the code will look something like Listing 3. But that’s a lot of work to do something as simple as sort by last name and then by age. This is exactly where the new closures feature will be of help, making it easier to write the Comparator (see Listing 4).

    Listing 3 

     Collections.sort(people, new Comparator<Person>() {
          public int compare(Person lhs, Person rhs) {
            if (lhs.getLastName().equals(rhs.getLastName())) {
              return lhs.getAge() - rhs.getAge();
            }
            else
              return lhs.getLastName().compareTo(rhs.getLastName());
          }
        }); 
    

    Listing 4 

    Collections.sort(people, (lhs, rhs) -> {
          if (lhs.getLastName().equals(rhs.getLastName()))
            return lhs.getAge() - rhs.getAge();
          else
            return lhs.getLastName().compareTo(rhs.getLastName());
        }); 
    

    The Comparator is a prime example of the need for lambdas in the language: it’s one of the dozens of places where a one-off anonymous method is useful. (Bear in mind, this is probably the easiest—and weakest—benefit of lambdas. We’re essentially trading one syntax for another, admittedly terser, syntax, but even if you put this article down and walk away right now, a significant amount of code will be saved just from that terseness.)

    If this particular comparison is something that we use over time, we can always capture the lambda as a Comparator instance, because that is the signature of the method—in this case, "int compare(Person, Person)"—that the lambda fits, and store it on the Person class directly, making the implementation of the lambda easier (see Listing 5) and its use even more readable (see Listing 6).

    Listing 5 

    public class Person {
      // . . .
    
      public static final Comparator<Person> BY_LAST_AND_AGE =
        (lhs, rhs) -> {
          if (lhs.lastName.equals(rhs.lastName))
            return lhs.age - rhs.age;
          else
            return lhs.lastName.compareTo(rhs.lastName);
        };
    } 
    

    Listing 6 

     Collections.sort(people, Person.BY_LAST_AND_AGE); 
    

    BE ANONYMOUS

    The Comparator is a prime example of the need for lambdas in the language: it’s one of the dozens of places where a one-off anonymous method is useful.

    Storing a Comparator<Person> instance on the Person class is a bit odd, though. It would make more sense to define a method that does the comparison, and use that instead of a Comparator instance. Fortunately, Java will allow any method to be used that satisfies the same signature as the method on Comparator, so it’s equally possible to write the BY_LAST_AND_AGE Comparator as a standard instance or static method on Person (see Listing 7) and use it instead (see Listing 8).

    Listing 7 

      public static int compareLastAndAge(Person lhs, Person rhs) {
        if (lhs.lastName.equals(rhs.lastName))
          return lhs.age - rhs.age;
        else
          return lhs.lastName.compareTo(rhs.lastName);
      } 
    

    Listing 8 

    Collections.sort(people, Person::compareLastAndAge); 
    

    Thus, even without any changes to the Collections API, lambdas are already helpful and useful. Again, if you walk away from this article right here, things are pretty good. But they’re about to get a lot better.

    Changes in the Collections API

    With some additional APIs on the Collection classes themselves, a variety of new and more powerful approaches and techniques open up, most often leveraging techniques drawn from the world of functional programming. No knowledge of functional programming is necessary to use them, fortunately, as long you can open your mind to the idea that functions are just as valuable to manipulate and reuse as are classes and objects.

    Comparisons. One of the drawbacks to the Comparator approach shown earlier is hidden inside the Comparator implementation. The code is actually doing two comparisons, one as a “dominant” comparison over the other, meaning that last names are compared first, and age is compared only if the last names are identical. If project requirements later demand that sorting be done by age first and by last names second, a new Comparator must be written—no parts of compareLastAndAge can be reused.

    This is where taking a more functional approach can add some powerful benefits. If we look at that comparison as entirely separate Comparator instances, we can combine them to create the precise kind of comparison needed (see Listing 9).

    Listing 9 

    public static final Comparator<Person> BY_FIRST =
        (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
      public static final Comparator<Person> BY_LAST =
        (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
      public static final Comparator<Person> BY_AGE =
        (lhs, rhs) -> lhs.age – rhs.age; 
    

    Historically, writing the combination by hand has been less productive, because by the time you write the code to do the combination, it would be just as fast (if not faster) to write the multistage comparison by hand.

    As a matter of fact, this “I want to compare these two X things by comparing values returned to me by a method on each X” approach is such a common thing, the platform gave us that functionality out of the box. On the Comparator class, acomparing method takes a function (a lambda) that extracts a comparison key out of the object and returns a Comparator that sorts based on that. This means that Listing 9 could be rewritten even more easily as shown in Listing 10.

    Listing 10 

     public static final Comparator<Person> BY_FIRST =
        Comparators.comparing(Person::getFirstName);
      public static final Comparator<Person> BY_LAST =
        Comparators.comparing(Person::getLastName);
      public static final Comparator<Person> BY_AGE =
        Comparators.comparing(Person::getAge); 
    

    BE REDUCTIONIST

    Doing this bypasses an interesting opportunity to explore one of the more powerful features of the new Java API, that of doing a reduction—coalescing a collection of values down into a single one through some custom operations.

    Think for a moment about what this is doing: the Person is no longer about sorting, but just about extracting the key by which the sort should be done. This is a good thing—Person shouldn’t have to think about how to sort; Person should just focus on being a Person.

    It gets better, though, particularly when we want to compare based on two or more of those values.

    Composition. As of Java 8, the Comparator interface comes with several methods to combine Comparator instances in various ways by stringing them together. For example, the Comparator .thenComparing() method takes a Comparator to use for comparison after the first one compares. So, re-creating the “last name then age” comparison can now be written in terms of the two Comparator instances LAST and AGE, as shown in Listing 11. Or, if you prefer to use methods rather thanComparator instances, use the code in Listing 12.

    Listing 11 

     Collections.sort(people, Person.BY_LAST.
                                       .thenComparing(Person.BY_AGE)); 
    

    Listing 12 

    Collections.sort(people,
          Comparators.comparing(Person::getLastName)
                     .thenComparing(Person::getAge)); 
    

    By the way, for those who didn’t grow up using Collections.sort(), there’s now a sort() method directly on List. This is one of the neat things about the introduction of interface default methods: where we used to have to put that kind of noninheritance-based reusable behavior in static methods, now it can be hoisted up into interfaces. (See the previous article in this series for more details.)

    Similarly, if the code needs to sort the collection of Person objects by last name and then by first name, no new Comparator needs to be written, because this comparison can, again, be made of the two particular atomic comparisons shown in Listing 13.

    Listing 13 

        Collections.sort(people,
          Comparators.comparing(Person::getLastName)
          .thenComparing(Person::getFirstName)); 
    

    This combinatory “connection” of methods, known as functional composition, is common in functional programming and at the heart of why functional programming is as powerful as it is.

    It’s important to understand that the real benefit here isn’t just in the APIs that enable us to do comparisons, but the ability to pass bits of executable code (and then combine them in new and interesting ways) to create opportunities for reuse and design.Comparator is just the tip of the iceberg. Lots of things can be made more flexible and powerful, particularly when combining and composing them.

    Iteration. As another example of how lambdas and functional approaches change the approach to code, consider one of the fundamental operations done with collections: that of iterating over them. Java 8 will bring to collections a change via theforEach() default method defined on the Iterator and Iterable interfaces. Using it to print each of the items in the collection, for example, requires passing a lambda to the forEach method on an Iterator, as shown in Listing 14.

    Listing 14 

    people.forEach((it) -> System.out.println("Person: " + it)); 
    

    Officially, the type of lambda being passed in is a Consumer instance, defined in the java.util.function package. Unlike traditional Java interfaces, however, Consumer is one of the new functional interfaces, meaning that direct implementations will likely never happen—instead, the new way to think about it is solely in terms of its single, important method, accept, which is the method the lambda provides. The rest (such as compose and andThen) are utility methods defined in terms of the important method, and they are designed to support the important method.

    For example, andThen() chains two Consumer instances together, so the first one is called first and the second is called immediately after into a single Consumer. This provides useful composition techniques that are a little outside the scope of this article.

    BE A COLLECTOR

    It is ugly enough to fix. The code is actually a lot easier to write if we use the built-in Collector interface and its partner Collectors, which specifically do this kind of mutable-reduction operation.

    Many of the use cases involved in walking through a collection have the purpose of finding items that fit a particular criterion—for example, determining which of the Person objects in the collection are of drinking age, because the automated code system needs to send everyone in that collection a beer. This “act upon a thing coming from a group of things” is actually far more widespread than just operating upon a collection. Think about operating on each line in a file, each row from a result set, each value generated by a random-number generator, and so on. Java SE 8 generalized this concept one step further, outside collections, by lifting it into its own interface: Stream.

    Stream. Like several other interfaces in the JDK, the Stream interface is a fundamental interface that is intended for use in a variety of scenarios, including the Collections API. It represents a stream of objects, and on the surface of things, it feels similar to how Iterator gives us access one object at a time through a collection.

    However, unlike collections, Stream does not guarantee that the collection of objects is finite. Thus, it is a viable candidate for pulling strings from a file, for example, or other kinds of on-demand operations, particularly because it is designed not only to allow for composition of functions, but also to permit parallelization “under the hood.”

    Consider the earlier requirement: the code needs to filter out any Person object that is not at least 21 years of age. Once a Collection converts to a Stream (via the stream() method defined on the Collection interface), the filter method can be used to produce a new Stream through which only the filtered objects come (see Listing 15).

    Listing 15 

    people
          .stream()
          .filter(it -> it.getAge() >= 21) 
    

    The parameter to filter is a Predicate, an interface defined as taking one genericized parameter and returning a Boolean. The intent of the Predicate is to determine whether the parameter object is included as part of the returned set.

    The return from filter() is another Stream, which means that the filtered Stream is also available for further manipulation, such as to forEach() through each of the elements that come through the Stream, in this case to display the results (see Listing 16).

    Listing 16 

     people.stream()
          .filter((it) -> it.getAge() >= 21)
          .forEach((it) -> 
            System.out.println("Have a beer, " + it.getFirstName())); 
    

    This neatly demonstrates the composability of streams—we can take streams and run them through a variety of atomic operations, each of which do one—and only one—thing to the stream. Additionally, it’s important to note that filter() is lazy—it will filter only as it needs to, on demand, rather than going through the entire collection of Person objects and filtering ahead of time (which is what we’re used to with the Collections API).

    Predicates. It might seem odd at first that the filter() method takes only a single Predicate. After all, if a goal was to find all the Person objects whose age is greater than 21 and whose last name is Neward, it would seem that filter() could or should take a pair of Predicate instances. Of course, this opens a Pandora’s box of possibilities. What if the goal is to find all Person objects with an age greater than 21 and less than 65, and with a first name of at least four or more characters? Infinite possibilities suddenly open up, and the filter() API would need to somehow approach all of these.

    Unless, of course, a mechanism were available to somehow coalesce all of these possibilities down into a single Predicate. Fortunately, it’s fairly easy to see that any combination of Predicate instances can themselves be a single Predicate. In other words, if a given filter needs to have condition A be true and condition B be true before an object can be included in the filtered stream, that is itself a Predicate (A and B), and we can combine those two together into a single Predicate by writing a Predicate that takes any two Predicate instances and returns true only if both A and B each yield true.

    This “and”ing Predicate is—by virtue of the fact that it knows only about the two Predicate instances that it needs to call (and nothing about the parameters being passed in to each of those)— completely generic and can be written well ahead of time.

    If the Predicate closures are stored in Predicate references (similar to how Comparator references were used earlier, as members on Person), they can be strung together using the and() method on them, as shown in Listing 17.

    Listing 17 

     Predicate<Person> drinkingAge = (it) -> it.getAge() >= 21;
        Predicate<Person> brown = (it) -> it.getLastName().equals("Brown");
        people.stream()
          .filter(drinkingAge.and(brown))
          .forEach((it) ->
                    System.out.println("Have a beer, " +
                                       it.getFirstName()));
    

    As might be expected, and()or(), and xor() are all available. Make sure to check the Javadoc for a full introduction to all the possibilities.

    map() and reduce(). Other common Stream operations include map(), which applies a function across each element present within a Stream to produce a result out of each element. So, for example, we can obtain the age of each Person in the collection by applying a simple function to retrieve the age out of each Person, as shown in Listing 18.

    Listing 18

      IntStream ages =
          people.stream()
                .mapToInt((it) -> it.getAge()); 
    

    For all practical purposes, IntStream (and its cousins LongStream and DoubleStream) is a specialization of the Stream<T> interface (meaning that it creates custom versions of that interface) for those primitive types.

    This, then, produces a Stream of integers out of a Collection of Person instances. This is also sometimes known as a transformation operation, because the code is transforming or projecting a Person into an int.

    Similarly, reduce() is an operation that takes a stream of values and, through some kind of operation, reduces them into a single value. Reduction is an operation already familiar to developers, though they might not recognize it at first: the COUNT()operator from SQL is one such operation (reducing from a collection of rows to a single integer), as are the SUM()MAX(), and MIN() operators. Each of these takes a stream of values (rows) and produces a single value (the integer) by applying some operation (for example, increment a counter, add the value to a running total, select the highest, or select the lowest) to each of the values in the stream.

    So, for example, you could sum the values prior to dividing by the number of elements in the stream to obtain an average age. Given the new APIs, it’s easiest to just use the built-in methods, as shown in Listing 19.

    Listing 19

    int sum = people.stream()
                    .mapToInt(Person::getAge)
                    .sum(); 
    

    But doing this bypasses an interesting opportunity to explore one of the more powerful features of the new Java API, that of doing a reduction—coalescing a collection of values down into a single one through some custom operation. So, let’s rewrite the summation part of this using the new reduce() method: 

    .reduce(0, (l, r) -> l + r); 
    

    This reduction, also known in functional circles as a fold, starts with a seed value (0, in this case), and applies the closure to the seed and the first element in the stream, taking the result and storing it as the accumulated value that will be used as the seed for the next element in the stream.

    In other words, in a list of integers such as 1, 2, 3, 4, and 5, the seed 0 is added to 1 and the result (1) is stored as the accumulated value, which then serves as the left-hand value in addition to serving as the next number in the stream (1+2). The result (3) is stored as the accumulated value and used in the next addition (3+3). The result (6) is stored and used in the next addition (6+4), and the result is used in the final addition (10+5), yielding the final result 15. And, sure enough, if we run the code in Listing 20, we get that result.

    Listing 20 

    List<Integer> values = Arrays.asList(1, 2, 3, 4, 5);
        int sum = values.stream().reduce(0, (l,r) -> l+r);
        System.out.println(sum); 
    

    Note that the type of closure accepted as the second argument to reduce is an IntBinaryOperator, defined as taking two integers and returning an int result. IntBinaryOperator and IntBiFunction are examples of specialized functional interfaces—including other specialized versions for Double and Long—which take two parameters (of one or two different types) and return an int. These specialized versions were created mostly to ease the work required for using the common primitive types.

    IntStream also has a couple of helper methods, including the average()min(), and max() methods, that do some of the more common integer operations. Additionally, binary operations (such as summing two numbers) are also often defined on the primitive wrapper classes for that type (Integer::sumLong::max, and so on).

    More maps and reduction. Maps and reduction are useful in a variety of situations beyond just simple math. After all, in any case where a collection of objects can be transformed into a different object (or value) and then collected into a single value, map and reduction operations work.

    The map operation, for example, can be useful as an extraction or projection operation to take an object and extract portions of it, such as extracting the last name out of a Person object: 

    Stream lastNames =   people.stream()      .map(Person::getLastName);  
    

    Once the last names have been retrieved from the Person stream, the reduction can concatenate strings together, such as transforming the last name into a data representation for XML. See Listing 21.

    Listing 21 

    String xml =
          "<people data='lastname'>" +
          people.stream()
                .map(it -> "<person>" + it.getLastName() + "</person>")
                .reduce("", String::concat)
          + "</people>";
        System.out.println(xml); 
    

    And, naturally, if different XML formats are required, different operations can be used to control the contents of each format, supplied either ad hoc, as in Listing 21, or from methods defined on other classes, such as from the Person class itself, as shown in Listing 22, which can then be used as part of the map() operation to transform the stream of Person objects into a JSON array of object elements, as shown in Listing 23.

    Listing 22

    public class Person {
      // . . .
      public static String toJSON(Person p) {
        return
          "{" +
            "firstName: "" + p.firstName + "", " +
            "lastName: "" + p.lastName + "", " +
            "age: " + p.age + " " +
          "}";
      }
    } 
    

    Listing 23 

    String json =
          people.stream()
            .map(Person::toJSON)
            .reduce("[", (l, r) -> l + (l.equals("[") ? "" : ",") + r)
            + "]";
        System.out.println(json); 
    

    BE READY

    The release of Java SE 8 swiftly approaches. With it come not only the new linguistic lambda expressions (also called closures or anonymous methods)—along with some supporting language features—but also API and library enhancements that will make parts of the traditional Java core libraries easier to use.

    The ternary operation in the middle of the reduce operation is there to avoid putting a comma in front of the first Person serialized to JSON. Some JSON parsers might accept this format, but that is not guaranteed, and it looks ugly to have it there.

    It is ugly enough, in fact, to fix. The code is actually a lot easier to write if we use the built-in Collector interface and its partner Collectors, which specifically do this kind of mutable-reduction operation (see Listing 24). This has the added benefit of being much faster than the versions using the explicit reduce and String::concat from the earlier examples, so it’s generally a better bet.

    Listing 24

      String joined = people.stream()
                              .map(Person::toJSON)
                              .collect(Collectors.joining(", "));System.out.println("[" + joined + "]"); 
    

    Oh, and lest we forget our old friend Comparator, note that Stream also has an operation to sort a stream in-flight, so the sorted JSON representation of the Person list looks like Listing 25.

    Listing 25

    String json = people.stream()
                            .sorted(Person.BY_LAST)
                            .collect(Collectors.joining(", " "[", "]"));
        System.out.println(json); 
    

    This is powerful stuff.

    Parallelization. What’s even more powerful is that these operations are entirely independent of the logic necessary to pull each object through the Stream and act on each one, which means that the traditional for loop will break down when attempting to iterate, map, or reduce a large collection by breaking the collection into segments that will each be processed by a separate thread.

    Learn More


     
    Lambda Expressions

    The Stream API, however, already has that covered, making the XML or JSON map() and reduce() operations shown earlier a slightly different operation—instead of calling stream() to obtain aStream from the collection, use parallelStream() instead, as demonstrated in Listing 26.

    Listing 26

      people.parallelStream()
          .filter((it) -> it.getAge() >= 21)
          .forEach((it) ->
                    System.out.println("Have a beer " + it.getFirstName() +
                      Thread.currentThread())); 
    

    For a collection of at least a dozen items, at least on my laptop, two threads are used to process the collection: the thread named main, which is the traditional one used to invoke the main() method of a Java class, and another thread namedForkJoinPool.commonPool worker-1, which is obviously not of our creation.

    Obviously, for a collection of a dozen items, this would be hideously unnecessary, but for several hundred or more, this would be the difference between “good enough” and “needs to go faster.” Without these new methods and approaches, you would be staring at some significant code and algorithmic study. With them, you can write parallelized code literally by adding eight keystrokes (nine if you count the Shift key required to capitalize the s in stream) to the previously sequential processing.

    And, where necessary, a parallel Stream can be brought back to a sequential one by calling—you can probably guess—sequential() on it.

    The important thing to note is that regardless of whether the processing is better done sequentially or in parallel, the same Stream interface is used for both. The sequential or parallel implementation becomes entirely an implementation detail, which is exactly where we want it to be when working on code that focuses on business needs (and value); we don’t want to focus on the low-level details of firing up threads in thread pools and synchronizing across them.

    Conclusion

    Lambdas will bring a lot of change to Java, both in terms of how Java code will be written and how it will be designed. Some of these changes are already taking place within the Java SE libraries, and they will slowly make their way through many other libraries—both those owned by the Java platform and those out in “the wilds” of open source—as developers grow more comfortable with the abilities (and drawbacks) of lambdas.

    Numerous other changes are present within the Java SE 8 release. But if you understand how lambdas on collections work, you will have a strong advantage when thinking about how to leverage lambdas within your own designs and code, and you can create better-decoupled code for years to come.

  • 相关阅读:
    AJAX 应用 透过 JavaScript 调用 C# 函数
    快速搞懂 SQL Server 的锁定和阻塞
    国际财务报告准则 IFRS 与信息系统
    我的android阅读软件“微读”做最简单的手机阅读软件
    我的android阅读软件“微读”v2.0发布,加入新浪微博的支持
    iphone开发我的新浪微博客户端用户登录等待篇(1.4)
    iphone开发我的新浪微博客户端用户登录自定义弹出窗口篇(1.2)
    自定义实现类似android主界面的滑屏换屏控件
    我的android阅读软件“微读”v2.2又发布,加入微美图、微漫画、微美女阅读
    iphone开发我的新浪微博客户端用户登录OAuth授权认证篇(1.3)
  • 原文地址:https://www.cnblogs.com/davidwang456/p/3593171.html
Copyright © 2011-2022 走看看