How can I downcast to class' type E or at leas

2020-03-26 05:41发布

I have super abstract class Node and 50 types of subclasses SubNode.

I have a generic Class <E extends Node> which has a private var List<E> and a method which unfortunately has to accept superclass Node ALWAYS, cannot move it to just E:

 public void addSubElement (Node node){ 
        if (node instanceOf E) subElements.add((E)node); 
        else //Doing extra steps for occasional non-E nodes like discarding them silently without CastException; 
 }

Any solution (Reflection?) able to compile without warnings, throwing a CastException instead of adding any object due to type erasure??...

I don´t want to have to write same function for any type of subclass:

 public void addSubElement (Node node){                             
        if (node instanceOf SubNode1) subElements.add((SubNode1)node); 
        if (node instanceOf SubNode2) subElements.add((SubNode2)node); 
        //if (node instanceOf SubNode50....
 }

It would be so nice having a method like.

public void addSubElement (Node node){ 
        subElements.add((E)node.autoCastToSubClassOfAbstract("Node")); //Should throw CastException if non-E
 }

or

 public void addSubElement (Node node){ 
        subElements.add(node.autoCastTo("E")); //Should throw CastException if non-E
 }

4条回答
时光不老,我们不散
2楼-- · 2020-03-26 06:17

You have a flaw in your design. Either the signature of the method should be:

public void addSubElement (E node)

or subElements should be of type List<Node> instead List<E>.

Let's say your class is NodeList<E extends Node>, and then you create an instance:

NodeList<SubNode1> nl = new NodeList<SubNode1>();

Then the list will only accept instances of SubNode1, so you wouldn't be able to do

nl.addSubElement(subNode2Instance)

Update:

The only workaround that I found is this:

private static class G<E extends NodeB> {

    private E templateObject;

    private List<E> subElements = new ArrayList<E>();

    public G(E templateObject) {
        this.templateObject = templateObject;
    }

    public void addSubElement (NodeB node) {
        if (templateObject.getClass().isAssignableFrom(node.getClass())) {
            subElements.add((E) node);
        } else {
            throw new ClassCastException();
        }
    }

}
查看更多
相关推荐>>
3楼-- · 2020-03-26 06:17

When using generics, there are corner cases where you can't write valid code without suppressing warnings.

The pure OO approach for your problem would be to write on addSubElement() method for each type. That would give you one method per type in each type (N*N). You could add the special cases in the respective types.

Obviously for any significant number of different types (say more than three), the number of methods quickly explodes and you will find yourself in a situation where you have to cut&paste a lot of code.

Even if you need only to write a few of them and delegate most of the work to a generic addSubElement(Node) method, it would still create a technical debt because you'd need to write X methods for each new type.

So for your corner case, there is probably no way around instanceof and @SuppressWarnings("unchecked").

[EDIT]

You could define an interface INode which Node has to implement. Your class could then look like this:

public Node<E,N extends INode> {

    @SuppressWarnings("unchecked")
    public void addSubElement(N n) { ... }
}

This pattern would allow you to either restrict the possible types of nodes which this implementation accepts or use Node for N for something that accepts anything and where you do special handling in the method.

The advantage here is that you could get compile time errors when you pass a type to an implementation which can't handle it. But you would still need a cast in the method and to suppress the warning.

查看更多
Melony?
4楼-- · 2020-03-26 06:20

If you are forced somehow to have a

public void addSubElement (Node node); 

then your only option is to use

public void addSubElement (Node node){ 
    subElements.add((E)node); 
}

There is no way to achieve this without getting a warning (which you of course can suppress).

This is fine as long as you are the only one using this method, you can just ignore the warning as long as you make sure to always call it with the correct argument.

查看更多
手持菜刀,她持情操
5楼-- · 2020-03-26 06:21

EDIT: FINAL ANSWER FROM ALL OTHER ANSWERS:

I was hesitating if accepting titofb answer, beause he is the one who appointed the good way. But I think it is enough targeted on y problem that should acept my own (for very 1st time), for the benefit of others who read it. Thanks to all!

Abstract Node class implements this:

    @SuppressWarnings("unchecked")
    protected final Class<E> getChildType(){
        return (Class<E>)(((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0]); 
    }

So any Subnode has always an available method which returns his own Type E from definition SubnodeN extends Node<SubNodeX> (SubnodeX = N in Node<E>). That means that we can do that in any node:

 public void addSubElement (Node<?> node){ 
      Class<E> expectedChildType = getChildType();
      if (expectedChildType.isAssignableFrom(node.getClass())){//if node instanceOf E
         subElements.add(expectedChildType.cast(node)); 
      }
      else throw new ClassCastException("A non-expected child was intended to add to this "+this.getClass().getSimpleName()+" element");
 }

And then here it is the magic. This is the default behavior. It warns you if some child was not expected, but you can override this method for any subnode for handling special cases:

 @Override
 public void addSubElement (Node<?> node){ 
      if (node instanceOf SubNode34) {/* Yes, our current subnode does not expect all elements intended to be added as E type nodes, we can silently discard or do whatever */}
      else super.addSubElement(node) //Parents default behavior
 }    
查看更多
登录 后发表回答