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

时间:2013-11-01 02:40:36

标签: java oop reflection runtime override

说我有两个班级:

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()而不是刚刚打印出的类来创建的类“汪”。

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

2 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分: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对象引用的实例,听起来就可以工作。

相关问题