¿Hay alguna manera de referirse al tipo actual con una variable de tipo?

Supongamos que estoy intentando escribir una función para devolver una instancia del tipo actual. ¿Hay alguna manera de hacer que T refiera al subtipo exacto (por lo que T debería referirse a B en la clase B )?

 class A {  foo(); } class B extends A { @Override T foo(); } 

Para construir sobre la respuesta de StriplingWarrior , creo que el siguiente patrón sería necesario (esta es una receta para una API jerárquica de comstackdor fluido).

SOLUCIÓN

Primero, una clase abstracta base (o interfaz) que establece el contrato para devolver el tipo de tiempo de ejecución de una instancia que extiende la clase:

 /** * @param  The runtime type of the implementor. */ abstract class SelfTyped> { /** * @return This instance. */ abstract SELF self(); } 

Todas las clases intermedias que se extienden deben ser abstract y mantener el parámetro de tipo recursivo SELF :

 public abstract class MyBaseClass> extends SelfTyped { MyBaseClass() { } public SELF baseMethod() { //logic return self(); } } 

Otras clases derivadas pueden seguir de la misma manera. Sin embargo, ninguna de estas clases se puede usar directamente como tipos de variables sin recurrir a los tipos de raw o comodines (lo que frustra el propósito del patrón). Por ejemplo (si MyClass no era abstract ):

 //wrong: raw type warning MyBaseClass mbc = new MyBaseClass().baseMethod(); //wrong: type argument is not within the bounds of SELF MyBaseClass mbc2 = new MyBaseClass().baseMethod(); //wrong: no way to correctly declare the type, as its parameter is recursive! MyBaseClass> mbc3 = new MyBaseClass>().baseMethod(); 

Esta es la razón por la que me refiero a estas clases como “intermedias”, y es por eso que todas deben ser marcadas como abstract . Para cerrar el ciclo y hacer uso del patrón, son necesarias las clases “hoja”, que resuelven el parámetro de tipo heredado SELF con su propio tipo e implementan self() . También deben marcarse como final para evitar romper el contrato:

 public final class MyLeafClass extends MyBaseClass { @Override MyLeafClass self() { return this; } public MyLeafClass leafMethod() { //logic return self(); //could also just return this } } 

Tales clases hacen que el patrón sea utilizable:

 MyLeafClass mlc = new MyLeafClass().baseMethod().leafMethod(); AnotherLeafClass alc = new AnotherLeafClass().baseMethod().anotherLeafMethod(); 

El valor aquí es que las llamadas a métodos se pueden encadenar hacia arriba y hacia abajo en la jerarquía de clases, manteniendo el mismo tipo de retorno específico.


RENUNCIA

Lo anterior es una implementación del patrón de plantilla curiosamente recurrente en Java. Este patrón no es intrínsecamente seguro y debe reservarse para el funcionamiento interno de la API interna. La razón es que no hay garantía de que el parámetro de tipo SELF en los ejemplos anteriores se resuelva en realidad con el tipo de tiempo de ejecución correcto. Por ejemplo:

 public final class EvilLeafClass extends MyBaseClass { @Override AnotherLeafClass self() { return getSomeOtherInstanceFromWhoKnowsWhere(); } } 

Este ejemplo expone dos agujeros en el patrón:

  1. EvilLeafClass puede “mentir” y sustituir cualquier otro tipo que extienda MyBaseClass por SELF .
  2. Independientemente de eso, no hay garantía de que self() realmente devuelva this , lo que puede o no ser un problema, dependiendo del uso del estado en la lógica base.

Por estas razones, este patrón tiene un gran potencial para ser mal usado o abusado. Para evitarlo, no permita que ninguna de las clases involucradas se extienda públicamente. Observe mi uso del constructor private MyBaseClass en MyBaseClass , que reemplaza el constructor público implícito:

 MyBaseClass() { } 

Si es posible, mantenga también self() package-private, para que no agregue ruido y confusión a la API pública. Lamentablemente, esto solo es posible si SelfTyped es una clase abstracta, ya que los métodos de interfaz son implícitamente públicos.

Como señala zhong.j.yu en los comentarios, el límite de SELF podría simplemente eliminarse, ya que al final no garantiza el “tipo propio”:

 abstract class SelfTyped { abstract SELF self(); } 

Yu aconseja confiar únicamente en el contrato, y evitar cualquier confusión o falso sentido de seguridad que provenga del límite recursivo no intuitivo. Personalmente, prefiero dejar el límite ya que SELF extends SelfTyped representa la expresión más cercana posible del tipo de self en Java. Pero la opinión de Yu definitivamente se alinea con el precedente establecido por Comparable .


CONCLUSIÓN

Este es un patrón valioso que permite llamadas fluidas y expresivas a su API de generador. Lo he usado un puñado de veces en trabajos serios, sobre todo para escribir un framework de generador de consultas personalizado, que permitiera llamar a sitios como este:

 List foos = QueryBuilder.make(context, Foo.class) .where() .equals(DBPaths.from_Foo().to_FooParent().endAt_FooParentId(), parentId) .or() .lessThanOrEqual(DBPaths.from_Foo().endAt_StartDate(), now) .isNull(DBPaths.from_Foo().endAt_PublishedDate()) .or() .greaterThan(DBPaths.from_Foo().endAt_EndDate(), now) .endOr() .or() .isNull(DBPaths.from_Foo().endAt_EndDate()) .endOr() .endOr() .or() .lessThanOrEqual(DBPaths.from_Foo().endAt_EndDate(), now) .isNull(DBPaths.from_Foo().endAt_ExpiredDate()) .endOr() .endWhere() .havingEvery() .equals(DBPaths.from_Foo().to_FooChild().endAt_FooChildId(), childId) .endHaving() .orderBy(DBPaths.from_Foo().endAt_ExpiredDate(), true) .limit(50) .offset(5) .getResults(); 

El punto clave es que QueryBuilder no era solo una implementación plana, sino que la “hoja” se extendía desde una compleja jerarquía de clases de constructores. El mismo patrón se usó para los ayudantes como Where , Having , Or , etc., todos los cuales necesitaban compartir un código significativo.

Sin embargo, no debe perder de vista el hecho de que todo esto solo equivale a azúcar sintáctico al final. Algunos progtwigdores experimentados toman una postura dura contra el patrón CRT , o al menos son escépticos de los beneficios que pesan sobre la complejidad añadida . Sus preocupaciones son legítimas.

En resumen, analice detenidamente si es realmente necesario antes de implementarlo, y si lo hace, no lo haga públicamente ampliable.

Debería poder hacer esto usando el estilo de definición genérico recursivo que Java usa para las enumeraciones:

 class A> { T foo(); } class B extends A { @Override B foo(); } 

Solo escribe:

 class A { A foo() { ... } } class B extends A { @Override B foo() { ... } } 

suponiendo que está utilizando Java 1.5+ ( tipos de retorno covariante ).

Puede que no haya entendido completamente la pregunta, pero ¿no es suficiente hacer esto solo (noten el lanzamiento a T)?

  private static class BodyBuilder { private final int height; private final String skinColor; //default fields private float bodyFat = 15; private int weight = 60; public BodyBuilder(int height, String color) { this.height = height; this.skinColor = color; } public T setBodyFat(float bodyFat) { this.bodyFat = bodyFat; return (T) this; } public T setWeight(int weight) { this.weight = weight; return (T) this; } public Body build() { Body body = new Body(); body.height = height; body.skinColor = skinColor; body.bodyFat = bodyFat; body.weight = weight; return body; } } 

entonces las subclases no tendrán que usar anulación o covarianza de tipos para hacer que los métodos de clase madre devuelvan referencia a ellos …

  public class PersonBodyBuilder extends BodyBuilder { public PersonBodyBuilder(int height, String color) { super(height, color); } } 

Si quieres algo parecido a Scala

 trait T { def foo() : this.type } 

entonces no, esto no es posible en Java. También debe tener en cuenta que no hay mucho que pueda devolver de una función similarmente escrita en Scala, aparte de this .

Encontré una forma de hacerlo, es una especie de tontería pero funciona:

En la clase de nivel superior (A):

 protected final  T a(T type) { return type } 

Asumiendo que C extiende B y B se extiende A.

Invocar:

 C c = new C(); //Any order is fine and you have compile time safety and IDE assistance. c.setA("a").a(c).setB("b").a(c).setC("c");