【问题标题】:Changing a method for all instances of class at runtime在运行时更改所有类实例的方法
【发布时间】:2013-11-12 06:04:30
【问题描述】:

假设我有两个班级:

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

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

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

【问题讨论】:

  • 听起来您对策略模式很感兴趣。 en.wikipedia.org/wiki/Strategy_pattern 可能是工厂模式。你的解释很不清楚,解释一下你想达到什么情况,而不是试图解释一个半途而废的解决方案。
  • AOP 和 Java 代理之类的东西可能会让您这样做,但也许无需反射或这些(复杂的)概念就可以满足您的实际需求。您能否谈谈您希望满足的真实场景,也许我们可以帮助您确定一种直截了当的方法?
  • @MarkElliot 我更新了一个我想要完成的例子。现在我意识到了,我一直试图通过举一个复杂的例子来问一个简单的问题
  • 我认为通过反射来做你想做的事情是不可行的。改变方法体不是反射可以做的事情。它也不能调整呼叫站点地址。
  • 你能创建自己的类来作为原始 Minecraft 类的适配器吗?将您不想拦截的调用定向到实际类,并为要拦截的调用提供自己的方法。我不知道 MC 设置是如何工作的,所以我只是提供选项。

标签: java oop reflection runtime overriding


【解决方案1】:

正如其他人所说,我认为不可能按照您需要的方式解决您的问题,涉及 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 对象引用的实例,听起来是可行的。

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 可能会使用各种调试器功能,但它们涉及动态重新编译类或拦截调用并模拟对类的更改。
    猜你喜欢
    • 2022-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多