In depth understanding of Lambda expressions in Java

  • 2020-04-01 04:02:07
  • OfStack

  Java 8 came along with a new feature: functional programming using Lambda expressions (jsr-335). Today we're going to talk about one part of Lambda: the virtual extension method, also known as the public defender method. This feature lets you provide the default implementation of methods in the interface definition. For example, you can declare a method definition for an existing interface (such as a List or Map) so that other developers don't have to re-implement the methods, which are sort of abstract classes, but are actually interfaces. Of course, Java 8 is still theoretically compatible with existing libraries.

The virtual extension approach brings the feature of multiple inheritance to Java, although the team claims that unlike multiple inheritance, the virtual extension approach is limited to behavioral inheritance. Perhaps you can see the shadow of multiple inheritance through this feature. But you can still simulate inheritance of instance state. I'll describe inheritance of implementation state through mixins in more detail in the next article in Java 8.

What are mixins ?

Mixins are composed abstract classes that are primarily used to add multiple services to a class in a multi-inheritance context, where multiple inheritance combines multiple mixins into your class. For example, if you have a class that represents "horse," you can instantiate the class to create an instance of "horse," and then extend it by inheriting things like "garage" and "garden."
 
Val myHouse = new House with Garage with Garden

Inheriting from mixins is not a specific specification, it's just a way to add functionality to an existing class. In OOP, with mixins, you have a way to improve class readability.

For example, in Python's   There is a way to use mixins in the socketserver module, where mixins help four services based on different sockets, including UDP and TCP services that support multiple processes and UDP and TCP services that support multiple threads.
 


class ForkingUDPServer(ForkingMixIn, UDPServer): pass
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
 
class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

What is a virtual extension method ?


Java 8 will introduce the concept of virtual extension methods, also known as the public defender method. Let's simplify this concept to VEM.

VEM is designed to provide default method definitions for Java interfaces, and you can use it to add new method definitions to existing interfaces, such as the collections API in Java. Such third-party libraries, such as Hibernate, do not have to duplicate all of the methods that implement these collection apis because some default methods are already provided.

Here is an example of how to define a method in an interface:
 


public interface Collection<T> extends Iterable<T> {
 
  <R> Collection<R> filter(Predicate<T> p)
    default { return Collections.<T>filter(this, p); }
 
}

Simulation of Java 8 mixing

Now let's achieve a blending effect with VEM, with a caveat: don't use it at work!

The following implementation is not thread-safe, and there may be memory leaks, depending on the hashCode and equals methods you define in the class, which is another drawback, which I'll discuss later.

First we define an interface (mock state Bean) and provide the default definition of the method:
 


public interface SwitchableMixin {
  boolean isActivated() default { return Switchables.isActivated(this); }
  void setActivated(boolean activated) default { Switchables.setActivated(this, activated); }
}

We then define a utility class that contains a Map instance to hold the association between the instance and the state, which is represented by a private nested class in the utility class:
 


public final class Switchables {
 
  private static final Map<SwitchableMixin, SwitchableDeviceState> SWITCH_STATES = new HashMap<>();
 
  public static boolean isActivated(SwitchableMixin device) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    return state != null && state.activated;
  }
 
  public static void setActivated(SwitchableMixin device, boolean activated) {
    SwitchableDeviceState state = SWITCH_STATES.get(device);
    if (state == null) {
      state = new SwitchableDeviceState();
      SWITCH_STATES.put(device, state);
    }
    state.activated = activated;
  }
 
  private static class SwitchableDeviceState {
    private boolean activated;
  }
 
}

Here is a use case that highlights inheritance of state:
 


private static class Device {}
 
private static class DeviceA extends Device implements SwitchableMixin {}
 
private static class DeviceB extends Device implements SwitchableMixin {}

"Something totally different."

The above implementation seems to work fine, but Oracle Java language architect Brian Goetz challenged me to say that the current implementation is not working (assuming thread safety and memory leaks are resolved)
 


interface FakeBrokenMixin {
  static Map<FakeBrokenMixin, String> backingMap
    = Collections.synchronizedMap(new WeakHashMap<FakeBrokenMixin, String>());
 
  String getName() default { return backingMap.get(this); }
  void setName(String name) default { backingMap.put(this, name); }
}
 
interface X extends Runnable, FakeBrokenMixin {}
 
X makeX() { return () -> { System.out.println("X"); }; }
 
  X x1 = makeX();
  X x2 = makeX();
  x1.setName("x1");
  x2.setName("x2");
 
  System.out.println(x1.getName());
  System.out.println(x2.getName());


Guess what happens when this code executes?
Problem solving

At first glance, the code for this implementation is fine. X is a one-method interface because getName and setName are already defined by default, but the Runable interface's run method is not defined, so we can generate an instance of X using a lambda expression and then provide an implementation of the run method, just like makeX. Therefore, you want the result of this program to be:
 


x1
x2

If you delete the call to the getName method, the result will be:
 


MyTest$1@30ae8764
MyTest$1@123acf34

These two lines show that the execution of the makeX method came from two different instances when it was currently generated by OpenJDK 8 (here I'm using OpenJDK 8 24.0-b07).

However, the current OpenJDK 8 does not reflect the behavior of the final Java 8. To solve this problem, you need to use the special parameter -xdlambdatomethod to run the javac command.

 


x2
x2

If the getName method is not called, it displays:
 


MyTest$$Lambda$1@5506d4ea
MyTest$$Lambda$1@5506d4ea

Each call to the makeX method appears to be a singleton instance from the same anonymous inner class, and if you look at the directory that contains the compiled Java class files, you will see that there is no file named MyTestClass$$$Lambda$1.class.

Because lambda expressions are not fully translated at compile time, and in fact are translated at compile and run time, the javac compiler turns lambda expressions into the JVM's new instruction invokedynamic (JSR292). This directive contains all the meta-information necessary to execute a lambda expression at run time. This includes the name of the method to be invoked, the input/output type, and a method called bootstrap. The bootstrap method is used to define the instance that receives the method call, and once the JVM executes the invokedynamic instruction, the JVM invokes the lambda metafactory method on the specific bootstrap.

To return to the question, the lambda expression turns into a private static method, () -> {System. Out. Println (" X "); } is transferred to MyTest:
 


private static void lambda$0() {
  System.out.println("X");
}

You can see this method if you use the javap decomcompiler and use the -private parameter, or you can use the -c parameter to see a more complete transformation.

When you run the program, the JVM calls lambda metafactory method to try to interpret the invokedynamic instruction. In our example, the first time makeX is called, lambda metafactory method generates an instance of X and dynamically links the run method to the lambda$0 method.
Has it been repaired? Is there a solution?

There is no immediate fix or solution to the problem. Although Oracle's Java 8 plan activates the -xdlambdatomethod parameter by default, because this parameter is not part of the JVM specification, implementations of different vendors and JVMS are different. With a lambda expression, the only thing you can expect is to implement your interface methods in the class.


Other methods

At this point, although our mimicry of mixins is not compatible with Java 8, it is possible to add multiple services to an existing class through multiple inheritance and delegation. This method is the virtual field pattern.

So come and see our Switchable.
 


interface Switchable {  boolean isActive();
  void setActive(boolean active);
}

We need a Switchable interface and provide an additional abstract method to return the implementation of the Switchable. The integrated methods contain default definitions that use getters to convert calls to the Switchable implementation:

 


public interface SwitchableView extends Switchable {
  Switchable getSwitchable();
 
 
  boolean isActive() default { return getSwitchable().isActive(); }
  void setActive(boolean active) default { getSwitchable().setActive(active); }
}

Next, we create a complete Switchable implementation:
 


public class SwitchableImpl implements Switchable {
 
 
  private boolean active;
 
 
  @Override
  public boolean isActive() {
    return active;
  }
 
 
  @Override
  public void setActive(boolean active) {
    this.active = active;
  }
}

Here is an example of using the virtual field pattern:
 


public class Device {}
 
 
public class DeviceA extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
 
 
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}
 
 
public class DeviceB extends Device implements SwitchableView {
  private Switchable switchable = new SwitchableImpl();
 
 
  @Override
  public Switchable getSwitchable() {
    return switchable;
  }
}

conclusion

In this article, we used two methods to add multiple services to a class through Java 8's virtual extension method. The first method USES a Map to store instance state, which is dangerous because it is not thread-safe and has memory leaks, which are completely dependent on different JVM implementations of the Java language. Another approach is to use the virtual field pattern to return the final implementation instance through an abstract getter. The second method is more independent and safer.

Virtual extension method is a new feature of Java, this article mainly introduces the implementation of multiple inheritance, you will have more in-depth research and other applications, don't forget to share with you.


Related articles: