Fluent API with inheritance and generics

后端 未结 5 774
谎友^
谎友^ 2020-12-09 02:13

I\'m writing a fluent API to configure and instantiate a series of \"message\" objects. I have a hierarchy of message types.

To be able to access method of subclasse

5条回答
  •  隐瞒了意图╮
    2020-12-09 02:31

    The compiler warns you of this unsafe operation, because it cannot factually check the correctness of your code. This makes it, as a matter of fact, unsafe and there is nothing you can do to prevent this warning. Even though an unsafe operation is not compile-checked, it can still be legitimate at run time. If you circumvent the compiler check, it is however your job to validate your own code for its use of correct types which is what the @SupressWarning("unchecked") annotation is for.

    To apply this to your example:

    public abstract class Message> {
    
      // ...
    
      @SupressWarning("unchecked")
      public T withID(String id) {
        return (T) this;
      }
    }
    

    is fine, because you can as a matter of fact tell with certainty that this Message instance is always of the type that is represented by T. But the Java compiler cannot (yet). As with other suppression warnings, the key to using the annotation is to minimize its scope! Otherwise, you can easily retain the annotation suppression by accident after you made code changes that render your former manual check for type safety as invalid.

    As you only return a this instance, you can however easily outsource the task to a specific methods as recommended in another answer. Define a protected method like

    @SupressWarning("unchecked")
    public T self() {
      (T) this;
    }
    

    and you can always call the mutator like here:

    public T withID(String id) {
      return self();
    }
    

    As another option, and if it is possible for you to implement, consider an immutable builder which only exposes its API by interfaces but implements a full builder. This is how I normally build fluent interfaces these days:

    interface Two { T make() }
    interface One {  Two do(S value) }
    
    class MyBuilder implements One, Two {
    
      public static One newInstance() {
        return new MyBuilder(null);
      }
    
      private T value; // private constructors omitted
    
      public  Two do(S value) {
        return new MyBuilder(value);
      }
    
      public T make() {
        return value;
      }
    }
    
    
    

    You can, of course, create smarter constructions where you avoid the unused fields. If you want to look at examples of me using this approach, look at my two projects which use fluent interfaces quite heavily:

    1. Byte Buddy: API for defining a Java class at run time.
    2. PDF converter: A conversion software for converting files using MS Word from Java.

    提交回复
    热议问题