ورود

View Full Version : سوال در مورد cast کردن یک آبجکت



Sina.iRoid
چهارشنبه 10 تیر 1394, 10:38 صبح
سلام
دوستان من می خوام متد equals و خودم پیاده سازی کنم. ابتدا یک کلاسی تعریف کردم که کسر اعداد و حساب می کنه. مثلا جمع کسر 2/3 و کسر 2/5 و حساب می کنه. حالا در این کلاس من این دو کسر و با هم مقایسه کنم که آیا با هم برابر هستند یا نه!؟ ابتدا به کد زیر نگاه کنید:

کد کلاس RationalNumber:

public class RationalNumber {

private int top;
private int bottom;

// constructor
public RationalNumber(int top, int bottom) {
this.top = top;
this.bottom = bottom;
}

// add
public void add(RationalNumber k) {
top = (top * k.bottom) + (k.top * bottom);
bottom = bottom * k.bottom;
}

// subtract
public void subtract(RationalNumber k) {
top = (top * k.bottom) - (k.top * bottom);
bottom = bottom * k.bottom;
}

// equals
public boolean equals(Object obj) {
if (obj instanceof RationalNumber) {
RationalNumber k = (RationalNumber) obj;
if (((k.top * bottom) - (k.bottom * top) == 0)) {
return true;
} else {
return false;
}
} else {
return false;
}
}

@Override
public String toString() {
return top + "/" + bottom;
}
}


و این هم کد کلاس اصلی:


public class MainClass {


public static void main(String[] args) {

RationalNumber num1 = new RationalNumber(3, 2);
RationalNumber num2 = new RationalNumber(3, 2);

if (num1.equals(num2)) {
System.out.println("True");
}else {
System.out.println("False");
}

}


}


نکته ای که من در مورد مقایسه آبجکت ها می دونم اینه که باید اون دو آبجکت از جنس هم باشند؟ درسته؟ ما در خط 28 اومدیم گفتیم که آیا obj از جنس کلاس RationalNumber هست یا نه!؟ خب مسلما هست. چون obj از کلاس Object ساخته شده و تمام کلاس های جاوا از کلاس Object ارث بری می کنند. سوال اینجاست که چرا اومدیم این مقایسه رو انجام دادیم و در خط بعد اومدیم obj و cast کردیم به کلاس RationalNumber؟
برنامه درسته و اجرا میشه و فقط همین قسمت و متوجه نشدم؟
ممنون میشم اگر راهنماییم کنید :)

azimi.moja
پنج شنبه 11 تیر 1394, 00:35 صبح
سلام. چون داره متد equals از رابط Object پیاده سازی میشه. حالا اون رابط مگه میدونه کلاس شما چیه؟ پس Object تحویل میده و شما cast میکنی که بفهمی این شی اصلا اونی که می خوای هست؟ اگر نیست که دیگه مفهومی نداره عمل equals. بعد اگر نمی خوای بعدا ارث ببری حتما final تعریف کن که نشه ازش ارث برد وگرنه در equals با فرزندانش دچار مشکل میشی.

-سیّد-
پنج شنبه 11 تیر 1394, 06:56 صبح
سلام
چند تا نکته بگم:

اول: همونطور که دوستمون گفتن، شما تابع equals رو از کلاس Object به ارث می‌بری و داری رونویسی‌اش می‌کنی. در نتیجه با توجه به این که کلاس Object یه کلاس generic نیست، مجبوره توی تابع equals اش یه Object ورودی بگیره. پس شما هم مجبوری اینجا یه Object ورودی بگیری.

دوم:


نکته ای که من در مورد مقایسه آبجکت ها می دونم اینه که باید اون دو آبجکت از جنس هم باشند؟ درسته؟

نه درست نیست! :)
هیچ کس ما رو مجبور نکرده که حتماً موقع مقایسه‌ی دو تا شیء، باید اون دو تا از یک نوع باشن تا equals شون مقدار true برگردونه (هر چند در اکثر قریب به اتفاق مواقع اینطور هست، ولی همیشه نه). توضیحات بیشتر رو اینجا نوشتم:
http://blog.yooz.ir/?q=node/21

البته همونطور که توی اون پست هم نوشتم، توی توضیحات equals اومده که «بهتره» پیاده‌سازی شما از equals متقارن باشه، بنابراین اگه می‌خواین این توصیه رو گوش بدین باید مواظب باشین!

سوم:


ما در خط 28 اومدیم گفتیم که آیا obj از جنس کلاس RationalNumber هست یا نه!؟ خب مسلما هست.

نه مسلماً نیست! شما نمی‌تونید مطمئن باشید که مسلماً ورودیتون از جنس RationalNumber هست. مثلاً:

RationalNumber n = new RationalNumber(1, 5);
n.equals("Hello!");

اینجا من تابع equals رو با ورودی String فراخوانی کردم. پس اون چکی که شما کردید اینجا داره کار می‌کنه و اگه برش دارید، توی این مثال من توی تابع equals موقع cast کردن، به ClassCastException برخورد خواهید کرد.

چهارم:

بعد اگر نمی خوای بعدا ارث ببری حتما final تعریف کن که نشه ازش ارث برد وگرنه در equals با فرزندانش دچار مشکل میشی.
اینقدر که شما قاطعیت توی این جمله به کار بردید درست نیست! حتماً توی equals با فرزندانش دچار مشکل نمی‌شیم. بله باید موقع ارث‌بری از این کلاس حواس آدم به تابع equals باشه و اگه لازمه توی بچه‌هاش هم رونویسی‌اش کنه. ولی به طور کلی مشکلی ایجاد نمی‌شه. مثالش توی خود جاوا هست: کلاس AbstractList تابع equals رو رونویسی کرده، و دو تا فرزند معروفش هم ArrayList و LinkedList هستن که این تابع رو رونویسی هم نکردن و مشکلی هم ایجاد نمی‌شه.
مشکل وقتی می‌تونه ایجاد بشه که کلاس فرزند یه سری فیلد جدید اضافه کنه که توی equals تأثیر داشته باشن. در نتیجه یه پدر خودش رو ممکنه با یه فرزند برابر بدونه، ولی فرزنده خودش رو با پدرش برابر ندونه. حالا این ممکنه مشکل ایجاد بکنه یا نکنه. بستگی به تعریف شما و کاربردتون داره. می‌شه مثلاً توی تابع equals کلاس فرزند بررسی کرد که اگه ورودی از نوع فرزند نیست، ولی از نوع پدر هست، فقط super.equals رو فراخوانی کنیم. بازم می‌گم بستگی به تعریف شما داره.

در نهایت یه نکته‌ی بی‌ربط بگم: توی نوشتن تابع equals خوبه که چند تا چیز رو رعایت کنید:
اول اول با == چک کنید شیء ورودی عین شیء خودتون هست یا نه.
بعدش چک کنید اگه هم‌نوع با شما نیست همونجا return کنید و این همه if و else تو در تو نزنید که آدم چشمش (به همراه مغزش!) موقع خوندن کد از حدقه در بیاد!!!
و این که خوبه که یه سری چک رو یکی یکی انجام بدید و هر کدوم که اوکی نبود همونجا return false کنید، و در انتها که همه‌ی چک‌ها پاس شدن، return true کنید.
مثلاً اینطوری:

public boolean equals(Object obj) {
if (obj == this)
return true;

if (!(obj instanceof RationalNumber))
return false;

RationalNumber k = (RationalNumber) obj;
if(((k.top * bottom) - (k.bottom * top) != 0)
return false;
return true;
}

parsadev
پنج شنبه 11 تیر 1394, 08:05 صبح
سلام دوستان
من دنبال برنامه نویس جاوای کاربلد تو قم هستم. فکر میکنید پیدا میشه؟
ببخشید اگر بی ارتباط با موضوع بحث بود اما نمیدونم کجا باید بگردم :خجالت:

azimi.moja
پنج شنبه 11 تیر 1394, 12:01 عصر
دوست عزیز نگفتم حتما خطا میده گفتم دچار مشکل میشی. دلایل زیادیم برای حرفم دارم. چون هیچوقت نمیشه قوانین سه گاه و قانون لیسکوف رو باهم رعایت کرد و Inheritance هم داشت مگر از ارث بری استفاده نکنیم و از معرفی والد در شی فرزند استفاده کنیم. خواستید توضیح بیشتر می دم.

-سیّد-
پنج شنبه 11 تیر 1394, 12:56 عصر
دوست عزیز نگفتم حتما خطا میده گفتم دچار مشکل میشی. دلایل زیادیم برای حرفم دارم. چون هیچوقت نمیشه قوانین سه گاه و قانون لیسکوف رو باهم رعایت کرد و Inheritance هم داشت مگر از ارث بری استفاده نکنیم و از معرفی والد در شی فرزند استفاده کنیم. خواستید توضیح بیشتر می دم.
ممنون می‌شم توضیح بیشتر بدید. اگه مثال هم بزنید ممنون می‌شم.
و در ضمن اون مثالی که از جاوا زدم به نظر شما مشکلی داره؟ AbstractList و فرزندانش ArrayList و LinkedList.

Sina.iRoid
پنج شنبه 11 تیر 1394, 22:23 عصر
سلام
ابتدا سپاس از دوستان برای راهنمایی.

چند تا سوال.


نه مسلماً نیست! شما نمی‌تونید مطمئن باشید که مسلماً ورودیتون از جنس RationalNumber هست.

سوال من این هست که مگر تمام کلاس ها به طور پیش فرض از کلاس Object ارث بری نمی کنن؟ خب اگر این باشه RationalNumber صد در صد از جنس آبجکته. چون خودش داره از آبجکت ارث بری می کنه. (در مورد مثالتون درست متوجه نشدم. اجازه بدین به طوره عملی انجام بدم).



اول با == چک کنید شیء ورودی عین شیء خودتون هست یا نه.
در مورد این قسمت صحبتتون کمی شک دارم. از اونجایی که من اطلاع دارم، با ایجاد هر آبجکت، اون آبجکت قسمتی از حافظه رو به خودش اختصاص میده. حتی اگر از رویه یک کلاس دو تا آبجکت تعریف کنیم. بنابراین برای مقایسه آبجکت ها هرچند که برابر هم باشند، باز هم جاوا مقدار false و بر می گردونه. چون از لحاظ حافظه برابر نیستند. در کل متد equals هم برای همین نوشته شده. نمی دونم شایدم من اشتباه می کنم.

ahmad.mo74
پنج شنبه 11 تیر 1394, 23:42 عصر
سلام.

اینکه پارامتر ورودی متد equals از جنس Object هست میتونه دلایل مختلفی داشته باشه.

اول اینکه محدودیتی وجود نداشته باشه و هر 2 تا آبجکتی رو بشه با هم مقایسه کرد.
دوم اینکه، Generics تو ورژن 5 جاوا معرفی شد و قبل از اون نمیشد همچین کاری کرد :


public class Object<T> {


@Override
public boolean equals(T t) {
return ...;
}

}


و باید ورودی متد equals از جنس Object می بود.

برای توضیح نحوه نوشتن equals یه مثال میزنم :


public class Entry<K, V> {


private final K key;
private final V value;


public Entry(K key, V value) {
this.key = key;
this.value = value;
}


public K getKey() {
return key;
}


public V getValue() {
return value;
}


@Override
public boolean equals(Object obj) {
return obj == this
|| obj != null
&& obj instanceof Entry
&& Objects.equals(key, ((Entry) obj).key);
}


}


obj == this یعنی اینکه آدرس حافظه دو تا آبجکت رو با هم مقایسه کنه و اگر دقیقا همون آبجکت بود طبیعتا با هم برابر هستن.

مثلا یکی بیاد همچین کاری بکنه :


entry.equals(entry);


obj instanceof Entry هم برای اینه که مطمئن بشیم obj از جنس Entry هست تا موقع cast کردن توی خط بعدی اکسپشن رخ نده.

دیگه بقیش دلخواهه، مثلا میتونستیم اینطوری بنویسیمش :


@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !(obj instanceof Entry)) {
return false;
}
Entry other = (Entry) obj;
return Objects.equals(key, other.key) && Objects.equals(value, other.value);
}


اینم اتفاقیه که تو Objects.equals میفته :


public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}

-سیّد-
پنج شنبه 11 تیر 1394, 23:55 عصر
سوال من این هست که مگر تمام کلاس ها به طور پیش فرض از کلاس Object ارث بری نمی کنن؟ خب اگر این باشه RationalNumber صد در صد از جنس آبجکته. چون خودش داره از آبجکت ارث بری می کنه. (در مورد مثالتون درست متوجه نشدم. اجازه بدین به طوره عملی انجام بدم).

بله تمام کلاس‌ها فرزند کلاس Object هستند. یعنی برای هر شی x می‌شه گفت x instanceof Object برابر با true خواهد بود.
در نتیجه یک شیء از نوع RationalNumber هم اگه ساخته بشه، از جنس Object خواهد بود، در ضمن از جنس RationalNumber هم خواهد بود.
حالا نوع ورودی تابع equals چیه؟ Object هست. شما می‌تونید مطمئن باشید که هر شیء که به عنوان ورودی به تابع equals داده می‌شه حتماً از نوع RationalNumber خواهد بود؟ نخیر. توی مثالم هم من یه شیء از نوع مثلاً String به تابع equals دادم. البته می‌تونید مطمئن باشید که هر شیء ورودی تابع equals از جنس Object خواهد بود.

باز هم اگه ابهامی هست بفرمایید که سعی کنیم برطرفش کنیم.



در مورد این قسمت صحبتتون کمی شک دارم. از اونجایی که من اطلاع دارم، با ایجاد هر آبجکت، اون آبجکت قسمتی از حافظه رو به خودش اختصاص میده. حتی اگر از رویه یک کلاس دو تا آبجکت تعریف کنیم. بنابراین برای مقایسه آبجکت ها هرچند که برابر هم باشند، باز هم جاوا مقدار false و بر می گردونه. چون از لحاظ حافظه برابر نیستند. در کل متد equals هم برای همین نوشته شده. نمی دونم شایدم من اشتباه می کنم.
شما هر بار که یه شیء رو new می‌کنید، یه بخش جدید از حافظه به اون اختصاص داده می‌شه. در ضمن اشاره‌گر به اون بخش حافظه هم داخل متغیر مربوطه قرار داده می‌شه. یعنی:

MyObject x = new MyObject(10);

اینجا ۲ تا دستور داره اجرا می‌شه:
اول توسط دستور new MyObject یه بخش از حافظه برای نگهداری یه شیء از نوع MyObject تخصیص داده می‌شه که به اندازه‌ی یه شیء از این کلاس هست (مثلاً اگه توی این کلاس فقط یه int باشه، ۴ بایت به ازای اون int در نظر گرفته می‌شه، یه مقدار هم سربار هر شیء هست، مثلاً در مجموع می‌شه ۲۰ بایت (دقیقش بستگی به JVM شما داره)).
بعد توسط دستور مقداردهی متغیر x، یه فضای حافظه به اندازه‌ی یه اشاره‌گر (۴ بایت یا ۸ بایت، بسته به این که توی JVM شما اشاره‌گرها ۳۲ بیتی باشن یا ۶۴ بیتی) تخصیص داده می‌شه، توش آدرس حافظه‌ای که بالاتر به شیء MyObject اختصاص داده شده بود نوشته می‌شه، و آدرس خودش توی متغیر x قرار داده می‌شه.

حالا اگه بگید:

MyObject y = x;

یه حافظه‌ی دیگه برای y تخصیص داده می‌شه (۴ یا ۸ بایت) و توش همون آدرسی نوشته می‌شه که برای شیء مورد نظر تخصیص داده شده بود (یعنی توی حافظه‌های x و y که ۲ آدرس مختلف حافظه هستن، یک مقدار یکسان نوشته شده).
بنابراین در این حالت x == y هست. حالا شما اگه بگید:

System.out.println(x.equals(y));

در این حالت اگه اون چک == رو توی equals تون نوشته باشید، همونجا عمل می‌کنه و true برمی‌گردونه. اگه ننوشته باشید، می‌ره جلوتر و کل equals رو یه دور می‌ره (یعنی تک تک فیلدها رو بررسی می‌کنه که قطعاً با هم یکسان هستن، چون یه شیء هستن) و در انتها true برمی‌گردونه. یعنی همونطور که گفتم، اون چک == تنها تأثیری که داره اینه که در برخی موارد سریعتر true رو برمی‌گردونه و تأثیری روی صحت عملکرد کلاس شما نداره.

-سیّد-
جمعه 12 تیر 1394, 00:08 صبح
دوم اینکه، Generics تو ورژن 5 جاوا معرفی شد و قبل از اون نمیشد همچین کاری کرد :
...

جمله‌تون رو تکمیل می‌کنم:
و البته بعد از نسخه‌ی ۵ هم که این امکان وجود داشت، اگه Object رو به این صورت که گفتید تعریف می‌کردن، باعث می‌شد که مجبور باشید هر کلاسی تعریف می‌کنید به صورت generic تعریف کنید:

public class MyObject extends Object<MyObject> {
...
}

یعنی اشک برنامه‌نویسا در میومد!!!

البته می‌شد این یه مورد رو به صورت خاص توی کامپایلر بذارن که هر کلاسی به صورت پیش‌فرض از نوع generic خودش تعریف بشه. ولی توی زبان‌های برنامه‌نویسی یه قانونی داریم که می‌گه سعی کنید تعداد استثنائات زبان رو به حداقل برسونید، و این کار یه استثنا به زبان جاوا اضافه می‌کنه، که اگه دقت کنید می‌بینید که زبان جاوا زبان به شدت strict ای هست، در مقایسه با یه زبانی مثل PHP که کلاً بی در و پیکر هست و سرتاپا استثنا هست!!! (و یا مثلاً جاوا اسکریپت)

پ.ن. لطفاً طرفداران PHP نیان اینجا رو به توپ ببندن! من خودم سالهاست کد PHP می‌زنم و بسیار به این زبان علاقه دارم. بحث سر مقایسه‌ی بدون تعصب زبان‌ها با هم هست. در عوض، این strictness ای که جاوا داره، به مراتب کد زدن توش رو کندتر از PHP می‌کنه. یعنی کد ساده‌ای که توی PHP می‌شه با ۲ خط در عرض ۱۰ ثانیه نوشت و اجرا کرد، توی جاوا مجبورید ۱۰ خط بنویسید که برای نوشتن و اجراش حداقل ۲-۳ دقیقه از شما وقت می‌گیره! بگذریم!