本文系 Creating JVM language 翻译的第 19 篇。 原文中的代码和原文有不一致的地方均在新的代码仓库 中更正过,建议参考新的代码仓库。
源码 Github
Java 中的对象比较 对于 Java 初学者来说,对象比较或许是最让人头疼的事情了。
我们来看如下示例:
1 2 3 Integer a = 15; Integer b = 15; boolean areEqual = a == b;
这里有个隐式的的类型装箱,Integer.valueOf(15) 会返回缓存的缓存的 Integer 对象,因为引用一样,所以 areEqual 是 true。
上面代码执行完后,Java 菜逼理所当然的想,我可以用 == 来比较对象。
1 2 3 nteger a = 155; Integer b = 155; boolean areEqual = a == b;
areEqual 是 false, 这是因为 155 超过了缓存的阈值。
Strings 也有陷阱。比如通过 new 创建的对象获得一个新的引用,如果你通过双引号字符串给变量赋值,拿到的是一个缓存对象。
问题在于,在超过 99% 的情境下,我们比较的是对象是否相等,而不是引用是否相等。我希望 == 意味着相等,而 <, >, <=, >= 调用 compareTo。
我们一起来实现这个功能吧。
条件表达式的字节码生成 在第十部分,条件表达式中,我们引入了比较原始类型的操作。这里我们引入 compareTo,只需要在生成字节码的部分修改就可以了。
基本思路是,判断值,如果是原始类型,调用 compareTo, 如果是引用,调用 equals。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class ConditionalExpressionGenerator { //Constructor and fields public void generate(ConditionalExpression conditionalExpression) { Expression leftExpression = conditionalExpression.getLeftExpression(); Expression rightExpression = conditionalExpression.getRightExpression(); CompareSign compareSign = conditionalExpression.getCompareSign(); if (conditionalExpression.isPrimitiveComparison()) { generatePrimitivesComparison(leftExpression, rightExpression, compareSign); } else { generateObjectsComparison(leftExpression, rightExpression, compareSign); } Label endLabel = new Label(); Label trueLabel = new Label(); methodVisitor.visitJumpInsn(compareSign.getOpcode(), trueLabel); methodVisitor.visitInsn(Opcodes.ICONST_0); methodVisitor.visitJumpInsn(Opcodes.GOTO, endLabel); methodVisitor.visitLabel(trueLabel); methodVisitor.visitInsn(Opcodes.ICONST_1); methodVisitor.visitLabel(endLabel); } private void generateObjectsComparison(Expression leftExpression, Expression rightExpression, CompareSign compareSign) { Parameter parameter = new Parameter("o", new ClassType("java.lang.Object"), Optional.empty()); // #1 List<Parameter> parameters = Collections.singletonList(parameter); Argument argument = new Argument(rightExpression, Optional.empty()); List<Argument> arguments = Collections.singletonList(argument); switch (compareSign) { // #2 case EQUAL: case NOT_EQUAL: FunctionSignature equalsSignature = new FunctionSignature("equals", parameters, BultInType.BOOLEAN); // #3 FunctionCall equalsCall = new FunctionCall(equalsSignature, arguments, leftExpression); equalsCall.accept(expressionGenerator); // #4 methodVisitor.visitInsn(Opcodes.ICONST_1); methodVisitor.visitInsn(Opcodes.IXOR); // #5 break; case LESS: case GREATER: case LESS_OR_EQUAL: case GRATER_OR_EQAL: FunctionSignature compareToSignature = new FunctionSignature("compareTo", parameters, BultInType.INT); // #6 FunctionCall compareToCall = new FunctionCall(compareToSignature, arguments, leftExpression); compareToCall.accept(expressionGenerator); break; } } private void generatePrimitivesComparison(Expression leftExpression, Expression rightExpression, CompareSign compareSign) { leftExpression.accept(expressionGenerator); rightExpression.accept(expressionGenerator); methodVisitor.visitInsn(Opcodes.ISUB); } }
这里需要注意的是:
1 Equals 方法在 Object 类中声明如下:
1 2 3 public boolean equals(Object obj) { return (this == obj); }
因此参数需要是 java.lang.Object 类型,没有默认值(Optional.empty)
2 需要强制区分是否相等(== 或者 !=), 或者比较操作符(> < >= <=)。我们可以使用 compareTo 但是并不是所有的类都实现了 Comparable 接口。
3 如前面所述,equals 方法的入参是 java.lang.Object 并且返回值是布尔值。
4 为 equals 方法调用生成字节码。具体参考 CallExpressionGenerator 类。
5 如果对象相等,equals 返回 true ,否则返回 false, 原始类型的对比通过相减的方式,如果结果是 0 意味着相等。为了让对象可比较,我用了XOR(异或)逻辑指令,compareTo 方法和原始类型非常像。如果相同返回 0
6 创建 compareTo 的调用。他接受 java.lang.Object 类型参数,但是返回 int 类型的值。
示例 如下 Enkel 代码:1 2 3 4 5 6 7 8 9 EqualitySyntax { start { var a = new java.lang.Integer(455) var b = new java.lang.Integer(455) print a == b print a > b } }
生成字节码反编译成 Java 如下:
1 2 3 4 5 6 7 8 9 10 11 12 public class EqualitySyntax { public void start() { Integer var1 = new Integer(455); Integer var2 = new Integer(455); System.out.println(var1.equals(var2)); System.out.println(var1.compareTo(var2) > 0); } public static void main(String[] var0) { (new EqualitySyntax()).start(); } }
如你所见,== 被映射成 equals, > 被映射成 compareTo 操作。
全文完。