Użyte odwołanie do metody ma typ zwracany Integer
. Jednak niezgodność String
jest dozwolona w poniższym przykładzie.
Jak naprawić with
deklarację metody, aby uzyskać bezpieczny typ referencyjny metody bez ręcznego rzutowania?
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)
}
}
UŻYJ PRZYPADKU: typ bezpieczny, ale ogólny Konstruktor.
Próbowałem zaimplementować ogólny konstruktor bez przetwarzania adnotacji (autovalue) lub wtyczki kompilatora (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
zamiastinterface
konstruktora?getLength
, więc można go dostosować tak, aby zwracałObject
(lubSerializable
) w celu dopasowania parametru String.with
jest częścią problemu, gdy powracanull
. Podczas implementacji metodywith()
przy użyciuR
typu funkcji identycznegoR
z parametrem otrzymujesz błąd. Na przykład<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
R
byćInteger
. W tym celu musisz nam pokazać, w jaki sposób chcesz wykorzystać wartość zwracaną. Wygląda na to, że chcesz wdrożyć jakiś wzorzec konstruktora, ale nie mogę rozpoznać wspólnego wzorca ani twojej intencji.Odpowiedzi:
W pierwszym przykładzie,
MyInterface::getLength
i"I am NOT an Integer"
pomógł rozwiązać parametry rodzajoweT
iR
naMyInterface
iSerializable & Comparable<? extends Serializable & Comparable<?>>
odpowiednio.MyInterface::getLength
nie zawsze jest,Function<MyInterface, Integer>
chyba że wyraźnie to powiesz, co doprowadziłoby do błędu czasu kompilacji, jak pokazano w drugim przykładzie.źródło
R
):Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");
aby nie była kompilowana, lub (2) niech zostanie rozwiązana w sposób dorozumiany i miejmy nadzieję, że będzie przebiegać bez błędów w czasie kompilacjiTo wnioskowanie o typie, które odgrywa tutaj swoją rolę. Rozważ rodzajowy
R
w sygnaturze metody:W przypadku wymienionym poniżej:
typ
R
jest z powodzeniem wywnioskowany jakoa a
String
implikuje tego typu, stąd kompilacja się udaje.Aby wyraźnie określić typ
R
i znaleźć niezgodność, wystarczy zmienić wiersz kodu jako:źródło
Wynika to z faktu, że ogólny parametr typu
R
można wywnioskować jako Object, tj. Następujące kompilacje:źródło
Integer
, to właśnie tam wystąpi błąd kompilacji.Builder
jest tylko ogólnyT
, ale nie wR
. JestInteger
to po prostu ignorowane, jeśli chodzi o sprawdzanie typu konstruktora.R
wywnioskowano, żeObject
... nie bardzowith
że użyje zwrotnego typuR
. Oczywiście oznacza to, że nie ma sensownego sposobu na rzeczywistą implementację tej metody w sposób, który faktycznie wykorzystuje argumenty.Object
).Ta odpowiedź jest oparta na innych odpowiedziach, które wyjaśniają, dlaczego nie działa zgodnie z oczekiwaniami.
ROZWIĄZANIE
Poniższy kod rozwiązuje problem, dzieląc bifunkcję „z” na dwie płynne funkcje „z” i „powrót”:
(jest nieco nieznany)
źródło