如何在 Kotlin 中地道地编写 Java 函数式接口的默认方法?

How to write Java's default method of a functional interface in Kotlin idiomatically?

我正在尝试转换名为 Chain of Responsibility in Kotlin idiomatically. But I'm not getting any clue on converting the default method appendNext() of Java interface in Kotlin. I tried some already existing questions like and this 的设计模式的 Java 代码,但它们似乎不适用于我的用例。

我尝试将默认方法 appendNext() 转换为 Kotlin 中的扩展函数。但显然 Kotlin 似乎没有找到方法 Logger.message() 并抛出 NoSuchMethodError.

我在以下代码片段中给出了原始 Java 代码和到目前为止我尝试过的 Kotlin 代码。

我更喜欢此代码的 Kotlin 惯用解决方案,而不使用 @JvmDefault 注释。代码应该像 Java 一样简洁,如果不是更多的话。任何帮助将不胜感激。

Java代码

这是设计模式责任链的正确工作 Java 代码:

import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;

@FunctionalInterface
public interface Logger {
    public enum LogLevel {
        INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

        public static LogLevel[] all() {
            return values();
        }
    }

    abstract void message(String msg, LogLevel severity);

    default Logger appendNext(Logger nextLogger) {
        return (msg, severity) -> {
            message(msg, severity);
            nextLogger.message(msg, severity);
        };
    }

    static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
        EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
        return (msg, severity) -> {
            if (set.contains(severity)) {
                stringConsumer.accept(msg);
            }
        };
    }

    static Logger consoleLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
    }

    static Logger emailLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
    }

    static Logger fileLogger(LogLevel... levels) {
        return writeLogger(levels, msg -> System.err.println("Writing to Log File: " + msg));
    }

    public static void main(String[] args) {
        // Build an immutable chain of responsibility
        Logger logger = consoleLogger(LogLevel.all())
                .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
                .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));

        // Handled by consoleLogger since the console has a LogLevel of all
        logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
        logger.message("Order record retrieved.", LogLevel.INFO);

        // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
        logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
        logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);

        // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
        logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING);
        logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR);
    }
}

Kotlin 代码

到目前为止,这是我尝试过的。我将 Enum 移动到一个单独的文件并将所有内容都放在顶层。看看 appendNext() 方法,这似乎是问题的原因。

Logger.kt

import java.util.*
import java.util.function.Consumer

interface Logger {
    fun message(message: String, severity: LogLevel)
}

fun Logger.appendNext(nextLogger: Logger): Logger {
    return object: Logger {
        override fun message(message: String, severity: LogLevel) {
            message(message, severity)
            nextLogger.message(message, severity)
        }
    }
}

fun writeLogger(
    stringConsumer: Consumer<String>,
    vararg levels: LogLevel
): Logger {
    val set = EnumSet.copyOf(listOf(*levels))
    return object: Logger {
        override fun message(message: String, severity: LogLevel) {
            if (set.contains(severity)) {
                stringConsumer.accept(message)
            }
        }
    }
}

fun consoleLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Writing to console: $msg") },
        *levels
    )
}

fun emailLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Sending via email: $msg") },
        *levels
    )
}

fun fileLogger(vararg levels: LogLevel): Logger {
    return writeLogger(
        Consumer { msg: String -> System.err.println("Writing to Log File: $msg") },
        *levels
    )
}

fun main() {
    // Build an immutable chain of responsibility
    val logger = consoleLogger(*LogLevel.all())
        .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
        .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))

    // Handled by consoleLogger since the console has a LogLevel of all
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
    logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR)
    logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE)

    // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
    logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING)
    logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR)
}

LogLevel.kt

enum class LogLevel {
    INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

    companion object {
        public fun all(): Array<LogLevel> {
            return values()
        }
    }
}

我不明白你为什么要添加原始 Java 代码中不存在的 currentLogger 属性。

如果您想要与 Java 中相同的行为,其中一个实现可以覆盖 appendNext() 的默认实现,它将如下所示:

fun interface Logger {
    fun message(message: String, severity: LogLevel)

    fun appendNext(nextLogger: Logger): Logger {
        return Logger { message, severity ->
            message(message, severity)
            nextLogger.message(message, severity)
        }
    }
}

如果您不打算覆盖此功能,则将其移至扩展功能会更合适。然后“覆盖”它需要编写另一个具有相同签名的扩展函数并导入那个函数来使用它。这就是标准库函数的组织方式。仍然不是万无一失的,但是将函数放在界面中会更强烈地表明它 意味着 被覆盖。

fun interface Logger {
    fun message(message: String, severity: LogLevel)
}

fun Logger.appendNext(nextLogger: Logger): Logger {
    return Logger { message, severity ->
        message(message, severity)
        nextLogger.message(message, severity)
    }
}

编辑:此外,您不需要使用 Consumer,因为在 Kotlin 中函数首先是 class 类型。比如把Consumer<String>换成(String) -> Unit然后直接用stringConsumer(message)调用,而不是stringConsumer.accept(message).

此处最佳解决方案的关键是使用 Functional interfaces(在 Kotlin 1.4 中引入)

同时为了使代码更简洁和惯用,请考虑使用 single-expression fuctions,删除可以推断的类型,并将 EnumSet.copyOf(listOf(*levels)) 重写为辅助函数,而无需创建额外的对象:

import java.util.*
import java.util.function.Consumer

fun interface Logger {
    fun message(msg: String, severity: LogLevel)
}

fun Logger.appendNext(nextLogger: Logger) = Logger { msg, severity ->
    message(msg, severity)
    nextLogger.message(msg, severity)
}

inline fun <reified E : Enum<E>> enumSetOf(e: Array<out E>): EnumSet<E> =
    EnumSet.noneOf(E::class.java).also { result -> e.forEach { result.add(it) } }

fun writeLogger(levels: Array<out LogLevel>, stringConsumer: Consumer<String>) = Logger { msg, severity ->
    if (severity in enumSetOf(levels)) {
        stringConsumer.accept(msg)
    }
}

fun consoleLogger(vararg levels: LogLevel) =
    writeLogger(levels) { msg -> System.err.println("Writing to console: $msg") }

fun emailLogger(vararg levels: LogLevel) =
    writeLogger(levels) { msg -> System.err.println("Sending via email: $msg") }

fun fileLogger(vararg levels: LogLevel) =
    writeLogger(levels) { msg -> System.err.println("Writing to Log File: $msg") }

fun main() {
    // Build an immutable chain of responsibility
    val logger = consoleLogger(*LogLevel.all())
        .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
        .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))

    // Handled by consoleLogger since the console has a LogLevel of all
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    // Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
    logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR)
    logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE)

    // Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
    logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING)
    logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR)
}