Java 在运行时更改类的所有实例的方法
假设我有两门课: AnimalPen.javaJava 在运行时更改类的所有实例的方法,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"));
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