2014-09-20 78 views
-1

我正在爲Minecraft服務器實現CraftBukkit編寫一個插件,並且遇到了需要轉換爲通過反射發現的類的問題。Java中的動態類型轉換

這是交易。我寫的原代碼看起來像這樣,不相關的部分刪除:

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; 
} 

現在,這種方法成功地檢索兩個版本所需的類目前支持。我用實例變量和編輯構造函數傳遞這些追加至改寫任務類,和我刪除了特定版本的進口:

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)方法。

我接受所有建議,因爲我在這裏面臨死衚衕。如果問題有更好的解決方法,我也可以改變。

謝謝!

+0

-1 to * Minecraft服務器實現Bukkit *,用於故意忽略Java的[命名包](http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html)公約。更糟的是,用這個版本污染它。你知道這樣做的原因(也許是好的)嗎?我知道,這並不能幫助你。把它看作是道義上的支持(另外,我當然想成爲最先提到這一點的人;-)。但是,我會看看我能否找出更有用的答案。 – 2014-09-20 10:12:57

+0

我認爲這樣做是因爲兩個獨立版本中的類往往不同,因此導致插件在不同版本中失敗 - 這迫使開發人員爲各種原因檢查插件。這絕對不是這樣做的最好方式。 – funstein 2014-09-20 10:22:21

回答

0

在閱讀了Gerold Broser的迴應之後,我意識到我將不得不修改我的方法,以創建某種類型的處理器類來執行特定於版本的操作 - 當然這將是一個接口分別由每個版本的類來實現。

但是,當我意識到Maven不會讓我調用同一個groupid.artifactid對象的兩個版本時,這就成了一個問題。

我很快做了一些研究,發現了mbaxter的Multiple Versions Tutorial以及AbstractionExamplePlugin實現,它完美地演示了這種方法。

該方法完美工作,是每個Bukkit開發人員應該使用的。如果需要,Here是我的完成插件,供進一步參考。

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; 
        } 
    

當然,這在某種程度上是醜陋的,但是在特定情況下,這是首先出現在我腦海中的可能性。

0

在面向對象的語言中,我們可以訪問爲此目的而開發的各種設計模式。我們特別使用兩種模式。

Adapter Pattern用於爲許多不同的實現提供相同的接口。它有時被稱爲墊片。您爲每個服務器的每個版本創建一個類,並將庫導入到每個服務器。該類實現了它們共同擁有的接口。

Factory Pattern用於在適配器類中進行選擇。您可以使用所需的任何方法來確定您擁有哪個服務器版本,並且會創建一個實現適當接口的對象。主要代碼保持不變。它調用工廠來獲取知道如何處理服務器的對象。

這種方法的優點有幾個。您不會通過導入重疊的庫來污染名稱空間。隨着新服務器版本的添加,主代碼更不容易發生變化;唯一需要編寫的代碼是新的服務器墊片和確定生成哪個適配器的工廠。