ClassCastException when calling TreeSet.cont

2019-01-20 19:39发布

问题:

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.

回答1:

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.



回答2:

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;
}

}