I am wondering if it is possible to restrain a method declared on an interface to require a type within a type bound. In effect, I'd like to offer a method for casting a type that is somewhat type-safe in the absense of being able to provide real type-safety.
As an example, consider a hierarchy with a base type Base
which is inherited via an intermediate interface. Typically, one knows about a type being of interface FirstInterface
but not what specific class is implementing it. I'd like a method that allows casting to either implementation of an interface without allowing to cast to other implementations of Base
as demonstrated in the following example:
interface Base<TYPE extends Base<TYPE>> {
default <CAST extends TYPE> CAST as(Class<? extends CAST> type) {
if (type.isInstance(this)) {
return type.cast(this);
} else {
throw new IllegalArgumentException();
}
}
}
interface FirstIface<TYPE extends FirstIface<TYPE>> extends Base<TYPE> { }
class FirstClassA implements FirstIface<FirstClassA> { }
class FirstClassB implements FirstIface<FirstClassB> { }
interface SecondIface<TYPE extends SecondIface<TYPE>> extends Base<TYPE> { }
class SecondClassA implements SecondIface<SecondClassA> { }
class SecondClassB implements SecondIface<SecondClassB> { }
interface ThirdIface<TYPE extends ThirdIface<TYPE>> extends FirstIface<TYPE>, SecondIface<TYPE> { }
class ThirdClassA implements ThirdIface<ThirdClassA> { }
class ThirdClassB implements ThirdIface<ThirdClassB> { }
I'd hope to being able to make the following code compile in Java:
FirstIface<?> i = new FirstClassA();
FirstClassA a = i.as(FirstClassA.class); // desired: compiles, now: compiler error
FirstClassB b = i.as(FirstClassB.class); // desired: runtime exception, now: compiler error
The same should work for the hierarchy of ThirdIFace
, whereas the following code should render a compiler error:
SecondIface<?> i = new SecondClassA();
SecondClassA a = i.as(SecondClassA.class); // now and desired: compiler error
SecondClassB b = i.as(SecondClassB.class); // now and desired: compiler error
Is there any way to declare Base.as
to withhold this requirement? The code is auto-generated, so it would also be possible to provide an override in the interfaces which are auto-generated (as are the classes). When overrides are used, a scenario of SecondIface extends FirstIface
.
If your goal is to only allow calls that are likely to succeed, with likely to succeed meaning that we know (statically) that SUB
is a subtype of SUPER
when attempting to cast an instance of type SUPER
to type SUB
, I'm not sure that is possible.
A problem, even after overcoming the issue of not having a type variable for, let's say, the intermediate self type (FirstIface<?>
in your example), is that the compiler will infer SUPER==Object
(or SUPER==Base<?>
) if necessary to satisfy SUB extends SUPER
.
The code is auto-generated, so it would also be possible to provide an override in the interfaces which are auto-generated
I don't think that would help. The goal would be for methods in sub-interfaces to have more restrictive parameter types than what is declared in the supertype, but parameter types are not covariant (covariant parameter types would violate the Liskov substitution principle)
But since the wildcard could be any subtype of FirstIface, it does not allow for any subtype of FirstIFace
Yeah. We only have 1) the self type variable TYPE
which is for the final implementing type, and 2) a type for the Base
interface. We don't have a way to write down the type for the intermediate type information that is known at the call site.
I suspect the closest approximation to your goal would be to:
Object
for the supertype to make the code compile.Of course, this isn't a good solution, since avoiding type inference is impractical. Here is a full example:
public class Hello {
interface Base<TYPE extends Base<TYPE>> {}
interface FirstIface<TYPE extends FirstIface<TYPE>> extends Base<TYPE> {}
static final class FirstClassA implements FirstIface<FirstClassA> { }
static final class FirstClassB implements FirstIface<FirstClassB> { }
interface SecondIface<TYPE extends SecondIface<TYPE>> extends Base<TYPE> { }
static final class SecondClassA implements SecondIface<SecondClassA> { }
static final class SecondClassB implements SecondIface<SecondClassB> { }
public static void main(String[] args) {
FirstIface<?> i = new FirstClassA();
FirstClassA a = Hello.<FirstIface<?>, FirstClassA>as(i, FirstClassA.class); // works
FirstClassB b = Hello.<FirstIface<?>, FirstClassB>as(i, FirstClassB.class); // runtime error
SecondClassA c = Hello.<FirstIface<?>, SecondClassA>as(i, SecondClassA.class); // compile error
SecondClassB d = Hello.<FirstIface<?>, SecondClassB>as(i, SecondClassB.class); // compile error
}
static <SUPER, SUB extends SUPER> SUB as(SUPER obj, Class<? extends SUB> c) {
return (SUB) obj;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With