Search code examples
javainterfacecompiler-errorsrunnable

My implementation of a `Runnable` cannot resolve a method on an object of a declared interface


I have a simple Runnable implementation in Java 15.

package work.basil.example;

public class MonsterRunnable < Monster > implements Runnable
{
    private final Monster monster;

    // Constructor
    public MonsterRunnable ( Monster monster )
    {
        this.monster = monster;
    }

    @Override
    public void run ( )
    {
        this.monster.actOut(); // 🡄 Compiler error: Cannot resolve method 'actOut' in 'Monster'
    }
}

The compiler in my IDE (IntelliJ 2020.3) cannot resolve the method actOut on the this.monster.actOut(); line of the run method.

Yet that method is clearly defined on the interface, shown next.

package work.basil.example;

public sealed interface Monster
        permits Demon, Witch
{
    String name ( ); // This getter method implemented implicitly by `record` in our concrete classes `Demon` & `Witch`.

    public void actOut ( );  // 🡄 Method is declared, yet cannot be resolved in `Runnable`. 
}

My little demo app is using the Java 15 preview features JEP 360: Sealed Classes (Preview) and JEP 384: Records (Second Preview). But I do not see how that might be a source of trouble here.


I suppose the above should be enough code for the Question. But I can show the pair of implementations of the interface, Demon and Witch.

package work.basil.example;

public record Demon(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " buys a soul." );
    }
}

… and …

package work.basil.example;

public record Witch(String name) implements Monster
{
    @Override
    public void actOut ( )
    {
        System.out.println( "This monster " + this.getClass().getSimpleName() + " named " + this.name + " casts a spell." );
    }
}

Finally, here is a demonstration of the actOut method working — if I kill the line this.monster.actOut(); in the offending run method.

package work.basil.example;

/**
 * Hello world!
 */
public class MonterMash
{
    public static void main ( String[] args )
    {
        System.out.println( "Hello World!" );
        MonterMash monterMash = new MonterMash();
        monterMash.demo();
    }

    private void demo ( )
    {
        Monster monster = new Witch( "Rowena" );
        System.out.println( "monster = " + monster );
        monster.actOut();
    }
}

When run:

Hello World!

monster = Witch[name=Rowena]

This monster Witch named Rowena casts a spell.


Solution

  • The signature public class MonsterRunnable < Monster > implements Runnable introduces a generic parameter Monster for MonsterRunnable. A generic parameter makes a class .. generic. That means you can use the class with whatever type.

    E.g. when you use a List<String>, <String> is the argument, the type of the elements. List itself is declared as public interface List<E> extends Collection<E>. Notice that E is the generic parameter; a variable of type List<String> uses that parameter to reference a list of Strings. You can also reference a list of Integers: List<Integer>. You can use List with whatever element type as it is generic by having the generic parameter E. E is for "element" and is Java convention: see Type Parameter Naming Conventions. E is used within List to express methods and so on.

    So here in your example, the generic parameter Monster "hides" the type Monster, which is your class. The introduced generic type Monster doesn't have the method actOut, which is what the compiler tells you.

    What you want is that monster is of a type that extends Monster, simple inheritance. Therefore, just drop the generic parameter and you'll have what you want:

    public class MonsterRunnable implements Runnable
    

    Your note in the comment section:

    The actually goal of this entire experiment was to get a generics type declared for a Runnable implementation.

    You need to express that the generic in MonsterRunnable is an implementation of Monster:

    package work.basil.example;
    
    public class MonsterRunnable < T extends Monster > implements Runnable
    {
        private final T monster;
    
        // Constructor
        public MonsterRunnable ( T monster )
        {
            this.monster = monster;
        }
    
        @Override
        public void run ( )
        {
            this.monster.actOut();
        }
    }
    

    Now MonsterRunnable is generic by using T, which is restricted to extend (implement) Monster.