4

The following two code samples represent the same logic. Check to see if a string is null and branch based upon that check. The first sample compiles safely. The second produces a type mismatch error related to Java generics. My question seems simple enough, but it eludes me. Why does the compiler treat these two statements differently? How can I better understand what's going on here?

/* compiles cleanly */
protected Collection<String> getUserRoles(Object context,
        Set<String> mappableRoles) {
    String cookieValue = extractCookieValue(context);
    if (cookieValue != null) {
        return securityService.getRolesForUser(cookieValue);
    } else {
        return Collections.emptySet();
    }
}


/* produces a compiler error */
protected Collection<String> getUserRoles(Object context,
            Set<String> mappableRoles) {
    String cookieValue = extractCookieValue(context);
    return cookieValue == null ? Collections.emptySet()
            : securityService.getRolesForUser(cookieValue);
}

The compiler error from Eclipse.

Type mismatch: cannot convert from Set<capture#1-of ? extends Object> to Collection<String>

As requested, here's the relevant portion of the SecurityService interface.

public interface SecurityService {
    public Set<String> getRolesForUser(String userId);
}
Mechanical snail
  • 29,755
  • 14
  • 88
  • 113
Mike Yockey
  • 4,565
  • 22
  • 41

3 Answers3

7

The problem should reside in how the compiler interprets the return value of the ternary operator. You might want to take a look at part 15.25 of the JLS or at this question (kinda related because it's even further complicated by autoboxing and it throws an error at runtime instead of at compile time).

Hope this puts you on the right direction.

Community
  • 1
  • 1
Marsellus Wallace
  • 17,991
  • 25
  • 90
  • 154
  • It looks to me then that the capture conversion process is the culprit then. Most of that documentation is well over my head, but at least I can point to a piece of the generics puzzle. Thanks. – Mike Yockey Jan 24 '12 at 18:43
5

It's because Collections.emptySet() returns an untyped Set. Instead, try this:

Collections.<String>emptySet()
mike9322
  • 634
  • 4
  • 4
  • That does remove the compiler error, but why? I'm most interested in why the type inference can be made from the if/else construct and not the ternary operator. – Mike Yockey Jan 24 '12 at 15:19
  • Apologies - I missed that part of the question. @Gevorg provides links that explain it much better than I could. – mike9322 Jan 24 '12 at 15:24
1

Collections.emptySet() is declared as

public static final <T> Set<T> emptySet()

The first T is used for Type Inference. The second getUserRoles implementation is too complex for java compiler to detect right type. That is the reason of the issue. Work around:

protected Collection<String> getUserRoles(Object context,
            Set<String> mappableRoles) {
    String cookieValue = extractCookieValue(context);
    Collection<String> a = null;
    return cookieValue == null ? a = Collections.emptySet()
            : securityService.getRolesForUser(cookieValue);
}
e-zinc
  • 4,491
  • 2
  • 20
  • 17