I have an interface, such as:
public interface IParent {
}
And then:
public interface IClassA extends IParent {
@Nonnull
IClassAKind getKind ();
@Nonnull
@Nonempty
default String getKindID ()
{
return getKind ().getID ();
}
}
And a second one similar:
public interface IClassB extends Parant {
@Nonnull
IClassBKind getKind ();
@Nonnull
@Nonempty
default String getKindID ()
{
return getKind ().getID ();
}
}
So both child interfaces have the same methods, but not the parent.
The issue is when I have an instance, that can be of any type, but I want to call the getKind()
.
As for now I do:
IParent aCurrentObject = getImplementation();
IClassKind kind = null;
if(aCurrentObject instanceof IClassA){
kind = ((IClassA) aCurrentObject).getKind();
} else {
kind = ((IClassB) aCurrentObject).getKind();
}
I'm doing these if
and casing too many times in the code.
Any idea how to make it nicer?
CodePudding user response:
It looks that you have an issue with IClassAKind
and IClassBKind
. So, I think you need something like this
public interface IClassKind {
IClassKind getKind();
}
class IClassAKind implements IClassKind {
@Override
public IClassAKind getKind() {
// return A-Kind object
}
}
class IClassBKind implements IClassKind {
@Override
public IClassBKind getKind() {
// return B-Kind object
}
}
So, after his change, your could will be without any casting
IParent aCurrentObject = getImplementation();
IClassKind kind = kind = aCurrentObject.getKind();
Also, need to update IClassA
and IClassB
to use the interface IClassKind
instead of a concrete class. For example
public interface IClassA extends IParent {
@Nonnull
IClassKind getKind();
// ...
}
CodePudding user response:
Why don't you declare getKind()
and getKindID()
in the parent interface ? Both child interfaces will inherit of these methods.
public interface IParent {
@Nonnull
IParent getKind ();
@Nonnull
@Nonempty
default String getKindID ()
{
return getKind().getID ();
}
}
public interface IClassA extends IParent {}
public interface IClassB extends IParent {}
CodePudding user response:
Try using generics as the following:
public interface IParent<T> {
T getKind();
}
IClassA
public interface IClassA extends IParent<IClassAKind> {
@Nonnull
@Nonempty
default String getKindID ()
{
return getKind().getID ();
}
}
IClassB
public interface IClassB extends IParent<IClassBKind> {
@Nonnull
@Nonempty
default String getKindID ()
{
return getKind().getID();
}
}
CodePudding user response:
No, as you designed it, java intentionally doesn't offer anything. There's a good reason for that. I changed some names but other than that, the code below is identical to what you have:
interface HandheldObject {}
interface Gun implements HandheldObject {
void shoot(Person p);
}
interface Camera implements HandheldObject {
void shoot(Person p);
}
If java makes it easy to call a shoot(Person) method if it exists, then you will definitely end up killing somebody by accident. That would be bad. Hence, nope, you can't do that, and that's a good thing.
The specific principle at work here is that fields and methods aren't 'defined' by their name alone; they are defined by not just their name but also the fully qualified name of the type that defines them. The actual name of a shoot method in the example above is com.foo.pkgThatStuffIsIn.Gun.shoot(Person)
- and thus confusion is impossible. There's no way that fully qualified affair is going to accidentally conflict with something completely different.
The alternative ('just call shoot
if it exists') is called structural typing and it's not a property java has (intentionally). In languages like python or javascript, you really can write the java equivalent of:
Object o = ....;
o.shoot(myFriend);
and if o happens to be a camera, that'll make a picture, if it happens to be a gun, uhoh, and if it's something else, you get an error at runtime that 'shoot' isn't a thing. Java just doesn't 'do' structural typing like this. Some hybrid languages (like scala) let you create an ersatz type that implies 'anything that has a shoot(Person) method', but java isn't one of those.
Solution
The java way to solve this problem is to roll with it: Make a type. In my example, Camera's shoot and Gun's shoot are utterly unrelated; the fact that their method names are identical is just a weird coincidence in the english language. There shouldn't be a solution and fortunately there isn't. But in your example, perhaps IClassAKind
's getKind()
method and IClassBKind
's getKind()
method are related. That is:
- They mean, semantically, the same thing.
- The author of IClassAKind and the author of IClassBKind are aware of this and intended it to mean the same thing. In addition they know that it has to mean the same thing in the future too: They are ready to promise that future updates won't change this, and if they do change this, they will mark the new version as backwards incompatible.
If that is the case, then the general concept of this getKind stuff is expressible on its own, so do that:
public interface IParent {}
public abstract class Kind { ... }
public interface Kinded {
@Nonnull
Kind getKind();
@Nonnull @Nonempty
default String getKindId() {
return getKind.getId();
}
}
public interface IClassA extends IParent, Kinded {}
public interface IClassB extends IParent, Kinded {}
You didn't explain what the actual underlying model of any of this is, so that's just so you get the idea. Perhaps Kinded
should be defined as interface Kinded implements IParent
. It's also possible you definition of Kind
needs some methods, and it's even possibly you want a specific subtype of IParent to come with a more specific kind of 'Kind', and involve some self-referential generics to sort this out. Self-ref generics gets a bit complicated, as you are now trying to establish a link between the IClassA
and ClassAKind
types. As a simple example, a single type having this loop:
interface Foobar<A extends Foobar<A>> {
A returnSelf();
public default A name(String name);
}
// can be implemented:
class SpecificFoobar extends Foobar<SpecificFoobar> {
SpecificFoobar returnSelf() {
return this;
}
// ...
}
and this lets you do stuff like:
SpecificFoobar sf = new SpecificFoobar().name("Hello");
without the generics that wouldn't compile (the name
impl might return this;
but if the Foobar interface defines it as public Foobar name(String name);
then javac doesn't know that, it only knows that it returns Foobar
, not SpecificFoobar
.
If this all sounds complicated to you, and the first snippet I showed is good enough, then forget about this. As I said, it's a bit complicated; generics can be like that.