La référence de méthode utilisée a un type de retour Integer
. Mais un incompatible String
est autorisé dans l'exemple suivant.
Comment corriger la with
déclaration de méthode pour sécuriser le type de référence de méthode sans transtyper manuellement?
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)
}
}
USE CASE: un générateur de type sûr mais générique.
J'ai essayé d'implémenter un générateur générique sans traitement d'annotation (autovalue) ou plugin de compilation (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
au lieu d'uninterface
pour le constructeur?getLength
, il peut donc être ajusté pour retournerObject
(ouSerializable
) pour correspondre au paramètre String.with
fait partie du problème à son retournull
. Lors de l'implémentation de la méthodewith()
en utilisant réellement leR
type de la fonction comme le même àR
partir du paramètre, vous obtenez l'erreur. Par exemple<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
R
êtreInteger
. Pour cela, vous devez nous montrer comment vous souhaitez utiliser la valeur de retour. Il semble que vous souhaitiez implémenter une sorte de modèle de générateur, mais je ne peux pas reconnaître un modèle commun ou votre intention.Réponses:
Dans le premier exemple,
MyInterface::getLength
et a"I am NOT an Integer"
aidé à résoudre les paramètres génériquesT
etR
àMyInterface
etSerializable & Comparable<? extends Serializable & Comparable<?>>
respectivement.MyInterface::getLength
n'est pas toujours unFunction<MyInterface, Integer>
sauf si vous le dites explicitement, ce qui entraînerait une erreur de compilation comme le montre le deuxième exemple.la source
R
):Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");
pour qu'il ne soit pas compilé, ou (2) qu'il soit résolu implicitement et, espérons-le, sans erreur de compilationC'est l'inférence de type qui joue son rôle ici. Considérez le générique
R
dans la signature de la méthode:Dans le cas indiqué:
le type de
R
est inféré avec succès commeet a
String
implique par ce type, donc la compilation réussit.Pour spécifier explicitement le type de
R
et découvrir l'incompatibilité, on peut simplement changer la ligne de code comme:la source
C'est parce que votre paramètre de type générique
R
peut être inféré comme étant Object, c'est-à-dire que les compilations suivantes:la source
Integer
, ce serait là que l'erreur de compilation se produit.Builder
est uniquement générique dansT
, mais pas dansR
. CeciInteger
est simplement ignoré en ce qui concerne la vérification de type du constructeur.R
est supposé êtreObject
... pas vraimentwith
serait utiliséR
. Bien sûr, cela signifie qu'il n'y a aucun moyen significatif d'implémenter cette méthode d'une manière qui utilise réellement les arguments.Object
).Cette réponse est basée sur les autres réponses qui expliquent pourquoi cela ne fonctionne pas comme prévu.
SOLUTION
Le code suivant résout le problème en divisant la bifonction "avec" en deux fonctions fluides "avec" et "renvoyant":
(est peu familier)
la source