我的业余时间正在研究一个小的Altair模拟器。
经过很长时间的C#工作后,我决定再次尝试使用Java,所以请原谅我的主要愚蠢行为。
本质上,我使用命令模式和org.reflections来简化机器指令的实现。这是我的反射代码。
instructions = new ArrayList<IInstruction>();
Reflections reflections = new Reflections("com.cjm.eaglestar.instructions");
Set<Class<? extends IInstruction>> subTypesOf = reflections.getSubTypesOf(IInstruction.class);
for(Class<? extends IInstruction> command : subTypesOf){
try {
instructions.add(command.getDeclaredConstructor().newInstance());
}
catch(Exception e){
System.out.println(e.toString());
}
}
这是我的说明界面:
package com.cjm.eaglestar.instructions;
public interface IInstruction{
byte OpCode = 0;
String Mnemonic = "XXX";
void Execute();
}
这是一个具体的实现:
package com.cjm.eaglestar.instructions;
import static com.cjm.eaglestar.Eaglestar.machineState;
public class LDAInstruction implements IInstruction {
public byte OpCode = Byte.valueOf("00111010",2);
public String Mnemonic = "LDA";
public void Execute(){
machineState.programCounter++;
byte lowByte = machineState.memory[machineState.programCounter & 0xFF];
machineState.programCounter++;
byte highByte = machineState.memory[machineState.programCounter & 0xFF];
machineState.registers.A = machineState.memory[(lowByte & 0xFF) + ((highByte & 0xFF) * 256)];
}
public LDAInstruction(){
}
}
这是奇怪的地方。我使用OpCode来遍历所有指令,并且当我将执行的OpCode与反映的指令进行比较时,即使我不是说super,该比较也使用超类的默认操作码(0)。
IntelliJ中的效果甚至更奇怪。将鼠标悬停在上面可以显示超级操作码,但是深入研究指令集可以看到正确的具体操作码。这是一些奇怪的照片。
任何帮助将不胜感激,因为我正疯狂地迷路,这阻碍了任何前进的进展。谢谢。
编辑:刚在我的屏幕快照中注意到它说操作码= 0(用户参数)。请忽略此。通常是58岁,我只是跳过了一步来获取屏幕截图。
最佳答案
接口没有字段,“字段” IInstructions.OpCode
是一个常量(隐式public static final
),而LDAInstruction.OpCode
是一个字段,与给定的代码无关。使用IInstrunctions instructions
时,表达式instructions.OpCode
引用常量(请注意,以这种方式引用静态成员被认为是一种不好的做法,大多数IDE和静态分析器都会对此发出警告)。
这也解释了IntelliJ显示的值:在循环中,代码引用了IInstructions.OpCode
,因此它显示了值0
,而在检查实际对象时,它知道它是LDAInstruction
的实例,其中包含一个值为opCode
的字段58
。
IntelliJ对代码的渲染也对此提供了提示:紫色斜体渲染是一个静态字段,而紫色“正常”(非斜体)渲染是一个实例字段。
您应该从接口中删除OpCode
,而是声明一个getter(byte getOpCode()
,然后子类需要实现该getter以返回该指令的正确代码。
我还建议您熟悉Java命名约定:一个常数全为大写(例如OP_CODE
),而字段以小写字母开头(例如opCode
),这些字段通常不是public
,并且在在这种情况下,它可能应该是一个最终字段,因此不能(偶然或有意)对其进行修改。
您还应该考虑将其实现为枚举,因为从技术上讲,您只需要一个指令的单个实例,例如
public enum Instruction {
LDA(Byte.valueOf("00111010",2)) {
@Override
public void execute(MachineState machineState) {
// your implementation.
}
},
// other instructions
;
private final byte opCode;
Instruction(byte opCode) {
this.opCode = opcode;
}
public abstract void execute(MachineState machineState);
public final byte getOpCode() {
return opCode;
}
}
在这种情况下,您可以使用enum
name()
方法,而不是定义单独的助记符字段+ getter。您可以使用枚举静态方法valueOf(String)
来按名称获取实例。但是,取决于执行实现的大小和指令的数量,这可能不是最佳解决方案。
另一种方法是使用抽象类代替接口(或除了接口之外),例如:
public abstract class AbstractInstruction {
private final byte opCode;
private final String mnemonic;
protected AbstractInstruction(byte opCode, String mnemonic) {
this.opCode = opCode;
this.mnemonic = mnemonic;
}
public abstract execute(MachineState machineState);
public final byte getOpCode() {
return opCode;
}
public final String getMnemonic() {
return mnemonic;
}
}