সংকলনের সময় লাম্বদা রিটার্ন টাইপ কেন পরীক্ষা করা হয় না?


38

ব্যবহৃত পদ্ধতির রেফারেন্সের রিটার্ন টাইপ রয়েছে Integer। তবে Stringনিম্নলিখিত উদাহরণে একটি বেমানান অনুমোদিত।

withম্যানুয়ালি ingালাই ছাড়াই পদ্ধতি রেফারেন্স টাইপটি নিরাপদ পেতে পদ্ধতি ঘোষণাকে কীভাবে ঠিক করবেন ?

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

}

ব্যবহারের পরিস্থিতি: এক প্রকার নিরাপদ তবে জেনেরিক নির্মাতা।

আমি টিকা প্রক্রিয়াকরণ (অটোয়ালিউ) বা সংকলক প্লাগইন (লম্বোক) ছাড়াই জেনেরিক বিল্ডার বাস্তবায়নের চেষ্টা করেছি

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
অবাক আচরণ আগ্রহের বাইরে: আপনি যখন বিল্ডারের জন্য classপরিবর্তে একটি ব্যবহার করেন তখন কি এটি একই হয় interface?
গেমড্রয়েডস

কেন এটি অগ্রহণযোগ্য? প্রথম ক্ষেত্রে, আপনি প্রকারটি দেবেন না getLength, সুতরাং স্ট্রিং প্যারামিটারটির সাথে মিলিয়ে ফিরতে Object(বা Serializable) সামঞ্জস্য করা যায় ।
থিলো

1
আমার ভুল হতে পারে তবে আমি মনে করি আপনার পদ্ধতিটি withফিরে আসার সাথে সাথে সমস্যার একটি অংশ null। প্যারামিটার থেকে with()ফাংশনের ধরণটি Rএকই হিসাবে ব্যবহার করে পদ্ধতিটি প্রয়োগ করার সময় Rআপনি ত্রুটি পান। উদাহরণস্বরূপ<R> R with(Function<T, R> getter, T input, R returnValue) { return getter.apply(input); }
গেমড্রয়েডস

2
সুতরাং, আপনার কোডটি বা পদ্ধতিটি আপনার পদ্ধতিটি আসলে কী করা উচিত এবং আপনাকে কেন হওয়া দরকার Rসে সম্পর্কে আপনার কোড সরবরাহ করা উচিত Integer। এর জন্য, আপনাকে কীভাবে আপনি ফেরতের মানটি ব্যবহার করতে চান তা আমাদের দেখানো দরকার। দেখে মনে হচ্ছে আপনি কোনওরকম বিল্ডার-প্যাটার্ন বাস্তবায়ন করতে চান তবে আমি কোনও সাধারণ প্যাটার্ন বা আপনার অভিপ্রায় সনাক্ত করতে পারি না।
এসফিস

1
ধন্যবাদ। আমি সম্পূর্ণ আরম্ভের জন্য পরীক্ষা করার বিষয়েও ভেবেছিলাম। তবে যেহেতু সংকলনের সময় আমি এটি করার কোনও উপায় দেখতে পাচ্ছি না তাই আমি ডিফল্ট মানগুলি নাল / 0 দিয়ে আটকাতে পছন্দ করি। সংকলনের সময় অ ইন্টারফেস পদ্ধতিগুলি কীভাবে পরীক্ষা করতে হয় তা সম্পর্কে আমার কোনও ধারণা নেই। মতো অ ইন্টারফেস ব্যবহার রানটাইম এ ".with (মি -> 1) .returning (1)" ইতিমধ্যেই একটি প্রাথমিক java.lang.NoSuchMethodException ফলাফল
jukzi

উত্তর:


27

প্রথম উদাহরণে, MyInterface::getLengthএবং "I am NOT an Integer"জেনেরিক পরামিতি সমাধান করতে সাহায্য করেছিল Tএবং Rকরতে MyInterfaceএবং Serializable & Comparable<? extends Serializable & Comparable<?>>যথাক্রমে।

// 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>আপনি স্পষ্টভাবে এটি না বলে না সবসময়ই তা নয় , যা দ্বিতীয় উদাহরণ হিসাবে দেখানো হয়েছে এমন একটি সংকলন-সময় ত্রুটির দিকে পরিচালিত করবে।

// 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");

এই উত্তরটি কেন পুরোপুরি অন্য ইন্টেন্টের ব্যাখ্যা করা হয় সেই প্রশ্নের পুরোপুরি উত্তর দেয়। মজাদার. আর এর মতো শব্দগুলি অকেজো। আপনি কি সমস্যার কোনও সমাধান জানেন?
jukzi

@ জুকজি (১) পরিষ্কারভাবে পদ্ধতি ধরণের পরামিতিগুলি (এখানে, R) সংজ্ঞায়িত করুন : Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "I am NOT an Integer");এটি সংকলন না করার জন্য, বা (২) এটি সুস্পষ্টভাবে সমাধান করা যাক এবং আশা করি কোনও সংকলন-সময় ত্রুটি না নিয়ে এগিয়ে যেতে হবে
অ্যান্ড্রু টবিলকো

11

এটি এখানে টাইপ অনুমান যা এর ভূমিকা পালন করে। Rপদ্ধতি স্বাক্ষরের জেনেরিক বিবেচনা করুন :

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

তালিকাভুক্ত হিসাবে ক্ষেত্রে:

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

ধরণের Rসাফল্যের সাথে অনুমান করা হয়

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

এবং একটি Stringএই ধরণের দ্বারা বোঝায়, তাই সংকলন সফল হয়।


সুস্পষ্টভাবে প্রকারের ধরণ নির্ধারণ করতে Rএবং অসঙ্গতিটি সনাক্ত করতে, কেউ কেবল কোডের রেখাটি এইভাবে পরিবর্তন করতে পারে:

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

R কে <ইন্টিজার> হিসাবে স্পষ্টভাবে ঘোষণা করা আকর্ষণীয় এবং কেন এটি ভুল হয়ে যায় এই প্রশ্নের সম্পূর্ণ উত্তর দেয়। তবে আমি এখনও প্রকারটি স্পষ্ট না জানিয়ে সমাধানের সন্ধান করছি। কোন ধারণা?
jukzi

@ জুকজি আপনি কী ধরণের সমাধান খুঁজছেন? কোডটি ইতিমধ্যে সংকলিত হয়েছে, যদি আপনি এটি এটির মতো ব্যবহার করতে চান। আপনি যা সন্ধান করছেন তার একটি উদাহরণ বিষয়গুলি আরও পরিষ্কার করে দেওয়া ভাল be
নামান

11

এটি আপনার জেনেরিক ধরণের পরামিতিটি Rঅবজেক্ট হিসাবে অনুমান করা যেতে পারে, যেমন নীচের সংকলন:

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

1
হুবহু, যদি ওপি পদ্ধতিটির ফলাফলটি একটি পরিবর্তনশীল প্রকারের জন্য নির্ধারিত করে, তবে Integerসেখানে সংকলন ত্রুটি ঘটবে।
sepp2k

@ sepp2k বাদে Builderকেবলমাত্র জেনেরিক Tতবে এর মধ্যে নয় RIntegerনির্মাতার বিষয়ে যতটা টাইপ-চেক করা উচিত কেবল এটিকে উপেক্ষা করা হচ্ছে।
থিলো

2
Rঅনুমান করা হয়Object ... সত্যই নয়
নমন

@ থিলো আপনি ঠিকই বলেছেন আমি ধরে নিলাম যে রিটার্নের ধরণটি withব্যবহার করবে R। অবশ্যই এর অর্থ আসলে যুক্তিগুলি ব্যবহার করে এমন পদ্ধতিতে বাস্তবে কার্যকর করার কোনও অর্থবহ উপায় নেই।
sepp2k

1
নমন, আপনি ঠিক বলেছেন, আপনি এবং অ্যান্ড্রু সঠিক অনুমানক প্রকারের সাথে আরও বিস্তারিতভাবে উত্তর দিয়েছিলেন। আমি কেবল একটি সহজ ব্যাখ্যা দিতে চেয়েছিলাম (যদিও এই প্রশ্নটির দিকে তাকানো যে কেউ সম্ভবত টাইপের অনুক্রম এবং অন্য ধরণের জানে Object)।
sfiss

0

এই উত্তরটি অন্যান্য উত্তরের উপর ভিত্তি করে যা ব্যাখ্যা করে কেন এটি প্রত্যাশার মতো কাজ করে না।

সমাধান

নিম্নলিখিত কোডটি বিভাজনটি "" দিয়ে "দুটি সাশ্রয়ী ফাংশন" দিয়ে "এবং" ফিরে "বিভক্ত করে সমস্যার সমাধান করে:

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

(কিছুটা অপরিচিত)


আরো দেখুন stackoverflow.com/questions/58376589 সরাসরি সমাধান জন্য
jukzi
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.