What is the benefit of extending a generic by specifying the new type as actual type of generic

前端 未结 5 715
逝去的感伤
逝去的感伤 2021-01-05 14:27

I saw this pattern somewhere:

class A extends B {

}

This structure is a little unusual to extend a generic by specifying the new

相关标签:
5条回答
  • 2021-01-05 14:42

    You might do something like this when dealing with recursive data structures. For example, nodes in a graph or a tree could be defined as a collection of other nodes:

    class Node extends AbstractList<Node> {
        ...
    }
    

    Equally you might see something like this if the abstract/generic type is meant for comparing objects of a similar type, such as is the case with java.lang.Comparable:

    class MyObject implements Comparable<MyObject> {
        public int compareTo(MyObject other) { ... }
    }
    
    0 讨论(0)
  • 2021-01-05 14:43

    It is indeed perplexing, since the two types A and B<A> appear to rely on each other to exist; that doesn't make much sense in ordinary OOP, so what is it for? I found 3 use cases for this pattern.

    Composition turned Inheritance

    Say a Node has a list of child Nodes. The usual design is through composition

    class Node
        ArrayList<Node> children = ...
    

    Sometimes for a small performance gain, people use inheritance instead

    class Node extends ArrayList<Node>
        // the super class represents the children...
    

    This is a little confusing, but there's nothing hard to understand. We know it's just a convenience, it does not try to convey that a node is a list of nodes.

    LoadableComponent can be considered of this use case. It's arguably a less ideal design than a composition approach

    class ComponentLoader<C>
        C get(){...}
    
    class EditIssue
        final ComponentLoader<EditIssue> loader = new ComponentLoader<EditIssue>(){
            @Override void load(){...}
            @Override void isLoaded(){...}
        };
    
    EditIssue compo = ...
    compo.loader.get().doSomething();
    

    The designer might find this approach more boiler platey.

    Method Chaining

    Instead of writing

    foo.doA();
    foo.doB();
    

    a lot of people would rather want to write

    foo.doA().doB();
    

    Unfortunately the language doesn't directly support method chaining even though it is becoming an increasingly desired feature. The workaround is for doA() to return foo. It is a little dirty but acceptable.

    However if foo is in a type hierarchy the workaround is broken

    class Bar
        Bar doA()
    
    class Foo extends Bar
        Foo doB();
    
    foo.doA().doB(); // doesn't compile, since doA() returns Bar
    

    So some people call for a special "self type" to solve this problem. Let's say there's a keyword This to represent "self type"

    class Bar
        This doA()
    
    foo.doA().doB(); // works, doA() returns the type of foo, which is Foo
    

    It appears that method chaining is the only use case for "self type", so the language probably will never introduce it (it's better to just support method chaining directly)

    People found out that generics provides a workaround for this problem

    class Bar<This>
        This doA()
    
    class Foo extends Bar<Foo>
    
    Foo has a method "Foo doA()", inherited from Bar<Foo>
    

    This is the most popular use case for the A extends B<A> pattern. It is an isolated workaround/trick. It adds no semantics in relationship between A and B.

    It is also a popular practice to constraint This like

    class Bar<This extends Bar<This>>
    

    It is ugly and useless, I strongly recommend against it. Simply use "This" as a convention to indicate what it is for.

    LoadableComponent can also fall in this use case. In a simpler design we could do

    class LoadableComponent
        void ensureLoaded()
    
    class EditIssue extends LoadableComponent
    
    EditIssue compo = ...
    compo.ensureLoaded();
    compo.doSomething();
    

    To support method chaining of the last two lines, LoadableComponent is designed in its current form, so that we can write compo.get().doSomething()

    Something more meta

    So the previous two use cases are kind of hacks. What if there's a genuine constraint between A and B<A>?

    Rather than serving as an ordinary super type, B is more meta, it describes that a type A should have some properties that reference A itself. This is not inheritance in traditional OOP's sense, it is something more abstract. (Though it is still implemented through traditional inheritance mechanism, it's imaginable that the language can promote it as a standalone concept.)

    Comparable is of this use case. It describes that a certain type can compare to itself. Since it is not a traditional OOP type, ideally we should never declare an object with static type Comparable. We don't see it in public method return/parameter type, it won't make much sense. Instead we see things like

    <T extends Comparable<T>> 
    void sort(List<T>)
    

    here the method requires a type that conforms to the Comparable pattern.

    (I don't really know what I'm talking about in this section)

    0 讨论(0)
  • 2021-01-05 14:45

    Of course the OOP answer is that A is a B. If A were not a B than A should merely compose itself with a B to make use of B's functionality.

    Presumably B also has some general implementations which take advantage of restrictions placed on the generic type.

    Another use case would be for B to look something like:

    abstract class B<T extends B<T>> {
        public T createCopy(T t);
    }
    

    Now subclasses can implement createCopy and client code can safely use it without having to cast... e.g.

    class A extends B<A> {
        public A createCopy(A t) {
            return new A(t); //copy constructor
        }
    }
    

    Compare the above to:

    abstract class B {
        public B createCopy(B t);
    }
    class A extends B {
        public B createCopy(B t) { //Is the copy an A or a different subtype of B? We don't know.
            return new A(t); //copy constructor
        }
    }
    
    0 讨论(0)
  • 2021-01-05 15:00

    This pattern is the same as any other sub-class. What's really happening when a generic is used is the JVM is creating a copy (not actually a copy, but it's kinda-sorta like that) of a class, and replacing all the spots where the generic is used with the specified type.

    So, to answer your question, all that pattern is doing is substituting B<A> for B in which all the uses of A are substituted with whatever class A is. Potential uses for this are in cases where you are customizing a data structure (from java.util.Collections) for a specific class, such as using bitshifts to compact a Collection<Boolean> into a smaller amount of memory. I hope that makes sense!

    0 讨论(0)
  • 2021-01-05 15:01

    Take this example:

    E extends Comparable<E>
    

    This means that E must be a type that knows how to compare to itself, hence, the recursive type definition.

    Don't know if it has any official names, but I would call it recursive generic type pattern.

    0 讨论(0)
提交回复
热议问题