sistema - Los genéricos de Java parecen funcionar de manera diferente para las clases que para los métodos
ruby duck typing (6)
// Type mismatch
Aunque MyWaterFowl implementa esas interfaces.
No se trata del tipo D
implementa esas interfaces (y / o la extensión de una clase). Una variable de tipo genérico está vinculada a un argumento de tipo específico. Ese tipo puede ser completamente diferente a MyWaterFowl
por lo que no puede usarlos de manera intercambiable.
Para responder a su edición, está haciendo dos cosas completamente diferentes en sus dos fragmentos. La variable de tipo se declara con algunos límites. Por lo tanto, se garantiza que es un tipo que implementa alguna interfaz (o extiende alguna clase), pero no se sabe qué tipo es, en cualquier caso.
Quiero aclarar las dos cosas que hiciste, es decir. Lo que espera en su pregunta y la solución que dio en su respuesta.
Los genéricos son una característica de tiempo de compilación donde el código del servidor , por ejemplo
class Activate<D extends CanWalk & CanQuack> {
D instance;
public Activate(D d) {
this.instance = d;
}
public D getInstance() {
return instance ;
}
}
declara una variable de tipo. Esta es una variable. En el contexto de su declaración, no conoce su tipo exacto en el momento de la compilación.
El código del cliente , por ejemplo,
new Activate<>(new MyWaterFowl());
une el tipo MyWaterFowl
a la variable de tipo declarada en Activate
. Así que el código del cliente sabe lo que D
es en tiempo de compilación.
Si lo siguiente
public D getInstance() {
D someRef = new MyWaterFowl();
return someRef;
}
Estaba permitido en el código del servidor, esto fallaría
Activate<SomeOtherBird> activate = new Activate<>(new SomeOtherBird());
SomeOtherBird reference = activate.getInstance();
Los genéricos garantizan que getInstance()
es de tipo seguro porque se declara que devuelve cualquier tipo que esté vinculado a la variable de tipo D
En este caso, es SomeOtherBird
. Si se permitiera el código getInstance()
anterior, el tipo de seguridad se rompería, ya que getInstance()
devolvería algo distinto de lo que estaba vinculado a él.
Esto no cambia el hecho de que dentro de su código de servidor (la clase genérica), conoce los límites de D
, es decir. Es tanto un CanQuack
como un CanWalk
. Por lo tanto, cualquier cosa que pueda hacer un objeto de esos tipos, también puede hacerlo un objeto referenciado por una variable D
Estoy siguiendo esto:
http://rickyclarkson.blogspot.com/2006/07/duck-typing-in-java-and-no-reflection.html
Y estoy tratando de adaptar esto:
<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
A esto:
public class Activate<D extends CanQuack & CanWalk> {
D d = new MyWaterFowl(); //Type mismatch
}
Aunque MyWaterFowl implementa esas interfaces.
Me gustaría una solución que nunca mencione MyWaterFowl en los <> ya que eventualmente lo inyectaré (o cualquier otra cosa que implemente esas interfaces).
Si su respuesta es básicamente "No puede hacer eso, ¿qué tipo sería?". Explique por qué funciona para el método doDucklikeThings y declare de manera concluyente si es imposible hacer lo mismo con una clase o, si es posible, cómo hacerlo.
La T en DoDucklikeThings debe ser algo válido ya que está funcionando. Si pasara eso a una clase, ¿qué pasaría?
Según lo solicitado, aquí está el código MyWaterFowl:
package singleResponsibilityPrinciple;
interface CanWalk { public void walk(); }
interface CanQuack { public void quack(); }
interface CanSwim { public void swim(); }
public class MyWaterFowl implements CanWalk, CanQuack, CanSwim {
public void walk() { System.out.println("I''m walkin` here!"); }
public void quack() { System.out.println("Quack!"); }
public void swim() { System.out.println("Stroke! Stroke! Stroke!"); }
}
Recuerda que he confirmado que doDucklikethings funciona. Necesito la sintaxis que me permitirá inyectar todo lo que implemente las interfaces necesarias.
Los genéricos funcionan igual para las clases que para los métodos.
Pareces malinterpretar un poco a los genéricos. Un parámetro de tipo <T extends CanQuack & CanWalk>
no es una definición de un nuevo alias de tipo válido para el alcance del método o la clase, es un marcador de posición para algún tipo , que más tarde otra persona rellena.
Para un método genérico como tu ejemplo.
<T extends CanQuack & CanWalk> void doDucklikeThings(T t)
{
t.quack();
t.walk();
}
la persona que llama al método decide qué tipo sustituir por T (dentro de los límites establecidos por los límites). Así que uno puede usar
doDuckLikeThings(myWaterfowl);
y el compilador adivina qué tipo de parámetro desea aquí (probablemente el tipo de la variable myWaterfowl
).
Para una clase genérica, es el código que crea una instancia de la clase quien decide el parámetro de tipo real. Asi que puedo decir
Activate<Duck> duckActivator = new Activate<Duck>();
Después de esto, la instancia de duckActivator
es una instancia de Activate<Duck>
, y dentro de ella, D
está ahora vinculada a Duck
. Ahora, obviamente, su declaración de variable dice
Duck d = new MyWaterfowl();
y esto no funciona.
Tampoco funcionaría en un método genérico, este tampoco es válido:
<T extends CanQuack & CanWalk> void doDucklikeThings()
{
T t = new MyWaterfowl();
}
Me gustaría una solución que nunca mencione
MyWaterFowl
en los<>
ya que eventualmente lo inyectaré (o cualquier otra cosa que implemente esas interfaces).
Entonces tampoco menciones a su constructor. Quienquiera que esté inyectando su objeto tipo pato también tiene que decidir el parámetro de tipo real, es decir, este debe ser el que crea la instancia Activate
. Y esto no puede cambiar después de la construcción de su objeto Activate
.
Una solución candidata al problema real.
Si esta restricción no es adecuada para usted (por ejemplo, porque la misma instancia de Activate
tendrá que poder trabajar con diferentes tipos de objetos tipo pato uno tras otro), tal vez un parámetro de tipo no sea para usted.
Tal vez un contenedor genérico con un parámetro de tipo comodín para una variable ayude en su lugar.
Toma este envoltorio genérico (o algo similar):
public class Holder<T> {
private final T t;
public T get() { return t; }
public Holder(T t) { this.t = t; }
}
Entonces, su clase Activate no necesita el parámetro de tipo, sino que solo contiene una variable de titular con un parámetro de tipo comodín. (Tenga en cuenta que la instanciación de Holder real tiene un argumento de tipo no comodín).
public class Activate {
private Holder<? extends CanQuack & CanWalk> holder;
public <D extends CanQuack & CanWalk> void setDuckLike(D d) {
this.holder = new Holder<D>(d);
}
public Activate() {}
public void doDucklikeThings() {
holder.get().quack();
holder.get().walk();
}
}
Ahora haces algo como esto:
Activate a = new Activate();
a.setDuckLike(new Duck());
a.doDucklikeThings();
a.setDuckLike(new MyWaterfowl());
a.doDucklikeThings();
¿Cuál espera que sea el tipo real de D
en el segundo fragmento de código?
Digamos que algo hace esto:
Activate<Daffy> myActivate = Activate<Daffy>();
¿Qué debería pasar entonces? Esto significa que D
debe ser del tipo Daffy
pero está intentando configurar d
en una instancia de MyWaterFowl
.
El tipo de clase que extiende el objeto es diferente con el tipo de objeto. Esto significa que no puede instanciar una súper clase para sus extensores.
Cuando compilas esto:
1 public class Sample<T extends Object> {
2 public Sample() {
3 T t = new Object();
4 }
5 }
tienes error de incompatible types
.
Sample.java:3: error: incompatible types
T t = new Object();
^
required: T
found: Object
where T is a type-variable:
T extends Object declared in class Sample
1 error
Es lo mismo que cuando lo haces en forma no genérica:
1 public class Sample{
2 public Sample() {
3 }
4 }
5
6 class SampleExtends extends Sample {
7 public SampleExtends() {
8
9 }
10 }
11
12 class User {
13 public User() {
14 SampleExtends se = new Sample();
15 }
16 }
obtienes este error del compilador: tipos incompatibles
Sample.java:14: error: incompatible types
SampleExtends se = new Sample();
^
required: SampleExtends
found: Sample
1 error
En realidad se puede hacer.
El código genérico entre <> ''s está bien. No es diferente para los métodos y las clases. Solo necesitaba terminar de hacer la inyección de dependencia:
public class Activate<D extends CanQuack & CanWalk> {
private D d;
Activate(D d) {
this.d = d;
}
void doDuckLikeThings() {
d.quack();
d.walk();
//d.swim(); //Doesn''t work
//And shouldn''t since that was the whole point of ISP.
}
}
public class MainClass {
public static void main(String[] args) {
Activate<MyWaterFowl> a = new Activate<>(new MyWaterFowl());
a.doDuckLikeThings();
}
}
Pensé que le daría una respuesta que dice qué hacer para solucionarlo.
Esto no funciona, porque la clase / método es genérico y la persona que llama a su clase / método puede configurar D
en MyAmericanEagle
.
Activate<MyAmericanEagle> active = new Activate<>();
Entonces su código resultaría en
MyAmericanEagle d = new MyWaterFowl();
Dado que eso no tiene sentido (daría como resultado la excepción ClassCastException), el compilador lo rechaza.