2015-02-05 14 views
7

Tôi đang cố gắng phát triển một dự án trong Groovy và tôi đã tìm thấy một số thử nghiệm của mình không thành công theo cách kỳ lạ: Tôi có một giao diện Version extends Comparable<Version> với hai lớp con cụ thể. Cả hai ghi đè equals(Object)compareTo(Version) - tuy nhiên, nếu tôi cố so sánh hai trường hợp Version có các loại bê tông khác nhau bằng cách sử dụng ==, kiểm tra bình đẳng không thành công mặc dù thanh toán equalscompareTo vượt qua.Trong Groovy, tại sao hành vi của thay đổi '==' cho các giao diện mở rộng So sánh?

Nếu tôi xóa phần extends Comparable<Version> của Version, tôi nhận được hành vi mong đợi - == cho kết quả tương tự như equals.

Tôi đã đọc ở đâu đó rằng các đại biểu Groovy ==-equals() trừ lớp thực hiện Comparable, trong trường hợp này nó đại biểu compareTo. Tuy nhiên, tôi đang tìm trường hợp cả hai tuyên bố hai trường hợp của Version để được bình đẳng nhưng chưa kiểm tra ==.

Tôi đã tạo SSCCE thể hiện hành vi này here.

Mã đầy đủ cũng được cung cấp dưới đây:

// Interface extending Comparable 
interface Super extends Comparable<Super> { 
    int getValue() 
} 

class SubA implements Super { 
    int getValue() { 1 } 
    int compareTo(Super that) { this.value <=> that.value } 
    boolean equals(Object o) { 
     if (o == null) return false 
     if (!(o instanceof Super)) return false 
     this.value == o.value 
    } 
} 

class SubB implements Super { 
    int getValue() { 1 } 
    int compareTo(Super that) { this.value <=> that.value } 
    boolean equals(Object o) { 
     if (o == null) return false 
     if (!(o instanceof Super)) return false 
     this.value == o.value 
    } 
} 

// Interface not extending Comparable 
interface AnotherSuper { 
    int getValue() 
} 

class AnotherSubA implements AnotherSuper { 
    int getValue() { 1 } 
    boolean equals(Object o) { 
     if (o == null) return false 
     if (!(o instanceof AnotherSuper)) return false 
     this.value == o.value 
    } 
} 

class AnotherSubB implements AnotherSuper { 
    int getValue() { 1 } 
    boolean equals(Object o) { 
     if (o == null) return false 
     if (!(o instanceof AnotherSuper)) return false 
     this.value == o.value 
    } 
} 


// Check with comparable versions 
def a = new SubA() 
def b = new SubB() 

println "Comparable versions equality check: ${a == b}" 
println "Explicit comparable equals check: ${a.equals(b)}" 
println "Explicit comparable compareTo check: ${a.compareTo(b)}" 

// Check with non-comparable versions 
def anotherA = new AnotherSubA() 
def anotherB = new AnotherSubB() 

println "Non-comparable versions equality check: ${anotherA == anotherB}" 
println "Explicit non-comparable equals check: ${anotherA.equals(anotherB)}" 

gì tôi nhận được lại là:

Comparable versions equality check: false 
Explicit comparable equals check: true 
Explicit comparable compareTo check: 0 
Non-comparable versions equality check: true 
Explicit non-comparable equals check: true 

EDIT
Tôi nghĩ rằng tôi hiểu tại sao điều này xảy ra bây giờ, nhờ các JIRA discussion rằng Poundex liên kết với bên dưới.

Từ Groovy của DefaultTypeTransformation class, được sử dụng để xử lý kiểm tra bình đẳng/so sánh, tôi cho rằng phương pháp compareEqual là lần đầu tiên gọi khi một tuyên bố của mẫu x == y đang được đánh giá:

public static boolean compareEqual(Object left, Object right) { 
    if (left == right) return true; 
    if (left == null || right == null) return false; 
    if (left instanceof Comparable) { 
     return compareToWithEqualityCheck(left, right, true) == 0; 
    } 
    // handle arrays on both sides as special case for efficiency 
    Class leftClass = left.getClass(); 
    Class rightClass = right.getClass(); 
    if (leftClass.isArray() && rightClass.isArray()) { 
     return compareArrayEqual(left, right); 
    } 
    if (leftClass.isArray() && leftClass.getComponentType().isPrimitive()) { 
     left = primitiveArrayToList(left); 
    } 
    if (rightClass.isArray() && rightClass.getComponentType().isPrimitive()) { 
     right = primitiveArrayToList(right); 
    } 
    if (left instanceof Object[] && right instanceof List) { 
     return DefaultGroovyMethods.equals((Object[]) left, (List) right); 
    } 
    if (left instanceof List && right instanceof Object[]) { 
     return DefaultGroovyMethods.equals((List) left, (Object[]) right); 
    } 
    if (left instanceof List && right instanceof List) { 
     return DefaultGroovyMethods.equals((List) left, (List) right); 
    } 
    if (left instanceof Map.Entry && right instanceof Map.Entry) { 
     Object k1 = ((Map.Entry)left).getKey(); 
     Object k2 = ((Map.Entry)right).getKey(); 
     if (k1 == k2 || (k1 != null && k1.equals(k2))) { 
      Object v1 = ((Map.Entry)left).getValue(); 
      Object v2 = ((Map.Entry)right).getValue(); 
      if (v1 == v2 || (v1 != null && DefaultTypeTransformation.compareEqual(v1, v2))) 
       return true; 
     } 
     return false; 
    } 
    return ((Boolean) InvokerHelper.invokeMethod(left, "equals", right)).booleanValue(); 
} 

Chú ý rằng nếu các LHS của biểu thức là một thể hiện của Comparable, vì nó là trong ví dụ tôi cung cấp, so sánh được giao cho compareToWithEqualityCheck:

private static int compareToWithEqualityCheck(Object left, Object right, boolean equalityCheckOnly) { 
    if (left == right) { 
     return 0; 
    } 
    if (left == null) { 
     return -1; 
    } 
    else if (right == null) { 
     return 1; 
    } 
    if (left instanceof Comparable) { 
     if (left instanceof Number) { 
      if (right instanceof Character || right instanceof Number) { 
       return DefaultGroovyMethods.compareTo((Number) left, castToNumber(right)); 
      } 
      if (isValidCharacterString(right)) { 
       return DefaultGroovyMethods.compareTo((Number) left, ShortTypeHandling.castToChar(right)); 
      } 
     } 
     else if (left instanceof Character) { 
      if (isValidCharacterString(right)) { 
       return DefaultGroovyMethods.compareTo((Character)left, ShortTypeHandling.castToChar(right)); 
      } 
      if (right instanceof Number) { 
       return DefaultGroovyMethods.compareTo((Character)left,(Number)right); 
      } 
     } 
     else if (right instanceof Number) { 
      if (isValidCharacterString(left)) { 
       return DefaultGroovyMethods.compareTo(ShortTypeHandling.castToChar(left),(Number) right); 
      } 
     } 
     else if (left instanceof String && right instanceof Character) { 
      return ((String) left).compareTo(right.toString()); 
     } 
     else if (left instanceof String && right instanceof GString) { 
      return ((String) left).compareTo(right.toString()); 
     } 
     if (!equalityCheckOnly || left.getClass().isAssignableFrom(right.getClass()) 
       || (right.getClass() != Object.class && right.getClass().isAssignableFrom(left.getClass())) //GROOVY-4046 
       || (left instanceof GString && right instanceof String)) { 
      Comparable comparable = (Comparable) left; 
      return comparable.compareTo(right); 
     } 
    } 

    if (equalityCheckOnly) { 
     return -1; // anything other than 0 
    } 
    throw new GroovyRuntimeException(
      MessageFormat.format("Cannot compare {0} with value ''{1}'' and {2} with value ''{3}''", 
        left.getClass().getName(), 
        left, 
        right.getClass().getName(), 
        right)); 
} 

xuống gần b ottom, phương pháp có một khối đại diện cho so sánh với phương pháp compareTo, nhưng chỉ khi một số điều kiện nhất định được đáp ứng. Trong ví dụ tôi cung cấp, không có điều kiện nào trong số này thỏa mãn, bao gồm kiểm tra isAssignableFrom, vì các lớp mẫu tôi cung cấp (và mã trong dự án của tôi mang lại cho tôi vấn đề) là anh chị em và do đó không thể gán cho nhau.

Tôi cho rằng tôi hiểu tại sao kiểm tra được không bây giờ, nhưng tôi vẫn còn bối rối hơn những điều sau đây:

  1. Làm thế nào để làm được việc này?
  2. Lý do đằng sau điều này là gì? Đây có phải là lỗi hoặc tính năng thiết kế không? Có lý do nào khiến hai lớp con của một lớp siêu phổ biến không nên so sánh với nhau?
+3

Có vẻ như bạn có thể có https://jira.codehaus.org/browse/GROOVY-3364 này (tôi đã thử địa phương với phiên bản 2.4.0 và thấy kết quả tương tự như bạn) – Poundex

+0

@Poundex Cảm ơn bạn đã liên kết. Tôi đã thấy một trong các nhận xét đề cập rằng '<=>' và '==' đi qua [ở đây] (https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/ thời gian chạy/typehandling/DefaultTypeTransformation.java) - của sở thích cụ thể là 'compareToWithEqualityCheck' và' compareEqual'. Tôi vẫn không chắc chắn chính xác những gì đang xảy ra, mặc dù. – Tagc

Trả lời

2

Câu trả lời cho lý do tại sao Comparable được sử dụng cho == nếu có sẵn dễ dàng. Đó là vì BigDecimal. Nếu bạn thực hiện một BigDecimal trong số "1.0" và "1.00" (sử dụng Strings không tăng gấp đôi!), Bạn nhận được hai BigDecimal không bằng nhau theo bằng, bởi vì họ không có quy mô tương tự. Mặc dù chúng có giá trị như nhau, đó là lý do tại sao compareTo sẽ xem chúng như nhau.

Sau đó, tất nhiên cũng có GROOVY-4046, hiển thị trường hợp trong đó chỉ gọi trực tiếp compareTo sẽ dẫn đến một ClassCastException. Vì ngoại lệ này là bất ngờ ở đây, chúng tôi quyết định thêm một kiểm tra cho khả năng chuyển nhượng.

Để giải quyết vấn đề này, bạn có thể sử dụng <=> thay vào đó bạn đã tìm thấy. Có, họ vẫn xem qua số DefaultTypeTransformation để bạn có thể so sánh ví dụ: int và dài. Nếu bạn không muốn điều đó, thì gọi compareTo trực tiếp là con đường để đi. Nếu tôi hiểu lầm bạn và bạn muốn thực sự có bằng, tốt, sau đó bạn nên gọi bằng tất nhiên thay vào đó.

+0

Cảm ơn bạn đã phản hồi chi tiết. Sử dụng compareTo (thông qua toán tử tàu vũ trụ) không hoạt động, nhưng không thuận tiện: tôi cần thực hiện một số thứ như '(v1 <=> v2) == 0' để kiểm tra tính bình đẳng. Điều tôi muốn là có thể sử dụng '==' để kiểm tra sự bình đẳng. 'v1.equals (v2)' sẽ hoạt động, nhưng tôi muốn sử dụng '==' nếu có thể. Đây có lẽ là một câu hỏi ngớ ngẩn vì tôi vẫn còn tương đối mới đối với Groovy nhưng không có cách nào tôi có thể ghi đè toán tử '==' cho các lớp cụ thể này để làm cho chúng ngay lập tức ủy nhiệm thành 'equals' hoặc' compareTo' như tôi định nghĩa họ trong lớp học? – Tagc

+1

Trong trường hợp này, tôi sợ cách duy nhất là sử dụng biến đổi AST và thay đổi BinaryExpression thành MethodCallExpression. Không chắc bạn muốn đi xa đến thế. Nhưng bạn đã làm cho tôi nghĩ nếu gọi compareTo thực sự là cần thiết ... nhưng ngay cả sau đó, điều này sẽ là cho một phiên bản Groovy tương lai – blackdrag

+0

Nếu đó là cách duy nhất thì tôi sẽ gắn bó với 'bằng 'bây giờ và đánh dấu câu trả lời của bạn là được chấp nhận. Tôi không chắc liệu việc loại bỏ lệnh gọi 'compareTo' là cần thiết hay không, nhưng tôi sẽ nói rằng tôi tin rằng các kiểm tra '==' sẽ vượt qua trong trường hợp của tôi nếu yêu cầu' left.getClass(). IsAssignableFrom (phải. getClass()) 'là thư giãn để chỉ kiểm tra hai lớp xuất phát từ một số giao diện mở rộng' Comparable' phổ biến hoặc thực hiện superclass. Tôi không biết nếu điều đó sẽ tạo ra các vấn đề khác, nhưng tôi cảm thấy 'ConcreteVersionA' và' ConcreteVersionB' nên được so sánh lẫn nhau (với '=='). – Tagc

Các vấn đề liên quan