0

Im stumped. I declare my set thusly:

    private Set<Long> applicationIds;

Then I populate it like this:

public void setApplicationIds( Set<Long> applicationIds ) {
    this.applicationIds = new TreeSet<Long>( applicationIds );
    this.applications = null;
}

Then I attempt to use it:

public List<Application> getApplications() {
    if ( applications == null ) {
        applications = new ArrayList<Application>();
        if ( applicationIds != null ) {
            for ( Application application : availableApplications ) {
                if ( applicationIds.contains( Long.valueOf( application.getId() ) ) ) {
                    applications.add( application );
                }
            }
        }
    }
    return applications;
}

And I end up with this:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
    at java.lang.Long.compareTo(Long.java:50)
    at java.util.TreeMap.getEntry(TreeMap.java:346)
    at java.util.TreeMap.containsKey(TreeMap.java:227)
    at java.util.TreeSet.contains(TreeSet.java:234)
    at org.mitre.asias.pf.pnp.viewmodel.Subscription.getApplications(Subscription.java:84)

The line causing the exception (line 84 from the stack trace) is this one:

                if ( applicationIds.contains( Long.valueOf( application.getId() ) ) ) {

Perhaps I am missing something, but if the declaration is Set<Long> and I am calling the contains method passing in a Long.valueOf value, how can I be getting this exception?

This is a model bean for a JSF application. I am using Java 6, Tomcat 6.0.32, mojarra 2.1.14, but none of that should really matter as the Generics are supposed to prevent this kind of problem compile time...

-------------- EDIT -----------------

Its actually the JSF... I threw together a super simplified example with this setter:

public void setSelectedValues(Set<Long> selectedValues) {
    this.selectedValues = selectedValues;
    if (logger.isTraceEnabled()) {
        StringBuilder message = new StringBuilder("Selected values:");
        for (Object value : selectedValues) {
            message.append("\n\t'").append(value.getClass().getName())
                    .append("': '").append(value.toString()).append("'");
        }
        logger.trace(message.toString());
    }
    this.selections = null;
}

bound to this component:

<p:selectManyCheckbox id="numbers"
   value="#{controller.selectedValues}" layout="pageDirection">
  <f:selectItems value="#{controller.availableValues}" />
</p:selectManyCheckbox>

which writes this to the log:

15:45:16.887 [http-bio-8080-exec-9] TRACE com.pastdev.learn.debug.Controller - Selected values:
    'java.lang.String': '1'
    'java.lang.String': '5'

So, the simple answer is the correct one (thank you @PaulTomblin for emphasizing this). The setter is getting called with a Set that contains Strings. So now, what is the best process for conversion? Will I need to iterate through the list casting each value to a Long?

As a side note, I tested this on Tomcat 7 using Java 7 and the ClassCastException went away, however, the contains method always returns false as should be expected.

-------------- EDIT 2 -----------------

I found an answer with the correct way to bind my component here.

-------------- EDIT 3 -----------------

And here is a better explanation of the problem.

Community
  • 1
  • 1
Lucas
  • 14,227
  • 9
  • 74
  • 124
  • 2
    Looks like your `Set` actually contains at least one String. Where is that Set coming from? – Thilo Dec 27 '12 at 01:38
  • 1
    Are you sure `application.getId()` is returning something that Long.valueOf will legitimately convert to a long? – Paul Tomblin Dec 27 '12 at 01:40
  • 1
    @PaulTomblin: If not, that would have been a compile error (or at least a different stack trace). – Thilo Dec 27 '12 at 01:42
  • I agree to @PaulTomblin (+1) and you might need to detail out `applications` and `availableApplications ` as well. – PCM Dec 27 '12 at 01:43
  • 1
    `setApplicationIds` must have been called with a wrong set. Use a debugger. – Joop Eggen Dec 27 '12 at 01:52
  • @Thilo, no it would not be a compile error - Long.valueOf expects a string - it doesn't know until runtime if the string is a valid long or not. – Paul Tomblin Dec 27 '12 at 12:21
  • @PaulTomblin, you are right that it wouldn't be compile time, but Thilo is right that it would be a different stack trace. Would have thrown the `ClassCastException` at the point of the `valueOf` call. – Lucas Dec 27 '12 at 17:12
  • 1
    Ten to one you called "setApplicationIds" with a collection that held non-Longs that you casted to Set. – Paul Tomblin Dec 27 '12 at 17:33
  • @PaulTomblin, thank you for continuing to force me to look at the obvious. See my update, turns out its JSF, and probably something I should have just known about to start with. Anyway, thanks again. – Lucas Dec 27 '12 at 21:05
  • @JoopEggen, Sets are difficult to use debugger to look into as I dont know how to look at the types of the entries. But using logger and iterating over the set worked for me. In general though, I agree with the suggestion. – Lucas Dec 27 '12 at 21:07

2 Answers2

5

Perhaps I am missing something, but if the declaration is Set and I am calling the contains method passing in a Long.valueOf value, how can I be getting this exception?

Note that in Java, generic type annotations are only hints for the compiler and have no effect at runtime, so it is possible to violate these constraints at runtime (but there will be a compiler warning somewhere).

Looks like your Set<Long> actually contains at least one String. Where is that Set coming from?

as the Generics are supposed to prevent this kind of problem compile time

Yes, there should be a warning somewhere in your code about missing generic types or unchecked casts. It is only a warning because generics are optional. In the places where you did use them, it would have been an error.

Thilo
  • 257,207
  • 101
  • 511
  • 656
  • "but there will be a compiler warning somewhere" Assuming that the TreeSet API is type-safe, that is – newacct Dec 27 '12 at 05:29
  • 1
    If you have a `TreeSet` with no `` and you passed it into setApplicationIds by casting it to `TreeSet`, it's quite possible it has things that aren't Longs in them, and then you'll get the ClassCastException when you try to call `.contains` on it. As Henry Spencer famously said "If you lie to the compiler, the program will get its revenge". – Paul Tomblin Dec 27 '12 at 20:02
  • 1
    @RyanStewart, good call... It was JSF calling the `setApplicationIds()` and it is sending a `Set` containing `String`s – Lucas Dec 27 '12 at 21:09
  • A reference to the oracle documentation and some code to debugging the problem: http://docs.oracle.com/javase/1.5.0/docs/guide/language/generics.html – jenaiz Jan 25 '13 at 13:27
0

OK @Lucas I just gave a try using quick and dirty code using the code you posted as part of the question and it works fine. May be you need to check your Application class once more.

package javaapplication2;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;

public class JavaApplication2 {


private static Set<Long> applicationIds;

private List<Application> applications;

private static final List<Application> availableApplications = new ArrayList<>();

public static void main(String[] args) {

    JavaApplication2 proj = new JavaApplication2();
    for (int i = 1; i <= 11; i++) {
        Application application = new Application();
        application.setId(i);
        availableApplications.add(application);
    }
    Set<Long> applicationIds1 = new TreeSet<>();
    applicationIds1.add(11L);
    applicationIds1.add(12L);
    applicationIds1.add(13L);

    proj.setApplicationIds(applicationIds1);
    for (Application appl : proj.getApplications()) {
        System.out.println(appl.getId());
    }
}

public void setApplicationIds(Set<Long> applicationIds) {
    this.applicationIds = new TreeSet<Long>(applicationIds);
    this.applications = null;
}

public List<Application> getApplications() {
    if (applications == null) {
        applications = new ArrayList<Application>();
        if (applicationIds != null) {

            for (Application application : availableApplications) {
                if (applicationIds.contains(Long.valueOf(application.getId()))) {
                    applications.add(application);
                }
            }
        }
    }
    return applications;
}

}

And:

package javaapplication2;

class Application {
private int id;

public int getId() {
    return id;
}

public void setId(int id) {
    this.id = id;
}

}
PCM
  • 873
  • 8
  • 23