This (self) v generikách

Tohle byl pro mě nějakou dobu oříšek, než jsem narazil na pár článků s překvapivým - ne dokonalým, ale přeci jen nějakým řešením.

Problém je jednoduchý, chtěl bych aby bylo možné v nějaké abstraktní třídě definovat cosi jako:


/** poznámka: toto je nesmysl, ale vyjadřuje
moji snahu o vyjádření vazeb **/
abstract class AbstractClass<T is this> {
   T getMe();
}

Což jsem potřeboval z důvodu získání reference na AOP proxy obalující moji třídu - v níže uvedených odkazech podobná potřeba vznikla při implementaci builder patternu.

Hezké řešení pro tento problém neexistuje, ale je možné napsat generickou vazbu, která podobný zápis umožní.


public abstract class AbstractParent<SelfType
                extends AbstractParent<SelfType>> {
	protected final StringBuilder data = new StringBuilder();
	@SuppressWarnings("unchecked")
	protected SelfType getMe() {
		return (SelfType) this;
	}
	public SelfType addText(String something) {
		data.append(something);
		return getMe();
	}
	public String buildString() {
		return data.toString();
	}
}
/**
*  PRVNÍ POTOMEK - DEFINUJE NOVOU VLASTNÍ METODU, ZBYTEK DĚDÍ
**/
public class Bar extends AbstractParent<Bar> {
	public Bar makeLowerCase() {
		//nasbíraná data jen lowercasneme
		data.replace(0, data.length(), data.toString().toLowerCase());
		return getMe();
	}
}
/**
* DRUHÝ POTOMEK JE ANALOGIE K PRVNÍMU, JEN DEFINUJE JINOU METODU</pre>
**/public class Foo extends AbstractParent<Foo> {
	public Foo makeUpperCase() {
		//nasbíraná data jen uppercasneme
		data.replace(0, data.length(), data.toString().toUpperCase());
		return getMe();
	}
}

Dosažení mého původního cíle mohu dokumentovat na tomto testu, který se podaří zkompilovat (stejně tak funguje správně i auto-completion v IDE):


@Test
public void tryGenerics() {
	assertEquals(
		"hello world!",
		new Bar().addText("Hello ").addText("world!")
				 .makeLowerCase().buildString()
	);
	assertEquals(
		"HELLO WORLD!",
		new Foo().addText("Hello ").addText("world!")
				 .makeUpperCase().buildString()
	);
}

Duležitá pasáž ve výše uvedeném příkladě je toto (pokud byste sami nepostřehli):

class AbstractParent<SelfType extends AbstractParent<SelfType>>

Což říká, že SelfType musí být potomek stejné třídy jako je ta, kterou právě definujeme. Je to poměrně obskurní zápis, který by mne (přiznám se) rozhodně nenapadl. Důležité je, že potomci musí ve své hlavičce uvést také poměrně zvláštní deklaraci (bez níž správného vyhodnocení generik nedosáhneme):

class Bar extends AbstractParent<Bar>

Možná tenhle trik znáte, ale já jsem o něm docela dlouho nevěděl - nedařilo se mi správně zeptat Googlu, jelikož jsem se motal pořád okolo klíčového slova this, protože intuitivnější by mě skutečně přišlo použití tohoto slova. Klíčem k rozluštění je hledat řetězec java generics selftype.

Třeba se Vám můj “objev” bude také hodit.

Reference k problému