Java 在运行时更改类的所有实例的方法

Java 在运行时更改类的所有实例的方法,java,oop,reflection,runtime,overriding,Java,Oop,Reflection,Runtime,Overriding,假设我有两门课: 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"));

假设我有两门课:

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

}
public class Dog extends Animal {

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

    @Override
    public void makeSound() {
        System.out.println("Woof");
    }
}
是否有任何可能的方法可以使用反射来更改dog中的
makeSound()
,而不修改这些类中的任何一个?例如(这只是一个建议。我对反射没有最好的理解),我创建了一个名为
Dog
的类,它扩展了
Animal
,然后在运行时将上面发布的
Dog.java
与我创建的一个交换,以便每次调用
new Dog()
,它将创建我的狗的一个新实例,而不是上面写的那个。我需要这样做,这样我就不需要知道何时创建新的Dog实例,但它们使用的是我用修改的
makeSound()
创建的类,而不是只打印“Woof”的类


举一个更好的例子,作为一个实验,我试图在不修改服务器本身源代码的情况下修改Minecraft服务器。我正在通过将代码加载到JVM中,我希望通过修改实体类中的方法,然后将它们加载到服务器中,从而直接更改实体的行为。我知道这是可以通过反射实现的,我只是或多或少地想知道是否可以在运行时用一个修改过的类“交换”整个类。

不,您不能在运行时用反射来更改该方法。反射可用于调用方法、访问字段等。在运行时没有可能改变Java类的技术,这基本上意味着将源代码重新编译为字节代码

正如其他人所说,我认为不可能以您需要的方式解决有关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已经为AnimalPen创建了一个对象:

jshell> /vars
|    AnimalPen testAnimals = AnimalPen@51081592
只需按预期更改对象,我们就可以:

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

jshell> testAnimals.allMakeSound()
Woof
Meaw
但有趣的是当我们换课的时候。将打印代码更改为“System.out.println(sound+”“+sound+“grr”);”:

结果是:

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

如我们所见,testAnimal对象中的对象的方法发生了更改。它适用于所有实例。由于某种原因,我无法更改testAnimal对象本身。当我尝试它时,我的实例被破坏了。但是对于testAnimal对象引用的那些实例,这听起来是可行的。

听起来您对策略模式很感兴趣。可能是工厂模式。您的解释非常不清楚,请解释您想要达到的情况,而不是试图解释一个半途而废的解决方案。AOP和Java代理之类的东西可能会让您做到这一点,但可能不需要思考或这些(复杂的)概念就可以满足您的实际需求。你能谈谈你想要满足的真实场景吗?也许我们可以帮助你确定一种直接的方法吗?@MarkElliot我更新了一个我试图实现的例子。现在我意识到了这一点,我一直试图通过给出一个复杂的例子来问一个简单的问题。我认为通过反思来做你想做的事情是不可行的。改变方法体不是反射所能做到的;它也不能调整呼叫站点地址。你能创建自己的类作为原始minecraft类的适配器吗?将您不想拦截的调用定向到实际的类,并为您想要拦截的类提供您自己的方法。我不知道MC设置是如何工作的,所以我只是提供一些选项。可能会使用各种调试器功能,但它们涉及动态重新编译类或拦截调用并模拟对类的更改。
jshell> /edit Dog 
|  modified class Dog
jshell> testAnimals.allMakeSound()
Woof Woof grr
Meaw Meaw grr