枚举更改与添加新的异常。需要重新编译?

时间:2016-07-01 16:47:10

标签: java exception enums

此问题基于Clean Code,章节“函数”“The Error.java依赖磁铁”

public enum Error {
      OK,
      INVALID,
      NO_SUCH
}

作者声称,在发生更改时,必须重新编译导入和使用此enum的所有类。这与添加新的Exception衍生物相反。在后一种情况下,据说不需要重新编译。

但是,如果您通过添加新的派生Exception来更改包,那么如果您使用包含派生异常的包,那么如果您的代码依赖于它将需要的包被重新编译?或者,如果代码实际上使用了一个新派生的Exceptions,那么代码是否只需要重新编译?

2 个答案:

答案 0 :(得分:3)

这一段还有一点点。我引用:

  像这样的类是依赖磁铁;许多其他类必须导入和使用它们。因此,当Error枚举更改时,所有其他类都需要重新编译和重新部署。

这一开始令人困惑,但事后才有意义。

enum保证在其中声明枚举常量;在枚举中添加另一个值需要另外编译才能获取该更改。对于使用枚举的所有类都是如此,即使他们没有使用该值。

另一方面,如果你声明了几个独立的Exception派生类,如果你的特定类不需要那个特定的Exception,它就不需要重新编译它利用它。

这与你的下一个问题有关:

  

... [我]知道,如果您通过添加新的派生Exception更改软件包,则使用包含派生例外的软件包,如果您的代码依赖于在包上它需要重新编译?

这与我在Java程序中看到的更常见的趋势(以及与J1中标识的代码气味冲突)有关:除非您需要包中的所有内容,否则不要导入整个包。 Your imports may suddenly use the same class name to mean completely different things.

我怀疑在包级别依赖方面存在任何实质性差异;我无法找到任何证据表明Java有条件地编译选择的类,但我也不相信它会重新编译你的课程只是为了包含你的例外情况。也没有使用。

答案 1 :(得分:1)

由于它使用方法签名等方式,Java在二进制兼容性方面非常聪明,因此在大多数情况下,易于维护兼容性。您可以添加子类并将它们传递给接受其超类的方法,即使这些方法没有使用子类编译,您可以向类添加方法并添加接口以供它们实现,以及二进制兼容性在大多数情况下仍然是相同的。这对于枚举来说并非如此。枚举通常使用.ordinal()方法(编译时)来执行某些任务(请参阅下面的更多字节码内容),这就是为什么您可能需要在类更改时重新编译。

Makoto的答案很好地涵盖了概念性内容,但直接原因与javac生成的字节码有关。

以下课程:

public class TestEnums {

    public static void main(String[] args) throws Throwable {
        Matter matter = Matter.SOLID;
        switch (matter) {
            case SOLID:
                System.out.println("a");
                break;
            case LIQUID:
                System.out.println("b");
                break;
            case GAS:
                System.out.println("c");
                break;
        }
    }

    private enum Matter {
        SOLID,
        LIQUID,
        GAS
    }

}

生成以下字节码(在main方法中):

 public static main([Ljava/lang/String;)V throws java/lang/Throwable 
   L0
    LINENUMBER 27 L0
    GETSTATIC TestEnums$Matter.SOLID : LTestEnums$Matter;
    ASTORE 1
   L1
    LINENUMBER 28 L1
    GETSTATIC TestEnums$1.$SwitchMap$TestEnums$Matter : [I
    ALOAD 1
    INVOKEVIRTUAL TestEnums$Matter.ordinal ()I
    IALOAD
    TABLESWITCH
      1: L2
      2: L3
      3: L4
      default: L5
   L2
    LINENUMBER 30 L2
   FRAME APPEND [TestEnums$Matter]
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "a"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L6
    LINENUMBER 31 L6
    GOTO L5
   L3
    LINENUMBER 33 L3
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "b"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L7
    LINENUMBER 34 L7
    GOTO L5
   L4
    LINENUMBER 36 L4
   FRAME SAME
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "c"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 39 L5
   FRAME SAME
    RETURN
   L8
    LOCALVARIABLE args [Ljava/lang/String; L0 L8 0
    LOCALVARIABLE matter LTestEnums$Matter; L1 L8 1
    MAXSTACK = 2
    MAXLOCALS = 2

越过字节码的详细程度,switch语句的主要逻辑以TableSwitch的形式出现:

INVOKEVIRTUAL TestEnums$Matter.ordinal ()I
IALOAD
TABLESWITCH
  1: L2
  2: L3
  3: L4
  default: L5

如果你看,你会看到交换机调用.ordinal()方法来确定要转到哪个值,所以如果你在枚举的开头插入一个值:

private enum Matter {
    PLASMA, //Lying elementary school science teachers don't tell you about this one
    SOLID,
    LIQUID,
    GAS
}

序数改变了。由于字节码在操作枚举时使用了序数方法,因此可能必须重新编译代码以保持与枚举的兼容性。