Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it good practice for a class to implement itself in a generic interface?

Tags:

java

generics

Apologies for the question title, I couldn't put this into words easily.

I've just come across this in some code:

public class MyClass implements Message<MyClass> {...}

I understand what it does but I've never seen a class declared in this way before.

The disadvantage I see is that now MyClass is a Message and needs to include implemented methods that are unrelated to its primary purpose.

One advantage I see (other than it reduces the number of other classes I would otherwise need to write) is that for things like Comparable, MyClass would know how to compare itself to other instance, which in turn would make for more concise code.

Is this good practice? Are there any rules-of-thumb?

like image 362
Dave Richardson Avatar asked Jul 19 '12 12:07

Dave Richardson


People also ask

Can a generic class implement an interface?

Only generic classes can implement generic interfaces. Normal classes can't implement generic interfaces.

Can a non-generic class implement a generic interface?

It is possible to create a non-generic class that implements a generic interface, provided that the type parameters are provided.

Can an interface implement itself?

Interfaces can contain methods, properties, events, and indexers. The interface itself does not provide implementations for the members that it declares. The interface merely specifies the members that shall be supplied by classes or structs that implement the interface.

Can a class implementing an interface have its own methods?

An implemented class can of course have methods NOT declared in its interface . But is bound to implement methods which are declared in the interface unless it is declared abstract .


2 Answers

This is more or less the only way in Java to have an interface with methods that refer to the implementing class itself. So, for example, you might write a binary tree node interface:

interface TreeNode<N extends TreeNode<N>> {
  N getLeftChild();
  N getRightChild();
  void setLeftChild(N node);
  void setRightChild(N node);
}

and then you have classes like

class TreeNodeWithInt extends TreeNode<TreeNodeWithInt> {
  int value;
  TreeNodeWithInt leftChild;
  TreeNodeWithInt rightChild;
  public TreeNodeWithInt getLeftChild() { return leftChild; }
  public void setLeftChild(TreeNodeWithInt newLeft) { leftChild = newLeft; }
  ...
}

If we didn't have the N type parameter, you would be forced to write unsafe code like

class TreeNodeWithInt extends TreeNode {
  int value;
  TreeNodeWithInt leftChild;
  TreeNodeWithInt rightChild;

  public void setLeftChild(TreeNode node) {
    // note the dangerous cast!
    leftChild = (TreeNodeWithInt) node;
  }
}

The key issue here is that interfaces aren't allowed to refer to the type of the class that's implementing the interface, when they describe the input and return types of methods. So instead, you include a "self" generic type parameter. It's a relatively common idiom in code that makes extensive use of generics.

As you've correctly identified, it's frequently used with Comparable specifically, because you should only be able to compare objects to other objects of the same type. Indeed, Collections.sort is specified to take a List<T> where T extends Comparable<? super T>, which means that T is comparable to at least other values of type T, but possibly others as well.

(Finally, as you might expect -- since it's the only way to achieve this behavior, it's not "good" or "bad" practice. That said, the goal of the technique is to avoid writing methods that compile without warnings but can end up throwing ClassCastException, which is itself a good practice.)

like image 148
Louis Wasserman Avatar answered Oct 31 '22 04:10

Louis Wasserman


It is neither "good practice" or "bad practice".

The real question is whether it accurately models what you are trying to achieve. In some cases it will, in others it won't.

And if a particular use of the "meta-pattern" results in unwanted and / or meaningless methods, then you have a strong case that it is not the right solution.

Are there any rules-of-thumb?

If it doesn't work in a particular use-case, don't use it in that use-case :-)

like image 23
Stephen C Avatar answered Oct 31 '22 03:10

Stephen C