JavaRush /Java Blog /Random-KO /자바 코어. 인터뷰 질문, 2부
Andrey
레벨 26

자바 코어. 인터뷰 질문, 2부

Random-KO 그룹에 게시되었습니다
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 블록 내 finally 블록 내 try 블록 내 캐치 블록 내 finally 블록 내 try 블록 내 캐치 블록 내 finally 블록 놀랍게도 메서드는 finalize어떤 스레드에서도 실행되지 않았습니다. 이것이 내 말이 증명됩니다. 그 이유는 종료자가 별도의 가비지 수집기 스레드에 의해 실행되기 때문이라고 생각합니다. JVM(Java Virtual Machine)이 너무 일찍 종료되면 가비지 수집기가 종료자를 생성하고 실행할 시간이 충분하지 않습니다. 이 방법을 사용하지 않는 다른 이유는 finalize()다음과 같습니다.
  1. 이 메서드는 finalize()생성자와 같은 체인에서는 작동하지 않습니다. 즉, 클래스 생성자를 호출하면 슈퍼클래스 생성자가 무조건 호출됩니다. 하지만 메소드의 경우에는 finalize()이런 일이 발생하지 않습니다. 슈퍼클래스 메서드는 finalize()명시적으로 호출되어야 합니다.
  2. 메서드에 의해 발생한 모든 예외는 finalize가비지 수집기 스레드에 의해 무시되며 더 이상 전파되지 않습니다. 즉, 이벤트가 로그에 기록되지 않습니다. 이건 정말 나쁜 일이지, 그렇지?
  3. finalize()또한 해당 메서드가 클래스에 있으면 성능이 크게 저하됩니다 . 효과적인 프로그래밍(2판)에서 Joshua Bloch는 다음과 같이 말했습니다.
    “그렇습니다. 그리고 한 가지 더: 종료자를 사용하면 성능이 크게 저하됩니다. 내 컴퓨터에서 간단한 객체를 생성하고 파괴하는 데 걸리는 시간은 약 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있으며 이 추상 기본 클래스는 호흡이라는 메서드를 구현하므로 모든 동물이 동일한 방식으로 수행됩니다. 내 수업에는 어떤 동사가 적용될 수 있고 다른 수업에도 적용될 수 있나요? 이러한 각 동사에 대한 인터페이스를 만듭니다. 예를 들어 모든 동물은 먹을 수 있으므로 인터페이스를 만들고 그 인터페이스를 구현 하도록 하겠습니다 . 인터페이스를 구현하기에 충분 하지만 ( 나를 좋아할 수 있음) 전부는 아닙니다. 누군가 말했습니다: 가장 큰 차이점은 구현하려는 위치입니다. 인터페이스를 생성할 때 인터페이스를 구현하는 클래스로 구현을 이동할 수 있습니다. 추상 클래스를 생성하면 모든 파생 클래스의 구현을 한 곳에서 공유하고 코드 복제와 같은 많은 나쁜 일을 피할 수 있습니다. DogAnimalvoid Breathe()IFeedableAnimalDogHorseILikeable

StringBuffer는 어떻게 메모리를 절약하나요?

클래스는 String불변 객체로 구현됩니다. 즉, 처음에 객체에 무언가를 넣기로 결정하면 String가상 머신이 원래 값과 정확히 같은 크기의 고정 길이 배열을 할당한다는 의미입니다. 그러면 이는 가상 머신 내에서 상수로 처리되어 문자열 값이 변경되지 않으면 상당한 성능 향상을 제공합니다. 그러나 어떤 방식으로든 문자열의 내용을 변경하기로 결정한 경우 가상 머신이 실제로 수행하는 작업은 원래 문자열의 내용을 임시 공간에 복사하고 변경한 다음 해당 변경 사항을 새 메모리 배열에 저장하는 것입니다. 따라서 초기화 후에 문자열 값을 변경하는 것은 비용이 많이 드는 작업입니다. StringBuffer반면, 가상 머신 내부에서는 동적으로 확장되는 어레이로 구현됩니다. 즉, 기존 메모리 셀에서 수정 작업이 발생할 수 있으며 필요에 따라 새 메모리가 할당됩니다. StringBuffer그러나 가상 머신의 내용은 각 인스턴스에서 일관성이 없는 것으로 간주되므로 가상 머신이 최적화를 수행할 수 있는 방법이 없습니다 .

Thread 대신 Object 클래스에 wait 및 inform 메소드가 선언된 이유는 무엇입니까?

wait, notify, 메소드는 notifyAll스레드가 공유 리소스에 액세스할 수 있도록 하고 공유 리소스가 힙의 모든 Java 객체일 수 있는 경우에만 필요합니다. 따라서 이러한 메서드는 기본 클래스에 정의되어 Object각 개체에 스레드가 모니터에서 대기할 수 있도록 하는 컨트롤이 있습니다. Java에는 공유 리소스를 공유하는 데 사용되는 특별한 개체가 없습니다. 그러한 데이터 구조는 정의되어 있지 않습니다. Object따라서 공유 리소스가 될 수 있고, wait(), notify(), 와 같은 도우미 메서드를 제공할 수 있는 것은 클래스의 책임입니다 notifyAll(). Java는 Charles Hoare의 모니터 아이디어에 기반을 두고 있습니다. Java에서는 모든 객체에 모니터가 있습니다. 스레드는 모니터에서 대기하므로 대기를 수행하려면 두 가지 매개변수가 필요합니다.
  • 스레드
  • 모니터(모든 개체).
Java 디자인에서는 스레드를 정확하게 정의할 수 없으며 항상 코드를 실행하는 현재 스레드입니다. 그러나 모니터(메서드를 호출할 수 있는 개체 wait)를 정의할 수 있습니다. 다른 스레드가 특정 모니터를 기다리도록 강제할 수 있다면 "침략"이 발생하여 병렬 프로그램 설계/프로그래밍이 어려워지기 때문에 이는 좋은 설계입니다. Java에서는 다른 스레드를 방해하는 모든 작업이 더 이상 사용되지 않습니다(예: stop()).

Java에서 교착 상태를 생성하고 수정하는 프로그램을 작성하세요.

Java에서 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");
			}
		}
	}
};
이 클래스를 다시 실행하면 이제 교착 상태가 표시되지 않습니다. 이것이 교착 상태를 피하고 교착 상태가 발생할 경우 제거하는 데 도움이 되기를 바랍니다.

직렬화 가능 인터페이스를 구현하는 클래스에 직렬화 가능하지 않은 구성 요소가 포함되어 있으면 어떻게 되나요? 이 문제를 해결하는 방법은 무엇입니까?

NotSerializableException이 경우 실행 중에 throw됩니다 . 이 문제를 해결하려면 매우 간단한 해결책이 있습니다. 이 확인란을 선택하세요 transient. 이는 선택된 필드가 직렬화되지 않음을 의미합니다. 이러한 필드의 상태도 저장하려면 이미 Serializable. readResolve()및 메소드 를 사용해야 할 수도 있습니다 writeResolve(). 요약해보자:
  • 먼저 필드를 직렬화할 수 없도록 만드세요 transient.
  • 먼저 스레드를 writeObject호출하여 모든 필드가 아닌 필드를 저장한 다음 나머지 메서드를 호출하여 직렬화할 수 없는 개체의 개별 속성을 직렬화합니다.defaultWriteObjecttransient
  • 에서는 readObject먼저 defaultReadObject스트림을 호출하여 모든 비 transient필드를 읽은 다음 다른 메서드( 에서 추가한 메서드에 해당 writeObject)를 호출하여 비 transient개체를 역직렬화합니다.

Java의 임시 및 휘발성 키워드 설명

"키워드는 transient직렬화되지 않는 필드를 나타내는 데 사용됩니다." Java 언어 사양에 따르면 변수는 일시적 표시기로 표시되어 객체의 지속 상태의 일부가 아님을 나타낼 수 있습니다. 예를 들어, 다른 필드에서 파생된 필드를 포함할 수 있으며 직렬화를 통해 상태를 복원하는 것보다 프로그래밍 방식으로 필드를 얻는 것이 더 좋습니다. 예를 들어 클래스에서 (이사) 및 (이자율) BankPayment.java과 같은 필드를 직렬화할 수 있으며 역직렬화 후에도 언제든지 (발생 이자)를 계산할 수 있습니다. 기억한다면 Java의 각 스레드는 자체 로컬 메모리를 가지며 이 로컬 메모리에서 읽기/쓰기 작업을 수행합니다. 모든 작업이 완료되면 변수의 수정된 상태를 모든 스레드가 변수에 액세스하는 공유 메모리에 씁니다. 일반적으로 이는 가상 머신 내부의 일반 스레드입니다. 그러나 휘발성 수정자는 해당 변수에 대한 스레드의 액세스가 항상 해당 변수의 자체 복사본과 메모리에 있는 변수의 마스터 복사본과 일치해야 함을 가상 머신에 알려줍니다. 이는 스레드가 변수의 상태를 읽으려고 할 때마다 내부 메모리 상태를 지우고 주 메모리에서 변수를 업데이트해야 함을 의미합니다. 잠금 없는 알고리즘에 가장 유용합니다. 공유 데이터를 저장하는 변수를 휘발성으로 표시한 다음 해당 변수에 액세스하기 위해 잠금을 사용하지 않고 한 스레드에서 수행한 모든 변경 사항이 다른 스레드에 표시됩니다. 또는 계산이 반복되지 않도록 "발생 후" 관계를 생성하여 변경 사항이 실시간으로 표시되도록 하려는 경우에도 마찬가지입니다. 다중 스레드 환경에서 불변 객체를 안전하게 게시하려면 Volatile을 사용해야 합니다. 필드 선언은 모든 스레드가 항상 인스턴스에 대해 현재 사용 가능한 참조를 볼 수 있도록 보장합니다. principalrateinterestVolatilepublic 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