This discussion is archived
5 Replies Latest reply: Jan 17, 2013 3:54 PM by jtahlborn RSS

Set<String> keySet = props.keySet() not compiled, can someone explain why?

985575 Newbie
Currently Being Moderated
I just ran into a problem with javac, this code compiles fine in jdeveloper 10.1.3 but doesn't compile with the javac task in Ant 1.6.5

Compiling these 2 lines

Properties props = new Properties();
Set<String> keySet2 = props.keySet();

results in this error.

incompatible types
[javac] found : java.util.Set<java.lang.Object>
[javac] required: java.util.Set<java.lang.String>
[javac] Set<String> keySet2 = props.keySet();


Given the assignment context, why can't the compiler infer that the Set returns by keySet is a Set<String>?

However, casting p to a Map before calling keySet() compiles fine.

Properties props = new Properties();
Set<String> keySet2 = ((Map)props).keySet();

This is really weird. Could it be just a bug in the javac implementation that comes with Ant? I'm wondering if there's some fundamental about generics that I'm missing. Can anyone shed some light on this?

Edited by: 982572 on Jan 17, 2013 12:25 PM
  • 1. Re: Set<String> keySet = props.keySet() not compiled, can someone explain why?
    jtahlborn Expert
    Currently Being Moderated
    For better or worse, Properties implements "Map<Object,Object>", not "Map<String,String>", hence the return value of Properties.keySet() is "Set<Object>".

    In this code:
    Set<String> keySet2 = ((Map)props).keySet();
    you sidestep the compiler by discarding the generic information, in which case all bets are off.
  • 2. Re: Set<String> keySet = props.keySet() not compiled, can someone explain why?
    985575 Newbie
    Currently Being Moderated
    Ah, thanks. I think that answers some of what I didn't get. The part about discarding the generic information makes sense.

    There's something else I'm not understanding.

    Consider this line of code

    Set<String> keySet2 = (Set<String>)props.keySet();

    JDeveloper 10.1.3.3. for some reason is able to compile this code (it's using jdk 1.5.0_06).

    I tried the same code in Eclipse with jdk 1.6.0_26 and it doesn't compile.

    I believe the latter is correct. Set<String> and Set<Object> aren't related so the compiler shouldn't have allowed a cast from one to the other ... but somehow the code is compiling in JDeveloper 10.1.3.3. This is really throwing me off ...

    What's also really weird is that JDeveloper can't compile this line

    Set<String> keySet2 = props.keySet();

    but to me this is pretty much identical to

    Set<String> keySet2 = (Set<String>)props.keySet(); // although it's an explicit cast

    and JDeveloper can compile this fine ...


    So anyway, if props.keySet() returns Set<Object> and at compile time the compiler doesn't allow casting to Set<String>, how would I go about putting everything in the Set<Object> returned by keySet() into a Set<String>? I know I can iterate through the Set<Object> returned and then just add each String to Set<String>.

    This would work,

    Set<? super String> keySet = props.keySet();

    but then it's not really accurate ... even though Object is the only ancestor of the String class, the <? super String> bound suggests that one can put something other than String into keySet and operate on the elements as though they are String, which is not the correct message to convey.

    I wonder why Properties doesn't extend Hashtable<String,String> ... but there's probably a reason and given that it extends Hashtable<Object, Object> instead, is there no way to assign the return value of Properties.keySet() to a Set<String> by using some Java generic notation/syntax?

    I know this would work as well, but that's just discarding the generic information in the first place

    Set<String> keySet = (Set<String>)(Set)props.keySet();

    I suppose going back to the iterator is the only way to basically copy props.keySet() into a Set<String>?

    Edited by: 982572 on Jan 17, 2013 1:56 PM

    Edited by: 982572 on Jan 17, 2013 2:19 PM

    Edited by: 982572 on Jan 17, 2013 2:37 PM

    Edited by: 982572 on Jan 17, 2013 2:38 PM

    Edited by: 982572 on Jan 17, 2013 2:42 PM
  • 3. Re: Set<String> keySet = props.keySet() not compiled, can someone explain why?
    EJP Guru
    Currently Being Moderated
    Set<String> keySet2 = (Set<String>)props.keySet();
    That should compile. What error message did you get?
  • 4. Re: Set<String> keySet = props.keySet() not compiled, can someone explain why?
    985575 Newbie
    Currently Being Moderated
    EJP wrote:
    Set<String> keySet2 = (Set<String>)props.keySet();
    That should compile. What error message did you get?
    The compiler is saying "Cannot cast from Set<Object> to Set<String>", which makes sense to me since props.keySet() returns Set<Object>, and I don't think it's possible to cast it to Set<String>, but if it is then I'm totally off about how Java generics work.

    But then what really throws me off is the fact that JDeveloper 10.1.3.3 is actually able to compile the code while the javac in Ant 1.6.5 and jdk 1.6.0_026 aren't so I'm now questioning what I thought was correct.

    Edited by: 982572 on Jan 17, 2013 2:14 PM
  • 5. Re: Set<String> keySet = props.keySet() not compiled, can someone explain why?
    jtahlborn Expert
    Currently Being Moderated
    982572 wrote:
    EJP wrote:
    Set<String> keySet2 = (Set<String>)props.keySet();
    That should compile. What error message did you get?
    no, that shouldn't compile. a cast from "Set<Object>" to "Set<String>" is invalid.
    The compiler is saying "Cannot cast from Set<Object> to Set<String>", which makes sense to me since props.keySet() returns Set<Object>, and I don't think it's possible to cast it to Set<String>, but if it is then I'm totally off about how Java generics work.
    you are correct, that code should not compile.
    But then what really throws me off is the fact that JDeveloper 10.1.3.3 is actually able to compile the code while the javac in Ant 1.6.5 and jdk 1.6.0_026 aren't so I'm now questioning what I thought was correct.
    i have no idea which JDeveloper is okay with the code. sounds like you need a new tool.

    as for an easy way to copy a "Set<Object>" into a "Set<String>", there isn't one in the jdk. there may be some third-party libraries with relevant convenience methods (e.g. google guava).

    you could easily create a utility method to copy between 2 collections in a generic safe fashion:
    public static <T> void addAll(Collection<?> src, Collection<T> dest, Class<? extends T> clazz) {
      for(Object s : src) {
        dest.add(clazz.cast(s));
      }
    }

Legend

  • Correct Answers - 10 points
  • Helpful Answers - 5 points