Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 Interface with default Methods not working when Jacoco Enabled

We are having an interface with default methods and we implemented that interface in both Java and Kotlin classes and we provided the implementation for the non default methods.

When we run in debug mode (which doesn't have testCoverageEnabled = true) and the app works as expected. But when we run in different config with testCoverageEnabled = true, the app is crashing with below error

java.lang.NoSuchMethodError: No static method $$triggerInterfaceInit()V in class Lcom/ui/viewholders/CAViewContract$$CC; or its super classes (declaration of 'ui.viewholders.CAViewContract$$CC' appears in /data/app/SMCXbiLYvHb1Kk08Kee__g==/base.apk)
    at home.c.CCFragment.<clinit>(Unknown Source:0)
    at home.HomePageCardProvider.getFragment(HomePageCardProvider.java:17)
    at home.HomeFragment.handleCardFragment(HomeFragment.java:172)

Note: 1. JaCoCo version: "0.8.0" 2. Operating system: Android with minSdk 21

If we change the minSdk to 24, with testCoverageEnabled = true itself, it is working. We are not able to figure out the exact problem.

like image 796
Dinesh Balasubramanian Avatar asked Jun 06 '18 07:06

Dinesh Balasubramanian


People also ask

How can you avoid diamond problems with default methods in Java 8?

How to avoid Diamond Problem With Default Methods in Java 8. In order to solve this error, you need to override the write() method in your implementation class i.e. class Multitalented here, this will remove the ambiguity, making the compiler happy enough to compile this class.

What is the use of default method in interface in java 8?

Default methods enable you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, default methods enable you to add methods that accept lambda expressions as parameters to existing interfaces.

Why default methods are introduced in interface?

The default methods were introduced to provide backward compatibility so that existing interfaces can use the lambda expressions without implementing the methods in the implementation class. Default methods are also known as defender methods or virtual extension methods.

Are java methods public by default?

Like regular interface methods, default methods are implicitly public; there's no need to specify the public modifier. Unlike regular interface methods, we declare them with the default keyword at the beginning of the method signature, and they provide an implementation.


1 Answers

This problem can occur if you want to invoke the default implementation of a method that hasn't a default implementation in the interface that your class explicitly implements it. (But has a default implementation in a base (parent, super) interface of that interface).

Example: Suppose these defenitions:

class A implements DerivedInterface /*, ...*/ {
    @Override public void methodFromBaseInterface() {
        DerivedInterface.super.methodFromBaseInterface(); // Error:
            // NoSuchMethodError: No static method methodFromBaseInterface
    }
    // ...
}

interface DerivedInterface extends BaseInterface {
    // ... 
    // `methodFromBaseInterface` hasn't overriden here.
}

interface BaseInterface {
    default void methodFromBaseInterface() { /* ...*/ }
    // ...
}

Then execute:

A a = new A();
a.methodFromBaseInterface(); // This cause above error!

And you get an error at mentioned point!

(Marginal note: You may need to define at least one method in DerivedInterface to avoid getting NoClassDefFoundError at runtime!)


This is similar to a bug! We used super keyword. Why expected static method?!! Another point is that the above code hasn't any problem and you can run it in any Java 8 compatible environment without any problem!

I think the problem is related to incomplete support of Java 8 language APIs in Android platform:

Android Studio 3.0 and later supports all Java 7 language features and a subset of Java 8 language features that vary by platform version.

Specially see Java 8 Language API and Compatible minSdkVersion table in that page:

java.lang.FunctionalInterface : API level 24 or higher.


Workarounds I found:

  1. If you have access to the definition of DerivedInterface simply override methodFromBaseInterface and explicitly delegates it to its super interface:

    interface DerivedInterface extends BaseInterface {
        @Override default void methodFromBaseInterface() {
            BaseInterface.super.methodFromBaseInterface();
        }
        // ...
    }
    
  2. Define a middle class that implements BaseInterface and derive A from it. Then run methodFromBaseInterface indirectly throw the middle class:

    class MiddleClass /*extends B*/ implements BaseInterface {}
    
    class A extends MiddleClass implements DerivedInterface {
        @Override public void methodFromBaseInterface() {
            super.methodFromBaseInterface(); // Indirectly from `MiddleClass`
        }
        // ...
    }
    

    Note: Uncomment /*extends B*/ if your A class previously has a super named B.

like image 182
Mir-Ismaili Avatar answered Oct 07 '22 23:10

Mir-Ismaili