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.
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 String
s. You can also reference a list of Integer
s: 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
.