java - Generics hell: hamcrest matcher como parámetro de método
(2)
Entonces, tengamos una lista de cadenas y una función que toma un emparejador Hamcrest y devuelve un resultado del método matches()
del emparejador proporcionado:
public boolean matchIt(final Matcher<? super List<String>> matcher) {
final List<String> lst = obtainListFromSomewhere();
return matcher.matches(lst);
}
Hasta ahora tan bueno. Ahora puedo llamar fácilmente:
matchIt(empty());
matchIt(anything());
matchIt(hasItem("item"));
matchIt(everyItem(equalToIgnoringCase("item")));
... ya que todos estos métodos estáticos de fábrica producen un emparejador que se ajusta a la firma del método Matcher<? super List<String>>
Matcher<? super List<String>>
.
Sin embargo, creo que un matcher que acepta un Iterable de objetos debe ser aceptado por el método matchIt()
también:
matchIt(everyItem(anything()));
Así que cambié ingenuamente la firma del método matchIt()
:
public boolean matchIt(final Matcher<? super List<? super String>> matcher);
Pero no funciona en absoluto. No solo no acepta todos los everyItem(anything())
, sino que ni siquiera acepta todos los artículos correctos anteriormente everyItem(equalToIgnoringCase("item"))
dicen (1.7.0_05 versión del compilador):
actual argument Matcher<Iterable<String>> cannot be converted to Matcher<? super List<? super String>> by method invocation conversion
¿Que? Entonces, ¿qué está mal aquí? ¿Es la firma del método matchIt()
o la firma de everyItem()
está mal diseñada? ¿O es solo que el sistema de genéricos de Java no se puede reparar? Muchas gracias por tus comentarios!
EDITAR @rlegendi mi intención aquí es proporcionar una interfaz para que el cliente agregue y ejecute predicados sobre la lista. Ese es el método matchIt()
. Llamar a matchIt(anything())
tiene sentido en este escenario, el cliente quiere saber si la lista es algo. Llamar a matchIt(empty())
significa que el cliente quiere saber si la lista está vacía. Viceversa para matchIt(everyItem(equalToIgnoringCase("item")))
y matchIt(hasItem("item"))
.
Mi objetivo aquí es tener la mejor firma posible del método matchIt()
. El Matcher<? super List<String>>
Matcher<? super List<String>>
funciona bien para todos los escenarios anteriores. Sin embargo, creo que al cliente también se le debe permitir agregar Matcher<Iterable<Object>>
(por ejemplo, matchIt(everyItem(notNullValue())
tiene mucho sentido aquí, el cliente quiere saber si cada elemento de String de la lista es no nulo).
Sin embargo, no puedo encontrar la firma correcta, matchIt(Matcher<? super List<? super String>>)
no funciona para everyItem(notNullValue());
Yo uso Hamcrest 1.3.
EDIT 2:
Creo que he encontrado mi raíz malentendido.
La everyItem(anything())
devuelve un objeto de tipo Matcher<Iterable<Object>>
. Así que puedo hacer fácilmente Matcher<Iterable<Object>> m = everyItem(anything());
Sin embargo, lo que no entiendo es por qué no puedo hacer Matcher<? super List<? super String>> m1 = m;
Matcher<? super List<? super String>> m1 = m;
. Parece que Matcher<Iterable<Object>>
no es Matcher<? super List<? super String>>
Matcher<? super List<? super String>>
Matcher<? super List<? super String>>
pero no entiendo por qué.
Ni siquiera puedo hacer Matcher<? super List<?>> m1 = m;
Matcher<? super List<?>> m1 = m;
. Matcher<Iterable<Object>>
no es Matcher<? super List<?>>
Matcher<? super List<?>>
? ¿Por qué?
Sin embargo, creo que un matcher que acepta un Iterable de objetos debe ser aceptado por el método matchIt () también
No, eso no es correcto. En lugar de Iterable
, consideremos la List
por el momento. Así que tienes un Matcher<List<Object>>
, y su método de matches
toma una List<Object>
. Ahora, ¿tomaría esto una List<String>
? No. Y probablemente ya sepa por qué, porque podría agregar un objeto de tipo Object
a la lista.
Ahora, sé que al nombrar a la clase Matcher
, esperas que el método de las matches
sea de solo lectura y no mute la lista que se le ha asignado. Sin embargo, no hay garantía de eso. Si de hecho no agrega nada a la lista, el tipo correcto para el comparador sería Matcher<List<?>>
, el cual (1) no permite que el método de matches
agregue nada a la lista excepto null
, y (2) Permitirá que el método de matches
tome una lista de cualquier tipo.
Creo que su actual método con firma public boolean matchIt(final Matcher<? super List<String>> matcher)
ya permite Matcher<List<?>>
(o Matcher<Iterable<?>>
).
¿Algo malo con esto?
public boolean matchIt(final Matcher<? extends Iterable<String>> matcher);