Java Forum / General / June 2006
Is there a way to declare a reference as being a type implementing multiple interfaces?
Oliver Wong - 26 Jun 2006 16:06 GMT Let's say I have these two interfaces and these two classes:
<code> public interface IOne { public void methodOne(); }
public interface ITwo { public void methodTwo(); }
public class A implements IOne, ITwo { /*Implementation of the methods here*/ }
public class B implements IOne, ITwo { /*Implementation of the methods here*/ } </code>
And now I have code, for which either A or B would do the job fine, or even some as of yet unknown class C, as long as it implements the IOne and ITwo interfaces.
That is, I'd like to declare a reference as being of type IOne and ITwo at the same time: something like:
<code> private int myCode(IOne ITwo myVar, int someParam) { myVar.methodOne(); myVar.methodTwo(); return someParam; } </code>
Assume that classes A and B come from third parties, so I can't just invent a new interface IOneAndTwo, and have A and B implement those. All the solutions I can come up with are a bit messy.
<code> private int myCode(IOne myVar, int someParam) { if (!(myVar instanceof ITwo)) { throw new IllegalArgumentException("myVar has to implement ITwo"); } myVar.methodOne(); ((ITwo)myVar).methodTwo(); return someParam; } </code>
<code> private int myCode(IOne myVar1, ITwo myVar2 int someParam) { if (myVar1 != myVar2) { throw new IllegalArgumentException("myVar1 and myVar2 must refer to the same object"); } myVar1.methodOne(); myVar2.methodTwo(); return someParam; } </code>
etc.
Any ideas on how to declare (or simulate) a multiple-interface-type reference?
- Oliver
Robert Klemme - 26 Jun 2006 16:25 GMT > Let's say I have these two interfaces and these two classes: > [quoted text clipped - 40 lines] > throw new IllegalArgumentException("myVar has to implement ITwo"); > } You don't need that exception as the client will be bitten by a ClassCastException anyway if he passes an instance that doesn't implement the other interface.
> myVar.methodOne(); > ((ITwo)myVar).methodTwo(); [quoted text clipped - 13 lines] > } > </code> That looks even more awkward that the first one.
> Any ideas on how to declare (or simulate) a multiple-interface-type > reference? If these interfaces are so closely related one should probably inherit the other one. Other than that I don't know
Kind regards
robert
Oliver Wong - 26 Jun 2006 16:30 GMT >> Any ideas on how to declare (or simulate) a multiple-interface-type >> reference? > > If these interfaces are so closely related one should probably inherit the > other one. Other than that I don't know Right, I forgot to mention I can't edit the interfaces either. Specifically, I'm working with the Eclipses interfaces pentuplet: IDocument, IDocumentExtension, IDocumentExtension2, IDocumentExtension3 and IDocumentExtension4. As long as the object getting passed in implements all five of those interfaces, my code will work. Now I'd like to specify the requirement that the object implements all five of those interfaces in the method signature somehow.
I agree with you about the interfaces should be inheritting each other. I've submitted that as a RFE to the Eclipse team, but until they make the changes and make an official release of their code, I gotta find a workaround.
- Oliver
Oliver Wong - 26 Jun 2006 17:32 GMT >> If these interfaces are so closely related one should probably inherit >> the other one. Other than that I don't know [quoted text clipped - 3 lines] > changes and make an official release of their code, I gotta find a > workaround. I submitted the bug, but they've responded with "there is no chance that we address this.": https://bugs.eclipse.org/bugs/show_bug.cgi?id=148660
I guess I'll have to stick with Stefans fix.
- Oliver
Patricia Shanahan - 26 Jun 2006 16:38 GMT ...
>> Assume that classes A and B come from third parties, so I can't just >> invent a new interface IOneAndTwo, and have A and B implement those. [quoted text clipped - 9 lines] > ClassCastException anyway if he passes an instance that doesn't > implement the other interface. An IllegalArgumentException, especially with a message naming the argument and saying what test it failed, may be easier to document and interpret than a ClassCastException.
Patricia
Chris Smith - 26 Jun 2006 16:43 GMT > That is, I'd like to declare a reference as being of type IOne and ITwo at > the same time: something like: [quoted text clipped - 6 lines] > } > </code> Nope, sorry. There are no intersection types in Java. I'd just pick one and check the other with instanceof. If it fits your aesthetic sense better (neither interface is "more" important), you might even eskew types here entirely and ask for an Object, which would make it more obvious that the client should check the documentation for a description of what kind of object to pass.
If you find yourself doing this a lot, then something like the following might come in handy:
class OneTwoWrapper implements IOne, ITwo { private IOne one; private ITwo two;
public OneTwoWrapper(Object obj) { one = (IOne) obj; two = (ITwo) obj; }
... implement forwarding methods ... }
 Signature Chris Smith - Lead Software Developer / Technical Trainer MindIQ Corporation
Stefan Ram - 26 Jun 2006 16:47 GMT >That is, I'd like to declare a reference as being of type IOne and ITwo at >the same time: something like: public class Main { static < T extends java.lang.Object & java.io.Serializable & java.lang.Comparable< T >> void f( final T t ){}
public static void main( final java.lang.String[] args ) { f( new java.lang.String( "0" )); f( new java.lang.Long( 0 )); }}
Chris Smith - 26 Jun 2006 16:49 GMT > static > < T extends java.lang.Object & > java.io.Serializable & > java.lang.Comparable< T >> > void f( final T t ){} Oh, wow! You're right.
 Signature Chris Smith - Lead Software Developer / Technical Trainer MindIQ Corporation
Stefan Ram - 26 Jun 2006 16:57 GMT >private int myCode(IOne ITwo myVar, int someParam) { interface IOne { void methodOne(); } interface ITwo { void methodTwo(); }
class A implements IOne, ITwo { public void methodOne(){} public void methodTwo(){} }
class B implements IOne, ITwo { public void methodOne(){} public void methodTwo(){} }
public class Main { private < T extends IOne & ITwo > int myCode( final T myVar, int someParam ) { myVar.methodOne(); myVar.methodTwo(); return someParam; }
public static void main( final java.lang.String[] args ) { new Main().myCode( new A(), 0 ); new Main().myCode( new B(), 0 ); }}
Oliver Wong - 26 Jun 2006 17:49 GMT >>private int myCode(IOne ITwo myVar, int someParam) { > [quoted text clipped - 18 lines] > public static void main( final java.lang.String[] args ) > { new Main().myCode( new A(), 0 ); new Main().myCode( new B(), 0 ); }} Thank you very much. This is much better than anything I've come up with. There's just one minor problem left. It's not a big issue if I can't resolve it, but if you can figure out a solution, it'll make my code a little bit more elegant. I have a factory method which uses a static Map for caching purposes. So the declaration of the field looks like:
<code> private static final Map<Object, Foo> mapping = new HashMap<Object, Foo>(); </code>
but ideally, I'd like to use your trick and write something like:
<code> private static final <T extends IOne & ITwo> Map<T, Foo> mapping = new HashMap<T, Foo>(); </code>
Unfortunately, my compiler complains "Syntax error on tokens, ReferenceType expected instead", which I guess means the grammar for the Java language doesn't support this kind of construct. Is there a solution to this one?
- Oliver
Stefan Ram - 26 Jun 2006 18:23 GMT >private static final <T extends IOne & ITwo> Map<T, Foo> mapping = new >HashMap<T, Foo>(); My own knowledge about type parameters is very irregular: I only know about those parts that recently came along my way incidentally, but I am not an expert in this field.
I was able to compile something similar to the above code after I removed »static« and moved the declaration of »T« to the enclosing class:
interface IOne { void methodOne(); } interface ITwo { void methodTwo(); }
class A implements IOne, ITwo { public void methodOne(){} public void methodTwo(){} }
class B implements IOne, ITwo { public void methodOne(){} public void methodTwo(){} }
public class Main< T extends IOne & ITwo > { private final java.util.Map<T, java.lang.Object> mapping = new java.util.HashMap<T, java.lang.Object>();
private int myCode( final T myVar, int someParam ) { myVar.methodOne(); myVar.methodTwo(); return someParam; }
public static void main( final java.lang.String[] args ) { new Main<A>().myCode( new A(), 0 ); new Main<B>().myCode( new B(), 0 ); java.lang.System.out.println( "2006-06-26T19:18:10+02:00" ); }}
Oliver Wong - 26 Jun 2006 18:44 GMT >>private static final <T extends IOne & ITwo> Map<T, Foo> mapping = new >>HashMap<T, Foo>(); [quoted text clipped - 6 lines] > code after I removed »static« and moved the declaration > of »T« to the enclosing class: [code snipped]
Yeah, I moved T to the enclosing class as well in my code. Unfortunately, I need the static, because the caching map is being used by a factory method:
<code> class Foo<T extends IOne & ITwo> { private static final Map<Object, Foo> mapping = new HashMap<Object,Foo>();
private Foo(T impl) { this.impl = impl; }
private final T impl;
public static <T extends IOne & ITwo> makeFoo(T impl) { if (!mapping.contains(impl)) { mapping.put(impl, new Foo(impl)); } return mapping.get(impl); } } </code>
- Oliver
Thomas Hawtin - 26 Jun 2006 19:16 GMT > Yeah, I moved T to the enclosing class as well in my code. > Unfortunately, I need the static, because the caching map is being used > by a factory method:
> class Foo<T extends IOne & ITwo> { > private static final Map<Object, Foo> mapping = new HashMap<Object,Foo>(); <?>
> private Foo(T impl) { > this.impl = impl; [quoted text clipped - 5 lines] > if (!mapping.contains(impl)) { > mapping.put(impl, new Foo(impl)); <T>
> } > return mapping.get(impl); > } > } Won't you still have problems with a single base type? It seems as if you have a heterogeneous map. The usual cure for that, is to add something like Class.cast to the key.
Being static you should also make it thread-safe. A ConcurrentHashMap would fit nicely.
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Oliver Wong - 26 Jun 2006 19:56 GMT >> Yeah, I moved T to the enclosing class as well in my code. >> Unfortunately, I need the static, because the caching map is being used [quoted text clipped - 23 lines] > have a heterogeneous map. The usual cure for that, is to add something > like Class.cast to the key. The Map isn't heterogeneous in the sense that all keys are known to implement IOne and ITwo simultaneously. So the type of the key would be something like <? extends IOne & ITwo> (if only the compiler accepted that).
> Being static you should also make it thread-safe. A ConcurrentHashMap > would fit nicely. Thanks, I'll fix that.
- Oliver
Thomas Hawtin - 26 Jun 2006 18:50 GMT > but ideally, I'd like to use your trick and write something like: > > <code> > private static final <T extends IOne & ITwo> Map<T, Foo> mapping = new > HashMap<T, Foo>(); > </code> If you were writing that for one interface, you would have:
private static final Map<? extends Appendable,String> map = createMap();
private static <T extends Appendable> Map<T,String> createMap() { return new java.util.HashMap<T,String>(); }
(Implicit capture is a bit more flexible than what you can write explicitly.)
I've been meaning to have a play with this for intersection types for some time.
You can slide in a layer of indirection. It does require some copy & paster work, but it be required too often. Here's some code I wrote a minute ago:
final class AppendableCharSequenceRef< T extends Appendable & CharSequence
> { // The basics...
private final T value;
public AppendableCharSequenceRef(T value) { this.value = value; }
public T get() { return value; }
// The trimmings...
public void get(Handler<Appendable,CharSequence> handler) { handler.handle(value, value); }
public < N extends Appendable & CharSequence > AppendableCharSequenceRef<?> ref(N value) { return this.value==value ? this : new AppendableCharSequenceRef<N>(value); }
public static final AppendableCharSequenceRef<?> NULL = newInstance(null);
public static < N extends Appendable & CharSequence > AppendableCharSequenceRef<N> newInstance( N value ) { return new AppendableCharSequenceRef<N>(value); }
public static < N extends Appendable & CharSequence > AppendableCharSequenceRef<?> of( N value ) { return NULL.ref(value); } }
interface Handler<A,B> { void handle(A asA, B asB); }
class Intersect { private AppendableCharSequenceRef<?> x;
public Intersect() { this.x = AppendableCharSequenceRef.NULL; }
public <T extends Appendable & CharSequence> Intersect(T x) { this.x = AppendableCharSequenceRef.of(x); }
public <T extends Appendable & CharSequence> void set(T x) { this.x = this.x.ref(x); }
public void set(AppendableCharSequenceRef<?> xRef) { if (xRef == null) { throw new NullPointerException(); } this.x = xRef; } public AppendableCharSequenceRef<?> getAsRef() { return x; } public Appendable getAsAppendable() { return x.get(); } public CharSequence getAsCharSequence() { return x.get(); }
public void get(Handler<Appendable,CharSequence> handler) { x.get(handler); }
public void append(char c) throws java.io.IOException { x.get().append(c); } public char getAt(int off) { return x.get().charAt(off); } }
Tom Hawtin
 Signature Unemployed English Java programmer http://jroller.com/page/tackline/
Hendrik Maryns - 27 Jun 2006 09:14 GMT Stefan Ram schreef:
>> private int myCode(IOne ITwo myVar, int someParam) { > [quoted text clipped - 15 lines] > myVar.methodTwo(); > return someParam; } Do note that, if you use this with 1.4 code, the erasure is private int myCode(final IOne myVar, int someParam); so interoperating might be difficult (solution: put another Object & between extends and IOne).
If I understood right, the reason why these intersection types where introduced was to provide backwards compatibility by erasure, but this is a nice application of it, which probably was unforeseen.
H. - -- Hendrik Maryns
================== http://aouw.org Ask smart questions, get good answers: http://www.catb.org/~esr/faqs/smart-questions.html
a_szczeblewski@poczta.onet.pl - 26 Jun 2006 19:18 GMT > Let's say I have these two interfaces and these two classes: > [quoted text clipped - 10 lines] > /*Implementation of the methods here*/ > } Can you do that?:
public interface IOneTwo extends IOne, ITwo {}
public class A implements IOneTwo
void method(IOneTwo ref) { ref.methodOne(); ref.methodTwo(); }
Oliver Wong - 26 Jun 2006 19:54 GMT >> Let's say I have these two interfaces and these two classes: >> [quoted text clipped - 22 lines] > ref.methodTwo(); > } No, I can't change the classes or the interfaces, as they're provided by a 3rd party.
- Oliver
Andrew McDonagh - 26 Jun 2006 19:50 GMT > Let's say I have these two interfaces and these two classes: > [quoted text clipped - 22 lines] > That is, I'd like to declare a reference as being of type IOne and ITwo > at the same time: something like: interface IOneAndITwo extends IOne, ITwo { }
> <code> > private int myCode(IOne ITwo myVar, int someParam) { [quoted text clipped - 7 lines] > invent a new interface IOneAndTwo, and have A and B implement those. All > the solutions I can come up with are a bit messy. snipped
> Any ideas on how to declare (or simulate) a multiple-interface-type > reference? Ok, so you cant change the classes to implement the new interface, you can create a new class that implements it and delegates to the 3rd party class's objects.....
class OneAndTwo implements IOneAndITwo {
One o = new com.othercompany.One(); Two t = new com.othercompany.Two();
public void methodOne() { o.methodOne(); }
public int method2() { return t.methodTwo(); } }
> - Oliver Oliver Wong - 26 Jun 2006 19:53 GMT > > Any ideas on how to declare (or simulate) a multiple-interface-type > > reference? [quoted text clipped - 16 lines] > } > } This sort of just moves the problem around, instead of solving it: I'd still need to ensure o and t refer to the same object (in case methodOne affects the state which would be visible in methodTwo).
- Oliver
Furious George - 27 Jun 2006 01:38 GMT > > > Any ideas on how to declare (or simulate) a multiple-interface-type > > > reference? [quoted text clipped - 20 lines] > still need to ensure o and t refer to the same object (in case methodOne > affects the state which would be visible in methodTwo). I don't see this as a problem. By encapsulation, you should not worry too much about the internal state of objects. But if you insist on worrying, you can specify the Constructors in OneAndTwo to ensure o and t are the same object like this:
private OneAndTwo ( ) { throw ( new IllegalArgumentException ( ) ) ; } // no no-argument constructor allowed private OneAndTwo ( IOne o , ITwo t ) { o=o ; t=t ; } public OneAndTwo ( A obj ) { this ( obj , obj ) ; } // o and t are the same public OneAndTwo ( B obj ) { this ( obj , obj ) ; } // B also implements IOne,ITwo but not OneAndTwo
> - Oliver Oliver Wong - 28 Jun 2006 15:10 GMT >> This sort of just moves the problem around, instead of solving it: >> I'd [quoted text clipped - 3 lines] > I don't see this as a problem. By encapsulation, you should not worry > too much about the internal state of objects. Well, what if the interfaces were like:
<code> public interface IOne { /** * Modifies something. Notifies all listeners of the changes. */ public void modifySomething(); }
public interface ITwo { /** * Prevents notification from occurring. Useful for batch modifications, * so that all listeners are notified after all the changes are made, instead * of after each change. Caller is responsible for calling * resumeNotifications(). */ public void temporarilyStopListenerNotification();
public void resumeNotifications(); } </code>
This is sort of the situation I have with Eclipse' IDocument, and IDocumentExtension interfaces.
> But if you insist on > worrying, you can specify the Constructors in OneAndTwo to ensure o and [quoted text clipped - 7 lines] > public OneAndTwo ( B obj ) { this ( obj , obj ) ; } // B also > implements IOne,ITwo but not OneAndTwo But now I'd have to enumerate all classes that actually implement IOne and ITwo, which may not be possible. You've got A and B here, but what about the unknown class C which may be developed by some third party in the future?
- Oliver
Furious George - 28 Jun 2006 17:32 GMT > >> This sort of just moves the problem around, instead of solving it: > >> I'd [quoted text clipped - 30 lines] > This is sort of the situation I have with Eclipse' IDocument, and > IDocumentExtension interfaces. Just because o and t refer to the same object, does not mean that the object was properly implemented. <source code> public class BadImp implements IOne, ITwo { public void modifySomething ( ) { ... } // do modifications and notify all listeners public void temporarilyStopListenerNotification { ... } // send garbage notification to some subscribers public void resumeNotifications ( ) { ... } // do nothing </source code>
OTH o and t could refer to different objects, yet be properly implemented </source code> public class GoodImplOne implements IOne { GoodImplOne ( GoodImplTwo two ) { two=two ; } public void modifySomething ( ) { doModification ( ) ; two.notify ( ) ; } private void doModification ( ) { ... } // do modifications } public class GoodImplTwo implements ITwo { public void notifiy ( ) { ... } notify all listeners now or when notifications has been resumed public void temporarilyStopListenerNotification { ... } public void resumeNotifications ( ) { ... } } </source code>
> > But if you insist on > > worrying, you can specify the Constructors in OneAndTwo to ensure o and [quoted text clipped - 12 lines] > the unknown class C which may be developed by some third party in the > future? In your original post, all your methods were private. It should be easy to enumerate all the classes that actually implement IOne and ITwo that you actually use. If you do not use class C in your code, why should you care about it? The OneAndTwo class may not be terribly useful to other people (who use class C), but who cares. They can write their own OneAndTwo class.
OTH, if you are writing methods that will be public (and thus used by other people) then this could be a problem. But I still maintain it is not necessary to insure that o and t are the same object.
> - Oliver Oliver Wong - 28 Jun 2006 18:22 GMT >> "Furious George" <bugme_69@hotmail.com> wrote in message
>> > But if you insist on >> > worrying, you can specify the Constructors in OneAndTwo to ensure o and [quoted text clipped - 19 lines] > that you actually use. If you do not use class C in your code, why > should you care about it? I don't actually "use" class A or B either, in my code. I only use the IOne and ITwo interface, and whatever gets passed in gets used. To use specific class names from my actual problem, I don't actually use the org.eclipse.jface.text.AbstractDocument; instead, I use the IDocument and IDocumentExtension interfaces. The JavaDocs for IDocument specifically say that clients are free to implement the interface with new classes.
The idea is that I write my code to work with certain interfaces, and if the user installs new plugins into Eclipse that add new implementations of IDocument and IDocumentExtension (that mysterious class C), my code will work with those new plugins without any change. The Eclipse platform itself will take care of passing the instance of C to my code.
> The OneAndTwo class may not be terribly > useful to other people (who use class C), but who cares. They can > write their own OneAndTwo class. They might never know about the OneAndTwo classes, particularly if they're just writing some random Eclipse plugin with no knowledge of my code at all. They'll read the Eclipse plugin API documentation, and only see the two interfaces. So they'll make a class which implements those two interfaces, and it should work with anything else that's hooked up into Eclipse, including my code.
> OTH, if you are writing methods that will be public (and thus used by > other people) then this could be a problem. But I still maintain it is > not necessary to insure that o and t are the same object. My original requirements is having a SINGLE objected passed in which implements two interfaces. Since the Java language doesn't directly support that[*], I'm "faking it" by having the method signature take two references. Conceptually, these two references should represent the same object, so that any state changes done to one will be reflected in the other.
As you said (in the comment that I snipped), there's always the possibility that whoever implements the interface correctly. But if that occurs, I can pass the buck, saying it's their code that's broken, not mine. I only have to worry about objects which respect their contracts, and make sure that my code fufills my end of the deal.
- Oliver
[*]: Except see Hendrik's solution posted elsewhere in this thread, which *almost* solves it.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|