Lamda dönüş tipi derleme zamanında neden kontrol edilmiyor?


38

Kullanılan yöntem başvurusunun dönüş türü vardır Integer. Ancak Stringaşağıdaki örnekte uyumsuzluğa izin verilmektedir.

withManuel döküm olmadan yöntem referans türünü güvenli hale getirmek için yöntem bildirimi nasıl düzeltilir ?

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)
  }

}

KULLANIM ÖRNEĞİ: güvenli ancak genel bir Oluşturucu türü.

Ek açıklama işleme (otomatik değer) veya derleyici eklentisi (lombok) olmadan genel bir oluşturucu uygulamaya çalıştım

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());
  }

}

1
şaşırtıcı davranış. İlgisiz: İnşaatçı için bir classyerine kullanmak aynı interfacemıdır?
GameDroids

Neden kabul edilemez? İlk durumda, türünü vermezsiniz getLength, böylece String parametresiyle eşleşecek şekilde döndürülebilir Object(veya Serializable) ayarlanabilir .
Thilo

1
Yanılıyor olabilirim, ancak yönteminizin withgeri döndüğünde sorunun bir parçası olduğunu düşünüyorum null. Yöntemi with(), işlev Rtürünü Rparametreden aynı şekilde kullanarak uygularken hatayı alırsınız. Örneğin<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
GameDroids

2
jukzi, belki kodu veya sizin yöntemi ile aslında ne yapması gerektiğini konusunda bir açıklama olmalı ve neden ihtiyaç Rolması Integer. Bunun için, dönüş değerini nasıl kullanmak istediğinizi bize göstermeniz gerekir. Görünüşe göre bir çeşit inşaatçı modeli uygulamak istiyorsunuz ama ortak bir deseni veya niyetinizi tanıyamıyorum.
sfiss

1
Teşekkürler. Ayrıca tam başlatma için kontrol etmeyi düşündüm. Ancak derleme zamanında bunu yapmanın bir yolu görmediğim için varsayılan null / 0 değerlerine bağlı kalmayı tercih ederim. Ayrıca derleme zamanında arabirim dışı yöntemleri kontrol etmek nasıl bir fikrim yok. Çalışma zamanında ".with (m -> 1) .returning (1)" gibi bir arabirim kullanmadan zaten java.lang.NoSuchMethodException
jukzi

Yanıtlar:


27

İlk örnekte, MyInterface::getLengthve "I am NOT an Integer"jenerik parametreleri çözmek için yardımcı Tve Rhiç MyInterfaceve Serializable & Comparable<? extends Serializable & Comparable<?>>sırasıyla.

// it compiles since String is a Serializable
Function<MyInterface, Serializable> function = MyInterface::getLength;
Builder.of(MyInterface.class).with(function, "I am NOT an Integer");

MyInterface::getLengthFunction<MyInterface, Integer>açıkça söylemediğiniz sürece her zaman bir değil , ikinci örnekte gösterildiği gibi derleme zamanı hatasına yol açar.

// it doesn't compile since String isn't an Integer
Function<MyInterface, Integer> function = MyInterface::getLength;
Builder.of(MyInterface.class).with(function, "I am NOT an Integer");

Bu cevap, intendetten sonra neden yorumlandığı sorusuna tam olarak cevap vermektedir. İlginç. R, işe yaramaz gibi geliyor. Soruna herhangi bir çözüm biliyor musunuz?
jukzi

@jukzi (1) yöntem türü parametrelerini açıkça tanımlayın (burada, R): derlememesini Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");sağlamak veya (2) derleme zamanı hataları olmadan örtülü ve umarım çözülmesine izin vermek
Andrew Tobilko

11

Burada rolünü oynayan tip çıkarım. RYöntem imzasında genel olanı düşünün :

<R> Builder<T> with(Function<T, R> getter, R returnValue)

Listelenen durumda:

Builder.of(MyInterface.class).with(MyInterface::getLength, "I am NOT an Integer");

türü Rbaşarıyla şu şekilde çıkarılır:

Serializable, Comparable<? extends Serializable & Comparable<?>>

ve a Stringbu tipte ima eder, dolayısıyla derleme başarılı olur.


Türünü açıkça belirtmek ve Ruyumsuzluğu bulmak için, kod satırını şu şekilde değiştirebilirsiniz:

Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "not valid");

R'yi <Integer> olarak açıkça bildirmek ilginçtir ve neden yanlış gittiğini tam olarak cevaplar. Ancak ben hala türü açık beyan etmeden bir çözüm arıyor. Herhangi bir fikir?
jukzi

@jukzi Ne tür bir çözüm arıyorsunuz? Kod olarak kullanmak istiyorsanız, kod zaten derlenir. Aradığınız şeyin bir örneği, işleri daha net hale getirmek için iyi olur.
Naman

11

Bunun nedeni, genel tür parametrenizin RNesne olarak çıkarılabilmesidir, yani aşağıdaki derlemeler:

Builder.of(MyInterface.class).with((Function<MyInterface, Object>) MyInterface::getLength, "I am NOT an Integer");

1
Tam olarak, OP yöntemin sonucunu bir tür değişkenine atadıysa Integer, derleme hatasının gerçekleştiği yer burasıdır.
sepp2k

@ sepp2k Bunun Builderyalnızca genel Tdeğil, genel değil R. Bu Integersadece inşaatçı tip kontrolü söz konusu olduğunda göz ardı edilmektedir.
Thilo

2
Rolmak anlaşılmaktadırObject gerçekten ...
Naman'ın

@Thilo Tabii ki haklısın. Dönüş türü withkullanacağını varsaydım R. Tabii ki bu, bu yöntemi gerçekte argümanları kullanan bir şekilde uygulamanın anlamlı bir yolu olmadığı anlamına gelir.
sepp2k

1
Naman, haklısın, sen ve Andrew bunu doğru çıkarım türüyle daha ayrıntılı olarak cevapladın. Ben sadece daha basit bir açıklama yapmak istedim (bu soruya bakan herkes muhtemelen sadece türden çıkarım ve diğer türler bilir Object).
9'da sfiss

0

Bu cevap neden beklendiği gibi çalışmadığını açıklayan diğer cevaplara dayanmaktadır.

ÇÖZÜM

Aşağıdaki kod, "ile" işlevini "iki" akıcı işleve "ve" döndürerek "bölerek sorunu çözer:

class Builder<T> {
...
class BuilderMethod<R> {
  final Function<T, R> getter;

  BuilderMethod(Function<T, R> getter) {
    this.getter = getter;
  }

  Builder<T> returning(R returnValue) {
    return Builder.this.with(getter, returnValue);
  }
}

<R> BuilderMethod<R> with(Function<T, R> getter) {
  return new BuilderMethod<>(getter);
}
...
}

MyInterface z = Builder.of(MyInterface.class).with(MyInterface::getLength).returning(1L).with(MyInterface::getNullLength).returning(null).build();
System.out.println("length:" + z.getLength());

// YIPPIE COMPILATION ERRROR:
// The method returning(Long) in the type BuilderExample.Builder<BuilderExample.MyInterface>.BuilderMethod<Long> is not applicable for the arguments (String)
MyInterface zz = Builder.of(MyInterface.class).with(MyInterface::getLength).returning("NOT A NUMBER").build();
System.out.println("length:" + zz.getLength());

(biraz tanıdık değil)


ayrıca doğrudan çözüm için stackoverflow.com/questions/58376589
jukzi
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.