如何使某些东西与数组和集合兼容?

How can I make something be compatible with both an array and a collection?

这听起来是个愚蠢的问题。然而,在我使用的 API 中,版本 1.7.2 有方法 Bukkit.getServer().getOnlinePlayers() 返回一个 Player[],而版本 1.7.10 有 Bukkit.getServer().getOnlinePlayers() 返回一个 Collection<Player>。我需要让我的插件与两者兼容。

我已经得到了两者的 API,但是除了创建单独的插件之外,我目前不知道如何做到这一点。

我目前只是将集合转换为数组。那么,如果版本低于 1.7.9,有什么办法(我已经可以得到版本)不使用 .toArray() 但因为它已经 returns 一个数组?

你做得很好。不幸的是,没有办法创建可以同时处理集合和数组的强类型方法。

(您可以创建接受 Object 的方法,然后使用 instanceof 检查它,转换并处理集合和数组,但这个解决方案很丑陋。)

你可以通过反思来做到这一点。像这样:

List<Player> getPlayers() {
    try {
        Method method = getMethod(Server.class, "getOnlinePlayers");
        Object result = method.invoke(Bukkit.getServer());
        if(result instanceof Player[])
            return Arrays.asList((Player[])result);
        else
            return (List<Player>)result;

    } catch(ReflectiveOperationException e) {

        // something went wrong! If you have a better way to handle problems, do that instead
        throw new RuntimeException(e);
    }
}

如果 class org.bukkit.Server 只是更改了方法的 return 类型 getOnlinePlayers() 而不是弃用它并引入新方法(哎呀!!!! ! 谁敢以这种方式更改已经发布的接口?),您没有像样的方法如何在一个代码中调用该方法。你只是 不能 写一些像

Server server = Bukkit.getServer();
// One of the branches will not be compilable
if (version <= xxxx) {
    Player[] players = server.getOnlinePlayers();
}
else {
    Collection<Player> players = server.getOnlinePlayers();
}

恐怕唯一的方法就是像其他人推荐的那样使用反射。

实际上有两种方法可以做到这一点。 @immibis 的回答给出了第一种方式。第二种方法涉及创建一个 "version adapter" API,其中包含针对不同版本(在本例中)Bukkit 的多个插件实现;例如

public interface BukkitVersionAdapter {
    Collection<Player> getOnlinePlayers();
    ...
}

public class BukkitVersionAdapterV1dot7 implements BukkitVersionAdapter {
    public Collection<Player> getOnlinePlayers() {
        return Arrays.asList(Bukkit.getServer().getOnlinePlayers());
    }
    ...
}

public class BukkitVersionAdapterV1dot8 implements BukkitVersionAdapter {
    public Collection<Player> getOnlinePlayers() {
        return Bukkit.getServer().getOnlinePlayers();
    }
    ...
}

特定于版本的适配器 类 然后需要针对各个 Bukkit 版本的 Bukkit API jar 进行编译。

然后当你启动你的主应用程序(或者我猜是 Bukkit 插件)时,你会做这样的事情:

String version = // get the Bukkit version
String className = // map the version to a class name; e.g.
                   // "pkg.BukkitVersionAdapterV1dot7" or
                   // "pkg.BukkitVersionAdapterV1dot8"
Class clazz = Class.forName(className);
BukkitVersionAdapter adapter = 
         (BukkitVersionAdapter) clazz.newInstance();

// Then ...
Collection<Player> players = adapter.getOnlinePlayers();

这样比较麻烦,但与使用反射调用相比有两个好处:

  1. 不同 Bukkit 版本的特定逻辑现在被隔离到代码库的一部分......而不是(可能)分散在各处。

  2. 反射的开销只在启动时产生。之后,通过适配器 API 对 Bukkit 进行的所有方法调用都是常规 Java 方法调用。

根据上下文,这些可能使适配器方法更合适。