views:

405

answers:

5

Hello!

I want to create an abstract collection class (called Space) and an abstract element class (called Atom). Instances of both have to know each other (exactly typed). That's the problem.

abstract class Space<A extends Atom>{
  // ...
}

abstract class Atom<S extends Space>{
  // ...
}

Not good:

"A extends Atom" means any Atom, but not a strongly typed one

"S extends Space" means any Space, but not a strongly typed one

I can't reach complete type-safety with the following attempts either:

abstract class Space<A extends Atom<? extends Space>>
abstract class Atom<S extends Space<? extends Atom>>

abstract class Space<S, A extends Atom<S extends Space<A>>>
abstract class Atom<A, S extends Space<A extends Atom<S>>>

and so on ...

Remember, these two classes are abstract, and I want that any possible two subclasses are typed according to each other. That means, the classes SomeSpace and SomeAtom in the following example must have a strong "type knowledge" of each other:

class SomeSpace extends Space<SomeAtom>
class SomeAtom extends Atom<SomeSpace>
A: 

The only way to make parameterized types in Java know anything at all about their actual concrete type is to pass in a type parameter at construction:

public Foo<T>(class<T> klass, // other parameters

The meta answer is that circular type dependencies are questionable, and indicate a need to revisit your design. Generally, a better solution is to have a hierarchical dependency on a third type:

public class SomeAtom extends Atom<Something>
public class SomeSpace extends Space<Something>

The meta-meta answer is that "type safety" has its limitations, no matter what the language: http://www.kdgregory.com/index.php?page=java.generics.cpp

kdgregory
+2  A: 
abstract class Space<S extends Space<S,A>, A extends Atom <A, S>> {}
abstract class Atom <A extends Atom <A,S>, S extends Space<S, A>> {}

Or, I guess, if you prefer

abstract class Space<S extends Space<S,A>, A extends Atom<S, A>> {}
abstract class Atom <S extends Space<S,A>, A extends Atom<S, A>> {}
Tom Hawtin - tackline
I would tend to make the order of parameters consisten.
Pete Kirkham
Tom, your proposal works, but a very special thing is not working (very strange), please see the whole code in my 'answer', I hope you can tell me what's the problem? Thank you
ivan_ivanovich_ivanoff
A: 

Thank you, "Tom Hawtin - tackline", I've incorporated your answer, and it fits. Additionally, following code demonstrates the (slightly strange) necessity to alter List type parameters (if you need List of atoms)

interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
    List<IAtom<? extends S, ? extends A>> getList();
}
interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
    S getSpace();
}

abstract class Space<S extends Space<S, A>, A extends Atom<S, A>>
    implements ISpace<S, A> {

    private final List<IAtom<? extends S, ? extends A>> atoms =
            new LinkedList<IAtom<? extends S, ? extends A>>();

    public Space() {
    }

    @Override
    public List<IAtom<? extends S, ? extends A>> getList() {
        return atoms;
    }
}

abstract class Atom<S extends Space<S, A>, A extends Atom<S, A>>
    implements IAtom<S, A> {

    private final S space;

    public Atom(S someSpace) {
        this.space = someSpace;

/// THIS WILL NOT WORK WITHOUT THOSE STRANGE LIST TYPE PARAMETERS
        space.getList().add(this);
    }

    @Override
    public S getSpace() {
        return space;
    }
}

class Space1 extends Space<Space1, Atom1> {
    public Space1() {
    }
}

class Atom1 extends Atom<Space1, Atom1> {
    public Atom1(Space1 someSpace) {
        super(someSpace);
    }
}

The idea behind the whole thing is: I need element objects which know their container type-safely and container objects which know their elements type-safely.

ivan_ivanovich_ivanoff
Remove all the `? extends`.
Tom Hawtin - tackline
You'll probably also need to add a method to Atom: abstract A getThis(); and use that instead of the raw this.
Tom Hawtin - tackline
As I said, without `? extends` it doesn't work.And, `A getThis(){ return this; }` doesn't work either :(
ivan_ivanovich_ivanoff
YES! Found a solution:`List<IAtom<? extends S, ? extends A>>`
ivan_ivanovich_ivanoff
+1  A: 

I wouldn’t try to solve that problem with generics. Instead, supply a Factory that always creates matching objects.

class UniverseFactory {
    public Space getSpace();
    public Atom getAtom();
}

You could then perform a type check in your implementations to ensure that only matching objects are used.

Bombe
+3  A: 

This works for me, although I got really confused about all those generic constraints. Which means I can't guarantee that it does what it's supposed to do:

interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
        List<? extends IAtom<S, A>> getList(); //// CHANGED
}

interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
        S getSpace();
}

abstract class Space<S extends Space<S, A>, A extends Atom<S, A>>
                implements ISpace<S, A> {

        private final List<Atom<S, A>> atoms = new LinkedList<Atom<S, A>>(); ////CHANGED

        public Space() {
        }
        public Space<S, A> getSpace() {
                return this;
        }
        @Override
        public List<Atom<S, A>> getList() {  //// CHANGED
                return atoms;
        }
}

abstract class Atom<S extends Space<S, A>, A extends Atom<S, A>>
                implements IAtom<S, A> {

        private final S space;

        public Atom(S someSpace) {
                this.space = someSpace;
                space.getList().add(this);
        }

        @Override
        public S getSpace() {
                return space;
        }

        public Atom<S, A> getAtom() {
                return this;
        }
}

class Space1 extends Space<Space1, Atom1> {
        public Space1() {
        }
}

class Atom1 extends Atom<Space1, Atom1> {
        public Atom1(Space1 someSpace) {
                super(someSpace);
        }
}
HerdplattenToni
Yes, this works also, thank you!So the 'secret' is to override getList() with a more concrete type than the interface defines.
ivan_ivanovich_ivanoff