2015-02-05 5 views
7

मैं ग्रोवी में एक प्रोजेक्ट विकसित करने की कोशिश कर रहा हूं और मुझे अपने कुछ परीक्षणों को एक अजीब तरीके से विफल करने में मिला है: मेरे पास दो ठोस उप-वर्गों के साथ एक इंटरफ़ेस Version extends Comparable<Version> है। दोनों ओवरराइड equals(Object) और compareTo(Version) - लेकिन, अगर मैं उस == का उपयोग कर विभिन्न ठोस प्रकार के होते हैं Version के दो उदाहरणों तुलना करने की कोशिश, समानता की जांच में विफल रहता है, भले ही स्पष्ट equals और compareTo जांचों में कामयाब।ग्रोवी में, '==' का व्यवहार तुलनात्मक रूप से विस्तारित इंटरफेस के लिए क्यों बदलता है?

अगर मैं Version की extends Comparable<Version> हिस्से को हटाने, मैं अपेक्षित व्यवहार मिलता है - ==equals होगा के रूप में एक ही परिणाम देता है।

जब तक वर्ग Comparable लागू करता है, जिसमें मामले compareTo प्रतिनिधियों यह मैं equals() को कहीं पढ़ा है कि ग्रूवी प्रतिनिधियों ==। हालांकि, मुझे ऐसे मामले मिल रहे हैं जहां दोनों Version के दो उदाहरण बराबर होने के लिए घोषित करते हैं और फिर भी == चेक विफल हो जाते हैं।

मैंने एक एसएससीसीई बनाया है जो इस व्यवहार को दर्शाता है here

// 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)}" 

क्या मैं वापस है हो रही है::

पूर्ण कोड भी नीचे दी गई है

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 

संपादित
मुझे लगता है कि मुझे समझ में क्यों यह अब होता है, करने के लिए धन्यवाद JIRA discussion कि पाउंडेक्स नीचे से जुड़ा हुआ है।

ग्रूवी के DefaultTypeTransformation class, जो समानता/तुलना चेकों को संभालने के लिए प्रयोग किया जाता है से, मुझे लगता है कि compareEqual विधि पहले कहा जाता है जब प्रपत्र x == y का एक बयान मूल्यांकन किया जा रहा है:

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(); 
} 

सूचना है कि अगर एलएचएस अभिव्यक्ति की, Comparable का एक उदाहरण है के रूप में यह उदाहरण मैं प्रदान में है, तुलना 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)); 
} 

नीचे ख के पास ओटोम, विधि में एक ब्लॉक है जो compareTo विधि, की तुलना को दर्शाता है, लेकिन केवल तभी कुछ शर्तों को संतुष्ट किया जाता है। उदाहरण में मैं प्रदान करता हूं, isAssignableFrom चेक सहित, इनमें से कोई भी स्थिति संतुष्ट नहीं है, क्योंकि उदाहरण के लिए मैं प्रदान करता हूं (और मेरी परियोजना में कोड जो मुझे समस्या दे रहा है) भाई बहन हैं, और इसलिए एक-दूसरे को असाइन नहीं किया जा सकता है।

मैं मैं समझता हूँ क्यों चेकों अब असफल रहे हैं लगता है, लेकिन मैं अभी भी निम्नलिखित बातों पर हैरान हूँ:

  1. मैं इस के आसपास कैसे मिलता है?
  2. इसके पीछे तर्क क्या है? क्या यह एक बग या एक डिजाइन सुविधा है? क्या कोई कारण है कि एक सामान्य सुपर क्लास के दो उप-वर्गों को एक दूसरे के साथ तुलनीय नहीं होना चाहिए?
+3

ऐसा लगता है कि आपके पास यह https://jira.codehaus.org/browse/GROOVY-3364 हो सकता है (मैंने स्थानीय रूप से 2.4.0 के साथ इसे आजमाया और आपके जैसा ही परिणाम देखा) – Poundex

+0

@Poundex लिंक के लिए धन्यवाद। मैंने देखा कि टिप्पणियों में से एक का उल्लेख है कि '<=>' और '==' [यहां] से जाएं (https://github.com/groovy/groovy-core/blob/master/src/main/org/codehaus/groovy/ रनटाइम/टाइपहेडलिंग/डिफॉल्ट टाइप टाइप ट्रांसफॉर्मेशन.जावा) - विशेष रुचि के 'तुलना करने के लिए तुलनात्मकता जांच' और 'तुलनात्मक' की तुलना करें। मुझे अभी भी यकीन नहीं है कि वास्तव में क्या हो रहा है, हालांकि। विस्तृत प्रतिक्रिया के लिए – Tagc

उत्तर

2

अगर मौजूदा है तो तुलनात्मक के लिए तुलनात्मक रूप से == के लिए उपयोग क्यों किया जाता है इसका जवाब। यह BigDecimal की वजह से है। यदि आप "1.0" और "1.00" से बाहर बिगडिसीमल बनाते हैं (स्ट्रिंग्स का उपयोग डबल्स नहीं करते हैं!) आपको दो बिगडिसीमल मिलते हैं जो बराबर के बराबर नहीं होते हैं, क्योंकि उनके पास समान पैमाने नहीं होता है। मूल्यवान वे बराबर हैं, यही कारण है कि तुलना करने के लिए उन्हें समान के रूप में देखेंगे।

तो निश्चित रूप से GROOVY-4046 भी है, जो एक ऐसा मामला दिखाता है जिसमें सीधे तुलना करने की तुलना में क्लासकास्ट अपवाद होता है। चूंकि यह अपवाद अप्रत्याशित है, इसलिए हमने असाइनमेंट के लिए चेक जोड़ने का निर्णय लिया है।

इसके आसपास पाने के लिए आप <=> का उपयोग कर सकते हैं, जिसे आप पहले ही पा सकते हैं। हां, वे अभी भी DefaultTypeTransformation से गुजरते हैं ताकि आप उदाहरण के लिए एक int और एक लंबी तुलना कर सकें। यदि आप इसे नहीं चाहते हैं, तो सीधे तुलना करने के लिए कॉल करने का तरीका है। अगर मैंने आपको गलत समझा है और आप वास्तव में बराबर होना चाहते हैं, तो आपको इसके बजाय पाठ्यक्रम के बराबर कॉल करना चाहिए।

+0

धन्यवाद। तुलना का उपयोग करना (स्पेसशिप ऑपरेटर के माध्यम से) काम करता है, लेकिन यह बहुत सुविधाजनक नहीं है: मुझे समानता के परीक्षण के लिए '(v1 <=> v2) == 0' जैसे कुछ करने की आवश्यकता होगी। मैं जो चाहता हूं वह समानता की जांच के लिए '==' का उपयोग करने में सक्षम होना है। 'v1.equals (v2)' काम करेगा, लेकिन यदि संभव हो तो मैं '==' का उपयोग करना पसंद करूंगा। यह शायद एक बेवकूफ सवाल है क्योंकि मैं अभी भी ग्रोवी के लिए अपेक्षाकृत नया हूं लेकिन ऐसा कोई तरीका नहीं है कि मैं इन विशेष वर्गों के लिए '==' ऑपरेटर को ओवरराइड कर सकूं ताकि उन्हें तत्काल 'बराबर' या 'तुलना करने के लिए' प्रतिनिधि बन सकें जैसा कि मैंने परिभाषित किया है उन्हें कक्षाओं में? – Tagc

+1

इस मामले में मुझे डर है कि एकमात्र तरीका एएसटी ट्रांसफॉर्म का उपयोग करना होगा और बाइनरीएक्सप्रेस को एक विधिCallExpression में बदलना होगा। यकीन नहीं है कि आप अब तक जाना चाहते हैं। लेकिन आपने मुझे कॉलिंग की तुलना में सोचने के लिए बनाया है, वास्तव में इसकी आवश्यकता है ... लेकिन फिर भी, यह भविष्य के ग्रोवी संस्करण – blackdrag

+0

के लिए होगा यदि यह एकमात्र तरीका है तो मैं अब के लिए 'बराबर' के साथ रहूंगा और अपना उत्तर स्वीकार कर लिया जाएगा। मुझे यकीन नहीं है कि 'तुलना करने के लिए' कॉल को समाप्त करना आवश्यक है या नहीं, लेकिन मैं कहूंगा कि मेरा मानना ​​है कि '==' चेक मेरे मामले में गुजर जाएगा यदि 'left.getClass()। IsAssignableFrom (दाएं। getClass()) 'कुछ सामान्य' तुलनात्मक 'विस्तारित इंटरफ़ेस या सुपरक्लास लागू करने से दो वर्गों को जांचने के लिए आराम से है। मुझे नहीं पता कि इससे अन्य समस्याएं पैदा होंगी, लेकिन मुझे लगता है कि 'कंक्रीटवर्सन ए' और 'कंक्रीटवर्सनबी' पारस्परिक रूप से तुलनीय होना चाहिए ('==' के साथ)। – Tagc

संबंधित मुद्दे

 संबंधित मुद्दे