JavaRush /בלוג Java /Random-HE /Java Core. שאלות ראיון, חלק ב'
Andrey
רָמָה

Java Core. שאלות ראיון, חלק ב'

פורסם בקבוצה
למי ששומע את המילה Java Core בפעם הראשונה, אלו הם היסודות הבסיסיים של השפה. עם הידע הזה, אתה יכול ללכת בבטחה להתמחות/התמחות.
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 block ב-catch block In finally block In end-try block ב-catch block In finally block ב-try block ב-catch block In finally block למרבה ההפתעה, השיטה finalizeלא בוצעה עבור אף שרשור. זה מוכיח את דברי. אני חושב שהסיבה לכך היא שהמסמרים מבוצעים על ידי חוט אספן אשפה נפרד. אם ה-Java Virtual Machine מסתיים מוקדם מדי, אזי לאספן האשפה אין מספיק זמן ליצור ולהפעיל מגמרים. סיבות נוספות לא להשתמש בשיטה finalize()עשויות להיות:
  1. השיטה finalize()לא עובדת עם רשתות כמו קונסטרוקטורים. זה אומר שכאשר אתה קורא לבנאי מחלקה, הבנאים של מחלקות העל ייקראו ללא תנאי. אבל במקרה של השיטה finalize(), זה לא יבוא בהמשך. finalize()יש לקרוא במפורש לשיטת superclass.
  2. כל חריג שנזרק על ידי השיטה finalizeמתעלם מהשרשור של אספן האשפה ולא יופץ בהמשך, מה שאומר שהאירוע לא יתועד ביומנים שלך. זה רע מאוד, לא?
  3. אתה גם מקבל עונש ביצוע משמעותי אם השיטה finalize()קיימת בכיתה שלך. ב- Effective Programming (מהדורה שנייה), ג'ושוע בלוך אמר:
    "כן, ועוד דבר: יש עונש גדול על ביצועים בעת שימוש במגמרים סופיים. במכונה שלי, הזמן ליצור ולהשמיד אובייקטים פשוטים הוא בערך 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). הדרך היחידה להימנע מתרחיש זה היא להשתמש בסנכרון בקוד שלך, או יותר טוב, להשתמש באוסף מסונכרן.

הסבר הפשטה ואנקפסולציה. איך הם מחוברים?

במילים פשוטות , " הפשטה מציגה רק את המאפיינים של אובייקט שמשמעותיים עבור התצוגה הנוכחית . " בתורת התכנות מונחה עצמים, ההפשטה כוללת את היכולת להגדיר אובייקטים המייצגים "שחקנים" מופשטים שיכולים לבצע עבודה, לשנות ולדווח על שינויים במצבם ו"לתקשר" עם אובייקטים אחרים במערכת. הפשטה בכל שפת תכנות עובדת בדרכים רבות. ניתן לראות זאת מיצירת שגרות להגדרת ממשקים עבור פקודות שפה ברמה נמוכה. חלק מההפשטות מנסות להגביל את רוחב הייצוג הכולל של צרכיו של מתכנת על ידי הסתרה מוחלטת של ההפשטות עליהן הן בנויות, כגון דפוסי עיצוב. בדרך כלל, ניתן לראות הפשטה בשתי דרכים: הפשטת נתונים היא דרך ליצור סוגי נתונים מורכבים ולחשיפת רק פעולות משמעותיות לאינטראקציה עם מודל הנתונים, ובו זמנית להסתיר את כל פרטי היישום מהעולם החיצון. הפשטת ביצוע היא תהליך זיהוי כל ההצהרות המשמעותיות וחשיפתן כיחידת עבודה. בדרך כלל אנו משתמשים בתכונה זו כאשר אנו יוצרים שיטה לבצע עבודה מסוימת. הגבלה של נתונים ושיטות בתוך מחלקות בשילוב עם ביצוע הסתרה (באמצעות בקרת גישה) נקראת לעתים קרובות אנקפסולציה. התוצאה היא סוג נתונים עם מאפיינים והתנהגות. אנקפסולציה כוללת בעצם גם הסתרת נתונים והסתרת יישום. "הקפל את כל מה שיכול להשתנות" . ציטוט זה הוא עיקרון עיצובי ידוע. לצורך העניין, בכל מחלקה, שינויים בנתונים יכולים להתרחש בזמן ריצה ושינויים ביישום יכולים לקרות בגרסאות עתידיות. לפיכך, אנקפסולציה חלה הן על הנתונים והן על היישום. אז הם יכולים להיות מחוברים כך:
  • הפשטה היא בעיקר מה שכיתה יכולה לעשות [רעיון]
  • Encapsulation היא יותר איך להשיג את הפונקציונליות הזו [יישום]

הבדלים בין ממשק למחלקה מופשטת?

ניתן לפרט את ההבדלים העיקריים כדלקמן:
  • ממשק לא יכול ליישם שום מתודה, אבל מחלקה מופשטת יכולה.
  • מחלקה יכולה ליישם ממשקים רבים, אך יכולה להיות בעלת מחלקה על אחת בלבד (מופשטת או לא מופשטת)
  • ממשק אינו חלק מהיררכיית מחלקות. מחלקות לא קשורות יכולות ליישם את אותו ממשק.
מה שאתה צריך לזכור הוא זה: "כאשר אתה יכול לתאר מושג באופן מלא במונחים של "מה הוא עושה" בלי צורך לציין "איך הוא עושה את זה", אז אתה צריך להשתמש בממשק. אם אתה צריך לכלול כמה פרטי יישום, אז אתה צריך לייצג את הרעיון שלך בכיתה מופשטת." כמו כן, בניסוח אחר: האם יש הרבה כיתות שניתן "לקבץ יחד" ולתאר אותן באמצעות שם עצם בודד? אם כן, צור מחלקה מופשטת עם השם של שם העצם הזה, ויורש ממנו מחלקות. למשל, Catויכול Dogלרשת מהמחלקה המופשטת Animal, ומחלקת הבסיס המופשטת הזו תטמיע את השיטה void Breathe()- breathe, שכל החיות יבצעו כך באותו אופן. אילו פעלים ניתן להחיל על הכיתה שלי ואפשר להחיל על אחרים? צור ממשק לכל אחד מהפעלים הללו. לדוגמה, כל בעלי החיים יכולים לאכול, אז אני אצור ממשק IFeedableואגרום לו Animalליישם את הממשק הזה. רק טוב מספיק כדי ליישם ממשק Dog( שיכול לאהוב אותי), אבל לא הכל. מישהו אמר: ההבדל העיקרי הוא היכן אתה רוצה את היישום שלך. כאשר אתה יוצר ממשק, אתה יכול להעביר את ההטמעה לכל מחלקה שמיישמת את הממשק שלך. על ידי יצירת מחלקה מופשטת, אתה יכול לשתף את היישום של כל המחלקות הנגזרות במקום אחד ולהימנע מהרבה דברים רעים כמו שכפול קוד. HorseILikeable

כיצד StringBuffer חוסך זיכרון?

המחלקה Stringמיושמת כאובייקט בלתי ניתן לשינוי, כלומר כאשר אתה מחליט בהתחלה להכניס משהו לאובייקט String, המכונה הוירטואלית מקצה מערך באורך קבוע בדיוק בגודל של הערך המקורי שלך. לאחר מכן זה יטופל כאל קבוע בתוך המכונה הוירטואלית, מה שמספק שיפור משמעותי בביצועים אם הערך של המחרוזת לא משתנה. עם זאת, אם תחליט לשנות את התוכן של מחרוזת בדרך כלשהי, מה שהמכונה הווירטואלית עושה בפועל הוא להעתיק את התוכן של המחרוזת המקורית למרחב זמני, לבצע את השינויים שלך, ואז לשמור את השינויים האלה במערך זיכרון חדש. לפיכך, ביצוע שינויים בערך של מחרוזת לאחר אתחול היא פעולה יקרה. StringBuffer, לעומת זאת, מיושם כמערך מתרחב דינמית בתוך המכונה הוירטואלית, מה שאומר שכל פעולת שינוי יכולה להתרחש בתא זיכרון קיים וזיכרון חדש יוקצה לפי הצורך. עם זאת, אין דרך עבור המכונה הוירטואלית לבצע את האופטימיזציה StringBufferמכיוון שהתוכן שלה נחשב לא עקבי בכל מופע.

מדוע שיטות ההמתנה וההודעה מוכרזות במחלקה Object במקום Thread?

השיטות wait, notify, notifyAllנחוצות רק כאשר אתה רוצה שלשרשורים שלך תהיה גישה למשאבים משותפים והמשאב המשותף יכול להיות כל אובייקט Java בערימה. לפיכך, שיטות אלו מוגדרות במחלקה הבסיסית Objectכך שלכל אובייקט יש פקד המאפשר ל-threads להמתין על הצג שלהם. ל- Java אין שום אובייקט מיוחד המשמש לשיתוף משאב משותף. לא מוגדר מבנה נתונים כזה. לכן, באחריות הכיתה Objectלהיות מסוגל להפוך למשאב משותף, ולספק שיטות עוזרות כגון wait(), notify(), notifyAll(). ג'אווה מבוססת על הרעיון של צ'ארלס הואר לגבי צגים. ב-Java, לכל האובייקטים יש צג. חוטים ממתינים במסכים, אז כדי לבצע את ההמתנה אנחנו צריכים שני פרמטרים:
  • חוט
  • צג (כל חפץ).
בעיצוב Java, לא ניתן להגדיר במדויק שרשור; זה תמיד השרשור הנוכחי שמבצע את הקוד. עם זאת, אנו יכולים להגדיר מוניטור (שהוא אובייקט שעליו אנו יכולים לקרוא שיטה wait). זהו עיצוב טוב מכיוון שאם נוכל להכריח כל שרשור אחר להמתין על צג ספציפי, זה יגרום ל"פלישה", המקשה על עיצוב/תכנות תוכניות מקבילות. זכור שב-Java, כל הפעולות שמפריעות לשרשורים אחרים פוסלות (לדוגמה, stop()).

כתוב תוכנית ליצירת מבוי סתום בג'אווה ותתקן אותה

ב-Java deadlock, זהו מצב שבו לפחות שני שרשורים מחזיקים חסימה במשאבים שונים, ושניהם ממתינים שהמשאב השני יהפוך לזמין כדי להשלים את המשימה שלהם. ואף אחד מהם לא מסוגל להשאיר מנעול על המשאב המוחזק. Java Core.  שאלות לראיון, חלק ב' - ב' תוכנית לדוגמה:
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, קרא defaultWriteObjectלשרשור כדי לשמור את כל transientהשדות הלא-שדות, ולאחר מכן קרא לשאר השיטות כדי להדביק את המאפיינים האישיים של האובייקט הלא ניתן להמשכה שלך.
  • ב- readObject, קרא תחילה defaultReadObjectלזרם כדי לקרוא את כל transientהשדות שאינם, ולאחר מכן קרא לשיטות אחרות (המתאימות לאלו שהוספת ב- writeObject) כדי לבטל את transientהאובייקט הלא-אובייקט שלך.

הסבר מילות מפתח חולפות ונדיפות ב-Java

"מילת המפתח transientמשמשת לציון שדות שלא יועברו בסידרה." על פי מפרט שפת Java: ניתן לסמן משתנים במחוון החולף כדי לציין שהם אינם חלק מהמצב המתמשך של האובייקט. לדוגמה, אתה עשוי להכיל שדות שנגזרו משדות אחרים, ועדיף להשיג אותם באופן תכנותי במקום לשחזר את מצבם באמצעות סדרה. לדוגמה, במחלקה, ניתן לבצע סדרה של BankPayment.javaשדות כמו principal(דירקטור) ו- (תעריף), וניתן לחשב (ריבית נצברת) בכל עת, גם לאחר דה-סריאליזציה. אם נזכור, לכל שרשור ב-Java יש זיכרון מקומי משלו והוא מבצע פעולות קריאה/כתיבה בזיכרון המקומי הזה. כאשר כל הפעולות מבוצעות, הוא כותב את המצב המשתנה של המשתנה לזיכרון המשותף, משם כל השרשורים ניגשים למשתנה. בדרך כלל, זהו חוט רגיל בתוך מכונה וירטואלית. אבל השינוי הנדיף אומר למכונה הוירטואלית שגישה של שרשור למשתנה הזה חייבת תמיד להתאים את העותק שלו של המשתנה הזה עם העותק הראשי של המשתנה בזיכרון. זה אומר שבכל פעם שרשור רוצה לקרוא את המצב של משתנה, עליו לנקות את מצב הזיכרון הפנימי ולעדכן את המשתנה מהזיכרון הראשי. שימושי ביותר באלגוריתמים ללא נעילה. אתה מסמן משתנה המאחסן נתונים משותפים כנדיפים, ואז אתה לא משתמש במנעולים כדי לגשת למשתנה הזה, וכל השינויים שנעשו על ידי שרשור אחד יהיו גלויים לאחרים. או אם ברצונך ליצור קשר "קרה-אחרי" כדי להבטיח שהחישובים לא יחזרו, שוב כדי להבטיח שהשינויים יהיו גלויים בזמן אמת. יש להשתמש ב-volatile כדי לפרסם בבטחה אובייקטים בלתי ניתנים לשינוי בסביבה מרובת הליכים. הצהרת השדה מבטיחה שכל השרשורים תמיד רואים את ההפניה הזמינה כרגע למופע. rateinterestVolatilepublic 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