La referencia del método utilizado tiene tipo de retorno Integer
. Pero String
se permite una incompatibilidad en el siguiente ejemplo.
¿Cómo arreglar la with
declaración del método para que el tipo de referencia del método sea seguro sin la conversión manual?
import java.util.function.Function;
public class MinimalExample {
static public class Builder<T> {
final Class<T> clazz;
Builder(Class<T> clazz) {
this.clazz = clazz;
}
static <T> Builder<T> of(Class<T> clazz) {
return new Builder<T>(clazz);
}
<R> Builder<T> with(Function<T, R> getter, R returnValue) {
return null; //TODO
}
}
static public interface MyInterface {
Integer getLength();
}
public static void main(String[] args) {
// missing compiletimecheck is inaceptable:
Builder.of(MyInterface.class).with(MyInterface::getLength, "I am NOT an Integer");
// compile time error OK:
Builder.of(MyInterface.class).with((Function<MyInterface, Integer> )MyInterface::getLength, "I am NOT an Integer");
// The method with(Function<MinimalExample.MyInterface,R>, R) in the type MinimalExample.Builder<MinimalExample.MyInterface> is not applicable for the arguments (Function<MinimalExample.MyInterface,Integer>, String)
}
}
CASO DE USO: un generador de tipo seguro pero genérico.
Traté de implementar un generador genérico sin procesamiento de anotaciones (autovalor) o complemento del compilador (lombok)
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class BuilderExample {
static public class Builder<T> implements InvocationHandler {
final Class<T> clazz;
HashMap<Method, Object> methodReturnValues = new HashMap<>();
Builder(Class<T> clazz) {
this.clazz = clazz;
}
static <T> Builder<T> of(Class<T> clazz) {
return new Builder<T>(clazz);
}
Builder<T> withMethod(Method method, Object returnValue) {
Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
if (returnValue == null) {
throw new IllegalArgumentException("Primitive value cannot be null:" + method);
} else {
try {
boolean isConvertable = getDefaultValue(returnType).getClass().isAssignableFrom(returnValue.getClass());
if (!isConvertable) {
throw new ClassCastException(returnValue.getClass() + " cannot be cast to " + returnType + " for " + method);
}
} catch (IllegalArgumentException | SecurityException e) {
throw new RuntimeException(e);
}
}
} else if (returnValue != null && !returnType.isAssignableFrom(returnValue.getClass())) {
throw new ClassCastException(returnValue.getClass() + " cannot be cast to " + returnType + " for " + method);
}
Object previuos = methodReturnValues.put(method, returnValue);
if (previuos != null) {
throw new IllegalArgumentException("Value alread set for " + method);
}
return this;
}
static HashMap<Class, Object> defaultValues = new HashMap<>();
private static <T> T getDefaultValue(Class<T> clazz) {
if (clazz == null || !clazz.isPrimitive()) {
return null;
}
@SuppressWarnings("unchecked")
T cachedDefaultValue = (T) defaultValues.get(clazz);
if (cachedDefaultValue != null) {
return cachedDefaultValue;
}
@SuppressWarnings("unchecked")
T defaultValue = (T) Array.get(Array.newInstance(clazz, 1), 0);
defaultValues.put(clazz, defaultValue);
return defaultValue;
}
public synchronized static <T> Method getMethod(Class<T> clazz, java.util.function.Function<T, ?> resolve) {
AtomicReference<Method> methodReference = new AtomicReference<>();
@SuppressWarnings("unchecked")
T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
@Override
public Object invoke(Object p, Method method, Object[] args) {
Method oldMethod = methodReference.getAndSet(method);
if (oldMethod != null) {
throw new IllegalArgumentException("Method was already called " + oldMethod);
}
Class<?> returnType = method.getReturnType();
return getDefaultValue(returnType);
}
});
resolve.apply(proxy);
Method method = methodReference.get();
if (method == null) {
throw new RuntimeException(new NoSuchMethodException());
}
return method;
}
// R will accep common type Object :-( // see /programming/58337639
<R, V extends R> Builder<T> with(Function<T, R> getter, V returnValue) {
Method method = getMethod(clazz, getter);
return withMethod(method, returnValue);
}
//typesafe :-) but i dont want to avoid implementing all types
Builder<T> withValue(Function<T, Long> getter, long returnValue) {
return with(getter, returnValue);
}
Builder<T> withValue(Function<T, String> getter, String returnValue) {
return with(getter, returnValue);
}
T build() {
@SuppressWarnings("unchecked")
T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, this);
return proxy;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
Object returnValue = methodReturnValues.get(method);
if (returnValue == null) {
Class<?> returnType = method.getReturnType();
return getDefaultValue(returnType);
}
return returnValue;
}
}
static public interface MyInterface {
String getName();
long getLength();
Long getNullLength();
Long getFullLength();
Number getNumber();
}
public static void main(String[] args) {
MyInterface x = Builder.of(MyInterface.class).with(MyInterface::getName, "1").with(MyInterface::getLength, 1L).with(MyInterface::getNullLength, null).with(MyInterface::getFullLength, new Long(2)).with(MyInterface::getNumber, 3L).build();
System.out.println("name:" + x.getName());
System.out.println("length:" + x.getLength());
System.out.println("nullLength:" + x.getNullLength());
System.out.println("fullLength:" + x.getFullLength());
System.out.println("number:" + x.getNumber());
// java.lang.ClassCastException: class java.lang.String cannot be cast to long:
// RuntimeException only :-(
MyInterface y = Builder.of(MyInterface.class).with(MyInterface::getLength, "NOT A NUMBER").build();
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
// RuntimeException only :-(
System.out.println("length:" + y.getLength());
}
}
class
lugar de uninterface
para el constructor?getLength
, por lo que se puede ajustar para devolverObject
(oSerializable
) para que coincida con el parámetro String.with
es parte del problema a medida que regresanull
. Al implementar el métodowith()
utilizando elR
tipo de función como el mismoR
del parámetro, se obtiene el error. Por ejemplo<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
R
serloInteger
. Para esto, debe mostrarnos cómo desea utilizar el valor de retorno. Parece que desea implementar algún tipo de patrón de construcción, pero no puedo reconocer un patrón común o su intención.Respuestas:
En el primer ejemplo,
MyInterface::getLength
y"I am NOT an Integer"
ayudó a resolver los parámetros genéricosT
yR
aMyInterface
ySerializable & Comparable<? extends Serializable & Comparable<?>>
respectivamente.MyInterface::getLength
no siempre es aFunction<MyInterface, Integer>
menos que usted lo diga explícitamente, lo que llevaría a un error en tiempo de compilación como lo mostró el segundo ejemplo.fuente
R
):Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");
para que no se compile, o (2) permita que se resuelva implícitamente y, con suerte, proceda sin errores en tiempo de compilaciónEs la inferencia de tipo que está jugando su papel aquí. Considere el genérico
R
en la firma del método:En el caso de la lista:
el tipo de
R
se infiere con éxito comoy a
String
implica por este tipo, por lo tanto la compilación tiene éxito.Para especificar explícitamente el tipo de
R
y descubrir la incompatibilidad, uno simplemente puede cambiar la línea de código como:fuente
Esto se debe a que
R
se puede inferir que su parámetro de tipo genérico es Object, es decir, las siguientes compilaciones:fuente
Integer
, allí sería donde se produce el error de compilación.Builder
solo es genérico enT
, pero no enR
. EstoInteger
solo se ignora en lo que respecta a la verificación de tipo del constructor.R
se infiere que esObject
... no realmentewith
usaríaR
. Por supuesto, eso significa que no hay una manera significativa de implementar ese método de una manera que realmente use los argumentos.Object
).Esta respuesta se basa en las otras respuestas que explican por qué no funciona como se esperaba.
SOLUCIÓN
El siguiente código resuelve el problema al dividir la bifunción "con" en dos funciones fluidas "con" y "regresar":
(es algo desconocido)
fuente