Referensi metode yang digunakan memiliki tipe kembali Integer
. Tapi tidak cocokString
diizinkan dalam contoh berikut.
Bagaimana cara memperbaiki with
deklarasi metode untuk mendapatkan tipe referensi metode yang aman tanpa casting secara 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)
}
}
KASUS PENGGUNAAN: jenis Builder yang aman tetapi generik.
Saya mencoba menerapkan pembangun generik tanpa pemrosesan anotasi (nilai otomatis) atau plugin kompiler (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
bukaninterface
untuk pembangun?getLength
, sehingga dapat disesuaikan untuk mengembalikanObject
(atauSerializable
) agar sesuai dengan parameter String.with
adalah bagian dari masalah saat kembalinull
. Saat mengimplementasikan metodewith()
dengan benar-benar menggunakan tipe fungsiR
sebagai samaR
dari parameter Anda mendapatkan kesalahan. Misalnya<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
R
menjadiInteger
. Untuk ini, Anda perlu menunjukkan kepada kami bagaimana Anda ingin memanfaatkan nilai pengembalian. Tampaknya Anda ingin menerapkan semacam pola pembangun, tetapi saya tidak dapat mengenali pola umum atau niat Anda.Jawaban:
Dalam contoh pertama,
MyInterface::getLength
dan"I am NOT an Integer"
membantu menyelesaikan parameter generikT
danR
ke masingMyInterface
-Serializable & Comparable<? extends Serializable & Comparable<?>>
masing.MyInterface::getLength
tidak selalu aFunction<MyInterface, Integer>
kecuali jika Anda secara eksplisit mengatakannya, yang akan menyebabkan kesalahan waktu kompilasi seperti yang ditunjukkan contoh kedua.sumber
R
):Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");
untuk membuatnya tidak dikompilasi, atau (2) biarkan diselesaikan secara implisit dan mudah-mudahan dilanjutkan tanpa kesalahan waktu kompilasiIni jenis inferensi yang memainkan perannya di sini. Pertimbangkan generik
R
dalam tanda tangan metode:Dalam hal ini seperti yang tercantum:
jenis
R
berhasil disimpulkan sebagaidan
String
tidak menyiratkan dengan tipe ini, maka kompilasi berhasil.Untuk secara eksplisit menentukan jenis
R
dan menemukan ketidakcocokan, seseorang dapat dengan mudah mengubah baris kode sebagai:sumber
Itu karena parameter tipe generik Anda
R
dapat disimpulkan sebagai Objek, yaitu kompilasi berikut:sumber
Integer
, itu akan menjadi tempat kesalahan kompilasi terjadi.Builder
hanya generik dalamT
, tetapi tidak dalamR
. IniInteger
hanya diabaikan sejauh memeriksa jenis pembangun yang bersangkutan.R
disimpulkan sebagaiObject
... tidak jugawith
akan digunakanR
. Tentu saja itu berarti tidak ada cara yang berarti untuk benar-benar mengimplementasikan metode itu dengan cara yang benar-benar menggunakan argumen.Object
).Jawaban ini didasarkan pada jawaban lain yang menjelaskan mengapa itu tidak berfungsi seperti yang diharapkan.
LARUTAN
Kode berikut memecahkan masalah dengan memisahkan bifungsi "dengan" menjadi dua fungsi yang lancar "dengan" dan "mengembalikan":
(agak asing)
sumber