Existe uma maneira de se referir ao tipo atual com uma variável de tipo?

Suponha que eu esteja tentando escrever uma function para retornar uma instância do tipo atual. Existe uma maneira de fazer T referir ao subtipo exato (então T deve referir-se a B na class B )?

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

Para construir a resposta do StriplingWarrior , acho que o seguinte padrão seria necessário (esta é uma receita para uma API hierárquica do construtor fluente).

SOLUÇÃO

Primeiro, uma class abstrata de base (ou interface) que estabelece o contrato para retornar o tipo de tempo de execução de uma instância estendendo a class:

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

Todas as classs de extensão intermediárias devem ser abstract e manter o parâmetro de tipo recursivo SELF :

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

Outras classs derivadas podem seguir da mesma maneira. Mas, nenhuma dessas classs pode ser usada diretamente como tipos de variables ​​sem recorrer a tipos de caracteres ou curingas (o que anula o propósito do padrão). Por exemplo (se MyClass não fosse 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 é a razão pela qual eu me refiro a essas classs como “intermediário”, e é por isso que todas elas devem ser marcadas como abstract . Para fechar o loop e fazer uso do padrão, são necessárias classs “leaf”, que resolvem o parâmetro de tipo herdado SELF com seu próprio tipo e implementam self() . Eles também devem ser marcados como final para evitar a quebra do contrato:

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

Essas classs tornam o padrão utilizável:

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

O valor aqui sendo que as chamadas de método podem ser encadeadas na hierarquia de classs, mantendo o mesmo tipo de retorno específico.


AVISO LEGAL

O acima é uma implementação do padrão de modelo curiosamente recorrente em Java. Esse padrão não é inerentemente seguro e deve ser reservado apenas para o funcionamento interno da própria API interna. O motivo é que não há garantia de que o parâmetro de tipo SELF nos exemplos acima será realmente resolvido para o tipo de tempo de execução correto. Por exemplo:

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

Este exemplo expõe dois furos no padrão:

  1. EvilLeafClass pode “mentir” e replace qualquer outro tipo que estenda MyBaseClass por SELF .
  2. Independentemente disso, não há garantia de que self() realmente retornará this , o que pode ou não ser um problema, dependendo do uso do estado na lógica base.

Por estas razões, este padrão tem um grande potencial para ser mal utilizado ou abusado. Para evitar isso, não permita que nenhuma das classs envolvidas seja estendida publicamente – observe meu uso do construtor private-package em MyBaseClass , que substitui o construtor público implícito:

 MyBaseClass() { } 

Se possível, mantenha self() package-private também, para não adicionar ruído e confusão à API pública. Infelizmente isso só é possível se o SelfTyped for uma class abstrata, já que os methods de interface são implicitamente públicos.

Como zhong.j.yu aponta nos comentários, o limite em SELF pode simplesmente ser removido, uma vez que, em última análise, falha em garantir o “tipo próprio”:

 abstract class SelfTyped { abstract SELF self(); } 

Yu aconselha a confiar apenas no contrato, e evitar qualquer confusão ou falsa sensação de segurança que vem do limite recursivo não intuitivo. Pessoalmente, prefiro deixar o limite desde que o SELF extends SelfTyped representa a expressão mais próxima possível do tipo self em Java. Mas a opinião de Yu definitivamente se alinha com o precedente definido por Comparable .


CONCLUSÃO

Este é um padrão digno que permite chamadas fluentes e expressivas para sua API de construtor. Eu usei um punhado de vezes em trabalho sério, principalmente para escrever uma estrutura de construtor de consulta personalizada, que permitia sites de chamadas 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(); 

O ponto principal é que o QueryBuilder não era apenas uma implementação simples, mas a “folha” que se estende de uma hierarquia complexa de classs do construtor. O mesmo padrão foi usado para os ajudantes como Where , Having , Or , etc., todos necessários para compartilhar códigos significativos.

No entanto, você não deve perder de vista o fato de que tudo isso só equivale a açúcar sintático no final. Alguns programadores experientes tomam uma posição dura contra o padrão CRT , ou pelo menos são céticos quanto aos seus benefícios contra a complexidade adicional . Suas preocupações são legítimas.

Bottom-line, dê uma boa olhada se é realmente necessário antes de implementá-lo – e se o fizer, não o torne publicamente extensível.

Você deve conseguir fazer isso usando o estilo de definição genérica recursiva que o Java usa para enums:

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

Apenas escreva:

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

supondo que você esteja usando o Java 1.5+ ( tipos de retorno covariantes ).

Eu posso não entender completamente a questão, mas não é suficiente fazer isso (note que lançar para 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; } } 

então as subclasss não terão que usar a sobreposição ou covariância de tipos para fazer com que methods de class mãe retornem referência a eles …

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

Se você quer algo parecido com o de Scala

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

então não, isso não é possível em Java. Você também deve notar que não há muito que você possa retornar de uma function similarmente digitada no Scala, além this .

Eu encontrei uma maneira de fazer isso, é meio bobo, mas funciona:

Na class de nível superior (A):

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

Assumindo C se estende B e B se estende A.

Invocando:

 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");