施鈞豪
施鈞豪

Reputation: 33

Cannot cast from List<Node> to List<Element>

everyone. I'm practicing dom4j and Xpath but got stuck on a problem.

I'm trying to:

List<Element> conList = (List<Element>)doc.selectNodes("//contact");

but got an Error:

Cannot cast from List<Node> to List<Element>

The code seems to be working fine in a teaching video, but didn't work in my computer.

Is it kind of an illegal operation? Can I solve the problem by any other way? Thanks.

Upvotes: 1

Views: 1868

Answers (2)

Michael Kay
Michael Kay

Reputation: 163458

It's a frustrating problem because you know (from the semantics of the XPath expression) that the items in the list will all be instances of Element, but there's no way of telling the Java compiler that. If the designers of DOM4J had thought a bit more carefully, they would have declared the result as List<? extends Node> which would have given you a bit more flexibility (though a cast would still give you compiler warnings). It's also frustrating because the reason for the rule is all about the dangers of adding inappropriate items to the list, and what you really want is an immutable list where the problem shouldn't arise (though it still does, because generics don't treat immutable collections specially).

The clean solution given by @MagnusLutz will satisfy the compiler, but it's expensive - it involves copying the list.

In Saxon 9.9 I designed a new API that tries to play nicely with generics, and it works with DOM4J, so give it a try. It also avoids the cost of compiling/interpreting an XPath expression for simple cases like this. But (sadly) it doesn't solve the underlying limitations of generics, especially in relation to constructs like XPath expressions where the type information available to the XPath compiler can't be shared with the Java compiler.

What do you actually want to do with the elements? If I needed to pass the list to a method that requires a List<Element> then I would probably use the solution from @MagnusLutz. If I just wanted to process the elements, I would do

for (Node n : doc.selectNodes("//contact")) {
   Element e = (Element)n;
   ...
}

Upvotes: 2

Magnus Lutz
Magnus Lutz

Reputation: 598

You cannot simply cast a generics based object with concrete parameter that way.

A nice java8 way to achieve your goal is:

List<Element> conList = doc.selectNodes("//contact")
.stream()
.map(node->(Element)node)
.collect(Collectors.toList());

Beware that for a general case in which you dont know if the list elements are actually an instance of your target class or interface, you may would want to assert that by filtering

List<Element> conList = doc.selectNodes("//contact")
.stream()
.filter(node->node instanceof Element)
.map(node->(Element)node)
.collect(Collectors.toList());

Upvotes: 2

Related Questions