Reputation: 12204
I'm designing a set of generic classes that hold Document
objects within Folder
objects:
// Folder, which holds zero or more documents
public interface Folder<DocType extends Document>
{
// Locate matching documents within the folder
public ArrayList<DocType> findDocuments(...);
...
}
// Document, contained within a folder
public interface Document
{
// Retrieve the parent folder
public Folder getFolder(); // Not correct
...
}
These classes are then extended for the actual implementation of the folder and document types. The trouble is that the Document.getFolder()
method needs to return an object of type Folder<DocType>
, where DocType
is the actual implementation type of Document
. Which means that the method needs to know what its own concrete class type is.
So my question is, should the Document
class be declared instead like this:
// Document, contained within a Folder
public interface Document<DocType extends Document>
{
// Retrieve the parent folder
public Folder<DocType> getFolder();
...
}
Or is there a simpler way to do this? The code above requires concrete implementations to look like this:
public class MyFolder
implements Folder<MyDocument>
{ ... }
public class MyDocument
implements Document<MyDocument>
{ ... }
It's the Document<MyDocument>
part that seems a bit strange to me; is it really necessary?
(Apologies if this is a duplicate; I couldn't find the exact answer I was looking for in the archives.)
ADDENDUM
The original code above used ArrayList<DocType>
, but like several posters have pointed out, I'd be better off returning a List
, e.g.:
public List<DocType> findDocuments(...);
(That method was not crucial to my problem, and my actual API returns an Iterator
, so I just used the first thing that came to mind in order to simplify the question.)
Upvotes: 2
Views: 2458
Reputation: 70564
That seems an aedequate way to adress your requirements, and no, there is no simpler way to express the same (in particular, Java has no syntax to constrain a type parameter to the type of this
).
However, since you extend both Document and Folder, perhaps your actually need a mutually recursive type bound. By which I mean that in your solution, the expression
new MyFolder().findDocuments().get(0).getFolder()
is of type Folder<MyDocument>
, not MyFolder
. If it needs to be MyFolder, the generics get ugly:
//Folder, which holds zero or more documents
interface Folder<D extends Document<D, F>, F extends Folder<D, F>> {
ArrayList<D> findDocuments();
}
// Document, contained within a folder
interface Document<D extends Document<D, F>, F extends Folder<D, F>> {
F getFolder();
}
You can then implement:
class MyFolder implements Folder<MyDocument, MyFolder> {
@Override
public ArrayList<MyDocument> findDocuments() {
return new ArrayList<>(Collections.singletonList(new MyDocument()));
}
}
class MyDocument implements Document<MyDocument, MyFolder> {
@Override
public MyFolder getFolder() {
return new MyFolder();
}
}
and now,
MyFolder folder = new MyFolder().findDocuments().get(0).getFolder();
compiles, too.
BTW, using ArrayList
in an interface is quite restrictive, as the kludge when I wanted to use Collections.singletonList
illustrates. You also (inadvertently?) deny yourself the services of Collections.unmodifiableList
and friends. Usually, one therefore leaves the List implementation to the discretion of the implementor by declaring
List<D> findDocuments();
instead.
Upvotes: 2
Reputation: 78589
The way you have it seems to be Ok for me, with the exception of a missing type parameters in the Document
and Folder
interfaces and the use of ArrayList
(an implementation) instead of List
(an interface).
interface Folder<T extends Document<T>>
{
public List<T> findDocuments();
}
interface Document<T extends Document<T>>
{
public Folder<T> getFolder();
}
Using the API is the best way to know, let's say we have an implementation of Document
called SpreadSheet
:
class SpreadSheet implements Document<SpreadSheet> {
@Override
public Folder<SpreadSheet> getFolder() {
return new SpreadSheetFolder();
}
}
And then you have a Folder
implementation called SpreadSheetFolder
:
class SpreadSheetFolder implements Folder<SpreadSheet>{
@Override
public List<SpreadSheet> findDocuments() {
return asList(new SpreadSheet(), new SpreadSheet());
}
}
Then when you use your API, you would do:
Document<SpreadSheet> d = new SpreadSheet();
Folder<SpreadSheet> folder = d.getFolder();
It works just fine.
Upvotes: 4