3

I'm working on a Java 7 based project. I wanted to introduce a generic parameter on one of the classes, so that I can slowly eliminate the class casts required to make the code work.

Let's introduce a class, that is similar to the one I'm working on:

public class A {
   public List<B> getB() { ... }
}

B has lots of child classes, which when used, need to be casted, which is not ideal obviously. The modification I'd like to make is like this:

public class A<T extends B> {
    public List<T> getB() {...}
}

This can eliminate the casting required in some cases. However, A is used in a big part of the project, which makes it not too efficient to go through and rewrite every case it's used.

I hoped, that using the raw A class will make it so that getB() will return a type of B. The reality is that if used raw, getB() will return a type of Object instead. Is it some Java 7 related behavior, or am I overlooking something? Is there a way to make getB() return a type of B when A is raw? I didn't seem to come across this issue in Java 8, though I haven't really worked on a project this poorly structured either.

Edit: In the comments, I was requested for concrete code example, where the issue occurs. Assuming the above classes (edited slightly to better fit my actual scenario):

A a = new A();
for(B b: a.getB()) { // Compiler error, because `getB()` returns a list of `Object`s instead of `B`s.
    ...
}

A<B> a = new A<B>();
for(B b: a.getB()) { // Everything is fine here obviously
    ...
}
László Stahorszki
  • 1,102
  • 7
  • 23
  • 2
    It wouldn't be `Object`, [it would be `B`](https://ideone.com/RDE919), since that's the erasure of `T`. But generally, no, there is no shortcut here: you need to suck it up and add all of the type parameters everywhere. – Andy Turner Jun 20 '19 at 08:04
  • Are you sure you're getting an instance of type `Object` instead of `B`? I tried and I'm getting type `B`. I used Java 8, though. – lealceldeiro Jun 20 '19 at 08:06
  • 1
    Can you provide us with a small use case? I mean, an example of where it is returning an `Object` instead of `B`. – lealceldeiro Jun 20 '19 at 08:11
  • edited my original post – László Stahorszki Jun 20 '19 at 08:41
  • 1
    *Is it some Java 7 related behavior*: no.*Is there a way to make getB() return a type of B when A is raw?* no. Just don't use raw types. Do the right thing, and refactor the code properly. https://stackoverflow.com/questions/2770321/what-is-a-raw-type-and-why-shouldnt-we-use-it – JB Nizet Jun 20 '19 at 08:51
  • Even though the question is probably good, you have a weird way of structuring it. You probably mean that once you introduce a generic parameter to an _existing_ class, all the _callers_ will use raw type of the class, thus no type safety, as they will now all return `Object`, right? If so, yhis is not specific to java-7, at all. – Eugene Jul 10 '19 at 16:40

1 Answers1

1

Your intention seems to be that, assuming a class C extends B, you could have an A<C> whose getB() method returns a List<C>. But if you allow old code to treat the method of the same object as returning List<B>, it could call getB().add(new B()) on it, which would subvert the entire type safety.

Of course, it might be that the old code never does that, perhaps, the list is even immutable, but the compiler’s type system doesn’t know.

Since you have to touch the code anyway, to benefit from the new generic type, you can extend the class, e.g.

public class A {
    public List<B> getB() { ... }
}

public class ExtendedA<T extends B> extends A {
    public List<B> getB() {
        return Collections.unmodifiableList(getT());
    }
    public List<T> getT() { ... }
}

You can only get the benefit of the new type parameter T for new or updated code, when an ExtendedA is created and consistently passed through the entire code path. But at any point where the ExtendedA is passed to old code, it continues to work as before, as long as the old code obeys the restrictions mentioned above. The code must not attempt to add B instances when not knowing the actual type parameter. But in this solution, the restriction is enforced at runtime by the unmodifiableList wrapper, so the generic type safety is retained, which is the reason why the signature of the wrapper method allows the type transition from List<T> to List<B>.

The best migration strategy would be to adapt all creation sites of As as soon as possible, as then, the A class could become abstract, just serving as an interface for old code whereas ExtendedA is the actual type safe API and implementation for all new and updated code.

Holger
  • 285,553
  • 42
  • 434
  • 765