4

Suppose I have below code snippet with JDK 1.7. Using Generics, I am getting confused how to achieve a generic parameterized type with exclusive, not inclusive bounds with using extends. For example:

abstract class BaseAbstract {

}

class ChildWhichExtendsBaseAbstract extends BaseAbstract {

}

class SomeOtherClassWhichUseGenerics<T extends BaseAbstract> {

}

public class Test {

    public static void main(String[] args) {

        /**
         * I just don't want JVM to compile below line, because I want to use
         * generic type with {@link SomeOtherClassWhichUseGenerics} which must
         * not be {@link BaseAbstract}
         */
        SomeOtherClassWhichUseGenerics<BaseAbstract> obj1 = 
                new SomeOtherClassWhichUseGenerics<BaseAbstract>();

        /**
         * I just want JVM to compile below line, because I want to use generic
         * type with {@link SomeOtherClassWhichUseGenerics} which must be only
         * subclasses of BaseAbstract
         */
        SomeOtherClassWhichUseGenerics<ChildWhichExtendsBaseAbstract> obj2 = 
                new SomeOtherClassWhichUseGenerics<ChildWhichExtendsBaseAbstract>();
    }
}

I think this should be one of the known problems/issues/requirements. Is there a work around possible?

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
Vishal Zanzrukia
  • 4,902
  • 4
  • 38
  • 82
  • 1
    When we use bounded type parameter name followed by `extends` we specify a upper bound. In your case it will allow base class and all its derived classes. I do not think you can restrict it to only derived classes. – akhil_mittal Aug 14 '15 at 05:15
  • A workaround is probably some sort of a runtime check of the elements' type when you add them, but that doesn't help you at compile time. – Yosef Weiner Aug 14 '15 at 05:17
  • that I know, but what if I want to achieve this? no way? any work-arounds? – Vishal Zanzrukia Aug 14 '15 at 05:17

2 Answers2

4

extends means an inclusive upper bound.

That's it, end of discussion. There isn't really a way to say extends T but not T. From the JLS §4.5.1:

A type argument T1 is said to contain another type argument T2, written T2 <= T1, if the set of types denoted by T2 is provably a subset of the set of types denoted by T1 under the reflexive and transitive closure of the following rules (where <: denotes subtyping (§4.10)):

// Snip

  • T <= ? extends T

That's it. T is contained within ? extends T.

Additionally, even if you could bound it in this way, it wouldn't really do much. Let's say you could; this would be illegal:

public List<? exclusive-extends MyClass> list = new ArrayList<>();
list.add(new MyClass());

But this anonymous class wouldn't be:

public List<? exclusive-extends MyClass> list = new ArrayList<>();
list.add(new MyClass(){});

There may be a workaround to your real problem, however.

It sounds to me like the real issue is that you don't want anybody to be putting instances of BaseAbstract into your generic type; you only want subclasses.

Well, doing that is easy enough to achieve; BaseAbstract can't be instantiated at all, ever, because it's an abstract class. So as long as the parameterized type is an abstract class, you're ok.

If you have a real class here, and not an abstract class, you can simply put a dummy abstract class in between, e.g.

class Foo { }
abstract class AbstractFoo extends Foo { } // Newly added
class Bar extends AbstractFoo { } // previously extended Foo
class Baz extends AbstractFoo { } // previously extended Foo
durron597
  • 31,968
  • 17
  • 99
  • 158
2

I see the only option: make BaseAbstract package-private and put all the children in the same package. This way BaseAbstract will not be accessible from outside. For example, consider the following code.

// a/BaseAbstract.java
package a;

abstract class BaseAbstract {

}

// a/ChildWhichExtendsBaseAbstract.java
package a;

public class ChildWhichExtendsBaseAbstract extends BaseAbstract {

}

// a/SomeOtherClassWhichUseGenerics.java
package a;

public class SomeOtherClassWhichUseGenerics<T extends BaseAbstract> {

}

// b/Test.java
package b;

import a.ChildWhichExtendsBaseAbstract;
import a.SomeOtherClassWhichUseGenerics;

public class Test {

    public static void main(String[] args) {

        /**
         * Compile error as BaseAbstract is invisible
         */
        SomeOtherClassWhichUseGenerics<BaseAbstract> obj1 = 
                new SomeOtherClassWhichUseGenerics<BaseAbstract>();

        /**
         * Compiles correctly
         */
        SomeOtherClassWhichUseGenerics<ChildWhichExtendsBaseAbstract> obj2 = 
                new SomeOtherClassWhichUseGenerics<ChildWhichExtendsBaseAbstract>();
    }
}

The limitation of this approach is that you should define all the possible child classes in the same package a.

Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334