پیادهسازی یا override کردن Equals به شیوهای صحیح
override کردن تابع equals در صورتی که درست انجام نشود میتواند به سرعت اتفاقات بدی ایجاد کند. در این مطلب یک مورد از اشکالاتی که میتواند رخ دهد را توضیح میدهیم.
بررسی کدهای قدیمی و مشکلات و کارایی آنها نشان میدهد که بسیاری از مشکلات کد سرنخی در override کردن ناصحیح توابع Object.equals(Object) دارد. هرچند مفهومی که پشت تابع equals است ساده به نظر میرسد، Josh Bloch در کتاب Effective Java اشاره کرده است که” override کردن equal ساده به نظر میرسد اما راههای بسیاری برای اشتباه کردن وجود دارد و پیامدهای آن میتواند وخیم باشد. راحتترین راه برای جلوگیری از این مشکلات override نکردن تابع equals است که در آن حالت هر نمونه تنها با خودش برابر خواهد بود”
اما بیایید به یکی از این “راههای بسیار” برای اشتباه کردن اشاره کنیم. fail شدن مقایسه مشخصات دقیق دو شیئ که برای تساوی مورد ارزیابی قرار میگیرند.
کد زیر برای کلاس MismatchedFieldAccessor است. تابع equals این کلاس مشکل دارد به این دلیل که صفت someString را به طور مستقیم با مقداری که از شئ دیگر getSomeString دریافت میکند مقایسه مینماید.
در بیشتر کلاسهای جاوا مقایسه یک فیلد از یک کلاس با تابع get درست کار میکند چرا که این تابع فیلد مربوطه را برمیگرداند. در این مثال این تابع چیزی بیشتر از یک فیلد ساده برمیگرداند که مقایسه فیلد را با تابع get ناسازگار میکند. (توجه کنید که ایده داشتن یک تابع get که چنین کارهایی را بکند توصیه نمیشود اما به عنوان یک مثال ساده آورده شده است)
package dustin.examples.brokenequals; import java.util.Objects; /** * Demonstrate problem with mismatched field/accessor in * overridden equals(Object) implementation. */ public final class MismatchedFieldAccessor { private final String someString; public MismatchedFieldAccessor(final String newString) { someString = newString; } public String getSomeString() { return someString != null ? someString : ""; } @Override public boolean equals(final Object other) { if (this == other) { return true; } if (other == null || getClass() != other.getClass()) { return false; } final MismatchedFieldAccessor that = (MismatchedFieldAccessor) other; return Objects.equals(this.someString, that.getSomeString()); } @Override public int hashCode() { return someString != null ? someString.hashCode() : 0; } }
کلاس بالا اگر با یک تست واحد مناسب تست شود fail خواهد شد. دو تست واحدی که در زیر آورده شده است، به مشکلات متد equals بالا اشاره میکند.
public void testEqualsOnConstructedWithNull() { final MismatchedFieldAccessor accessor = new MismatchedFieldAccessor(null); Assert.assertEquals(null, accessor.getSomeString()); } @Test public void testEqualsWithEqualsVerifier() { EqualsVerifier.forClass(MismatchedFieldAccessor.class).verify(); }
اولین تست با خطای زیر انجام میشود
java.lang.AssertionError: Expected :null Actual :
دومین تست از کتابخانه EqualsVerifier استفاده میکند تا مشکل پیادهسازی equals را روشن کند
java.lang.AssertionError: Reflexivity: object does not equal an identical copy of itself: dustin.examples.brokenequals.MismatchedFieldAccessor@0 If this is intentional, consider suppressing Warning.IDENTICAL_COPY For more information, go to: http://www.jqno.nl/equalsverifier/errormessages at nl.jqno.equalsverifier.EqualsVerifier.handleError(EqualsVerifier.java:381) at nl.jqno.equalsverifier.EqualsVerifier.verify(EqualsVerifier.java:367) at dustin.examples.brokenequals.MismatchedFieldAccessorTest.testEqualsWithEqualsVerifier(MismatchedFieldAccessorTest.java:36)
در این مطلب یکی از چندین راه خطا کردن در نوشتن متد equals آورده شده است. خوشبختانه حل این مشکل ساده است: همواره یک فیلد یکسان یا شئ خروجی یکسان از توابع را با هم مقایسه کنید. در مثال این مطلب مقایسه دو فیلد someString به طور مستقیم میتوانست نتیجه درستی در بر داشته باشد.
منبع: