Java 8 Features (Lambda expressions with Android)

[TOC]

The resources

http://www.importnew.com/16436.html

http://www.infoq.com/cn/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

https://github.com/bazelbuild/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/desugar

Mastering Lambdas- Java Programming in a Multicore World

http://www.lambdafaq.org/

Lambdas in Java: An In-Depth Analysis

http://www.javac.info/

On the pros and cons of Java 7 closures versus Lambda expressions

https://martinfowler.com/bliki/Lambda.html

A Definition of Closures

The Original ‘Lambda Papers’ by Guy Steele and Gerald Sussman

http://viralpatel.net/blogs/Lambda-expressions-java-tutorial/

https://stackoverflow.com/questions/21887358/reflection-type-inference-on-java-8-lambdas

history

-2. In 1997, internal classes were added

-1. August 2006, Closures for Java, first announced the idea

-0.5. Oracle acquired Sun in April 2009

  1. In December 2009, I formally proposed The idea: Project Lambda: The Straw-Man Proposal
  2. In January 2010, 0.1 ver
  3. In February 2010, 0.15 ver
  4. In April 2010, Neal Gafter suggested that Lambda would be late for the release of JDK 7 (mainly not up to release standards) and delayed to Java 8
  5. In November 2010, JSR 335 was launched, Lambda Expressions for the JavaTM Programming Language
  6. In May 2014, the final version of JSR 335 was released, and Java finally got Lambda

The basic definition

Lambda calculus (lambda Calculus) is a formal system developed from mathematical logic with rules of variable binding and substitution to study how functions are abstractly defined, how functions are applied, and recursively.

Although Java has used generics and inheritance to abstract data, there is no proper way to abstract data processing, so Lambda expressions were introduced to solve this problem.

Lambda has two main features in programming languages: anonymous functions and closures.

Anonymous functions

In computers, Lambda is typically an anonymous function, and an anonymous function is essentially a function that abstracts out a set of operations.

Let’s say we want to add 1 to an array, we can do that very cleanly.

Arrays.asList(1.2.3.4)
        .stream()
        .map(i -> i+1)// The list is all +1, but notice that there is no change to the original list, but a new list is generated // (no side effects)
        .forEach(i -> System.out.println(i));
Copy the code

closure

Java does not fully support function closures directly (for local variables), but you can simulate closures through classes or arrays.

  1. Use function closures directly
public static Map<String, Supplier> createCounter(int initValue) { 
    // the enclosing scope
    int count = initValue;   // this variable is effectively final
    Map<String, Supplier> map = new HashMap<>();
    map.put("val", () -> count);
    map.put("inc", () -> count++);  // error,can't compile
    return map;
}
Copy the code
  1. Java implements closures through classes
private static class MyClosure {
    public int value;
    public MyClosure(int initValue) { this.value = initValue; }}public static Map<String, Supplier> createCounterWithClosure(int initValue) {
    MyClosure closure = new MyClosure(initValue);  // ugly
    Map<String, Supplier> counter = new HashMap<>();
    counter.put("val", () -> closure.value);
    counter.put("inc", () -> closure.value++); 
    return counter;
}
// also can represent this way
 public static Map<String, Supplier> createCounter(int initValue) { 
        final int[] count = {initValue}; // use an array wrapper
        Map<String, Supplier> map = new HashMap<>();
        map.put("val", () -> count[0]);
        map.put("inc", () -> count[0] + +);return map;
    }
Copy the code

Java does not support this syntactically, as JavaScript does, for several reasons:

** If local variables are allowed to be modified, then there are additional considerations in multi-threaded situations, such as synchronization.

For example, if your method is executed on the main thread and the Lambda expression is created on the child thread, then if the

How should the synchronization of the main thread be handled when modifying a local variable in a Lambda expression? And then you have to distribute the variables

On the heap, there are still quite a few Java programmers who are not happy with this

Allocate memory on the heap. .

It can also cause a memory leak (the main thread runs out and the child thread holds the reference).

** This is a result of the previous issue, locking, volatile, and CAS all have performance costs if synchronization is required

** In functional thinking, modifying a variable in a Lambda actually represents a side effect. Eliminating side effects is done by

Passing local variables to the next method also improves the fault tolerance of concurrent programs, so it is best not to use the previous ones

Techniques to implement this feature.

int sum = 0;
integerList.forEach(e -> { sum += e; };  //wrong
// should do this way
int sum = integerList.stream()   // java 8 way
                    .mapToInt(Integer::intValue)  // Reduce the cost of packing and improve performance
                    .sum();
Copy the code

Java Lambda

Expression syntax

(params) -> expression

(params) -> statement

(params) -> { statements }

list.stream().map(i -> i+1);  // (params) -> expression

run(() -> System.out.println(233));  // (params) -> statement

run(() ->{   //(params) -> { statements }
    System.out.println(233);
    System.out.println(123);
});

private static void run(Runnable r){
   r.run();
}

// some other lambda sample
1. (int x, int y) -> x + y                          
// takes two integers and returns their sum
2. (x, y) -> x - y                                  
// takes two numbers and returns their difference
3. () - >42                                         
// takes no values and returns the secret of the universe 
4. (String s) -> System.out.println(s)              
// takes a string, prints its value to the console, and returns nothing 
5. x -> 2 * x                                       
// takes a number and returns the result of doubling it
6. c -> { int s = c.size(); c.clear(); return s; } 
// takes a collection, clears it, and returns its previous size
Copy the code

Features:

1. As you can see, parameter types can be declared (such as String) or inferred automatically by the compiler.

2. If statements are made, an explicit return is required.

3. If there is only one parameter, omit the parentheses. But for zero or more arguments, parentheses are required.

Tips:

It is common to abbreviate the names of variables inside lambda expressions. This keeps the code shorter and on the same line. So, a or B or x or y is better than even or odd.

Why Lambda ?

The biggest advantage of Lambda expressions in Java is that they can be combined with Java 8’s Stream feature to make it easier to manipulate collections concurrently.

Previously, if you wanted to process a list concurrently, you had to maintain the code (using synchronization mechanisms such as locks or CAS), but in 1.8, you can hand it over to the class library, and you just need to write the business code.

However, in order for developers to enjoy these benefits, a collection method function needs to be provided. But we know that functions are not first-class citizens in Java, that passing parameters does not pass functions, and that using anonymous inner classes would be too clunky, so Lambda was born.

If RxJava was used in the project, the code would still be less elegant without Lambda.

Also, using Lambda does not introduce a new namespace like anonymous inner classes, if anonymous inner classes are used:

public class Test {
  public static void main(String args[]){
      Test t = new Test();
      t.run();
  }
  
  public void run(a){
        new EnClosing(){
            @Override
            public void run(a) {
                System.out.println(toString()); // "EnClosing"
                // use outclass's toString(awkward)
                System.out.println(Test.this.toString()); //"Test"}}; }@Override
  public String toString(a) {
      return "Test";
  }
  public class EnClosing{
          public void run(a){}@Override
          public String toString(a) {
              return "EnClosing"; }}}Copy the code

But in fact, this point is also teased after the change, in the first version of Java7 Lambda, in fact, did not solve the above problems.

Lambda of actual combat

In general, lambda-substitutable interfaces in the JDK are labeled @FunctionalInterface.

The purpose of this interface is to check if the interface is the work of SAM (Single Abstract Method). If the annotation is not on the interface, or if the interface has multiple unimplemented interfaces, the IDE will report an error.

If the interface has only one abstract method, why is it so troublesome to call it? For example:

default void forEach(Consumer<T> action) {
    for (T t : this) {
        action.accept(t);  // why not just use `action(t)`?
        //action(t);}}Copy the code

The reason goes like this: since method namespaces are not in conflict with variable namespaces, it can sometimes be confusing, and creating a new syntax for Lambda expressions alone can be confusing to many Java developers (laughs).

If you’re interested, take a look: Noun Kingdom Java

void action(T t) {... }default void forEach(Consumer<T> action) {
    for (T t : this) {
        action(t);  // who use use it? the local variable or the method?}}Copy the code

event

// before
mPMChatBottomView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                LogUtil.d(TAG, "ClickView: "+ v); }});// after
mPMChatBottomView.setOnClickListener((v) -> LogUtil.d(TAG, "ClickView: " + v));
Copy the code

The sorting

List<Integer> list = Arrays.asList(4.3.2.1.7.8.9.5.10);
// Even numbers are sorted first, odd numbers are sorted second
// 3
Collections.sort(list,(a,b) -> {
    boolean remainder1 = (a % 2= =0);
    boolean remainder2 = (b % 2= =0);
    returnremainder1? (remainder2? a-b:-1):(remainder2?1:a-b);
});
Copy the code

handler post

mainHandler.post(() -> tv.setText("test"));
Copy the code

JDK’s Lambda

In java.util.function, a number of interfaces are included:

For example, there’s an interface called Predicate, which can be used for filtering:

public interface Predicate<T> {
    // Check whether the parameters meet the requirements
    boolean test(T t);
	// Check both conditions
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    / / the not
    default Predicate<T> negate(a) {
        return(t) -> ! test(t); }/ / similar to | |
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    // Produces a Predicate that checks whether two objects are equal, great for list operations
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null== targetRef) ? Objects::isNull : object -> targetRef.equals(object); }}Copy the code

Use cases:

public static void main(String args[]){
    List<String> languages = Arrays.asList("Java"."Scala"."C++"."Haskell"."Lisp");

    System.out.println("Languages which starts with J :");
    filter(languages, (str)->str.startsWith("J"));

    System.out.println("Languages which ends with a ");
    filter(languages, (str)->str.endsWith("a"));

    System.out.println("Print all languages :");
    filter(languages, (str)->true);

    System.out.println("Print no language : ");
    filter(languages, (str)->false);

    System.out.println("Print language whose length greater than 4:");
    filter(languages, (str)->str.length() > 4);

    System.out.println("Print Only Lisp!");
    filter(languages, Predicate.isEqual("Lisp"));

}


public static <T> void filter(List<T> names, Predicate<T> condition) {
    for(T name: names)  {
        if(condition.test(name)) {
            System.out.println(name + ""); }}// can also present this simple way
    names.stream().filter(condition);
}
Copy the code

Results:

Languages which starts with J : Java Languages which ends with a Java Scala Print all languages : Java Scala C++ Haskell Lisp Print no language : Print language whose length greater than 4: Scala Haskell Print Only Lisp! Lisp

Use or and and:

Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
languages.stream()
        .filter(startsWithJ.and(fourLetterLong))
        .forEach((n) -> System.out.println("nName, which starts with 'J' and four letter long is : " + n));

languages.stream()
        .filter(startsWithJ.or(fourLetterLong))
        .forEach((n) -> System.out.println("nName, which starts with 'J' or four letter long is : " + n));
Copy the code

Results:

nName, which starts with ‘J’ and four letter long is : Java nName, which starts with ‘J’ or four letter long is : Java nName, which starts with ‘J’ or four letter long is : Lisp

Using Stream:

// Capitalize the strings and link them with commas
List<String> G7 = Arrays.asList("USA"."Japan"."France"."Germany"."Italy"."U.K."."Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(","));
System.out.println(G7Countries);
Copy the code

Results:

USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

The basic interfaces are as follows, all for the Stream library service (the details of these operators are not covered here) :

Those of you who have worked with RxJava will be familiar with these interfaces, because RxJava basically reuses these names and is Java 6 compliant.

Note that the difference between a Stream and an Observable is that a Java 8 Stream can only be used once, pull-base, whereas an Observable can be subscribed to multiple times, push-base.

Lambda expressions and exceptions

Lambda expressions still suck at handling exceptions, the main problem being Checked Exception.

List<File> files  = getFileList();
files.stream().map((File a) -> a.getCanonicalPath());  // can't compile
// you can't do like this
try{
    files.stream().map((File a) -> a.getCanonicalPath());
} catch (IOException e){
    e.printStackTrace();
}
// you need do this (garbage code)
files.stream().map((File a) -> {
    try {
        return a.getCanonicalPath();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return null;
});

Copy the code

But best in functional programming, Exception implies side effects, so Exception and lambda expressions don’t go together.

In normal programming, use Checked exceptions as little as possible.

Mehtod References (Method References)

To simplify Lambda methods even more, it can be intertranslated with Lambda expressions:

// static method
Arrays.sort(integerArray, Integer::compareUnsigned);
Arrays.sort(integerArray, (x,y) -> Integer.compareUnsigned(x, y));
// Bound Instance, fixed Instance call
list.forEach(System.out::println);
list.forEach(x -> System.out.println(x));
// Unbound Instance, called by different instances
list.forEach(Age::printAge);
list.forEach(age -> age.printAge());
// constructor
List<String> strList = Arrays.asList("1"."2"."3");
List<Integer> intList = strList.stream().map(Integer::new).collect(Collectors.toList());
List<Integer> intList = 
strList.stream().map(s -> new Integer(s)).collect(Collectors.toList());
Copy the code

Default methods

The reason Default methods were introduced was to extend the collection class’s interface without affecting the developer (because every new method introduced into the interface requires that the implementation class be fully implemented).

Such as the List. The foreach:

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) { action.accept(t); }}Copy the code

With the introduction of these new features, it feels like the interface is slowly evolving towards the level of abstract classes, probably because of the compromises that Java single inheritance entails.

But what if the Default method conflicts with the superclass method?

Here’s a set of principles:

  1. Classes always win. Methods in parent Classes always take precedence over default methods
  2. Otherwise, if multiple interfaces have the same Default interface, the more specific interface method takes precedence.
    public interface A {
        default void hello(a) { System.out.println("Hello World from A"); }}public interface B extends A {
        default void hello(a) { System.out.println("Hello World from B"); }}public class C implements B.A {
        public static void main(String... args) {
            new C().hello();  // print Hello World from B}}Copy the code

But what if there’s no relationship between A and B? This will cause a conflict. See:

http://www.lambdafaq.org/how-are-conflicting-method-declarations-resolved/

Future ideas for Java collections frameworks

As the cost of hardware has fallen and multi-core high-performance servers have become more common, parallel concurrency has become an age-old problem. The Members of the Java class library thought they could use some of the features of functional programming to simplify concurrent programming, starting with Fork-Join in Java 7. From the Stream Library to Java 8, the Java Library wants developers to work as little as possible.

However, the current Java 8 Stream generates a lot of intermediate variables during the conversion process, which is not a good thing for GC-type languages, so this class library will definitely optimize efficiency (more flexible and simple concurrency at the bottom) and memory (similar to rxJava -> rxJava2) in the future. For developers, it doesn’t matter because the API is still there. If you want to improve efficiency in the future, you can simply switch Java 8 to Java 9.

The design philosophy of the Stream API stems from Pipeline in Unix systems

Lambda and generic

While Lambda may seem like a complete replacement for inner classes, be aware that it has a pitfall: you can’t get generic information.

public class Erasure {
    static class RetainedFunction implements Function<Integer.String> {
        public String apply(Integer t) {
            returnString.valueOf(t); }}public static void main(String[] args) throws Exception {
        Function<Integer,String> f0 = new RetainedFunction();
        Function<Integer,String> f1 = new Function<Integer,String>() {
            public String apply(Integer t) {
                returnString.valueOf(t); }}; Function<Integer,String> f2 = String::valueOf; Function<Integer,String> f3 = i -> String.valueOf(i);for (Function<Integer,String> f : Arrays.asList(f0, f1, f2, f3)) {
            try {
                System.out.println(f.getClass().getMethod("apply", Integer.class).toString());
            } catch (NoSuchMethodException e) {
                System.out.println(f.getClass().getMethod("apply", Object.class).toString()); } System.out.println(Arrays.toString(f.getClass().getGenericInterfaces())); }}}Copy the code

Results:

public java.lang.String com.company.Erasure$RetainedFunction.apply(java.lang.Integer) [java.util.function.Function<java.lang.Integer, java.lang.String>]

public java.lang.String com.company.Erasure$1.apply(java.lang.Integer) [java.util.function.Function<java.lang.Integer, java.lang.String>]

public java.lang.Object com.company.Erasure?Lambda$1/1096979270.apply(java.lang.Object) [interface java.util.function.Function]

public java.lang.Object com.company.Erasure?Lambda$2/1747585824.apply(java.lang.Object) [interface java.util.function.Function]

You can see that Lambda expressions do not have generic information, so do not use Lambda expressions where generics information is needed.

For example, for the event bus in our project, if we use Lambda expressions, we won’t get generics:

public interface OnEvent<T> {
    void onRecv(T event);
}

public <T> Eventor addOnEvent(OnEvent<T> onEvent){
    int code = parseCode(onEvent); // Get hashCode based on the generic information
    if(code == -1) {
        throw new RuntimeException("addOnEvent ERROR!!!");
    }
    cbMap.put(code, onEvent);
    EventImpl.getInstance().register(onEvent);
    return this;
}

int parseCode(OnEvent cb){
    if(cb ! =null){
        Type[] genericType = cb.getClass().getGenericInterfaces();
        if(genericType[0] instanceof ParameterizedType){
            ParameterizedType p = (ParameterizedType)genericType[0];
            Type[] args = p.getActualTypeArguments();
            int code = args[0].hashCode();
            returncode; }}return -1;
}
Copy the code

Although some projects can use string constant pools to get the type information for Lambda, compatibility and other solutions are not good enough to consider abandoning them.

The specific implementation

Most translation strategies are translated as inner classes (inherited from Object), but here we present it in terms of methods to help you understand.

There are mainly two cases to consider (capturing variables and not capturing variables) :

1. Do not capture variables:

Methods like this are generated:

private static java.lang.Object lambda$0(java.lang.String);

Note that $0 here does not represent the inner class; it is just to show the compiled code.

Since this method does not capture external variables, the Lambda factory caches them for reuse purposes.

Anonymous inner classes usually require manual optimization (static anonymous inner classes + singletons) to avoid extra object generation, whereas for Lambda expressions without variable capture, the JVM already does this for us (singletons caching).

2. Capture variables:

Such as:

int offset = 100;
Function<String, Integer> f = s -> Integer.parseInt(s) + offset; 
Copy the code

Lambda expressions that capture variables are a bit more complicated. As before, the Lambda expression is extracted into a static method, except that the captured variables are passed into the method as normal arguments, thus generating an implementation like this:

static Integer lambda$1(int offset, String s) {    
  return Integer.parseInt(s) + offset;
}
Copy the code

This also explains why we cannot modify the referenced external variable, because it is introduced as a parameter, much the same as a non-static anonymous inner class operation.

However, if a Lambda expression uses attributes of an instance of a class, its corresponding generated methods can be instance methods, rather than static methods, to avoid passing in extra arguments.

The functional supports concurrency much better than the imperative, minimizing the capture of external variables if possible.

Because capturing variables may not only implicitly generate bridge methods (accesss$num methods generated to access external private variables), but also because Lambda expressions are not intended for reuse (singleton caching), synchronization problems can occur in multithreaded environments.

Embrace functions and eliminate side effects

Why Lambda use invokedynamic?

Why are Java 8 Lambda expressions based on InvokeDynamic?

Lambda expressions in Java 8 are based on InvokeDynamic to dynamically generate anonymous class bytecodes at run time.

This obviously incurs additional runtime overhead, so why design it this way?

Lambda expressions are assumed to generate bytecode directly during compilation (in the decoding sugar phase).

It’s going to be a lot harder to optimize in the future (because of the pit you stepped on).

So benefit 1: you can optimize dynamically in the future (currently using internal classes, you can use the optimized MethodHandle in the future (which has a reflection penalty for now).

Benefit 2: Reduces the size of bytecode generated at compile time, which can be implemented differently by different JVM languages.

Android Lambda

Android Studio 3.0 uses Lambda’s secret

Since DVM and ART are already dead, it is not possible to modify the version to support Java 8 features.

(SDK < 24 equals Java 7), (SDK >= 24 equals Java 8)

So how do you get developers to use Lambda while still being compatible with older systems?

The Android Team decided to use Desugar. Java to solve the problem.

I did a little digging,

InvokeDynamicLambdaMethodCollector used first visitor pattern access all using the InvokeDynamic bytecode, to collect all the Lambda expressions in the project, and then to LambdaDesugaring title, sugar.

In LambdaDusugaring, the inner class InvokedynamicRewriter is used to generate a class name for each Lambda expression in the following format:

internalName + “? Lambda$” + (lambdaCount++)

For example: LogInActivity? Lambda$1

Then Lambda class names and the need to generate information such as bridge method, pass to LambdaClassMaker to generate the anonymous inner class (through the Java. Lang. Invoke. MethodHandle to produce).

When the generated class is finished, a singleton object will be generated on the previous inner class if it is stateless Lambda classes that do not capture external variables.

// Generate a singleton
super.visitFieldInsn(
              Opcodes.GETSTATIC,
              lambdaClassName,
              LambdaClassFixer.SINGLETON_FIELD_NAME,
              desc.substring("()".length()));
Copy the code

Using Lambda expressions increases compilation time relatively, depending on the number of classes.

But I see a todo from the author:

TODO(kmb): Scan constant pool instead of visiting the class to find bootstrap methods etc.

If you are scanning constant pools, the scan time should be much lower.

Considerations for using Lambda on Android

If you are developing a Library for others to use, Ok, don’t use Java 1.8 for now

If you are a multi-module project, be sure to set up Java 1.8 in the main Module

Specific reason please refer to: https://developer.android.com/studio/write/java8-support.html