Java中的动态类型转换

时间:2014-09-20 09:35:42

标签: java reflection casting bukkit

我正在为Minecraft服务器实现CraftBukkit编写一个插件,我遇到了一个问题,我需要将其转换为通过反射找到的类。

这是交易。我写的原始代码看起来像这样,删除了不相关的部分:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import net.minecraft.server.v1_7_R3.EntityAnimal;
import net.minecraft.server.v1_7_R3.EntityHuman;
import org.bukkit.craftbukkit.v1_7_R3.entity.CraftAnimals;
import org.bukkit.craftbukkit.v1_7_R3.entity.CrafteEntity;

import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;

public class Task extends BukkitRunnable {
    private static final int MATING_DISTANCE = 14;
    private final JavaPlugin plugin;
    private final Random randomizer;
    private boolean mateMode;
    private double chance;

    public Task(JavaPlugin plugin, double chance, boolean mateMode) {
        this.plugin = plugin;
        this.randomizer = new Random();
        this.chance = chance;
        this.mateMode = mateMode;
        this.theTaskListener = listener;
    }

    public void run() {
        List<World> worlds = plugin.getServer().getWorlds();
        Iterator<World> worldIterator = worlds.iterator();

        while (worldIterator.hasNext()) {
            World world = worldIterator.next();
            Collection<Animals> animals = world.getEntitiesByClass(Animals.class);

            Iterator<Animals> animalIterator = animals.iterator();
            while (animalIterator.hasNext()) {
                Animals animal = (Animals) animalIterator.next();
                EntityAnimal entity = (EntityAnimal) ((CraftEntity) ((CraftAnimals) animal)).getHandle();
                EntityHuman feeder = null;
                entity.f(feeder);
            }
        }
    }
}

但是,正如您在导入中看到的那样,此代码仅从一个版本的Minecraft服务器软件包 - v1_7_R3导入了类。现在的问题是,我希望增加对此的支持,并且我希望能够在不为每个版本的Minecraft创建我的插件的单独版本的情况下这样做。尽管包中的大多数类都是相同的(至少是我需要的所有类),但包名称是不同的,因此无法使用静态导入(或者至少我认为是这样)?

所以,我决定使用反射来获得我需要的正确类(这段代码在另一个类中):

private static final String[] requiredClasses = {
    "net.minecraft.server.%s.EntityAnimal",
    "net.minecraft.server.%s.EntityHuman",
    "org.bukkit.craftbukkit.%s.entity.CraftAnimals",
    "org.bukkit.craftbukkit.%s.entity.CraftEntity"
};
public static final String[] supportedVersions = {
    "v1_7_R3",
    "v1_7_R4"
};

public Class<?>[] initializeClasses() {
    String correctVersion = null;
    for (int i = 0; i < supportedVersions.length; i++) {
        String version = supportedVersions[i];
        boolean hadIssues = false;
        for (int j = 0; j < requiredClasses.length; j++) {
            String className = requiredClasses[j];
            try {
                Class.forName(String.format(className, version));
            } catch (ClassNotFoundException e) {
                getLogger().log(Level.INFO, String.format("The correct version isn't %s.", version));
                hadIssues = true;
                break;
            }
        }

        if (!hadIssues) {
            correctVersion = version;
            break;
        }
    }

    Class[] classes = new Class[requiredClasses.length];

    if (correctVersion != null) {
        getLogger().log(Level.INFO, String.format("The correct version is %s.", correctVersion));
        for (int i = 0; i < requiredClasses.length; i++) {
            String className = requiredClasses[i];
            try {
                classes[i] = Class.forName(String.format(className, correctVersion));
            } catch (ClassNotFoundException e) {}
        }
    } else {
        getLogger().log(Level.WARNING, "The version of Minecraft on this server is not supported.");
        getLogger().log(Level.WARNING, "Due to this, the plugin will self-disable.");
        getLogger().log(Level.WARNING, "To fix this issue, get  build that supports your version.");
        this.setEnabled(false);
    }

    return classes;
}

现在,此方法成功检索了当前支持的两个版本中所需的类。我使用实例变量和编辑的构造函数将这些传递给重写的Task类,并删除了特定于版本的导入:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import org.bukkit.World;
import org.bukkit.entity.Animals;
import org.bukkit.entity.Entity;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;

public class Task extends BukkitRunnable {

    private static final int MATING_DISTANCE = 14;
    private final JavaPlugin plugin;
    private final Random randomizer;
    private boolean mateMode;
    private double chance;

    private Class entityAnimal;
    private Class entityHuman;
    private Class craftAnimals;
    private Class craftEntity;

    public Task(JavaPlugin plugin, Class[] classes, double chance, boolean mateMode) {
        this.plugin = plugin;
        this.randomizer = new Random();
        this.chance = chance;
        this.mateMode = mateMode;

        this.entityAnimal = classes[0];
        this.entityHuman = classes[1];
        this.craftAnimals = classes[2];
        this.craftEntity = classes[3];
    }

现在,我如何重写Task.run()方法以便它将使用反射类?涉及到大量的类型转换,不幸的是,由于Minecraft代码中的超载数量过多,所有这些都是必需的。例如,entity.f(EntityHuman human)方法不能简单地通过执行entity.f(null)来调用,因为还有其他重载的entity.f(Object object)方法。

我对所有建议都持开放态度,因为我在这里面临死胡同。如果有更好的方法解决问题,我也可以改为。

谢谢!

3 个答案:

答案 0 :(得分:0)

只是一个头脑风暴的想法。如果:

  • 导入所有支持的版本
  • 完全引用相应的包类型
  • 检查针对特定运行时的版本(假设可以某种方式获取)

    import net.minecraft.server.v1_7_R3.*;
    import net.minecraft.server.v1_7_R4.*;
    
    enum Version {
        V1_7_R3,
        V1_7_R4
    }
    
    Version currentVersion;
    
    net.minecraft.server.v1_7_R3.EntityAnimal animal3;
    net.minecraft.server.v1_7_R4.EntityAnimal animal4;
    
    // obtain currentVersion
    
    switch ( currentVersion ) {
        case V1_7_R3:
            animal3.method();
            break;
        case V1_7_R4:
            animal4.method();   
            break;
        }
    

当然,这在某种程度上是丑陋的,但在特定情况下,首先出现在我脑海中的可能性。

答案 1 :(得分:0)

在面向对象的语言中,我们可以访问为此目的而开发的各种设计模式。我们特别使用两种模式。

Adapter Pattern用于为许多不同的实现提供相同的接口。它有时被称为垫片。您为每个服务器的每个版本创建一个类,将库导入每个服务器。该类实现了它们共同拥有的接口。

Factory Pattern用于在适配器类中进行选择。您可以使用所需的任何方法来确定您拥有的服务器版本,并创建一个实现正确接口的对象。主要代码保持不变。它调用工厂来获取一个知道如何处理服务器的对象。

这种方法的优点有几个。您不会通过导入重叠库来污染名称空间。随着新服务器版本的添加,主代码更不容易改变;唯一需要编写的代码是新的服务器填充程序和确定要生成哪个适配器的工厂。

答案 2 :(得分:0)

在阅读了Gerold Broser的回复之后,我意识到我必须以某种方式修改我的方法,以便创建某种类型的处理程序类来执行特定于版本的操作 - 当然这将是一个接口,每个版本将由一个类单独实现。

然而,当我意识到Maven不会让我调用同一个groupid.artifactid对象的两个版本时,这就成了一个问题。

我很快做了一些研究,发现了mbaxter的Multiple Versions Tutorial以及AbstractionExamplePlugin实现,这完美地证明了这种方法。

该方法运行良好,是每个Bukkit开发人员应该使用的方法。 Here我已完成的插件,以便在必要时进一步参考。