手拉手教你實現一門程式語言 Enkel, 系列 19

KevinOfNeu發表於2018-09-06

本文系 Creating JVM language 翻譯的第 19 篇。 原文中的程式碼和原文有不一致的地方均在新的程式碼倉庫中更正過,建議參考新的程式碼倉庫。

原始碼

Github

手拉手教你實現一門程式語言 Enkel, 系列 19

Java 中的物件比較

對於 Java 初學者來說,物件比較或許是最讓人頭疼的事情了。

我們來看如下示例:

Integer a = 15;
Integer b = 15;
boolean areEqual = a == b;
複製程式碼

這裡有個隱式的的型別裝箱,Integer.valueOf(15) 會返回快取的快取的 Integer 物件,因為引用一樣,所以 areEqual 是 true。

上面程式碼執行完後,Java 菜逼理所當然的想,我可以用 == 來比較物件。

nteger a = 155;
Integer b = 155;
boolean areEqual = a == b;
複製程式碼

areEqual 是 false, 這是因為 155 超過了快取的閾值。

Strings 也有陷阱。比如通過 new 建立的物件獲得一個新的引用,如果你通過雙引號字串給變數賦值,拿到的是一個快取物件。

問題在於,在超過 99% 的情境下,我們比較的是物件是否相等,而不是引用是否相等。我希望 == 意味著相等,而 <, >, <=, >= 呼叫 compareTo。

我們一起來實現這個功能吧。

條件表示式的位元組碼生成

在第十部分,條件表示式中,我們引入了比較原始型別的操作。這裡我們引入 compareTo,只需要在生成位元組碼的部分修改就可以了。

基本思路是,判斷值,如果是原始型別,呼叫 compareTo, 如果是引用,呼叫 equals。

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 類中宣告如下:
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 程式碼:

EqualitySyntax {

 start {
    var a = new java.lang.Integer(455)
    var b = new java.lang.Integer(455)
    print a == b
    print a > b
 }
}
複製程式碼

生成位元組碼反編譯成 Java 如下:

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 操作。

相關文章