Home > Software engineering >  Read/write operations with capture of ? in Java
Read/write operations with capture of ? in Java

Time:07-06

I have a generic class with type T, that has both read a write methods using T. Let's illustrate it on List.

When I use it with a wildcard type, I come across a specific problem:

List<?> list = ...
list.add(list.get(0)); //here I get a compiler error

As I understand it, Java compiler should internally store the type as "capture of ?" and it should recognize, that the types used in add/get match.

If this is impossible to do, what is the "nicest" workaround? My thoughts:

  1. Helper methods

    private <S> void aaa(List<S> list) {
        list.add(list.get(0));
    }
    

    This works, but it would be impractical to have these methods for all possible combinations.

  2. Casting

    List<?> list;
    
    private <S> void aaa() {
        List<S> list = (List<S>)this.list
        list.add(list.get(0));
    }
    

    This works as well, but I don't like it either, plus I get an unchecked cast warning.

  3. Creating type

    class Foo<T> {
        List<T> list;
    
        private void aaa() {
            list.add(list.get(0));
        }
    }
    

    This works too, but it seems like a total overkill to define a type just for this purpose.

Thanks for your thoughts!

CodePudding user response:

As I understand it, Java compiler should internally store the type as "capture of ?" and it should recognize, that the types used in add/get match.

That's not what the capturing system does. Unfortunately.

this works but it would be impractical to have these methods for all possible combinations

'all possible combinations'? There's just the one combination, really. Or rather, the amount of times you perform such an interaction, where you feed the result of invoking a method on thing directly as parameter to another method on thing isn't exactly a common task. You'd have to write a method each time it happens, but, given that its rare, I don't see how that is impractical. Note that you can, of course, make that 0 a parameter if you like.

Creating type

This is a really bad idea. Not because its overkill. Because it leaks. Your generics are public. You'd want this aspect of it to be hidden. Or rather, the semantics of your type, whatever it might be, either involves including the type or not - and 'I have a need to copy an element in the list to the end of that list' should play absolutely no part whatsoever in your design decision about whether or not the type itself should have generics or not to represent the type of this list field/parameter you get.

what is the "nicest" workaround?

Pretty much the helper method, I think. Note that you if you really really want to, you can shove it in a local:

import java.util.*;

class Test {
  public static void main(String[] args) {
    List<String> a = new ArrayList<String>();
    a.add("Hello"); a.add("World");
    copyFirstToEnd(a);
    System.out.println(a);
  }

  private static void copyFirstToEnd(List<?> in) {
    class Helper {
      <S> void help(List<S> a) { a.add(a.get(0)); }
    }

    new Helper().help(in);
  }
}

works great. Unwieldy; code is kinda ugly. But absolutely nothing whatsoever can 'see' this other than your own eyeballs and only when explicitly looking at the code that powers this method. You won't see it in your outline. You won't see it in your javadoc. You won't see it in your auto-complete dialogs (other than when editing code inside copyFirstToEnd where that's a good thing), and of course anybody working on a source file that isn't this Test.java itself cannot even know you've made a type for this, even if they try to employ reflection*.

*) They can hack around and find the Test$Helper.class artefact but at this point that feels academic. That class screams in all possible ways that you shouldn't consider it as existing in the first place, and tooling and IDEs do indeed act exactly like that (keeping it entirely out of type finding dialogs and the like).

It's a shame you get an object alloc but it's got a single ref field at worst, and is thread-localized fast garbage. It's as cheap an alloc as you could possibly make, so the cost is probably effectively nil, given java's generation garbage collection systems and clear focus on supporting 'lots of small immutable objects' as a coding model fairly well.

  • Related