Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Save more on your purchases now! discount-offer-chevron-icon
Savings automatically calculated. No voucher code required.
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Java Coding Problems

You're reading from   Java Coding Problems Become an expert Java programmer by solving over 250 brand-new, modern, real-world problems

Arrow left icon
Product type Paperback
Published in Mar 2024
Publisher Packt
ISBN-13 9781837633944
Length 798 pages
Edition 2nd Edition
Languages
Arrow right icon
Author (1):
Arrow left icon
Anghel Leonard Anghel Leonard
Author Profile Icon Anghel Leonard
Anghel Leonard
Arrow right icon
View More author details
Toc

Table of Contents (16) Chapters Close

Preface 1. Text Blocks, Locales, Numbers, and Math 2. Objects, Immutability, Switch Expressions, and Pattern Matching FREE CHAPTER 3. Working with Date and Time 4. Records and Record Patterns 5. Arrays, Collections, and Data Structures 6. Java I/O: Context-Specific Deserialization Filters 7. Foreign (Function) Memory API 8. Sealed and Hidden Classes 9. Functional Style Programming – Extending APIs 10. Concurrency – Virtual Threads and Structured Concurrency 11. Concurrency ‒ Virtual Threads and Structured Concurrency: Diving Deeper 12. Garbage Collectors and Dynamic CDS Archives 13. Socket API and Simple Web Server 14. Other Books You May Enjoy
15. Index

43. Invoking default methods from Proxy instances

Starting with JDK 8, we can define default methods in interfaces. For instance, let’s consider the following interfaces (for brevity, all methods from these interfaces are declared as default):

Figure 2.26.png

Figure 2.26: Interfaces: Printable, Writable, Draft, and Book

Next, let’s assume that we want to use the Java Reflection API to invoke these default methods. As a quick reminder, the Proxy class goal is used to provide support for creating dynamic implementations of interfaces at runtime.

That being said, let’s see how we can use the Proxy API for calling our default methods.

JDK 8

Calling a default method of an interface in JDK 8 relies on a little trick. Basically, we create from scratch a package-private constructor from the Lookup API. Next, we make this constructor accessible – this means that Java will not check the access modifiers to this constructor and, therefore, will not throw an IllegalAccessException when we try to use it. Finally, we use this constructor to wrap an instance of an interface (for instance, Printable) and use reflective access to the default methods declared in this interface.

So, in code lines, we can invoke the default method Printable.print() as follows:

// invoke Printable.print(String)
Printable pproxy = (Printable) Proxy.newProxyInstance(
  Printable.class.getClassLoader(),
  new Class<?>[]{Printable.class}, (o, m, p) -> {
    if (m.isDefault()) {
      Constructor<Lookup> cntr = Lookup.class
        .getDeclaredConstructor(Class.class);
      cntr.setAccessible(true);
      return cntr.newInstance(Printable.class)
                 .in(Printable.class)
                 .unreflectSpecial(m, Printable.class)
                 .bindTo(o)
                 .invokeWithArguments(p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print("Chapter 2");

Next, let’s focus on the Writable and Draft interfaces. Draft extends Writable and overrides the default write()method. Now, every time we explicitly invoke the Writable.write() method, we expect that the Draft.write() method is invoked automatically behind the scenes. A possible implementation looks as follows:

// invoke Draft.write(String) and Writable.write(String)
Writable dpproxy = (Writable) Proxy.newProxyInstance(
 Writable.class.getClassLoader(),
  new Class<?>[]{Writable.class, Draft.class}, (o, m, p) -> {
   if (m.isDefault() && m.getName().equals("write")) {
    Constructor<Lookup> cntr = Lookup.class
     .getDeclaredConstructor(Class.class);
    cntr.setAccessible(true); 
    cntr.newInstance(Draft.class)
        .in(Draft.class)
        .findSpecial(Draft.class, "write",
           MethodType.methodType(void.class, String.class), 
           Draft.class)
        .bindTo(o)
        .invokeWithArguments(p);
    return cntr.newInstance(Writable.class)
        .in(Writable.class)
        .findSpecial(Writable.class, "write",
           MethodType.methodType(void.class, String.class), 
           Writable.class)
        .bindTo(o)
        .invokeWithArguments(p);
    }
    return null;
  });
// invoke Writable.write(String)
dpproxy.write("Chapter 1");

Finally, let’s focus on the Printable and Book interfaces. Book extends Printable and doesn’t define any methods. So, when we call the inherited print() method, we expect that the Printable.print() method is invoked. While you can check this solution in the bundled code, let’s focus on the same tasks using JDK 9+.

JDK 9+, pre-JDK 16

As you just saw, before JDK 9, the Java Reflection API provides access to non-public class members. This means that external reflective code (for instance, third-party libraries) can have deep access to JDK internals. But, starting with JDK 9, this is not possible because the new module system relies on strong encapsulation.

For a smooth transition from JDK 8 to JDK 9, we can use the --illegal-access option. The values of this option range from deny (sustains strong encapsulation, so no illegal reflective code is permitted) to permit (the most relaxed level of strong encapsulation, allowing access to platform modules only from unnamed modules). Between permit (which is the default in JDK 9) and deny, we have two more values: warn and debug. However, --illegal-access=permit; support was removed in JDK 17.

In this context, the previous code may not work in JDK 9+, or it might still work but you’ll see a warning such as WARNING: An illegal reflective access operation has occurred.

But, we can “fix” our code to avoid illegal reflective access via MethodHandles. Among its goodies, this class exposes lookup methods for creating method handles for fields and methods. Once we have a Lookup, we can rely on its findSpecial() method to gain access to the default methods of an interface.

Based on MethodHandles, we can invoke the default method Printable.print() as follows:

// invoke Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
    Printable.class.getClassLoader(),
    new Class<?>[]{Printable.class}, (o, m, p) -> {
      if (m.isDefault()) {
       return MethodHandles.lookup()
         .findSpecial(Printable.class, "print",  
           MethodType.methodType(void.class, String.class), 
           Printable.class)
         .bindTo(o)
         .invokeWithArguments(p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print("Chapter 2");

While in the bundled code, you can see more examples; let’s tackle the same topic starting with JDK 16.

JDK 16+

Starting with JDK 16, we can simplify the previous code thanks to the new static method, InvocationHandler.invokeDefault(). As its name suggests, this method is useful for invoking default methods. In code lines, our previous examples for calling Printable.print() can be simplified via invokeDefault() as follows:

// invoke Printable.print(String doc)
Printable pproxy = (Printable) Proxy.newProxyInstance(
  Printable.class.getClassLoader(),
    new Class<?>[]{Printable.class}, (o, m, p) -> {
      if (m.isDefault()) {
        return InvocationHandler.invokeDefault(o, m, p);
      }
      return null;
  });
// invoke Printable.print()
pproxy.print("Chapter 2");

In the next example, every time we explicitly invoke the Writable.write() method, we expect that the Draft.write() method is invoked automatically behind the scenes:

// invoke Draft.write(String) and Writable.write(String)
Writable dpproxy = (Writable) Proxy.newProxyInstance(
 Writable.class.getClassLoader(),
  new Class<?>[]{Writable.class, Draft.class}, (o, m, p) -> {
   if (m.isDefault() && m.getName().equals("write")) {
    Method writeInDraft = Draft.class.getMethod(
     m.getName(), m.getParameterTypes());
    InvocationHandler.invokeDefault(o, writeInDraft, p);
    return InvocationHandler.invokeDefault(o, m, p);
   }
   return null;
 });
// invoke Writable.write(String)
dpproxy.write("Chapter 1");

In the bundled code, you can practice more examples.

You have been reading a chapter from
Java Coding Problems - Second Edition
Published in: Mar 2024
Publisher: Packt
ISBN-13: 9781837633944
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €18.99/month. Cancel anytime