Java generics self-reference: is it safe?












16














I have this simple interface:



public interface Node<E extends Node<E>>
{
public E getParent();

public List<E> getChildren();

default List<E> listNodes()
{
List<E> result = new ArrayList<>();

// ------> is this always safe? <-----
@SuppressWarnings("unchecked")
E root = (E) this;

Queue<E> queue = new ArrayDeque<>();
queue.add(root);

while(!queue.isEmpty())
{
E node = queue.remove();

result.add(node);

queue.addAll(node.getChildren());
}

return result;
}
}


I see that this is always an instance of Node<E> (by definition).

But I can't imagine a case where this is not an instance of E...

Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??



Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??



Meanwhile, my brain is melting...





The previous class was a simplified example.

To show why I need a self-bound, I'm adding a bit of complexity:



public interface Node<E extends Node<E, R>, R extends NodeRelation<E>>
{
public List<R> getParents();

public List<R> getChildren();

default List<E> listDescendants()
{
List<E> result = new ArrayList<>();

@SuppressWarnings("unchecked")
E root = (E) this;

Queue<E> queue = new ArrayDeque<>();
queue.add(root);

while(!queue.isEmpty())
{
E node = queue.remove();

result.add(node);

node.getChildren()
.stream()
.map(NodeRelation::getChild)
.forEach(queue::add);
}

return result;
}
}

public interface NodeRelation<E>
{
public E getParent();

public E getChild();
}









share|improve this question





























    16














    I have this simple interface:



    public interface Node<E extends Node<E>>
    {
    public E getParent();

    public List<E> getChildren();

    default List<E> listNodes()
    {
    List<E> result = new ArrayList<>();

    // ------> is this always safe? <-----
    @SuppressWarnings("unchecked")
    E root = (E) this;

    Queue<E> queue = new ArrayDeque<>();
    queue.add(root);

    while(!queue.isEmpty())
    {
    E node = queue.remove();

    result.add(node);

    queue.addAll(node.getChildren());
    }

    return result;
    }
    }


    I see that this is always an instance of Node<E> (by definition).

    But I can't imagine a case where this is not an instance of E...

    Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??



    Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??



    Meanwhile, my brain is melting...





    The previous class was a simplified example.

    To show why I need a self-bound, I'm adding a bit of complexity:



    public interface Node<E extends Node<E, R>, R extends NodeRelation<E>>
    {
    public List<R> getParents();

    public List<R> getChildren();

    default List<E> listDescendants()
    {
    List<E> result = new ArrayList<>();

    @SuppressWarnings("unchecked")
    E root = (E) this;

    Queue<E> queue = new ArrayDeque<>();
    queue.add(root);

    while(!queue.isEmpty())
    {
    E node = queue.remove();

    result.add(node);

    node.getChildren()
    .stream()
    .map(NodeRelation::getChild)
    .forEach(queue::add);
    }

    return result;
    }
    }

    public interface NodeRelation<E>
    {
    public E getParent();

    public E getChild();
    }









    share|improve this question



























      16












      16








      16


      0





      I have this simple interface:



      public interface Node<E extends Node<E>>
      {
      public E getParent();

      public List<E> getChildren();

      default List<E> listNodes()
      {
      List<E> result = new ArrayList<>();

      // ------> is this always safe? <-----
      @SuppressWarnings("unchecked")
      E root = (E) this;

      Queue<E> queue = new ArrayDeque<>();
      queue.add(root);

      while(!queue.isEmpty())
      {
      E node = queue.remove();

      result.add(node);

      queue.addAll(node.getChildren());
      }

      return result;
      }
      }


      I see that this is always an instance of Node<E> (by definition).

      But I can't imagine a case where this is not an instance of E...

      Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??



      Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??



      Meanwhile, my brain is melting...





      The previous class was a simplified example.

      To show why I need a self-bound, I'm adding a bit of complexity:



      public interface Node<E extends Node<E, R>, R extends NodeRelation<E>>
      {
      public List<R> getParents();

      public List<R> getChildren();

      default List<E> listDescendants()
      {
      List<E> result = new ArrayList<>();

      @SuppressWarnings("unchecked")
      E root = (E) this;

      Queue<E> queue = new ArrayDeque<>();
      queue.add(root);

      while(!queue.isEmpty())
      {
      E node = queue.remove();

      result.add(node);

      node.getChildren()
      .stream()
      .map(NodeRelation::getChild)
      .forEach(queue::add);
      }

      return result;
      }
      }

      public interface NodeRelation<E>
      {
      public E getParent();

      public E getChild();
      }









      share|improve this question















      I have this simple interface:



      public interface Node<E extends Node<E>>
      {
      public E getParent();

      public List<E> getChildren();

      default List<E> listNodes()
      {
      List<E> result = new ArrayList<>();

      // ------> is this always safe? <-----
      @SuppressWarnings("unchecked")
      E root = (E) this;

      Queue<E> queue = new ArrayDeque<>();
      queue.add(root);

      while(!queue.isEmpty())
      {
      E node = queue.remove();

      result.add(node);

      queue.addAll(node.getChildren());
      }

      return result;
      }
      }


      I see that this is always an instance of Node<E> (by definition).

      But I can't imagine a case where this is not an instance of E...

      Since E extends Node<E>, shouldn't Node<E> also be equivalent to E by definition??



      Can you give an example of an object that's an instance of Node<E>, but it's not an instance of E??



      Meanwhile, my brain is melting...





      The previous class was a simplified example.

      To show why I need a self-bound, I'm adding a bit of complexity:



      public interface Node<E extends Node<E, R>, R extends NodeRelation<E>>
      {
      public List<R> getParents();

      public List<R> getChildren();

      default List<E> listDescendants()
      {
      List<E> result = new ArrayList<>();

      @SuppressWarnings("unchecked")
      E root = (E) this;

      Queue<E> queue = new ArrayDeque<>();
      queue.add(root);

      while(!queue.isEmpty())
      {
      E node = queue.remove();

      result.add(node);

      node.getChildren()
      .stream()
      .map(NodeRelation::getChild)
      .forEach(queue::add);
      }

      return result;
      }
      }

      public interface NodeRelation<E>
      {
      public E getParent();

      public E getChild();
      }






      java generics this self-reference






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 20 '18 at 19:26

























      asked Nov 20 '18 at 19:15









      Michele Mariotti

      5,72543163




      5,72543163
























          4 Answers
          4






          active

          oldest

          votes


















          12














          An easy example to illustrate the problem: a node of a different type of node:



          class NodeA implements Node<NodeA> {
          ...
          }


          And:



          class NodeB implements Node<NodeA> {
          ...
          }


          In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.






          share|improve this answer

















          • 2




            That's it! Thanks!
            – Michele Mariotti
            Nov 20 '18 at 19:39






          • 2




            I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
            – Eran
            Nov 20 '18 at 19:41












          • @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
            – Aleksandr Semyannikov
            Nov 20 '18 at 19:50












          • @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
            – ernest_k
            Nov 20 '18 at 20:13






          • 2




            @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
            – Aleksandr Semyannikov
            Nov 20 '18 at 20:35



















          2














          Without <E extends Node<E>>, you could have either of these cases:



          Node<Integer>


          where the generic type isn't a Node at all, or



          Node<DifferentNode>


          where the generic bounds don't match.



          That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.






          share|improve this answer





















          • I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
            – Michele Mariotti
            Nov 20 '18 at 19:27






          • 1




            Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
            – flakes
            Nov 20 '18 at 20:25












          • Not weird at all for something like IComparable in C# example
            – D. Ben Knoble
            Nov 21 '18 at 4:08



















          2














          The problem is not in E root = (E) this. It might work well until you start iterating through result of listNodes().



          That example demonstrates where exactly ClassCastException will be thrown:



          public interface Node<E extends Node<E>> {

          List<E> getRelatedNodes();

          default List<E> getAllNodes() {
          List<E> result = new ArrayList<>();
          result.add((E) this); //<--that cast is not a problem because of type erasure
          return result;
          }
          }

          class NodeA implements Node<NodeA> {

          public NodeA() {
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return null;
          }
          }

          class NodeB implements Node<NodeA> {

          private List<NodeA> relatedNodes;

          public NodeB(List<NodeA> relatedNodes) {
          this.relatedNodes = relatedNodes;
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return relatedNodes;
          }
          }


          Execute:



          List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
          for (NodeA node : nodes) { //ClassCastException will be thrown
          System.out.println(node);
          }





          share|improve this answer





















          • Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
            – Michele Mariotti
            Nov 21 '18 at 12:31



















          1














          With this sort of situation it is often useful to have a getThis method that (by convention) returns this.



          I would do the following



          public interface Node<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public List<R> getParents();
          public List<R> getChildren();
          public List<E> listDescendants() ;
          }
          public interface NodeRelation<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public E getParent();
          public E getChild();
          }
          abstract class ANode<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements Node<E,R> {
          abstract protected E getThis() ;
          public List<E> listDescendants()
          {
          List<E> result = new ArrayList<>();
          E root = getThis() ;
          ...
          return result;
          }

          }

          abstract class ARelation<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements NodeRelation<E,R> {
          }

          class CNode extends ANode<CNode, CRelation> {
          public CNode getThis() { return this ; }
          ...
          }

          class CRelation extends ARelation<CNode, CRelation> {
          ...
          }


          Although I might not bother with having both abstract class and interface layers.






          share|improve this answer



















          • 1




            Upvoted. Eventually, I ended up implementing the same thing :)
            – Michele Mariotti
            Nov 21 '18 at 12:03











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53400009%2fjava-generics-self-reference-is-it-safe%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          4 Answers
          4






          active

          oldest

          votes








          4 Answers
          4






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          12














          An easy example to illustrate the problem: a node of a different type of node:



          class NodeA implements Node<NodeA> {
          ...
          }


          And:



          class NodeB implements Node<NodeA> {
          ...
          }


          In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.






          share|improve this answer

















          • 2




            That's it! Thanks!
            – Michele Mariotti
            Nov 20 '18 at 19:39






          • 2




            I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
            – Eran
            Nov 20 '18 at 19:41












          • @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
            – Aleksandr Semyannikov
            Nov 20 '18 at 19:50












          • @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
            – ernest_k
            Nov 20 '18 at 20:13






          • 2




            @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
            – Aleksandr Semyannikov
            Nov 20 '18 at 20:35
















          12














          An easy example to illustrate the problem: a node of a different type of node:



          class NodeA implements Node<NodeA> {
          ...
          }


          And:



          class NodeB implements Node<NodeA> {
          ...
          }


          In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.






          share|improve this answer

















          • 2




            That's it! Thanks!
            – Michele Mariotti
            Nov 20 '18 at 19:39






          • 2




            I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
            – Eran
            Nov 20 '18 at 19:41












          • @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
            – Aleksandr Semyannikov
            Nov 20 '18 at 19:50












          • @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
            – ernest_k
            Nov 20 '18 at 20:13






          • 2




            @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
            – Aleksandr Semyannikov
            Nov 20 '18 at 20:35














          12












          12








          12






          An easy example to illustrate the problem: a node of a different type of node:



          class NodeA implements Node<NodeA> {
          ...
          }


          And:



          class NodeB implements Node<NodeA> {
          ...
          }


          In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.






          share|improve this answer












          An easy example to illustrate the problem: a node of a different type of node:



          class NodeA implements Node<NodeA> {
          ...
          }


          And:



          class NodeB implements Node<NodeA> {
          ...
          }


          In this case, E root = (E) this would resolve to NodeA root = (NodeA) this, where this is a NodeB. And that's incompatible.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 20 '18 at 19:36









          ernest_k

          20k42043




          20k42043








          • 2




            That's it! Thanks!
            – Michele Mariotti
            Nov 20 '18 at 19:39






          • 2




            I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
            – Eran
            Nov 20 '18 at 19:41












          • @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
            – Aleksandr Semyannikov
            Nov 20 '18 at 19:50












          • @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
            – ernest_k
            Nov 20 '18 at 20:13






          • 2




            @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
            – Aleksandr Semyannikov
            Nov 20 '18 at 20:35














          • 2




            That's it! Thanks!
            – Michele Mariotti
            Nov 20 '18 at 19:39






          • 2




            I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
            – Eran
            Nov 20 '18 at 19:41












          • @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
            – Aleksandr Semyannikov
            Nov 20 '18 at 19:50












          • @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
            – ernest_k
            Nov 20 '18 at 20:13






          • 2




            @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
            – Aleksandr Semyannikov
            Nov 20 '18 at 20:35








          2




          2




          That's it! Thanks!
          – Michele Mariotti
          Nov 20 '18 at 19:39




          That's it! Thanks!
          – Michele Mariotti
          Nov 20 '18 at 19:39




          2




          2




          I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
          – Eran
          Nov 20 '18 at 19:41






          I'm not sure that's correct. I was going to post something similar, but then I tested it, and E root = (E) this; doesn't throw an exception. E root = (E) this; is not resolved to NodeA root = (NodeA) this due to type erasure. If I'm not mistaken, it can only be resolved to Node root = (Node) this; @MicheleMariotti
          – Eran
          Nov 20 '18 at 19:41














          @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
          – Aleksandr Semyannikov
          Nov 20 '18 at 19:50






          @Eran I think ClassCastException may happens when you iterate through result of 'listDescendants()'
          – Aleksandr Semyannikov
          Nov 20 '18 at 19:50














          @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
          – ernest_k
          Nov 20 '18 at 20:13




          @Eran I'm very curious to know why it's not failing at that point. I know for sure that the types are incompatible, but I have to find an accurate explanation of why the CCE is not thrown on that line (could be that Java is using Object for erasure, but I don't know). Will comment when I find an exact reason.
          – ernest_k
          Nov 20 '18 at 20:13




          2




          2




          @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
          – Aleksandr Semyannikov
          Nov 20 '18 at 20:35




          @ernest_k I've just seen a bytecode, you were right about type erasure, 'E root = (E) this;' - actually that cast will be ignored by compiler. It casts objects only when generic is resolved by particular parameter, like when we are iterating a result list.
          – Aleksandr Semyannikov
          Nov 20 '18 at 20:35













          2














          Without <E extends Node<E>>, you could have either of these cases:



          Node<Integer>


          where the generic type isn't a Node at all, or



          Node<DifferentNode>


          where the generic bounds don't match.



          That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.






          share|improve this answer





















          • I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
            – Michele Mariotti
            Nov 20 '18 at 19:27






          • 1




            Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
            – flakes
            Nov 20 '18 at 20:25












          • Not weird at all for something like IComparable in C# example
            – D. Ben Knoble
            Nov 21 '18 at 4:08
















          2














          Without <E extends Node<E>>, you could have either of these cases:



          Node<Integer>


          where the generic type isn't a Node at all, or



          Node<DifferentNode>


          where the generic bounds don't match.



          That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.






          share|improve this answer





















          • I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
            – Michele Mariotti
            Nov 20 '18 at 19:27






          • 1




            Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
            – flakes
            Nov 20 '18 at 20:25












          • Not weird at all for something like IComparable in C# example
            – D. Ben Knoble
            Nov 21 '18 at 4:08














          2












          2








          2






          Without <E extends Node<E>>, you could have either of these cases:



          Node<Integer>


          where the generic type isn't a Node at all, or



          Node<DifferentNode>


          where the generic bounds don't match.



          That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.






          share|improve this answer












          Without <E extends Node<E>>, you could have either of these cases:



          Node<Integer>


          where the generic type isn't a Node at all, or



          Node<DifferentNode>


          where the generic bounds don't match.



          That said, it's not typical to see a bound this way, as Node<E> is expected to be a node that contains some value of type E, and children would be a List<Node<E>>, not a List<E>.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 20 '18 at 19:19









          chrylis

          50.4k1680117




          50.4k1680117












          • I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
            – Michele Mariotti
            Nov 20 '18 at 19:27






          • 1




            Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
            – flakes
            Nov 20 '18 at 20:25












          • Not weird at all for something like IComparable in C# example
            – D. Ben Knoble
            Nov 21 '18 at 4:08


















          • I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
            – Michele Mariotti
            Nov 20 '18 at 19:27






          • 1




            Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
            – flakes
            Nov 20 '18 at 20:25












          • Not weird at all for something like IComparable in C# example
            – D. Ben Knoble
            Nov 21 '18 at 4:08
















          I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
          – Michele Mariotti
          Nov 20 '18 at 19:27




          I know it's not typical, but I have a complex case where self-bound is required. Please, see the update on the question. Thanks :)
          – Michele Mariotti
          Nov 20 '18 at 19:27




          1




          1




          Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
          – flakes
          Nov 20 '18 at 20:25






          Yeah, very weird to see "curiously recursive template pattern" used for a graph structure.
          – flakes
          Nov 20 '18 at 20:25














          Not weird at all for something like IComparable in C# example
          – D. Ben Knoble
          Nov 21 '18 at 4:08




          Not weird at all for something like IComparable in C# example
          – D. Ben Knoble
          Nov 21 '18 at 4:08











          2














          The problem is not in E root = (E) this. It might work well until you start iterating through result of listNodes().



          That example demonstrates where exactly ClassCastException will be thrown:



          public interface Node<E extends Node<E>> {

          List<E> getRelatedNodes();

          default List<E> getAllNodes() {
          List<E> result = new ArrayList<>();
          result.add((E) this); //<--that cast is not a problem because of type erasure
          return result;
          }
          }

          class NodeA implements Node<NodeA> {

          public NodeA() {
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return null;
          }
          }

          class NodeB implements Node<NodeA> {

          private List<NodeA> relatedNodes;

          public NodeB(List<NodeA> relatedNodes) {
          this.relatedNodes = relatedNodes;
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return relatedNodes;
          }
          }


          Execute:



          List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
          for (NodeA node : nodes) { //ClassCastException will be thrown
          System.out.println(node);
          }





          share|improve this answer





















          • Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
            – Michele Mariotti
            Nov 21 '18 at 12:31
















          2














          The problem is not in E root = (E) this. It might work well until you start iterating through result of listNodes().



          That example demonstrates where exactly ClassCastException will be thrown:



          public interface Node<E extends Node<E>> {

          List<E> getRelatedNodes();

          default List<E> getAllNodes() {
          List<E> result = new ArrayList<>();
          result.add((E) this); //<--that cast is not a problem because of type erasure
          return result;
          }
          }

          class NodeA implements Node<NodeA> {

          public NodeA() {
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return null;
          }
          }

          class NodeB implements Node<NodeA> {

          private List<NodeA> relatedNodes;

          public NodeB(List<NodeA> relatedNodes) {
          this.relatedNodes = relatedNodes;
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return relatedNodes;
          }
          }


          Execute:



          List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
          for (NodeA node : nodes) { //ClassCastException will be thrown
          System.out.println(node);
          }





          share|improve this answer





















          • Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
            – Michele Mariotti
            Nov 21 '18 at 12:31














          2












          2








          2






          The problem is not in E root = (E) this. It might work well until you start iterating through result of listNodes().



          That example demonstrates where exactly ClassCastException will be thrown:



          public interface Node<E extends Node<E>> {

          List<E> getRelatedNodes();

          default List<E> getAllNodes() {
          List<E> result = new ArrayList<>();
          result.add((E) this); //<--that cast is not a problem because of type erasure
          return result;
          }
          }

          class NodeA implements Node<NodeA> {

          public NodeA() {
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return null;
          }
          }

          class NodeB implements Node<NodeA> {

          private List<NodeA> relatedNodes;

          public NodeB(List<NodeA> relatedNodes) {
          this.relatedNodes = relatedNodes;
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return relatedNodes;
          }
          }


          Execute:



          List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
          for (NodeA node : nodes) { //ClassCastException will be thrown
          System.out.println(node);
          }





          share|improve this answer












          The problem is not in E root = (E) this. It might work well until you start iterating through result of listNodes().



          That example demonstrates where exactly ClassCastException will be thrown:



          public interface Node<E extends Node<E>> {

          List<E> getRelatedNodes();

          default List<E> getAllNodes() {
          List<E> result = new ArrayList<>();
          result.add((E) this); //<--that cast is not a problem because of type erasure
          return result;
          }
          }

          class NodeA implements Node<NodeA> {

          public NodeA() {
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return null;
          }
          }

          class NodeB implements Node<NodeA> {

          private List<NodeA> relatedNodes;

          public NodeB(List<NodeA> relatedNodes) {
          this.relatedNodes = relatedNodes;
          }

          @Override
          public List<NodeA> getRelatedNodes() {
          return relatedNodes;
          }
          }


          Execute:



          List<NodeA> nodes = new NodeB(Arrays.asList(new NodeA())).getAllNodes(); //according to generic it is list of NodeA objects
          for (NodeA node : nodes) { //ClassCastException will be thrown
          System.out.println(node);
          }






          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 20 '18 at 20:15









          Aleksandr Semyannikov

          591216




          591216












          • Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
            – Michele Mariotti
            Nov 21 '18 at 12:31


















          • Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
            – Michele Mariotti
            Nov 21 '18 at 12:31
















          Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
          – Michele Mariotti
          Nov 21 '18 at 12:31




          Thank you for the punctualization. Indeed I was referring to general safeness, not limited to the single unchecked cast statement.
          – Michele Mariotti
          Nov 21 '18 at 12:31











          1














          With this sort of situation it is often useful to have a getThis method that (by convention) returns this.



          I would do the following



          public interface Node<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public List<R> getParents();
          public List<R> getChildren();
          public List<E> listDescendants() ;
          }
          public interface NodeRelation<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public E getParent();
          public E getChild();
          }
          abstract class ANode<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements Node<E,R> {
          abstract protected E getThis() ;
          public List<E> listDescendants()
          {
          List<E> result = new ArrayList<>();
          E root = getThis() ;
          ...
          return result;
          }

          }

          abstract class ARelation<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements NodeRelation<E,R> {
          }

          class CNode extends ANode<CNode, CRelation> {
          public CNode getThis() { return this ; }
          ...
          }

          class CRelation extends ARelation<CNode, CRelation> {
          ...
          }


          Although I might not bother with having both abstract class and interface layers.






          share|improve this answer



















          • 1




            Upvoted. Eventually, I ended up implementing the same thing :)
            – Michele Mariotti
            Nov 21 '18 at 12:03
















          1














          With this sort of situation it is often useful to have a getThis method that (by convention) returns this.



          I would do the following



          public interface Node<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public List<R> getParents();
          public List<R> getChildren();
          public List<E> listDescendants() ;
          }
          public interface NodeRelation<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public E getParent();
          public E getChild();
          }
          abstract class ANode<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements Node<E,R> {
          abstract protected E getThis() ;
          public List<E> listDescendants()
          {
          List<E> result = new ArrayList<>();
          E root = getThis() ;
          ...
          return result;
          }

          }

          abstract class ARelation<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements NodeRelation<E,R> {
          }

          class CNode extends ANode<CNode, CRelation> {
          public CNode getThis() { return this ; }
          ...
          }

          class CRelation extends ARelation<CNode, CRelation> {
          ...
          }


          Although I might not bother with having both abstract class and interface layers.






          share|improve this answer



















          • 1




            Upvoted. Eventually, I ended up implementing the same thing :)
            – Michele Mariotti
            Nov 21 '18 at 12:03














          1












          1








          1






          With this sort of situation it is often useful to have a getThis method that (by convention) returns this.



          I would do the following



          public interface Node<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public List<R> getParents();
          public List<R> getChildren();
          public List<E> listDescendants() ;
          }
          public interface NodeRelation<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public E getParent();
          public E getChild();
          }
          abstract class ANode<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements Node<E,R> {
          abstract protected E getThis() ;
          public List<E> listDescendants()
          {
          List<E> result = new ArrayList<>();
          E root = getThis() ;
          ...
          return result;
          }

          }

          abstract class ARelation<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements NodeRelation<E,R> {
          }

          class CNode extends ANode<CNode, CRelation> {
          public CNode getThis() { return this ; }
          ...
          }

          class CRelation extends ARelation<CNode, CRelation> {
          ...
          }


          Although I might not bother with having both abstract class and interface layers.






          share|improve this answer














          With this sort of situation it is often useful to have a getThis method that (by convention) returns this.



          I would do the following



          public interface Node<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public List<R> getParents();
          public List<R> getChildren();
          public List<E> listDescendants() ;
          }
          public interface NodeRelation<E extends Node<E, R>,
          R extends NodeRelation<E, R>>
          {
          public E getParent();
          public E getChild();
          }
          abstract class ANode<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements Node<E,R> {
          abstract protected E getThis() ;
          public List<E> listDescendants()
          {
          List<E> result = new ArrayList<>();
          E root = getThis() ;
          ...
          return result;
          }

          }

          abstract class ARelation<E extends ANode<E,R>,
          R extends ARelation<E,R>>
          implements NodeRelation<E,R> {
          }

          class CNode extends ANode<CNode, CRelation> {
          public CNode getThis() { return this ; }
          ...
          }

          class CRelation extends ARelation<CNode, CRelation> {
          ...
          }


          Although I might not bother with having both abstract class and interface layers.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 20 '18 at 23:30

























          answered Nov 20 '18 at 21:53









          Theodore Norvell

          7,20241637




          7,20241637








          • 1




            Upvoted. Eventually, I ended up implementing the same thing :)
            – Michele Mariotti
            Nov 21 '18 at 12:03














          • 1




            Upvoted. Eventually, I ended up implementing the same thing :)
            – Michele Mariotti
            Nov 21 '18 at 12:03








          1




          1




          Upvoted. Eventually, I ended up implementing the same thing :)
          – Michele Mariotti
          Nov 21 '18 at 12:03




          Upvoted. Eventually, I ended up implementing the same thing :)
          – Michele Mariotti
          Nov 21 '18 at 12:03


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53400009%2fjava-generics-self-reference-is-it-safe%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Create new schema in PostgreSQL using DBeaver

          Deepest pit of an array with Javascript: test on Codility

          Costa Masnaga