Interface FunctionalReference

  • All Superinterfaces:
    Serializable
    All Known Subinterfaces:
    AnnotationBuilder.MemberExtractor<A,​X>

    public interface FunctionalReference
    extends Serializable
    Interface that allows passing compile-time safe information around.

    This interface, when extended by functional interface, can be used for passing references to methods or constructors, or parameters of those between parts of program, with compile-time conformance checking enabled.

    Sometimes, when one part of the program needs to identify some construct of the program that is not known to it at time of its creation, we often use complete or partial reflection information to identify these constructs. For example:

    • Class literal, method name, and method parameter erasures literals to identify method
    • Class literal and constructor parameter erasure literals to identify constructor
    • Any of above and parameter index to identify type with additional annotations
    In any of above case, some of the elements can be omitted if they are 'assumed' from context. More concrete examples:
    • Serialization framework needs a method of creating object of class C, if not by no-argument constructor, a static method name as string in the C class can be passed.
    • Dynamic form library wants to set properties entered by user on bound object, so property name as string is passed.
    • Dependency Injection framework needs a way to pass dependencies to newly created object, so it requires annotation type to be included when searching for qualifiers on each dependency.

    Using this interface, above scenarios can be solved in clean, compile-time checked and refactor-safe ways.

    To use this interface, declare a functional interface that has method with signature that user needs to provide. This new interface needs to extend this one. Then, expect specified interface as a parameter for some method in place where reflection information would be passed. Expect client of this method to pass a non-class based implementation of this functional reference, preferably method/constructor reference, but lambda will also work if method name is not required and only actual parameter types and annotations are required. Document that requirement. After that on the passed interface implementation, execute introspect(), preferably once, and access expected information from it.

    Example:

         interface Getter<S, V> extends FunctionalReference {
                     V get(S source);
         }
    
         class DocumentCreator {
             public <S> useForSummaryExtraction(Getter<S, String> summaryExtractor) {
                 FunctionalReference.Introspection introspection = summaryExtractor.introspect()
                 String methodName = introspection.referencedMethodName();
                 this.extractors.put(OutlinePart.SUMMARY, methodName);
             }
         }
    
         class Person {
             String getSelfDescription() {  ... }
         }
    
         class PersonResume {
             String generate(Person person) {
                 DocumentCreator creator = ...
                 creator.useForSummaryExtraction(Person::getSelfDescription);
             }
         }
     

    In a pinch, this method can be also used to extract refactor-safe constants. Unfortunately this is not very clean way of representing it.

         private static final String SUMMARY_GETTER_NAME =
             ((Getter>Person,String>) Person::getSelfDescription).introspect().getReferencedMethodName();
     

    Introspected reference can be on of either of five 'modes'. Three of those are method references, one constructor reference and lambda. These modes are described in JSL section 15.13 and 15.27. This class named the modes for convenience:

    • Reference to static method (named "static"). This mode is expression in form ReferenceType::Identifier and matches static method in ReferenceType (ex. System::identityHashCode)
    • Reference to instance method without primary identifier (named "instance"). This mode is expression in form ReferenceType::Identifier and matches instance method in ReferenceType (ex Object::hashCode
    • Reference to instance method with primary identifier (named "bound", as the instance will be bound to the reference). This mode is expression in form Primary::Identifier and matches instance method in ReferenceType (ex. specificPerson::getSelfDescription)
    • Reference to constructor (named "constructor"). This mode is expression in form ReferenceType::new and matches constructor in Reference type (ex. Person::new)
    • Lambda expression (named "lambda").

    Although this interface can be used to extract information while application is in running cycle, it is strongly discouraged, because there is a performance hit any time the introspect() method is called. This should be only used when starting application - to configure objects.

    Using this method has some performance penalty, but it should be somewhat in range of using reflections in above scenarios. When method/constructor references are used to pass information around, no additional program constructs should be generated by the compiler. Lambdas will still produce additional synthetic method(s) in declaring class.

    • Method Detail

      • introspect

        default FunctionalReference.Introspection introspect()
        Analyzes reference and produces information access object.

        This method has some performance penalty and should be called once on any given reference. Produced introspection is almost immutable, and for the same instance, this method will produce the same result. Almost immutable, because it references all of the captures in passed lambda and bound instance of bound methods, so if those are not immutable, the introspection also isn't.

        This method will throw exception when the instance is not implemented by method/constructor reference or lambda (ex. it's implemented by class).

        Do not override this method, as this is missing the whole point of this mechanism.

        Implementation Requirements:
        Introspection is done by using SerializedLambda trick: if method/constructor reference or lambda implements Serializable, beside its invocation function, it also declares writeReplace method (described in Serializable documentation), which returns SerializedLambda. This object can be interrogated to extract:
        • What kind of implementation is provided
        • Where was it declared
        • Whats the signature of the target method
        • If its a bound method, what is the referenced instance
        • If its a lambda, what are the captured objects
        Using this information, and little reflection, all of the needed data can be extracted. This behavior is documented in LambdaMetafactory.altMetafactory(java.lang.invoke.MethodHandles.Lookup, java.lang.String, java.lang.invoke.MethodType, java.lang.Object...) and should be consistent between implementations.
        Returns:
        introspection of passed reference