为内部子接口创建“默认构造函数”

Posted

技术标签:

【中文标题】为内部子接口创建“默认构造函数”【英文标题】:Creating a "default constructor" for inner sub-interfaces 【发布时间】:2017-08-07 13:13:55 【问题描述】:

好的,标题可能很难理解。我没有找到正确的东西。 所以,基本上我使用 Java 8 函数来创建一个 Retryable API。我想要这些接口的简单实现,所以我在 Retryable 接口的每个实现中创建了一个 of(...) 方法,我们可以在其中使用 lambda 表达式,而不是手动创建匿名类。

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

public interface Retryable<T, R> extends Function<T, R>

    void retrying(Exception e);

    void skipping(Exception e);

    int trials();

    @Override
    default R apply(T t) 
        int trial = 0;
        while (true) 
            trial++;
            try 
                return action(t);
             catch (Exception e) 
                if (trial < trials()) 
                    retrying(e);
                 else 
                    skipping(e);
                    return null;
                
            
        
    

    R action(T input) throws Exception;

    interface RunnableRetryable extends Retryable<Void, Void> 

        static RunnableRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedRunnable runnable) 
            return new RunnableRetryable() 
                @Override
                public void retrying(Exception e) 
                    retrying.accept(e);
                

                @Override
                public void skipping(Exception e) 
                    skipping.accept(e);
                

                @Override
                public int trials() 
                    return trials;
                

                @Override
                public Void action(Void v) throws Exception 
                    runnable.tryRun();
                    return null;
                
            ;
        

        @FunctionalInterface
        interface CheckedRunnable extends Runnable 

            void tryRun() throws Exception;

            @Override
            default void run() 
                try 
                    tryRun();
                 catch (Exception e) 
                    throw new RuntimeException(e);
                
            
        
    

    interface ConsumerRetryable<T> extends Retryable<T, Void> 

        static <T> ConsumerRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) 
            return new ConsumerRetryable<T>() 
                @Override
                public void retrying(Exception e) 
                    retrying.accept(e);
                

                @Override
                public void skipping(Exception e) 
                    skipping.accept(e);
                

                @Override
                public int trials() 
                    return trials;
                

                @Override
                public Void action(T t) throws Exception 
                    consumer.tryAccept(t);
                    return null;
                
            ;
        

        @FunctionalInterface
        interface CheckedConsumer<T> extends Consumer<T> 

            void tryAccept(T t) throws Exception;

            @Override
            default void accept(T t) 
                try 
                    tryAccept(t);
                 catch (Exception e) 
                    throw new RuntimeException(e);
                
            
        
    

    interface SupplierRetryable<T> extends Retryable<Void, T> 

        static <T> SupplierRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) 
            return new SupplierRetryable<T>() 
                @Override
                public void retrying(Exception e) 
                    retrying.accept(e);
                

                @Override
                public void skipping(Exception e) 
                    skipping.accept(e);
                

                @Override
                public int trials() 
                    return trials;
                

                @Override
                public T action(Void v) throws Exception 
                    return supplier.tryGet();
                
            ;
        

        @FunctionalInterface
        interface CheckedSupplier<T> extends Supplier<T> 

            T tryGet() throws Exception;

            @Override
            default T get() 
                try 
                    return tryGet();
                 catch (Exception e) 
                    throw new RuntimeException(e);
                
            
        
    

    interface FunctionRetryable<T, R> extends Retryable<T, R> 

        static <T, R> FunctionRetryable of(Consumer<Exception> retrying, Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) 
            return new FunctionRetryable<T, R>() 
                @Override
                public void retrying(Exception e) 
                    retrying.accept(e);
                

                @Override
                public void skipping(Exception e) 
                    skipping.accept(e);
                

                @Override
                public int trials() 
                    return trials;
                

                @Override
                public R action(T t) throws Exception 
                    return function.tryApply(t);
                
            ;
        

        @FunctionalInterface
        interface CheckedFunction<T, R> extends Function<T, R> 

            R tryApply(T t) throws Exception;

            @Override
            default R apply(T t) 
                try 
                    return tryApply(t);
                 catch (Exception e) 
                    throw new RuntimeException(e);
                
            
        
    

但正如您所见,每个of(...) 方法中都有很多重复的代码。我可以在 Retryable 接口中创建一种“构造函数”(这不是正确的词,因为接口不能有构造函数),但我不知道如何。有人有想法吗?

【问题讨论】:

我看不出这些冗余内部接口存在的理由。对于不同参数化的Retryables,您只有多个工厂方法。 【参考方案1】:

主要问题是您的 API 爆炸式增长。所有这些扩展Retryable 的嵌套接口都没有添加任何功能,但是一旦它们成为API 的一部分,就需要此代码的用户来处理它们。此外,它们是导致代码重复的原因,因为这些冗余接口中的每一个都需要自己的实现,而所有实现基本上都在做同样的事情。

删除这些过时的类型后,您可以简单地将操作实现为委托:

public interface Retryable<T, R> extends Function<T, R>
    void retrying(Exception e);
    void skipping(Exception e);
    int trials();
    @Override default R apply(T t) 
        try  return action(t); 
        catch(Exception e) 
            for(int trial = 1; trial < trials(); trial++) 
                retrying(e);
                try  return action(t);  catch (Exception next)  e=next; 
            
            skipping(e);
            return null;
        
    

    R action(T input) throws Exception;

    public static Retryable<Void, Void> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedRunnable runnable) 
        return of(retrying, skipping, trials, x ->  runnable.tryRun(); return null; );
    

    @FunctionalInterface interface CheckedRunnable extends Runnable 
        void tryRun() throws Exception;
        @Override default void run() 
            try  tryRun();  catch (Exception e)  throw new RuntimeException(e); 
        
    

    public static <T> Retryable<T, Void> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedConsumer<T> consumer) 
        return of(retrying, skipping, trials,
                  value ->  consumer.tryAccept(value); return null; );
    

    @FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> 
        void tryAccept(T t) throws Exception;
        @Override default void accept(T t) 
            try  tryAccept(t);  catch (Exception e)  throw new RuntimeException(e); 
        
    

    public static <T> Retryable<Void, T> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedSupplier<T> supplier) 
        return of(retrying, skipping, trials, voidArg ->  return supplier.tryGet(); );
    

    @FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> 
        T tryGet() throws Exception;
        @Override default T get() 
            try  return tryGet(); 
            catch (Exception e)  throw new RuntimeException(e); 
        
    

    public static <T, R> Retryable<T, R> of(Consumer<Exception> retrying,
            Consumer<Exception> skipping, int trials, CheckedFunction<T, R> function) 
        return new Retryable<T, R>() 
            @Override public void retrying(Exception e)  retrying.accept(e); 
            @Override public void skipping(Exception e)  skipping.accept(e); 
            @Override public int trials()  return trials; 
            @Override public R action(T t) throws Exception 
                return function.tryApply(t);
            
        ;
    

    @FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> 
        R tryApply(T t) throws Exception;
        @Override default R apply(T t) 
            try  return tryApply(t); 
            catch (Exception e)  throw new RuntimeException(e); 
        
    

只需要一个实现类,它必须能够处理参数和返回值,其他的可以简单地使用适配器函数委托给它,要么删除参数,要么返回null,或两者兼而有之。

对于大多数用例,lambda 表达式的形状适合选择正确的方法,例如

Retryable<Void,Void> r = Retryable.of(e -> , e -> , 3, () -> );
Retryable<Void,String> s = Retryable.of(e -> , e -> , 3, () -> "foo");
Retryable<Integer,Integer> f = Retryable.of(e -> , e -> , 3, i -> i/0);

但有时,需要一点提示:

// braces required to disambiguate between Function and Consumer
Retryable<String,Void> c = Retryable.of(e->, e ->, 3,
                                        str ->  System.out.println(str); );

【讨论】:

你完全正确。我看得太远了。现在很清楚了。谢谢! @Holger 我们也有重试机制(谁没有?),但是这样做的 OP 想法和您的回答可能会给我们的代码带来新的形状......【参考方案2】:

看起来您可以将其中的一些因素考虑到(可能是包私有的)抽象类中:

abstract class AbstractRetryable<T, R> implements Retryable<T, R> 
    private final Consumer<Exception> retrying;
    private final Consumer<Exception> skipping;
    private final int                 trials;
    AbstractRetryable(Consumer<Exception> retrying,
                      Consumer<Exception> skipping,
                      int                 trials) 
        this.retrying = Objects.requireNonNull(retrying, "retrying");
        this.skipping = Objects.requireNonNull(skipping, "skipping");
        this.trials   = trials;
    
    @Override
    public void retrying(Exception x) 
        retrying.accept(x);
    
    @Override
    public void skipping(Exception x) 
        skipping.accept(x);
    
    @Override
    public int trials() 
        return trials;
    

唯一的问题是您使用的是子接口,因此您无法创建既扩展抽象类又实现子接口的匿名类。

然后您可以编写更多(同样,可能是包私有的)子类:

final class RunnableRetryableImpl
extends    AbstractRetryable<Void, Void>
implements RunnableRetryable 
    private final CheckedRunnable runnable;
    RunnableRetryableImpl(Consumer<Exception> retrying,
                          Consumer<Exception> skipping,
                          int                 trials,
                          CheckedRunnable     runnable) 
        super(retrying, skipping, trials);
        this.runnable = Objects.requireNonNull(runnable, "runnable");
    
    @Override
    public Void apply(Void ignored) 
        try 
            runnable.tryRun();
         catch (Exception x) 
            // BTW I would consider doing this.
            if (x instanceof RuntimeException)
                throw (RuntimeException) x;
            // I would also probably write a class like:
            // class RethrownException extends RuntimeException 
            //     RethrownException(Exception cause) 
            //         super(cause);
            //     
            // 
            // This way the caller can catch a specific type if
            // they want to.
            // (See e.g. java.io.UncheckedIOException)
            throw new RuntimeException(x);
        
        return null;
    

或者你可以通过使用本地类来减少行数:

static RunnableRetryable of(Consumer<Exception> retrying,
                            Consumer<Exception> skipping,
                            int                 trials,
                            CheckedRunnable     runnable) 
    Objects.requireNonNull(runnable, "runnable");
    final class RunnableRetryableImpl
    extends    AbstractRetryable<Void, Void>
    implements RunnableRetryable 
        RunnableRetryable() 
            // Avoid explicitly declaring parameters
            // and passing arguments.
            super(retrying, skipping, trials);
        
        @Override
        public Void apply(Void ignored) 
            try 
               runnable.tryRun();
             catch (Exception x) 
                if (x instanceof RuntimeException)
                    throw (RuntimeException) x;
                throw new RuntimeException(x);
            
            return null;
        
    
    return new RunnableRetryableImpl();

就个人而言,我认为我只会编写包私有实现而不是本地类,但它肯定需要相当多的样板代码。

另外,作为旁注,当您编写返回匿名类的工厂时,您应该在方法本身内部使用requireNonNull(就像我在示例of 方法中所做的那样)。这样,如果 null 被传递给该方法,该方法将抛出 NPE 而不是例如有些人打电话给retryingskipping 一段时间后抛出NPE。

【讨论】:

您可以简单地使用该类型创建第二个 catch 子句,而不是在 catch 子句中执行 instanceof,例如catch(RuntimeException|Error unchecked) throw unchecked; catch(Throwable checked) throw new RuntimeException(checked);

以上是关于为内部子接口创建“默认构造函数”的主要内容,如果未能解决你的问题,请参考以下文章

此类未定义公共默认构造函数,或构造函数引发异常。内部异常:java.lang.InstantiationException

构造函数用于创建类的实例对象,构造函数名应与类名相同,返回类型为void.

创建对象向量时,不为每个对象唯一地调用默认对象构造函数

构造函数与默认构造函数

构造函数与默认构造函数

未找到接口 java.util.List Rest API Spring boot 的主构造函数或默认构造函数