2

Say I have two classes:

AnimalPen.java

public class AnimalPen {

    private ArrayList<Animal> animals;

    public AnimalPen() {
        animals = new ArrayList<Animal>();
        animals.add(new Dog("Freddy"));
        animals.add(new Dog("Doggy"));

        for (Animal a : animals) {
            animal.makeSound();
        }
    }

}

Dog.java

public class Dog extends Animal {

    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}

Is there any possible way to use reflection to change what makeSound() in dog does, without modifying either of these classes? For example (this is just a suggestion. I don't have the best understanding of reflection) I make a class called Dog that extends Animal, and then at runtime exchange the Dog.java posted above with one that I created so that every single time new Dog() is called, it will create a new instance of my Dog instead of the one written above. I need this in a way so that I don't need to be knowing when new instances of Dog are being created, but they are using the class that I made with a modified makeSound() instead of the one that just prints out "Woof".

To give a better example, as an experiment I am trying to modify a Minecraft server without modifying the source code of the server itself. I am loading my code into the JVM via CraftBukkit and I want to directly change how entities behave by modifying methods in the entity classes and then loading them into the server. I know this is possible to do through reflection, I was just more or less wondering if it is possible to "swap" an entire class at runtime with a modified version of it.

4

2 回答 2

3

No you cannot alter the method at run time with reflection. Reflection can be used to call methods, access fields, etc. There is no possible technique to alter a Java class at run time which would basically mean recompiling the source to byte code.

于 2013-11-01T03:16:41.153 回答
0

正如其他人所说,我认为不可能按照您需要的方式解决您的问题,涉及 Minecraft 服务器和反射。

我只是做了一些测试,主要是为了教学目的。最近(从 2017 年开始)java 附带了 JShell,它可能会接近这个但仅限于 shell。

拿你的例子来说,下面的代码可以保存为 jsh 文件并在 jshell 上打开:

import java.io.*;
import java.math.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.prefs.*;
import java.util.regex.*;
import java.util.stream.*;

public class Animal {
    String name;
    String sound = "grr";
    public Animal(String name) {
        this.name = name;
    }

    public void makeSound() {
        System.out.println(sound);
    }

    public void setSound(String sound) {
        this.sound = sound;
    }
}

public class Dog extends Animal {

    public Dog(String name) {
        super(name);
    this.sound = "Woof";
    }

    @Override
    public void makeSound() {
        System.out.println(sound);
    }
}

public class AnimalPen {

    private ArrayList<Animal> animals;

    public AnimalPen() {
        animals = new ArrayList<Animal>();
        animals.add(new Dog("Freddy"));
        animals.add(new Dog("Doggy"));
        allMakeSound();
    }

    public void allMakeSound() {
        for (Animal a : animals) 
            a.makeSound();
    }

    public void setAnimalSound(int id, String newSound) {
        ((Animal)animals.get(id)).setSound(newSound);
    }
}

AnimalPen testAnimals = new AnimalPen();

在 jshell 上加载它会产生以下结果:

jshell> /open /home/me/tmp/animals.jsh
Woof
Woof

jshell 已经为 AnimalPen 创建了一个对象:

jshell> /vars
|    AnimalPen testAnimals = AnimalPen@51081592

只需按预期更改对象即可:

jshell> testAnimals.setAnimalSound(1,"Meaw");

jshell> testAnimals.allMakeSound()
Woof
Meaw

但有趣的是当我们改变班级时。将打印代码更改为 "System.out.println(sound + " " + sound +" grr");":

jshell> /edit Dog 
|  modified class Dog

结果是:

jshell> testAnimals.allMakeSound()
Woof Woof grr
Meaw Meaw grr

正如我们所见,testAnimal 对象内部的对象改变了它们的方法。它适用于所有实例。由于某种原因,我无法更改 testAnimal 对象本身。当我尝试它时,我的实例被破坏了。但是对于 testAnimal 对象引用的那些实例,它听起来很有效。

于 2019-04-12T19:47:08.147 回答