简体   繁体   中英

Why java cant infer right type with Self bound generic type in subclasses

Assume I have this code:

 class A<THIS extends A> {
        public THIS self() {
            return (THIS) this;
        }
    }

 class B extends A<B> { }

 A a = new A<>().self().self().self();  // OK
 B b = new B().self().self().self().self().self().self(); // OK

And it compiles fine. But when I add one more inheritance level it does not work.

    class A<THIS extends A> {
        public THIS self() {
            return (THIS) this;
        }
    }
    class B<THIS extends B> extends A<B> { }
    class C extends B<C> { }

  A<A> self2 = new A<A>().self().self().self();  // OK
  B<B> self = new B<B>().self().self().self().self(); // error -> A
  C self1 = new C().self().self();  // error -> A

I tried different generic types but nothing helps.

What must I do to make this code compile?

This approach is flawed.

It does not prevent a declaration such as

class D extends A<B> {
}

which will compile, but throw an exception at runtime, more specific:

new D().self().getClass() // => ClassCastException

To let classes provide functionality beyond their known superclass, you might try the adapter pattern.

It's base is usually an interface, like

interface Adaptable {
    T getAdapter(Class<? extends T> key);

    // for those who don't like to type long method names
    default T as(Class<? extends T> key) {
        return getAdapter(key);
    }
}

An implementation might look like

class A implements Adaptable {
    @Override
    public T getAdapter(Class<? extends T> key) {
        /*
         * To be less strict, one might also check for 'key.isInstance(this)',
         * but it's an implementation decision.
         */
        if(getClass() == key) {
            return key.cast(this);
        }
        return null;
    }
}

However, the adapter pattern allows to provide other objects, usually specialized views on the target, see the FileSource example below.

The major downside of this approach is that a client will always have to check on whether an adapter is available. However, if a client knew that an object is of the subclass it is looking for, it could simply cast it, so we don't really lose anything. The interface can also be extended using java.util.Optional , but the basic idea remains the same.

interface Adaptable {
    Optional<T> getAdapter(Class<? extends T> key);
}

For an example use case, let's say there is a Source class that models an available source for whatever process. As we know that source handling is often tricky and therefore hard to normalize into a single class or interface, we let the Source class implement Adaptable .

class Source implements Adaptable {
    @Override
    public Optional<T> getAdapter(Class<? extends T> key) {
        if(getClass() == key) {
            return Optional.of(key.cast(this));
        }
        return Optional.empty();
    }
}

Now there is a basic implementation, a FileSource , that is generally available as java.io.File .

class FileSource extends Source {
    private File pointer;

    public File asFile() {
        return pointer;
    }
}

A client can now check whether a source is available as file and perform some operation using the underlying java.io.File .

Source source;
...
source.getAdapter(FileSource.class).ifPresent(fileSource -> {
    File file = fileSource.asFile();
    // do your magic with 'file'
});

Even better, FileSource could simply provide an adapter for File . At this point, a client does not even need to care about the implementation subclass, but only on what s/he actually wants.

class FileSource extends Source {
    private File pointer;

    @Override
    public Optional<T> getAdapter(Class<? extends T> key) {
        if(File.class == key) {
            return Optional.of(key.cast(asFile()));
        }
        return super.getAdapter(key);
    }

    public File asFile() {
        return pointer;
    }
}

and

Source source;
...
source.getAdapter(File.class).ifPresent(file -> {
    // do your magic with file
});

After few hours of suffering I find right way.

class A<THIS extends A<THIS>> {
    public THIS self() {
        return (THIS) this;
    }
}
class B<T extends B<T>> extends A<T> { }
class C<T extends C> extends B<C<T>> { }
A a = new A<>().self().self().self().self().self().self();     // OK
B b = new B<>().self().self().self().self().self().self();     // OK
C c = new C<>().self().self().self().self().self().self();    // OK

And yes it not safe like @Izruo points.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM