JavaRush /وبلاگ جاوا /Random-FA /جاوا هسته. سوالات مصاحبه قسمت دوم
Andrey
مرحله

جاوا هسته. سوالات مصاحبه قسمت دوم

در گروه منتشر شد
برای کسانی که کلمه Java Core را برای اولین بار می شنوند، اینها پایه های اساسی زبان هستند. با این دانش می توانید با خیال راحت به دوره کارآموزی/کارآموزی بروید.
جاوا هسته.  سوالات برای مصاحبه، قسمت 2 - 1
این سوالات به شما کمک می کند قبل از مصاحبه دانش خود را تازه کنید یا چیز جدیدی برای خود بیاموزید. برای کسب مهارت های عملی، در JavaRush مطالعه کنید . مقاله اصلی پیوند به قسمت های دیگر: Java Core. سوالات مصاحبه قسمت 1 Java Core. سوالات مصاحبه قسمت 3

چرا باید از متد ()finalize اجتناب شود؟

همه ما این جمله را می دانیم که یک متد finalize()قبل از آزاد کردن حافظه اشغال شده توسط یک شی، توسط جمع کننده زباله فراخوانی می شود. در اینجا یک نمونه برنامه وجود دارد که ثابت می کند فراخوانی متد finalize()تضمینی نیست:
public class TryCatchFinallyTest implements Runnable {

	private void testMethod() throws InterruptedException
	{
		try
		{
			System.out.println("In try block");
			throw new NullPointerException();
		}
		catch(NullPointerException npe)
		{
			System.out.println("In catch block");
		}
		finally
		{
			System.out.println("In finally block");
		}
	}

	@Override
	protected void finalize() throws Throwable {
		System.out.println("In finalize block");
		super.finalize();
	}

	@Override
	public void run() {
		try {
			testMethod();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
public class TestMain
{
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
	for(int i=1;i< =3;i++)
	{
		new Thread(new TryCatchFinallyTest()).start();
	}
	}
}
خروجی: در بلوک try در بلوک catch در بلوک نهایی در بلوک تلاش در بلوک catch در بلوک نهایی در بلوک تلاش در بلوک catch در بلوک نهایی در کمال تعجب، این روش finalizeبرای هیچ رشته ای اجرا نشد. این حرف من را ثابت می کند. من فکر می کنم دلیلش این است که نهایی کننده ها توسط یک نخ زباله جمع کننده جداگانه اجرا می شوند. اگر ماشین مجازی جاوا خیلی زود پایان یابد، جمع‌آورنده زباله زمان کافی برای ایجاد و اجرای نهایی‌کننده‌ها را ندارد. دلایل دیگر برای عدم استفاده از روش finalize()ممکن است عبارتند از:
  1. این روش finalize()با زنجیره ها مانند سازنده کار نمی کند. این بدان معناست که وقتی سازنده کلاس را فراخوانی می کنید، سازنده های سوپرکلاس بدون قید و شرط فراخوانی می شوند. اما در مورد روش finalize()، این به دنبال نخواهد بود. متد superclass finalize()باید به صراحت فراخوانی شود.
  2. هر استثنایی که توسط روش پرتاب شود finalizeتوسط نخ جمع کننده زباله نادیده گرفته می شود و بیشتر منتشر نمی شود، به این معنی که رویداد در گزارش های شما ثبت نمی شود. این خیلی بد است، اینطور نیست؟
  3. finalize()همچنین اگر این روش در کلاس شما وجود داشته باشد، جریمه عملکرد قابل توجهی دریافت خواهید کرد . جاشوا بلوخ در برنامه‌نویسی مؤثر (ویرایش دوم) گفت:
    «بله، و یک چیز دیگر: جریمه عملکرد بزرگی هنگام استفاده از نهایی‌کننده‌ها وجود دارد. در دستگاه من، زمان ایجاد و از بین بردن اشیاء ساده تقریباً 5.6 نانوثانیه است.
    افزودن نهایی کننده زمان را به 2400 نانوثانیه افزایش می دهد. به عبارت دیگر، ایجاد و حذف یک شی با یک نهایی کننده تقریباً 430 برابر کندتر است.

چرا HashMap نباید در یک محیط چند رشته ای استفاده شود؟ آیا این می تواند یک حلقه بی نهایت ایجاد کند؟

می دانیم که HashMapاین یک مجموعه غیر همگام است که همتای همگام آن است HashTable. بنابراین، هنگامی که به یک مجموعه دسترسی دارید و در یک محیط چند رشته ای که همه رشته ها به یک نمونه از مجموعه دسترسی دارند، استفاده از آن HashTableبه دلایل واضح، مانند اجتناب از خواندن کثیف و اطمینان از سازگاری داده، ایمن تر است. در بدترین حالت، این محیط چند رشته ای باعث ایجاد یک حلقه بی نهایت می شود. بله این درست است. HashMap.get()ممکن است یک حلقه بی نهایت ایجاد کند. ببینیم چطور؟ اگر به کد منبع روش نگاه کنید HashMap.get(Object key)، به نظر می رسد:
public Object get(Object key) {
    Object k = maskNull(key);
    int hash = hash(k);
    int i = indexFor(hash, table.length);
    Entry e = table[i];
    while (true) {
        if (e == null)
            return e;
        if (e.hash == hash && eq(k, e.key))
            return e.value;
        e = e.next;
    }
}
while(true)اگر به دلایلی e.nextبتواند به خودش اشاره کند، همیشه می تواند قربانی یک حلقه بی نهایت در یک محیط زمان اجرا چند رشته ای شود. این یک حلقه بی پایان ایجاد می کند، اما چگونه e.nextبه خود (یعنی به e) اشاره می کند؟ این می تواند در روشی اتفاق بیفتد void transfer(Entry[] newTable)که هنگام HashMapتغییر اندازه آن فراخوانی می شود.
do {
    Entry next = e.next;
    int i = indexFor(e.hash, newCapacity);
    e.next = newTable[i];
    newTable[i] = e;
    e = next;
} while (e != null);
این قطعه کد مستعد ایجاد یک حلقه بی نهایت است اگر تغییر اندازه همزمان اتفاق بیفتد که رشته دیگری سعی می کند نمونه نقشه را تغییر دهد ( HashMap). تنها راه برای جلوگیری از این سناریو استفاده از همگام سازی در کد خود یا بهتر است از یک مجموعه همگام سازی شده است.

انتزاع و کپسولاسیون را توضیح دهید. چگونه به هم متصل هستند؟

به عبارت ساده ، " انتزاع فقط آن خصوصیاتی از یک شی را نشان می دهد که برای نمای فعلی مهم هستند . " در تئوری برنامه نویسی شی گرا، انتزاع شامل توانایی تعریف اشیایی است که نشان دهنده «بازیگران» انتزاعی هستند که می توانند کار را انجام دهند، تغییرات را در وضعیت خود تغییر دهند و گزارش دهند، و با اشیاء دیگر در سیستم «تعامل» داشته باشند. انتزاع در هر زبان برنامه نویسی به طرق مختلف کار می کند. این را می توان از ایجاد روتین هایی برای تعریف رابط برای دستورات زبان سطح پایین مشاهده کرد. برخی از انتزاع ها سعی می کنند وسعت بازنمایی کلی نیازهای برنامه نویس را با پنهان کردن کامل انتزاعاتی که بر اساس آنها ساخته شده اند، مانند الگوهای طراحی، محدود کنند. به طور معمول، انتزاع را می توان به دو صورت مشاهده کرد: انتزاع داده راهی برای ایجاد انواع داده های پیچیده و افشای تنها عملیات معنادار برای تعامل با مدل داده است، در حالی که در عین حال تمام جزئیات پیاده سازی را از دنیای خارج پنهان می کند. انتزاع اجرا فرآیند شناسایی تمام عبارات مهم و افشای آنها به عنوان یک واحد کاری است. ما معمولاً وقتی روشی را برای انجام برخی کارها ایجاد می کنیم از این ویژگی استفاده می کنیم. محدود کردن داده ها و روش ها در کلاس ها در ترکیب با انجام مخفی کردن (با استفاده از کنترل دسترسی) اغلب کپسوله سازی نامیده می شود. نتیجه یک نوع داده با ویژگی ها و رفتار است. کپسوله سازی اساساً شامل پنهان کردن داده ها و پنهان سازی پیاده سازی نیز می شود. "هر چیزی که می تواند تغییر کند را در خود محصور کنید" . این نقل قول یک اصل طراحی شناخته شده است. برای این موضوع، در هر کلاس، تغییرات داده‌ها می‌تواند در زمان اجرا و تغییرات پیاده‌سازی در نسخه‌های بعدی اتفاق بیفتد. بنابراین، کپسوله سازی هم برای داده ها و هم برای پیاده سازی اعمال می شود. بنابراین می توان آنها را به این صورت متصل کرد:
  • انتزاع بیشتر کاری است که یک کلاس می تواند انجام دهد [ایده]
  • کپسولاسیون بیشتر نحوه دستیابی به این قابلیت است [پیاده سازی]

تفاوت بین کلاس رابط و انتزاعی؟

تفاوت های اصلی را می توان به شرح زیر فهرست کرد:
  • یک رابط نمی تواند هیچ روشی را پیاده سازی کند، اما یک کلاس انتزاعی می تواند.
  • یک کلاس می تواند بسیاری از اینترفیس ها را پیاده سازی کند، اما فقط می تواند یک سوپرکلاس (انتزاعی یا غیرانتزاعی) داشته باشد.
  • یک رابط بخشی از سلسله مراتب کلاس نیست. کلاس های نامرتبط می توانند رابط کاربری مشابهی را پیاده سازی کنند.
چیزی که باید به خاطر بسپارید این است: «وقتی می‌توانید مفهومی را به طور کامل بر اساس «آنچه انجام می‌دهد» بدون نیاز به مشخص کردن «چگونه انجام می‌دهد» توصیف کنید، پس باید از یک رابط استفاده کنید. اگر نیاز دارید جزئیات پیاده سازی را درج کنید، پس باید مفهوم خود را در یک کلاس انتزاعی نشان دهید." همچنین، به بیان دیگر: آیا کلاس های زیادی وجود دارد که بتوان آنها را «با هم گروه کرد» و با یک اسم توصیف کرد؟ اگر چنین است، یک کلاس انتزاعی با نام این اسم ایجاد کنید و کلاس‌هایی را از آن به ارث ببرید. به عنوان مثال، Catو Dogمی تواند از کلاس abstract به ارث ببرد Animal، و این کلاس پایه انتزاعی متد void Breathe()- breathe را پیاده سازی می کند، که بنابراین همه حیوانات به یک روش انجام می دهند. چه افعالی را می توان در کلاس من اعمال کرد و می توان آن را برای سایرین به کار برد؟ برای هر یک از این افعال یک رابط ایجاد کنید. به عنوان مثال، همه حیوانات می توانند غذا بخورند، بنابراین من یک رابط ایجاد می کنم IFeedableو آن را Animalپیاده سازی می کنم. فقط به اندازه کافی خوب است که یک رابط را پیاده سازی کند Dog( قابلیت دوست داشتن من)، اما نه همه. یکی گفت: تفاوت اصلی این است که شما کجا می خواهید پیاده سازی کنید. هنگامی که یک رابط ایجاد می کنید، می توانید پیاده سازی را به هر کلاسی که رابط شما را پیاده سازی می کند منتقل کنید. با ایجاد یک کلاس انتزاعی، می‌توانید پیاده‌سازی تمام کلاس‌های مشتق‌شده را در یک مکان به اشتراک بگذارید و از بسیاری موارد بد مانند تکرار کد جلوگیری کنید. HorseILikeable

StringBuffer چگونه حافظه را ذخیره می کند؟

کلاس Stringبه عنوان یک شی غیرقابل تغییر پیاده سازی می شود، به این معنی که وقتی در ابتدا تصمیم می گیرید چیزی را در شی قرار دهید String، ماشین مجازی یک آرایه با طول ثابت دقیقاً به اندازه مقدار اصلی شما اختصاص می دهد. سپس به عنوان یک ثابت در داخل ماشین مجازی تلقی می شود که در صورت تغییر نکردن مقدار رشته، عملکرد قابل توجهی را بهبود می بخشد. با این حال، اگر تصمیم دارید محتویات یک رشته را به هر طریقی تغییر دهید، کاری که ماشین مجازی در واقع انجام می دهد این است که محتویات رشته اصلی را در فضای موقت کپی کرده، تغییرات خود را انجام دهید، سپس آن تغییرات را در یک آرایه حافظه جدید ذخیره کنید. بنابراین، ایجاد تغییرات در مقدار یک رشته پس از مقداردهی اولیه، یک عملیات گران است. StringBufferاز سوی دیگر، به‌عنوان یک آرایه در حال گسترش پویا در داخل ماشین مجازی پیاده‌سازی می‌شود، به این معنی که هرگونه عملیات اصلاحی می‌تواند روی سلول حافظه موجود رخ دهد و حافظه جدید در صورت نیاز تخصیص می‌یابد. با این حال، هیچ راهی برای ماشین مجازی برای انجام بهینه سازی وجود ندارد StringBufferزیرا محتویات آن در هر نمونه ناسازگار در نظر گرفته می شود.

چرا متدهای wait و notify به جای Thread در کلاس Object اعلام می شوند؟

متدهای wait, notify, notifyAllفقط زمانی مورد نیاز هستند که بخواهید رشته‌های شما به منابع مشترک دسترسی داشته باشند و منبع مشترک می‌تواند هر شی جاوا در پشته باشد. بنابراین، این متدها بر روی کلاس پایه تعریف می‌شوند Objectتا هر شیء دارای کنترلی باشد که به نخ‌ها اجازه می‌دهد روی مانیتور خود منتظر بمانند. جاوا هیچ شیء خاصی ندارد که برای به اشتراک گذاشتن یک منبع مشترک استفاده شود. چنین ساختار داده ای تعریف نشده است. بنابراین، این وظیفه کلاس است Objectکه بتواند به یک منبع مشترک تبدیل شود، و روش های کمکی مانند ،،، wait()ارائه دهد . جاوا بر اساس ایده چارلز هور از مانیتورها ساخته شده است. در جاوا همه اشیا دارای مانیتور هستند. رشته ها روی مانیتورها منتظر می مانند، بنابراین برای انجام انتظار به دو پارامتر نیاز داریم: notify()notifyAll()
  • یک نخ
  • مانیتور (هر شی).
در طراحی جاوا، یک رشته را نمی توان دقیقاً تعریف کرد، همیشه رشته فعلی است که کد را اجرا می کند. با این حال، ما می توانیم یک مانیتور (که یک شی است که می توانیم یک متد را بر روی آن فراخوانی کنیم wait) تعریف کنیم. این یک طراحی خوب است زیرا اگر بتوانیم هر رشته دیگری را مجبور کنیم روی یک مانیتور خاص منتظر بماند، منجر به "تهاجم" می شود که طراحی/برنامه نویسی برنامه های موازی را دشوار می کند. به یاد داشته باشید که در جاوا، تمام عملیاتی که با موضوعات دیگر تداخل دارند، منسوخ شده اند (به عنوان مثال، stop()).

برنامه ای برای ایجاد بن بست در جاوا بنویسید و آن را برطرف کنید

در جاوا deadlock، این وضعیتی است که در آن حداقل دو رشته یک بلوک را در منابع مختلف نگه می‌دارند و هر دو منتظر در دسترس قرار گرفتن منبع دیگر برای تکمیل کار خود هستند. و هیچ یک از آنها نمی توانند قفلی را بر روی منبع در حال نگهداری باقی بگذارند. جاوا هسته.  سوالات برای مصاحبه، قسمت 2 - 2 نمونه برنامه:
package thread;

public class ResolveDeadLockTest {

	public static void main(String[] args) {
		ResolveDeadLockTest test = new ResolveDeadLockTest();

		final A a = test.new A();
		final B b = test.new B();

		// Thread-1
		Runnable block1 = new Runnable() {
			public void run() {
				synchronized (a) {
					try {
					// Добавляем задержку, чтобы обе нити могли начать попытки
					// блокирования ресурсов
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					// Thread-1 заняла A но также нуждается в B
					synchronized (b) {
						System.out.println("In block 1");
					}
				}
			}
		};

		// Thread-2
		Runnable block2 = new Runnable() {
			public void run() {
				synchronized (b) {
					// Thread-2 заняла B но также нуждается в A
					synchronized (a) {
						System.out.println("In block 2");
					}
				}
			}
		};

		new Thread(block1).start();
		new Thread(block2).start();
	}

	// Resource A
	private class A {
		private int i = 10;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}

	// Resource B
	private class B {
		private int i = 20;

		public int getI() {
			return i;
		}

		public void setI(int i) {
			this.i = i;
		}
	}
}
اجرای کد بالا به دلایل بسیار واضح (که در بالا توضیح داده شد) منجر به بن بست می شود. حالا باید این مشکل را حل کنیم. من معتقدم که راه حل هر مشکلی در ریشه خود مشکل نهفته است. در مورد ما، مدل دسترسی به A و B مشکل اصلی است. بنابراین، برای حل آن، ما به سادگی ترتیب اپراتورهای دسترسی را به منابع مشترک تغییر می دهیم. پس از تغییر به این شکل خواهد بود:
// Thread-1
Runnable block1 = new Runnable() {
	public void run() {
		synchronized (b) {
			try {
				// Добавляем задержку, чтобы обе нити могли начать попытки
				// блокирования ресурсов
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			// Thread-1 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 1");
			}
		}
	}
};

// Thread-2
Runnable block2 = new Runnable() {
	public void run() {
		synchronized (b) {
			// Thread-2 заняла B но также нуждается в А
			synchronized (a) {
				System.out.println("In block 2");
			}
		}
	}
};
این کلاس را دوباره اجرا کنید و اکنون بن بست را نخواهید دید. امیدوارم این به شما کمک کند تا از بن بست جلوگیری کنید و در صورت مواجهه با آنها از شر آنها خلاص شوید.

اگر کلاس شما که رابط Serializable را پیاده سازی می کند شامل یک جزء غیرقابل سریال باشد چه اتفاقی می افتد؟ چگونه این را رفع کنیم؟

در این صورت در حین اجرا پرتاب می شود NotSerializableException. برای رفع این مشکل، یک راه حل بسیار ساده وجود دارد - این کادرها را علامت بزنید transient. این بدان معنی است که فیلدهای علامت زده سریالی نمی شوند. اگر می خواهید وضعیت این فیلدها را نیز ذخیره کنید، باید متغیرهای مرجع را در نظر بگیرید که قبلاً Serializable. همچنین ممکن است لازم باشد از روش readResolve()و استفاده کنید writeResolve(). بیایید خلاصه کنیم:
  • ابتدا فیلد خود را غیرقابل سریال سازی کنید transient.
  • ابتدا از رشته writeObjectفراخوانی کنید تا همه فیلدهای غیرقابل ذخیره سازی را ذخیره کند، سپس متدهای باقیمانده را برای سریال کردن خصوصیات فردی شی غیرقابل سریال خود فراخوانی کنید.defaultWriteObjecttransient
  • در readObjectابتدا defaultReadObjectجریان را فراخوانی کنید تا همه transientفیلدهای غیر را بخواند، سپس روش‌های دیگر (مرتبط با مواردی که در آن اضافه کرده‌اید writeObject) را فراخوانی کنید تا transientشی غیر اصلی خود را از حالت سریال خارج کنید.

کلمات کلیدی گذرا و فرار را در جاوا توضیح دهید

"کلمه کلیدی transientبرای نشان دادن فیلدهایی استفاده می شود که سریالی نمی شوند." با توجه به مشخصات زبان جاوا: متغیرها را می توان با نشانگر گذرا علامت گذاری کرد تا نشان دهد که بخشی از حالت پایدار شی نیستند. به عنوان مثال، شما ممکن است حاوی فیلدهایی باشید که از فیلدهای دیگر مشتق شده اند، و ترجیح داده می شود که آنها را از طریق برنامه نویسی به دست آورید تا اینکه وضعیت آنها را از طریق سریال سازی بازیابی کنید. به عنوان مثال، در یک کلاس، BankPayment.javaفیلدهایی مانند principal(مدیر) و rate(نرخ) را می توان سریال کرد و interest(بهره تعهدی) را می توان در هر زمان حتی پس از سریال زدایی محاسبه کرد. اگر یادمان باشد، هر رشته در جاوا حافظه محلی خود را دارد و عملیات خواندن/نوشتن را روی این حافظه محلی انجام می دهد. هنگامی که تمام عملیات انجام می شود، وضعیت تغییر یافته متغیر را در حافظه مشترک می نویسد، جایی که همه رشته ها به متغیر دسترسی پیدا می کنند. به طور معمول، این یک موضوع معمولی در داخل یک ماشین مجازی است. اما اصلاح‌کننده فرار به ماشین مجازی می‌گوید که دسترسی یک رشته به آن متغیر باید همیشه کپی خودش از آن متغیر با کپی اصلی متغیر در حافظه مطابقت داشته باشد. این بدان معناست که هر بار که یک رشته می خواهد وضعیت یک متغیر را بخواند، باید وضعیت حافظه داخلی را پاک کرده و متغیر را از حافظه اصلی به روز کند. Volatileدر الگوریتم های بدون قفل بسیار مفید است. شما متغیری را که داده‌های مشترک را ذخیره می‌کند به‌عنوان فرار علامت‌گذاری می‌کنید، سپس از قفل برای دسترسی به آن متغیر استفاده نمی‌کنید و همه تغییرات ایجاد شده توسط یک رشته برای دیگران قابل مشاهده خواهد بود. یا اگر می خواهید برای اطمینان از عدم تکرار محاسبات یک رابطه "اتفاق افتاده" ایجاد کنید، دوباره برای اطمینان از اینکه تغییرات در زمان واقعی قابل مشاهده هستند. فرار باید برای انتشار ایمن اشیاء تغییرناپذیر در یک محیط چند رشته ای استفاده شود. اعلام فیلد public volatile ImmutableObjectتضمین می کند که همه رشته ها همیشه مرجع موجود در حال حاضر به نمونه را می بینند.

تفاوت بین Iterator و ListIterator؟

می توانیم از , یا Iteratorبرای تکرار روی عناصر استفاده کنیم . اما فقط می توان از آن برای تکرار روی عناصر استفاده کرد . سایر تفاوت ها در زیر توضیح داده شده است. تو می توانی: SetListMapListIteratorList
  1. به ترتیب معکوس تکرار کنید
  2. هر جایی فهرست دریافت کنید
  3. هر جایی ارزش اضافه کنید
  4. هر مقدار را در موقعیت فعلی تنظیم کنید.
با آرزوی موفقیت در تحصیل!! نویسنده مقاله Lokesh Gupta مقاله اصلی Java Core. سوالات مصاحبه قسمت 1 Java Core. سوالات مصاحبه قسمت 3
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION