Submitted On 31-OCT-2006
jonbarril
A correction to my original submission:
The self-type pattern for generics is like not being able to fully subclass a "concrete" class. You can only fully subclass an abstract class, and subclassing a concrete class results in a half-baked version of a subclass, with the "this" type parameters and returns being that of the concrete super class, not the subclass.
Response to the evaluation:
Do a google search for "java generic self type". There is an awful lot of talk out there about a need for this, and what a pain it is not having it.
Covariant return just doesn't cut it. It is difficult to get it to work in the context of the self-type pattern, and does nothing for method parameters, resulting in a half-baked concrete subclass. As we all know, something that is half-baked is often worse than not having it at all because the inconsistencies in usage will drive you (designer and client) up the wall.
I can still remember the time when the java high priests swore that you'd never see generics in java, yet we now have generics. Might i suggest that the high priests give this problem a bit more thought before abandoning it.
I have developed a number of patterns for highly reusable code that don't quite work because of the lack of a strong sense of a self-type -- i.e. the ability to fully subclass a concrete class that relies on the self type. It seems a lot of folks out there are making do, but a major avenue of code reusability through the self type is stymied.
As for your example using clone(), it seems to me that super.clone() cannot be cast to "this" since the super class is not assignable to the "this" subclass. The notion of "this" is exactly what it says -- the type of "this" declared class, and the type to which any subclass of the "this" class can be assigned to.
Also, clone() may be an issue, but I have not seen it come up in the community that is discussing self-type. Why not just leave clone() and other such legacy methods alone for now (returning Object would be no worse than what we now have), and focus on the crux of the problem, which is the use of self-type in new highly reusable code.
Submitted On 04-NOV-2006
jonbarril
Given the current lack of a true self-type, the following is an example of the closest that I've been able to come to achieving generic "this-ness". I call it the RawType pattern, since it relies on the use of the raw type (and hence, has lots of compiler warnings, and other serious drawbacks, but no compiler errors).
==========
/**
* Pros: For variables, can consistently use the raw type. Only simple casting
* to (THIS) is sometimes needed.
*
* Cons: Lots of compiler warnings because of raw type. This-ness of methods
* using THIS as an argument or return value is NOT inherited by subclasses,
* requiring override in all descendant subclasses.
*
* @author Jon Barrilleaux (jonb@jmbaai.com) of JMB and Associates Inc.
* @version $Revision: 1.1 $
*/
public interface RawType<THIS extends RawType> {
public THIS getThis();
public THIS asUnmod();
}
===================
/**
*
* @author Jon Barrilleaux (jonb@jmbaai.com) of JMB and Associates Inc.
* @version $Revision: 1.1 $
*/
public interface MyObject<THIS extends MyObject>
extends RawType<THIS> {
public THIS asUnmod();
public THIS getThis();
public void setThisObject(THIS obj);
public THIS getThisObject();
public void setMyObject(MyObject obj);
public MyObject getMyObject();
// class
/**
* Default concrete implementation for instantiation.
*/
public static class Impl extends Base<MyObject> {
public final MyObject asUnmod() {
return new MyObject.Unmod<MyObject>(getThis()){};
}
}
/**
* Default abstract implementation for extension.
*/
public static abstract class Base<THIS extends MyObject>
implements MyObject<THIS> {
public final void setThisObject(THIS obj) {
_obj = obj;
}
public final THIS getThisObject() {
return _obj;
}
public final void setMyObject(MyObject obj) {
_myObj = obj;
}
public final MyObject getMyObject() {
return _myObj;
}
public final THIS getThis() {
return (THIS)this;
}
// personal
private THIS _obj;
private MyObject _myObj;
}
/**
* Abstract unmodifiable wrapper for extension.
*/
public static abstract class Unmod<THIS extends MyObject>
implements MyObject<THIS> {
public Unmod(THIS target) {
_target = target;
}
public final MyObject getMyObject() {
return _target.getMyObject();
}
public final THIS getThisObject() {
return (THIS)_target.getThisObject();
}
public final void setMyObject(MyObject obj) {
throw new UnsupportedOperationException();
}
public final void setThisObject(THIS obj) {
throw new UnsupportedOperationException();
}
public final THIS getThis() {
return (THIS)this;
}
public final THIS asUnmod() {
return getThis();
}
// personal
private THIS _target;
}
}
Submitted On 04-NOV-2006
jonbarril
================
/**
*
* @author Jon Barrilleaux (jonb@jmbaai.com) of JMB and Associates Inc.
* @version $Revision: 1.1 $
*/
public interface MySubObject<THIS extends MySubObject>
extends MyObject<THIS> {
public THIS asUnmod();
public THIS getThis();
public void setThisObject(THIS obj);
public THIS getThisObject();
public void setMyObject(MyObject obj);
public MyObject getMyObject();
public void setMySubObject(MySubObject obj);
public MySubObject getMySubObject();
// class
/**
* Default concrete implementation for use.
*/
public static final class Impl extends Base<MySubObject> {
public final MySubObject asUnmod() {
return new MySubObject.Unmod<MySubObject>(this){};
}
}
/**
* Default abstract implementation for extension.
*/
public static abstract class Base<THIS extends MySubObject>
extends MyObject.Base<THIS> implements MySubObject<THIS> {
public final void setMySubObject(MySubObject obj) {
_mySubObj = obj;
}
public final MySubObject getMySubObject() {
return _mySubObj;
}
// personal
private MySubObject _mySubObj;
}
/**
* Abstract unmodifiable wrapper for extension.
*/
public static abstract class Unmod<THIS extends MySubObject>
extends MyObject.Unmod<THIS> implements MySubObject<THIS> {
public Unmod(THIS target) {
super(target);
_target = target;
}
public MySubObject getMySubObject() {
return _target.getMySubObject();
}
public void setMySubObject(MySubObject obj) {
throw new UnsupportedOperationException();
}
// personal
private THIS _target;
}
}
Submitted On 04-NOV-2006
jonbarril
=======================
/*
* Project Gumbo - gumbo.sourceforge.net
* $Id: Test.java,v 1.1 2006/11/03 07:43:22 jonb Exp $
*
* This source is licensed under the GNU LGPL v2.1.
* Please read http://www.gnu.org/copyleft/lgpl.html for more information.
*
* This software comes with the standard NO WARRANTY disclaimer for any
* purpose. Use it at your own risk.
*/
package proto.java.generic.rawtype;
/**
*
* @author Jon Barrilleaux (jonb@jmbaai.com) of JMB and Associates Inc.
* @version $Revision: 1.1 $
*/
public class Test A {
public void testImpl() {
MyObject obj = new MyObject.Impl();
MySubObject subobj = new MySubObject.Impl();
}
public void testMyObject() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
obj = obj;
obj = objB;
obj = objI;
obj = subobj;
obj = subobjB;
obj = subobjI;
}
public void testMyObjectBase() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
objB = obj;
objB = objB;
objB = objI;
objB = subobj;
objB = subobjB;
objB = subobjI;
}
public void testMyObjectImpl() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
objI = obj;
objI = objB;
objI = objI;
objI = subobj;
objI = subobjB;
objI = subobjI;
}
public void testMySubObject() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
subobj = obj;
subobj = objB;
subobj = objI;
subobj = subobj;
subobj = subobjB;
subobj = subobjI;
}
public void testMySubObjectBase() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
subobjB = obj;
subobjB = objB;
subobjB = objI;
subobjB = subobj;
subobjB = subobjB;
subobjB = subobjI;
}
public void testMySubObjectImpl() {
MyObject obj = null;
MyObject.Base objB = null;
MyObject.Impl objI = null;
MySubObject subobj = null;
MySubObject.Base subobjB = null;
MySubObject.Impl subobjI = null;
subobjI = obj;
subobjI = objB;
subobjI = objI;
subobjI = subobj;
subobjI = subobjB;
subobjI = subobjI;
}
}
Submitted On 04-NOV-2006
jonbarril
/**
*
* @author Jon Barrilleaux (jonb@jmbaai.com) of JMB and Associates Inc.
* @version $Revision: 1.1 $
*/
public class TestB {
public void testMyObjectGet() {
MyObject obj = null;
obj = obj.getThisObject();
obj = obj.getThisObject().getThisObject();
obj = obj.getThisObject().getMyObject();
obj = obj.getThisObject().getMySubObject();
obj = obj.getMyObject();
obj = obj.getMyObject().getThisObject();
obj = obj.getMyObject().getMyObject();
obj = obj.getMyObject().getMySubObject();
obj = obj.getMySubObject();
obj = obj.getMySubObject().getThisObject();
obj = obj.getMySubObject().getMyObject();
obj = obj.getMySubObject().getMySubObject();
}
public void testMySubObjectGet() {
MySubObject subObj = null;
subObj = subObj.getThisObject();
subObj = subObj.getThisObject().getThisObject();
subObj = subObj.getThisObject().getMyObject();
subObj = subObj.getThisObject().getMySubObject();
subObj = subObj.getMyObject();
subObj = subObj.getMyObject().getThisObject();
subObj = subObj.getMyObject().getMyObject();
subObj = subObj.getMyObject().getMySubObject();
subObj = subObj.getMySubObject();
subObj = subObj.getMySubObject().getThisObject();
subObj = subObj.getMySubObject().getMyObject();
subObj = subObj.getMySubObject().getMySubObject();
}
public void testMyObjectSet() {
MyObject obj = null;
MySubObject subObj = null;
obj.setThisObject(obj);
obj.setThisObject(obj.getThisObject());
obj.setThisObject(obj.getMyObject());
obj.setThisObject(obj.getMySubObject());
obj.setThisObject(subObj);
obj.setThisObject(subObj.getThisObject());
obj.setThisObject(subObj.getMyObject());
obj.setThisObject(subObj.getMySubObject());
obj.setMyObject(obj);
obj.setMyObject(obj.getThisObject());
obj.setMyObject(obj.getMyObject());
obj.setMyObject(obj.getMySubObject());
obj.setMyObject(subObj);
obj.setMyObject(subObj.getThisObject());
obj.setMyObject(subObj.getMyObject());
obj.setMyObject(subObj.getMySubObject());
}
public void testMySubObjectSet() {
MyObject obj = null;
MySubObject subObj = null;
subObj.setThisObject(obj);
subObj.setThisObject(obj.getThisObject());
subObj.setThisObject(obj.getMyObject());
subObj.setThisObject(obj.getMySubObject());
subObj.setThisObject(subObj);
subObj.setThisObject(subObj.getThisObject());
subObj.setThisObject(subObj.getMyObject());
subObj.setThisObject(subObj.getMySubObject());
subObj.setMyObject(obj);
subObj.setMyObject(obj.getThisObject());
subObj.setMyObject(obj.getMyObject());
subObj.setMyObject(obj.getMySubObject());
subObj.setMyObject(subObj);
subObj.setMyObject(subObj.getThisObject());
subObj.setMyObject(subObj.getMyObject());
subObj.setMyObject(subObj.getMySubObject());
subObj.setMySubObject(obj);
subObj.setMySubObject(obj.getThisObject());
subObj.setMySubObject(obj.getMyObject());
subObj.setMySubObject(obj.getMySubObject());
subObj.setMySubObject(subObj);
subObj.setMySubObject(subObj.getThisObject());
subObj.setMySubObject(subObj.getMyObject());
subObj.setMySubObject(subObj.getMySubObject());
}
}
Submitted On 05-NOV-2006
sj_colebourne
This problem is not just limited to clone(). It has a big impact on immutable class design, where you need immutable mutators to return the type of the subclass not the superclass. In this scenario, you can end up repeating vast numbers of methods each of which just do super.xxx() in order to invoke the convariant return type.
Enhancing generics with this feature would be relatively simple, and would save a lot of lines of meaningless boilerplate code. And aren't immutable classes meant to be the way of the future?
Submitted On 06-NOV-2006
jonbarril
Ditto for unmodifiable classes, such as the Unmod wrapper classes in the examples above (note that most of the methods in the base class, the implementation class, and the wrappers are intentionally made final).
As formalized by Bloch in "Effective Java" (and no doubt many others before and after), classes should be designed for extension, which means that methods that are not intended to be extended should be final. This is the main reason for the odd, repetitive structure of the examples I included above. Given the current state of Java there is no way to write a base class with final methods that can be wrapped with an unmodifiable (or synchronized, or null object, etc) wrapper -- you have to define an interface, and then have a "family" of classes that implement the interface, which can declare the methods as final. As such, and as the previous commentor has correctly observed, you end up with a whole lot of boilerplate just to achieve a modicum of good solid reusable code. And, generics has, in many cases, exaccerbated this problem, not solved it.
Submitted On 26-JAN-2007
Miamidot
Would be useful for getting log4j handles.
private static Log LOG =
LogFactory.getLog(ThisClass);
Currently we have to modify the class name for the thousands of classes of each project to register log4j for each class.
As according to author of this issue, the following convenience should be provided,
class LinkLink{
public mkLink(ThisClass cref, SomeClass xref)
{ ... }
rather than,
class LinkLink{
public mkLink(LinkLink cref, SomeClass xref)
{ ... }
Submitted On 23-FEB-2007
Compl.Y.Still
I wonder how you've made the tricky javac to workaround Class<?> Object.getClass(); where:
"The result is of type Class<? extends X> where X is the erasure of the static type of the expression on which getClass is called."
Can't this be extended to a wider scope and with more formal syntax? such like the 'class' keyword in place of a type name?
Submitted On 13-APR-2007
theshade
Although this proposal has other uses, I'd like to point out that the use of method chaining is actually a work-around in the Java language for the with/endwith construct from for example Pascal.
Constructs like this are possible in Pascal:
WITH object1 BEGIN
.setTitle("My Title")
WITH .getAddress() BEGIN
.setStreetName("My Street")
END
END
The Java equivalent being used these days with method chaining looks something like this:
object1
.setTitle("My Title")
.getAddress()
.setStreetName("My Street");
Unfortunately it has numerous problems, some of which is being addressed by this RFE.
The power of method chaining (or the with/endwith construct) should be obvious when creating hierarchical structures in Java, like for example in Swing. Consider this Swing example:
{
JPanel panel1 = new JPanel();
panel1.setBackground(Color.RED);
JButton button1 = new JButton("Ok");
button1.setBackground(Color.GREEN);
JButton button2 = new JButton("Cancel");
button2.setBackground(Color.RED);
panel1.add(button1);
panel1.add(button2);
return panel1;
}
The example is very cumbersome. You are forced to declare various pointless variables to temporary hold objects so you can modify them, which makes the code hard to read and hard to re-use (can't copy and paste a piece of Swing UI code easily elsewhere).
With method chaining the example could look like this:
{
return new JPanel()
.setBackground(Color.RED)
.add(new JButton("Ok")
.setBackground(Color.GREEN))
.add(new JButton("Cancel")
.setBackground(Color.RED));
}
The above example suffers from the problem though that superclasses do not return this.getClass() as their return type.
With a with/endwith construct it could look like this:
{
return with new JPanel() {
.setBackground(Color.RED);
with(.add(new JButton("Ok")) {
.setBackground(Color.GREEN);
}
with(.add(new JButton("Cancel")) {
.setBackground(Color.RED);
}
}
}
Method chaining is an acceptable compromise at the moment but the underlying reason that it is being used so extensively in a lot of frameworks is in part the lack of a way to access objects multiple times without having to declare pointless temporary variables. With/Endwith constructs addressed this need in various other languages. Method chaining in part can address this for Java.
Submitted On 13-APR-2007
theshade
Slight typo in the last example, it should read:
{
return with(new JPanel()) {
.setBackground(Color.RED);
with(.add(new JButton("Ok")) {
.setBackground(Color.GREEN);
}
with(.add(new JButton("Cancel")) {
.setBackground(Color.RED);
}
}
}
Submitted On 09-MAY-2007
Hi,
I have needed this behavior for a while now. I have been able to sort of simulate it using the obscure syntax for explicit type parameters. It's pretty cool take a look.
public class TestGetThis {
public static void main(String[] args) {
new TestGetThis().run();
}
void run() {
SubClass1 subClass1 = new SubClass1();
subClass1.callBaseMethodWithGetThis();
subClass1.callSubMethodWithGetThis();
SubClass2 subClass2 = new SubClass2();
subClass2.classCastException();
}
class BaseClass {
void baseMethod() {
System.out.println("BaseClass.baseMethod()");
}
<SubClass extends BaseClass> SubClass getThis() {
@SuppressWarnings("unchecked")
SubClass subClass = (SubClass) this;
return subClass;
}
}
class SubClass1 extends BaseClass {
void subClass1Method() {
System.out.println("SubClass1.subClass1Method()");
}
void callBaseMethodWithGetThis() {
getThis().baseMethod();
}
void callSubMethodWithGetThis() {
this.<SubClass1> getThis().subClass1Method();
}
}
class SubClass2 extends BaseClass {
void classCastException() {
this.<SubClass1> getThis().subClass1Method();
}
}
}
Submitted On 25-MAY-2007
An important point here is that when this pattern is used for the "fluent interface"/method chaining pattern (as described in 6373386), that the JavaBean spec would need to allow non-void setters.
i.e.
public self setPropertyA() {..}
But I believe this is an important thing to have, and would have little effect on legacy code.
Submitted On 26-JUL-2007
alexsmail
This will also help in the correct implementation of the equals() method.
[code]
class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
[b]protected Object getEquivalenceClass() {
return Point.class;
}[/b]
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return [b]getEquivalenceClass().equals(p.getEquivalenceClass())[/b]
&& x == p.x && y == p.y;
}
}
class ColorPoint extends Point {
private Color c;
public ColorPoint(int x, int y, Color c) {
super(x, y);
this.c = c;
}
[b]protected Object getEquivalenceClass() {
return ColorPoint.class;
}[/b]
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
ColorPoint cp = (ColorPoint)o;
return super.equals(cp) && c == cp.c;
}
}
[/code]
See http://forum.java.sun.com/thread.jspa?messageID=9791653 for more details.
Submitted On 02-AUG-2008
UlfZi
Another approach could be to implicitly allow method invocation chaining for all void methods. This would save the return statement, and its superfluous byte code.
Submitted On 02-AUG-2008
UlfZi
What about to distinguish chainable from unchainable methods by dropping the 'void' keyword for chainables? So there would be no need to think about a new type, which is not generally usable e.g. for parameters.
OK, there would be some chance for confusion with constructors, but method names normally shouldn't start with a capital letter, but constructors should.
Submitted On 30-MAR-2009
UlfZibis
See also:
http://mail.openjdk.java.net/pipermail/coin-dev/2009-March/001180.html
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|