Op-code decoding in an emulator
I figured a much nicer way to deal with this.  Break it down into the repeatable elements, i.e. Addressing Mode and Operation then code them all separately and immutably, combining them in the right way to get all OpCodes.  Effectively doing functional programming in Java.

First: If you represent an operation, say ASL(arithmatic shift left) as an Enum (Operation), which has a parameter that is a lamba of a @FunctionalInterface (in this case called AddressedValueInstruction) and accepts two bytes and returns a result.
 (Note: RoxByte is just my custom implementation of a byte)

public enum Operation implements AddressedValueInstruction {
/** Shift all bits in byte left by one place, setting flags based on the result */
    ASL((a,r,m,v) -&gt; {
        final RoxByte newValue = a.asl(v);
        r.setFlagsBasedOn(newValue);
        return newValue;
    }),

    ...

    @Override
    public RoxByte perform(Mos6502Alu alu, Registers registers, Memory memory, RoxByte value) {
        return instruction.perform(alu, registers, memory, value);
    }


Second: If you represent the Addressing Mode as an Enum then -similar to Operation- have one of the Enum parameters as a @FunctionalInterface (called Addressable) lambda you can say MyEnum.address(AddressedValueInstruction) which will address a value, then sent it to the given operation then address the result.

public enum AddressingMode implements Addressable {
    /** Expects no argument, operation will be performed using the Accumulator Register*/
    ACCUMULATOR("Accumulator", 1, (r, m, a, i) -&gt; {
        final RoxByte value = r.getRegister(Registers.Register.ACCUMULATOR);
        r.setRegister(Registers.Register.ACCUMULATOR, i.perform(a, r, m, value));
    }),

    ...

    @Override
    public void address(Registers r, Memory m, Mos6502Alu alu, AddressedValueInstruction instruction) {
        address.address(r, m, alu, instruction);
    }


Third: This means you can create opcodes in the same way by creating combinations of addressing modes and operations with no specific code required, i.e. ASL_ACCUMULATOR(ACCUMULATOR, ASL) and it can be run with ASL_ACCUMULATOR.perform()

public enum OpCode implements Mos6502Instruction {
    ASL_A(0x0A, ACCUMULATOR, ASL),
    ...

    @Override
    public void perform(Mos6502Alu alu, Registers registers, Memory memory) {
        addressingMode.address(registers, memory, alu, operation::perform);
    }


This approach has the benefits of a class per opcode approach, without all the boiler plate.