-
Java 기본 및 핵심 개념
-
1. Java의 특징을 설명해주세요.
-
컴파일 언어와 인터프리터 언어에 대해 설명해 보세요.
-
바인딩에 대해 설명해 보세요.
-
Call by Value와 Call by Reference에 대해 설명해 보세요.
-
변수 및 데이터 타입
-
변수에 대해 설명해 보세요.
-
상수에 대해서 설명해 보세요.
-
리터럴에 대해서 설명해 보세요.
-
상수와 리터럴의 차이점은 무엇인지 설명해 보세요.
-
final 키워드가 변수에 어떤 영향을 주는지 설명해 보세요.
-
변수의 스코프란 무엇이며, 이것이 중요한 이유는 무엇인지 설명해 보세요.
-
변수를 사용하기 전에 초기화하는 이유가 무엇인지 설명해 보세요.
-
기본형 타입이란 무엇인지 설명해 보세요.
-
기본형 변수와 참조형 변수의 차이에 대해서 설명해 보세요.
-
변수의 종류는 어떤 것들이 있는지 설명해 보세요.
-
래퍼 클래스(Wrapper Class)에 대해서 설명해 보세요.
-
오토박싱과 언박싱이란 무엇인지 설명해 보세요.
-
문자열
-
String 객체를 생성하는 두 가지 주요 방법에 대해 설명해 보세요.
-
String pool에 대해서 설명해 보세요.
-
자바에서 String은 불변하다는 것은 무엇을 의미하는지 설명해 보세요.
-
equals와 ‘==’의 차이는 무엇인지 설명해 보세요.
-
String과 StringBuilder, StringBuffer의 차이점은 무엇이며, 문자열의 가변성이 중요한 상황에서는 어떤 것을 사용해야 하는지 그 이유에 대해 설명해 보세요.
Java 기본 및 핵심 개념
1. Java의 특징을 설명해주세요.
자바는 객체지향 프로그래밍 언어로, 기본 자료형을 제외한 모든 요소들이 객체로 표현되고, 객체지향 개념의 특징인 캡슐화, 상속, 추상화, 다형성이 잘 적용된 언어입니다. 자바는 JVM위에서 동작하기 때문에 운영체제에 독립적이며, GC를 통해 자동으로 메모리 관리가 가능합니다. 하지만, JVM 위에서 동작하기 때문에 상대적으로 속도가 느리며 다중 상속이나 타입에 엄격하고 제약이 많은 편입니다.
컴파일 언어와 인터프리터 언어에 대해 설명해 보세요.
컴파일 언어와 인터프리터 언어는 프로그램 코드를 어떻게 실행하는지에 따라 구분됩니다.
컴파일 언어는 컴파일러를 사용하는 언어에서 전체 코드가 한 번에 컴파일되어 실행 파일을 생성합니다. 생성된 이 실행 파일은 다른 컴퓨터에서 컴파일러나 소스 코드가 없어도 실행될 수 있습니다. 장점으로는 일반적으로 실행 속도가 더 빠를 수 있으나, 단점으로는 전체 코드가 컴파일되기 때문에 오류를 발견하기 위해 전체 컴파일 과정을 거쳐야 해서 디버깅이 복잡할 수 있고, 한 번 생성된 실행 파일은 특정 OS나 플랫폼에 종속적입니다. 대표적으로 컴파일 언어로는 자바와 C 그리고 C++이 있습니다.
그리고 인터프리터 언어는 코드가 한 줄씩 읽히면서 바로 실행되는 언어입니다. 소스 코드가 실시간으로 해석되기 때문에 원본 코드만 있으면 다양한 플랫폼에서 실행될 수 있습니다. 이 방식은 개발과 디버깅이 쉽고 코드의 변경 사항을 즉시 확인할 수 있습니다. 그러나 코드가 실시간으로 해석되어야 해서 실행 속도가 비교적 느릴 수 있습니다. 대표적으로 인터프리터 언어는 Python과 JavaScript가 있습니다.
자바의 경우 컴파일러는 통해 플랫폼에 독립적인 바이트 코드를 컴파일하고, 이 바이트 코드는 다양한 시스템에서 JVM에 의해 인터프리터 됩니다. 즉, 자바는 컴파일러와 인터프리터를 모두 사용하는 방식을 사용합니다.
바인딩에 대해 설명해 보세요.
바인딩은 사용되는 변수나 메서드의 이름이 실제로 저장된 메모리 위치나 실제로 실행될 메서드와 연결되는 과정입니다.
자바에서는 이러한 바인딩이 컴파일 타임과 런타임에 따라 다르게 동작합니다. 컴파일 타임에 이루어지는 바인딩은 정적 바인딩으로 메서드 호출이 컴파일러에 의해 결정됩니다. 이러한 바인딩은 변수의 데이터 타입에 기반하고, private, static 및 final 메서드와 같이 메서드 오버라이딩이 불가능한 경우에 사용됩니다.
런타임에 이루어지는 바인딩은 동적 바인딩으로 실제로 메모리에 호출될 메서드의 주소는 프로그램 실행 시점에 결정됩니다. 이는 객체지향 프로그래밍의 다형성을 가능하게 하고, 부모 클래스 타입의 참조 변수로 자식 클래스의 객체를 참조할 수 있고, 런타임에 어떤 클래스의 메서드를 호출할지 결정됩니다.
따라서, 바인딩 타입은 메서드와 변수가 언제 연결되는지 결정하고, 실행 흐름 제어와 성능 최적화에 중요한 역할을 합니다.
Call by Value와 Call by Reference에 대해 설명해 보세요.
Call by Value나 Call by Reference는 함수나 메서드에 인자를 전달하는 방식을 나타냅니다.
우선, Java에서 모든 메서드 호출은 Call by Value로 이루어집니다. 즉, 함수나 메서드를 호출할 때 실제 값이나 참조 값의 복사본이 전달되는 것을 의미합니다. 대부분 기본 데이터 타입들은 Call by Value 방식으로 실제 값을 메서드에 전달하니다. 이 방식에서 원래의 변수와 함수 내부의 변수가 서로 다른 메모리 공간에 위치하므로, 함수 내에서 변수의 값을 변경해도 원래의 변수 값에는 영향을 주지 않습니다.
Call by Reference는 객체나 배열과 같은 참조 데이터 타입이 메서드에 전달될 때 참조 값의 복사본이 전달되는 방식입니다. 이는 실제 객체의 메모리 주소를 가리키는 참조의 복사본이 전달되는 것을 의미합니다. 그렇기 때문에 메서드 내에서 해당 객체의 필드나 배열의 요소를 수정하면 원래의 객체나 배열도 변경됩니다. 그러나 전달받은 참조 데이터를 다른 객체나 배열로 변경하려고 해도 원래의 참조에는 영향을 주지 않습니다.
final/finally/finalize의 차이에 대해 설명해 보세요.
Java에서 변수에 final을 사용하면 의미 그대로 해당 변수는 상수가 되며, 초기화 이후에는 그 값을 변경할 수 없습니다. 그리고 final을 사용한 메서드는 다른 클래스가 상속할 때 해당 메서드 오버라이딩을 못하게 합니다. 즉, final 클래스는 다른 클래스에서 이 클래스를 상속할 수 없습니다.
finally는 예외 처리 구조인 try-catch와 함께 사용합니다. try 블록의 코드가 실행된 후, catch 블록의 예외 처리가 완료되면 항상 finally 블록의 코드가 실행되고, 이를 통해 예외가 발생해도 필수적인 리소스 해제나 정리 작업을 수행할 수 있습니다.
finalize는 Java의 Object 클래스에 정의된 메서드로, 객체가 가비지 컬렉터에 의해 회수될 때 호출되도록 설계되었습니다. 그러나 finalize는 예상치 못한 동작을 유발할 수 있고, GC가 발생하는 시점이 불분명하여 finalize 메서드가 실행될 시점에 대한 보장이 없습니다. 그리고 finalize 메서드가 오버라이딩되어 있으면 객체가 가비지 컬렉터에 의해 즉시 회수되지 않을 수 있기 때문에 사용을 권장하지 않습니다.
변수 및 데이터 타입
변수에 대해 설명해 보세요.
변수는 컴퓨터 메모리상의 저장 공간을 가리키는 식별자로, 데이터를 저장하고 참조하거나 변경할 수 있습니다. 모든 변수는 특정 타입을 가지고, 이 타입은 해당 변수가 어떤 종류의 데이터를 저장할 수 있는지를 결정합니다. 그리고 변수를 선언할 때 예상치 못한 값으로 발생할 수 있는 오류를 방지하기 위해 초기값을 할당할 수 있으며, 선언된 위치에 따라 특정 범위 내에서만 접근 및 사용이 가능합니다.
상수에 대해서 설명해 보세요.
상수는 초기화와 함께 할당한 값이 불변하기 때문에 오류를 줄이고 코드의 안정성을 높입니다. 그리고 특정 상수는 특정 타입의 값만 갖기 때문에 프로그램 내에서 타입에 따른 오류도 방지할 수 있습니다. 마지막으로 상수는 매직 넘버와 같은 직관적이지 않은 숫자 값들을 의미 있는 이름으로 대체하여 코드의 이도를 명확하게 표현하는 데 사용하기도 합니다.
리터럴에 대해서 설명해 보세요.
리터럴은 소스 코드에서 변수나 상수에 할당되는 구체적인 값 그 자체를 의미합니다. 그리고 리터럴은 프로그램 내에서 직접 사용되는 데이터 값들을 의미하며, 해당 데이터의 타입에 따라 다양한 형태로 표현됩니다.
상수와 리터럴의 차이점은 무엇인지 설명해 보세요.
상수와 리터럴은 둘 다 변경할 수 없는 값이지만, 사용 방식과 특징에서 차이점이 있습니다.
상수는 이름이 있는 고정된 값으로, 프로그램에서 재사용할 때 유용합니다. 한 번 정의하면, 이 이름을 통해 프로그램의 여러 위치에서 해당 상수 값을 사용할 수 있어 코드의 가독성과 유지보수가 용이해집니다.
반면, 리터럴은 이름이 없는 고정 값으로, 프로그램 소스 코드에서 직접 나타납니다. 같은 값을 프로그램의 여러 곳에서 사용하려면 각각의 위치에 동일한 리터럴을 반복해서 작성해야 합니다. 이는 상수를 사용하는 것에 비해서 가독성과 유지보수가 안 좋습니다.
그리고 상수는 메모리 공간을 차지하며, 이 공간에 이름이 할당됩니다. 이를 통해 프로그램이 실행될 때 메모리 내의 상수 값을 참조할 수 있습니다. 반면, 리터럴은 주로 컴파일 시 해당 값을 가지는 위치에 대체돼서 실행 시 별도의 메모리 공간을 차지하지 않습니다. 이 부분에서는 리터럴이 메모리 효율성 측면에서 상수보다 유리할 수 있습니다.
final 키워드가 변수에 어떤 영향을 주는지 설명해 보세요.
final 키워드를 선언한 변수는 불변성을 보장하기 때문에 값을 변경할 수 없습니다. final 변수는 선언하거나 생성자를 사용할 때 반드시 초기화해야 합니다. 이로 인해, 초기화되지 않은 final 변수를 사용하면 컴파일 에러가 발생하게 됩니다.
final은 변수의 값을 고정시키므로 코드 내에서 해당 변수의 값을 실수로 변경하는 것을 방지하므로 버그나 예기치 못한 행동을 방지할 수 있고, 컴파일러는 final 변수의 값을 알고 있기 때문에 최적화 단계에서 해당 변수를 직접 사용하여 약간의 성능 향상이 있을 수 있습니다.
변수의 스코프란 무엇이며, 이것이 중요한 이유는 무엇인지 설명해 보세요.
변수의 스코프는 프로그래밍에서 핵심적인 개념으로, 변수가 코드 내에서 어디서 접근하고 수정될 수 있는지를 정의하는 범위를 의미합니다. 스코프 관리의 중요성은 여러 가지 이유로 강조됩니다.
첫째, 변수의 스코프를 적절히 제한함으로써 객체지향의 캡슐화 및 정보 은닉 원칙을 구현할 수 있습니다. 이는 변수가 필요한 부분에서만 접근되고 사용되도록 보장합니다. 예를 들어, 지역 변수는 해당 스코프가 끝나면 메모리에서 해제되므로, 불필요한 메모리 사용을 방지하는 데 유리합니다.
둘째, 스코프 기반의 변수 관리를 통해 같은 이름을 가진 변수들이 서로 다른 스코프에서 독립적으로 작동할 수 있으므로 이름 충돌 없이 프로그램을 구현할 수 있습니다. 이는 변수가 어떤 목적으로 사용되는지 명확히 하여 코드의 가독성과 이해도를 높이는 데 기여합니다.
그러나, 스코프를 제대로 관리하지 않으면 의도치 않은 변수의 변경이나 접근이 발생하여 버그가 생길 가능성이 높아지므로 주의가 필요합니다. 즉, 변수의 스코프 관리는 프로그램의 안정성, 효율성 및 유지보수성을 높이는데 필수적인 요소입니다.
변수를 사용하기 전에 초기화하는 이유가 무엇인지 설명해 보세요.
자바에서는 변수를 초기화하는 것이 프로그램의 예측 가능성과 안정성을 크게 향상시킵니다. 변수를 선언과 동시에 초기화하면, 그 변수가 어떤 값으로 시작하는지 명확히 할 수 있으며, 이는 프로그램의 동작을 결정적으로 만들어 줍니다. 초기화된 변수는 코드의 일관성을 높이고, 유지보수 및 코드 리뷰 과정을 간소화하는 데 도움을 줍니다.
그리고 자바에서는 특히 지역 변수에 대해 명시적인 초기화를 강제합니다. 지역 변수를 초기화하지 않고 사용하려 하면 컴파일 오류가 발생하여 오류를 사전에 방지할 수 있습니다. 또한, 객체 참조 변수를 초기화하지 않을 경우 Null Pointer Exception의 원인이 될 수 있는데, 이를 통해 예상치 못한 오류를 미연에 방지할 수 있습니다.
성능 측면에서도 초기화는 중요한 역할을 합니다. 예를 들어, 큰 배열이나 컬렉션을 사용할 때 미리 초기 크기를 할당함으로써 메모리나 CPU 사용량의 최적화에 기여합니다. 클래스의 멤버 변수의 경우, 자동으로 기본값으로 초기화되지만, 명시적으로 초기화하는 것이 더 바람직합니다. 특히 생성자에서 멤버 변수를 초기화하는 것은 객체가 생성될 때 예상하는 상태로 시작하도록 하는 좋은 습관입니다.
기본형 타입이란 무엇인지 설명해 보세요.
기본형 타입은 자바에서 제공하는 데이터 타입 중 하나로, 실제 값을 메모리에 직접 저장합니다. 기본형 타입은 null 값을 가질 수 없으며, 자바에서 제공하는 각 타입별 기본값이 있습니다.
기본형 타입들로는 byte, short, int, long, float, double, char, boolean이 있습니다.
기본형 변수와 참조형 변수의 차이에 대해서 설명해 보세요.
기본형 변수와 참조형 변수의 차이점에 대해 설명드리겠습니다.
우선, 기본형 변수는 실제 값을 메모리상에 직접 저장하기 때문에 빠른 접근 속도를 가지며, 값을 스택 메모리 영역에 할당하여, 이 영역은 변수의 생명 주기가 끝나면 자동으로 해제가 됩니다.
참조형 변수의 경우에는 실제 데이터가 아닌 해당 데이터를 저장하고 있는 메모리의 주소값을 저장합니다. 여기서 참조 주소값은 스택 메모리 영역에 저장이 되고, 실제 객체의 데이터는 힙 메모리 영역에 저장이 됩니다.
변수의 종류는 어떤 것들이 있는지 설명해 보세요.
자바에서 변수는 데이터를 저장하고 참조하기 위한 메모리 공간을 나타내며, 세 가지 주요 타입으로 구분됩니다.
우선, 클래스 변수, 즉 static 변수는 클래스 수준에서 정의되며 static 키워드를 사용해 선언됩니다. 이런 변수는 클래스의 모든 인스턴스에 의해 공유되며, 클래스 로딩 시 메모리에 한 번 할당되어 클래스 전체에서 동일한 값을 유지합니다. 인스턴스 생성 없이 클래스 이름을 통해 접근할 수 있는 이 변수들은 오버사용 시 예기치 않은 결과나 복잡성을 초래할 수 있으므로 신중하게 사용해야 합니다.
다음으로, 인스턴스 변수는 객체 수준에서 정의되며 static 키워드 없이 클래스 내부에 선언됩니다. 각 객체는 생성될 때 이 변수를 위한 독립적인 메모리 공간을 갖으므로, 각 객체는 자신만의 변수 값을 가질 수 있습니다. 하지만 불필요한 메모리 사용을 방지하기 위해 과도한 인스턴스 변수 사용은 지양해야 합니다.
마지막으로, 지역 변수는 메서드, 생성자, 또는 특정 블록 내에서 선언되며, 해당 영역 내에서만 사용됩니다. 이 변수들은 메서드나 생성자가 호출될 때 생성되고 종료될 때 소멸합니다. 지역 변수는 기본적으로 초기화되지 않으며, 사용 전에 반드시 초기화되어야 합니다.
래퍼 클래스(Wrapper Class)에 대해서 설명해 보세요.
래퍼 클래스는 자바에서 기본형 데이터 타입을 객체 형태로 취급하기 위해 사용되는 클래스입니다.
자바가 객체 지향 프로그래밍 언어인 점을 고려할 때, 특정 상황에서 기본형 데이터 타입을 객체로 다루는 것이 필요합니다. 예를 들어, 자바의 컬렉션 프레임워크는 기본형 데이터 타입을 직접 저장할 수 없어, 대신 이들의 래퍼 클래스를 사용합니다. 래퍼 클래스는 기본형 데이터와 관련된 다양한 유틸리티 메서드를 제공하는 동시에, 기본형 데이터와는 달리 null 값을 허용하는 유연성을 갖고 있습니다. 이러한 특성 덕분에 래퍼 클래스는 자바 프로그래밍에서 중요한 역할을 하며, 다양한 상황에서 유용하게 사용됩니다.
오토박싱과 언박싱이란 무엇인지 설명해 보세요.
자바 5부터 도입된 오토박싱과 언박싱 기능은 개발자가 래퍼 클래스 변환 메서드를 직접 사용하지 않아도, 컴파일러가 자동으로 기본형 값과 래퍼 클래스 간의 변환을 처리해 주는 기능입니다.
오토박싱은 기본형 값을 해당하는 래퍼 클래스 객체로 자동 변환하는 과정을 말하며, 반대로 언박싱은 래퍼 클래스 객체를 기본형 값으로 자동 변환하는 것을 의미합니다.
이 기능의 도입으로 코드의 가독성이 크게 향상되었고, 개발자가 변환 메서드를 명시적으로 사용하지 않아도 되기 때문에 코드가 더 간결해졌습니다. 하지만 불필요한 박싱과 언박싱 연산이 반복되면 성능 저하를 초래할 수 있으며, 래퍼 클래스는 null 값을 가질 수 있지만 기본형은 그렇지 않기 때문에, 언박싱 과정에서 NullPointerException이 발생할 위험이 있습니다. 이러한 특징들을 고려하여 적절히 사용하는 것이 중요합니다.
문자열
String 객체를 생성하는 두 가지 주요 방법에 대해 설명해 보세요.
String 객체를 생성하는 방법은 두 가지가 있습니다.
첫 번째로, 큰 따옴표로 묶인 문자열 리터럴을 사용해 String 객체를 생성할 수 있습니다. 이 방법은 생성된 String 객체를 JVM의 String pool에 저장합니다. 만약, 동일한 문자열 리터럴이 이미 String pool에 저장되어 있다면, 새로운 객체를 생성하지 않고 기존 객체의 참조를 재사용할 수 있습니다.
두 번째로, new 키워드를 사용해 String 객체를 명시적으로 생성할 수 있습니다. 이 방법으로 생성된 String 객체는 힙 메모리 영역에 저장합니다. 따라서, 이 방법은 항상 새로운 객체를 생성하게 됩니다.
그래서 일반적으로 문자열 리터럴을 사용하는 방법이 메모리 효율성을 향상 시키기 때문에 권장되지만, 중요한 정보를 다루거나, 자주 변경되는 문자열, 동적으로 생성되거나 변경되는 문자열은 new 키워드를 통해 새로운 String 객체를 생성합니다.
String pool에 대해서 설명해 보세요.
String pool은 JVM의 힙 메모리 내에 위치한 저장소로, 불변성을 지닌 String 객체들을 저장하고 재사용하기 위해 사용됩니다. 이 매커니즘은 중복된 String 객체의 생성을 최소화해 메모리 효율성을 높입니다.
String pool의 사용은 메모리 사용을 최적화하는 데 도움을 주지만, 대규모 문자열 연산이 발생하는 경우 String pool의 검색 및 관리에서 오버헤드가 성능에 영향을 미칠 수 있습니다. 이러한 특징들은 고려해 String pool을 효율적으로 활용하는 것이 중요합니다.
자바에서 String은 불변하다는 것은 무엇을 의미하는지 설명해 보세요.
String의 불변성은 한 번 생성된 String 객체의 값을 변경할 수 없다는 것을 의미합니다. 그래서 어떤 연산을 수행하더라도 기존 String 객체는 유지되며, 변경이 필요한 경우 새로운 String 객체가 생성됩니다. 이러한 특성은 String pool의 구현을 가능하게 하고 메모리 효율성을 향상시킵니다.
또한, String 객체의 불변성은 여러 스레드에 의해 동시에 참조되어도 값이 변경되지 않습니다. 이는 추가적인 동기화가 없어도 스레드 안전성을 제공합니다.
하지만, 대량의 문자열 조작이 필요한 경우에는 매 연산마다 새로운 String 객체가 생성되어 성능상 오버헤드가 발생할 수 있습니다. 이러한 상황에서는 StringBuilder나 StringBuffer와 같은 가변 문자열 클래스 사용이 권장됩니다.
equals와 ‘==’의 차이는 무엇인지 설명해 보세요.
equals() 메서드는 Object 클래스에 정의된 메서드이며, 기본적으로 객체의 참조를 비교합니다. 그러나, 많은 클래스들에서는 equals() 메서드를 오버라이드하여 객체의 내용을 비교하도록 구현합니다. 이렇게 오버라이드된 equals() 메서드는 객체의 값이 실제로 동일한지를 확인하는 데 사용됩니다. 반면, == 연산자는 객체의 참조 자체를 비교합니다. 즉, 두 객체 참조가 메모리 상에서 같은 위치를 가리키는 확인합니다.
String과 StringBuilder, StringBuffer의 차이점은 무엇이며, 문자열의 가변성이 중요한 상황에서는 어떤 것을 사용해야 하는지 그 이유에 대해 설명해 보세요.
String은 불변성을 가집니다. 문자열을 수정할 때마다 기존 객체를 변경하지 않고 새로운 객체를 생성합니다. 그렇기 때문에 문자열 조작이 빈번하면 새 객체의 지속적인 생성은 메모리 낭비와 성능 저하를 일으킬 수 있습니다.
StringBuilder는 문자열의 가변성을 지원하고, 문자열을 조작할 때 기존 객체의 내용을 직접 변경하기 때문에, 메모리 효율성이 향상됩니다. 하지만 동기화를 지원하지 않아 단일 스레드 환경에는 성능이 좋지만, 멀티 스레드 환경에서는 여러 스레드 간의 충돌 위험이 있습니다.
StringBuffer도 문자열의 가변성을 지원하고, StringBuilder와 유사하게 작동합니다. 그러나 StringBuffer는 동기화를 지원하기 때문에 멀티 스레드 환경에서 사용할 수 있습니다. 하지만 이 동기화매커니즘은 단일 스레드 환경에서 성능 오버헤드를 초래할 수 있습니다.
'👨💻 기술면접 > 자바' 카테고리의 다른 글
[기술 면접] Tech Interview - Backend [02-JVM 및 메모리 관리] (0) | 2023.10.05 |
---|
Java 기본 및 핵심 개념
1. Java의 특징을 설명해주세요.
자바는 객체지향 프로그래밍 언어로, 기본 자료형을 제외한 모든 요소들이 객체로 표현되고, 객체지향 개념의 특징인 캡슐화, 상속, 추상화, 다형성이 잘 적용된 언어입니다. 자바는 JVM위에서 동작하기 때문에 운영체제에 독립적이며, GC를 통해 자동으로 메모리 관리가 가능합니다. 하지만, JVM 위에서 동작하기 때문에 상대적으로 속도가 느리며 다중 상속이나 타입에 엄격하고 제약이 많은 편입니다.
컴파일 언어와 인터프리터 언어에 대해 설명해 보세요.
컴파일 언어와 인터프리터 언어는 프로그램 코드를 어떻게 실행하는지에 따라 구분됩니다.
컴파일 언어는 컴파일러를 사용하는 언어에서 전체 코드가 한 번에 컴파일되어 실행 파일을 생성합니다. 생성된 이 실행 파일은 다른 컴퓨터에서 컴파일러나 소스 코드가 없어도 실행될 수 있습니다. 장점으로는 일반적으로 실행 속도가 더 빠를 수 있으나, 단점으로는 전체 코드가 컴파일되기 때문에 오류를 발견하기 위해 전체 컴파일 과정을 거쳐야 해서 디버깅이 복잡할 수 있고, 한 번 생성된 실행 파일은 특정 OS나 플랫폼에 종속적입니다. 대표적으로 컴파일 언어로는 자바와 C 그리고 C++이 있습니다.
그리고 인터프리터 언어는 코드가 한 줄씩 읽히면서 바로 실행되는 언어입니다. 소스 코드가 실시간으로 해석되기 때문에 원본 코드만 있으면 다양한 플랫폼에서 실행될 수 있습니다. 이 방식은 개발과 디버깅이 쉽고 코드의 변경 사항을 즉시 확인할 수 있습니다. 그러나 코드가 실시간으로 해석되어야 해서 실행 속도가 비교적 느릴 수 있습니다. 대표적으로 인터프리터 언어는 Python과 JavaScript가 있습니다.
자바의 경우 컴파일러는 통해 플랫폼에 독립적인 바이트 코드를 컴파일하고, 이 바이트 코드는 다양한 시스템에서 JVM에 의해 인터프리터 됩니다. 즉, 자바는 컴파일러와 인터프리터를 모두 사용하는 방식을 사용합니다.
바인딩에 대해 설명해 보세요.
바인딩은 사용되는 변수나 메서드의 이름이 실제로 저장된 메모리 위치나 실제로 실행될 메서드와 연결되는 과정입니다.
자바에서는 이러한 바인딩이 컴파일 타임과 런타임에 따라 다르게 동작합니다. 컴파일 타임에 이루어지는 바인딩은 정적 바인딩으로 메서드 호출이 컴파일러에 의해 결정됩니다. 이러한 바인딩은 변수의 데이터 타입에 기반하고, private, static 및 final 메서드와 같이 메서드 오버라이딩이 불가능한 경우에 사용됩니다.
런타임에 이루어지는 바인딩은 동적 바인딩으로 실제로 메모리에 호출될 메서드의 주소는 프로그램 실행 시점에 결정됩니다. 이는 객체지향 프로그래밍의 다형성을 가능하게 하고, 부모 클래스 타입의 참조 변수로 자식 클래스의 객체를 참조할 수 있고, 런타임에 어떤 클래스의 메서드를 호출할지 결정됩니다.
따라서, 바인딩 타입은 메서드와 변수가 언제 연결되는지 결정하고, 실행 흐름 제어와 성능 최적화에 중요한 역할을 합니다.
Call by Value와 Call by Reference에 대해 설명해 보세요.
Call by Value나 Call by Reference는 함수나 메서드에 인자를 전달하는 방식을 나타냅니다.
우선, Java에서 모든 메서드 호출은 Call by Value로 이루어집니다. 즉, 함수나 메서드를 호출할 때 실제 값이나 참조 값의 복사본이 전달되는 것을 의미합니다. 대부분 기본 데이터 타입들은 Call by Value 방식으로 실제 값을 메서드에 전달하니다. 이 방식에서 원래의 변수와 함수 내부의 변수가 서로 다른 메모리 공간에 위치하므로, 함수 내에서 변수의 값을 변경해도 원래의 변수 값에는 영향을 주지 않습니다.
Call by Reference는 객체나 배열과 같은 참조 데이터 타입이 메서드에 전달될 때 참조 값의 복사본이 전달되는 방식입니다. 이는 실제 객체의 메모리 주소를 가리키는 참조의 복사본이 전달되는 것을 의미합니다. 그렇기 때문에 메서드 내에서 해당 객체의 필드나 배열의 요소를 수정하면 원래의 객체나 배열도 변경됩니다. 그러나 전달받은 참조 데이터를 다른 객체나 배열로 변경하려고 해도 원래의 참조에는 영향을 주지 않습니다.
final/finally/finalize의 차이에 대해 설명해 보세요.
Java에서 변수에 final을 사용하면 의미 그대로 해당 변수는 상수가 되며, 초기화 이후에는 그 값을 변경할 수 없습니다. 그리고 final을 사용한 메서드는 다른 클래스가 상속할 때 해당 메서드 오버라이딩을 못하게 합니다. 즉, final 클래스는 다른 클래스에서 이 클래스를 상속할 수 없습니다.
finally는 예외 처리 구조인 try-catch와 함께 사용합니다. try 블록의 코드가 실행된 후, catch 블록의 예외 처리가 완료되면 항상 finally 블록의 코드가 실행되고, 이를 통해 예외가 발생해도 필수적인 리소스 해제나 정리 작업을 수행할 수 있습니다.
finalize는 Java의 Object 클래스에 정의된 메서드로, 객체가 가비지 컬렉터에 의해 회수될 때 호출되도록 설계되었습니다. 그러나 finalize는 예상치 못한 동작을 유발할 수 있고, GC가 발생하는 시점이 불분명하여 finalize 메서드가 실행될 시점에 대한 보장이 없습니다. 그리고 finalize 메서드가 오버라이딩되어 있으면 객체가 가비지 컬렉터에 의해 즉시 회수되지 않을 수 있기 때문에 사용을 권장하지 않습니다.
변수 및 데이터 타입
변수에 대해 설명해 보세요.
변수는 컴퓨터 메모리상의 저장 공간을 가리키는 식별자로, 데이터를 저장하고 참조하거나 변경할 수 있습니다. 모든 변수는 특정 타입을 가지고, 이 타입은 해당 변수가 어떤 종류의 데이터를 저장할 수 있는지를 결정합니다. 그리고 변수를 선언할 때 예상치 못한 값으로 발생할 수 있는 오류를 방지하기 위해 초기값을 할당할 수 있으며, 선언된 위치에 따라 특정 범위 내에서만 접근 및 사용이 가능합니다.
상수에 대해서 설명해 보세요.
상수는 초기화와 함께 할당한 값이 불변하기 때문에 오류를 줄이고 코드의 안정성을 높입니다. 그리고 특정 상수는 특정 타입의 값만 갖기 때문에 프로그램 내에서 타입에 따른 오류도 방지할 수 있습니다. 마지막으로 상수는 매직 넘버와 같은 직관적이지 않은 숫자 값들을 의미 있는 이름으로 대체하여 코드의 이도를 명확하게 표현하는 데 사용하기도 합니다.
리터럴에 대해서 설명해 보세요.
리터럴은 소스 코드에서 변수나 상수에 할당되는 구체적인 값 그 자체를 의미합니다. 그리고 리터럴은 프로그램 내에서 직접 사용되는 데이터 값들을 의미하며, 해당 데이터의 타입에 따라 다양한 형태로 표현됩니다.
상수와 리터럴의 차이점은 무엇인지 설명해 보세요.
상수와 리터럴은 둘 다 변경할 수 없는 값이지만, 사용 방식과 특징에서 차이점이 있습니다.
상수는 이름이 있는 고정된 값으로, 프로그램에서 재사용할 때 유용합니다. 한 번 정의하면, 이 이름을 통해 프로그램의 여러 위치에서 해당 상수 값을 사용할 수 있어 코드의 가독성과 유지보수가 용이해집니다.
반면, 리터럴은 이름이 없는 고정 값으로, 프로그램 소스 코드에서 직접 나타납니다. 같은 값을 프로그램의 여러 곳에서 사용하려면 각각의 위치에 동일한 리터럴을 반복해서 작성해야 합니다. 이는 상수를 사용하는 것에 비해서 가독성과 유지보수가 안 좋습니다.
그리고 상수는 메모리 공간을 차지하며, 이 공간에 이름이 할당됩니다. 이를 통해 프로그램이 실행될 때 메모리 내의 상수 값을 참조할 수 있습니다. 반면, 리터럴은 주로 컴파일 시 해당 값을 가지는 위치에 대체돼서 실행 시 별도의 메모리 공간을 차지하지 않습니다. 이 부분에서는 리터럴이 메모리 효율성 측면에서 상수보다 유리할 수 있습니다.
final 키워드가 변수에 어떤 영향을 주는지 설명해 보세요.
final 키워드를 선언한 변수는 불변성을 보장하기 때문에 값을 변경할 수 없습니다. final 변수는 선언하거나 생성자를 사용할 때 반드시 초기화해야 합니다. 이로 인해, 초기화되지 않은 final 변수를 사용하면 컴파일 에러가 발생하게 됩니다.
final은 변수의 값을 고정시키므로 코드 내에서 해당 변수의 값을 실수로 변경하는 것을 방지하므로 버그나 예기치 못한 행동을 방지할 수 있고, 컴파일러는 final 변수의 값을 알고 있기 때문에 최적화 단계에서 해당 변수를 직접 사용하여 약간의 성능 향상이 있을 수 있습니다.
변수의 스코프란 무엇이며, 이것이 중요한 이유는 무엇인지 설명해 보세요.
변수의 스코프는 프로그래밍에서 핵심적인 개념으로, 변수가 코드 내에서 어디서 접근하고 수정될 수 있는지를 정의하는 범위를 의미합니다. 스코프 관리의 중요성은 여러 가지 이유로 강조됩니다.
첫째, 변수의 스코프를 적절히 제한함으로써 객체지향의 캡슐화 및 정보 은닉 원칙을 구현할 수 있습니다. 이는 변수가 필요한 부분에서만 접근되고 사용되도록 보장합니다. 예를 들어, 지역 변수는 해당 스코프가 끝나면 메모리에서 해제되므로, 불필요한 메모리 사용을 방지하는 데 유리합니다.
둘째, 스코프 기반의 변수 관리를 통해 같은 이름을 가진 변수들이 서로 다른 스코프에서 독립적으로 작동할 수 있으므로 이름 충돌 없이 프로그램을 구현할 수 있습니다. 이는 변수가 어떤 목적으로 사용되는지 명확히 하여 코드의 가독성과 이해도를 높이는 데 기여합니다.
그러나, 스코프를 제대로 관리하지 않으면 의도치 않은 변수의 변경이나 접근이 발생하여 버그가 생길 가능성이 높아지므로 주의가 필요합니다. 즉, 변수의 스코프 관리는 프로그램의 안정성, 효율성 및 유지보수성을 높이는데 필수적인 요소입니다.
변수를 사용하기 전에 초기화하는 이유가 무엇인지 설명해 보세요.
자바에서는 변수를 초기화하는 것이 프로그램의 예측 가능성과 안정성을 크게 향상시킵니다. 변수를 선언과 동시에 초기화하면, 그 변수가 어떤 값으로 시작하는지 명확히 할 수 있으며, 이는 프로그램의 동작을 결정적으로 만들어 줍니다. 초기화된 변수는 코드의 일관성을 높이고, 유지보수 및 코드 리뷰 과정을 간소화하는 데 도움을 줍니다.
그리고 자바에서는 특히 지역 변수에 대해 명시적인 초기화를 강제합니다. 지역 변수를 초기화하지 않고 사용하려 하면 컴파일 오류가 발생하여 오류를 사전에 방지할 수 있습니다. 또한, 객체 참조 변수를 초기화하지 않을 경우 Null Pointer Exception의 원인이 될 수 있는데, 이를 통해 예상치 못한 오류를 미연에 방지할 수 있습니다.
성능 측면에서도 초기화는 중요한 역할을 합니다. 예를 들어, 큰 배열이나 컬렉션을 사용할 때 미리 초기 크기를 할당함으로써 메모리나 CPU 사용량의 최적화에 기여합니다. 클래스의 멤버 변수의 경우, 자동으로 기본값으로 초기화되지만, 명시적으로 초기화하는 것이 더 바람직합니다. 특히 생성자에서 멤버 변수를 초기화하는 것은 객체가 생성될 때 예상하는 상태로 시작하도록 하는 좋은 습관입니다.
기본형 타입이란 무엇인지 설명해 보세요.
기본형 타입은 자바에서 제공하는 데이터 타입 중 하나로, 실제 값을 메모리에 직접 저장합니다. 기본형 타입은 null 값을 가질 수 없으며, 자바에서 제공하는 각 타입별 기본값이 있습니다.
기본형 타입들로는 byte, short, int, long, float, double, char, boolean이 있습니다.
기본형 변수와 참조형 변수의 차이에 대해서 설명해 보세요.
기본형 변수와 참조형 변수의 차이점에 대해 설명드리겠습니다.
우선, 기본형 변수는 실제 값을 메모리상에 직접 저장하기 때문에 빠른 접근 속도를 가지며, 값을 스택 메모리 영역에 할당하여, 이 영역은 변수의 생명 주기가 끝나면 자동으로 해제가 됩니다.
참조형 변수의 경우에는 실제 데이터가 아닌 해당 데이터를 저장하고 있는 메모리의 주소값을 저장합니다. 여기서 참조 주소값은 스택 메모리 영역에 저장이 되고, 실제 객체의 데이터는 힙 메모리 영역에 저장이 됩니다.
변수의 종류는 어떤 것들이 있는지 설명해 보세요.
자바에서 변수는 데이터를 저장하고 참조하기 위한 메모리 공간을 나타내며, 세 가지 주요 타입으로 구분됩니다.
우선, 클래스 변수, 즉 static 변수는 클래스 수준에서 정의되며 static 키워드를 사용해 선언됩니다. 이런 변수는 클래스의 모든 인스턴스에 의해 공유되며, 클래스 로딩 시 메모리에 한 번 할당되어 클래스 전체에서 동일한 값을 유지합니다. 인스턴스 생성 없이 클래스 이름을 통해 접근할 수 있는 이 변수들은 오버사용 시 예기치 않은 결과나 복잡성을 초래할 수 있으므로 신중하게 사용해야 합니다.
다음으로, 인스턴스 변수는 객체 수준에서 정의되며 static 키워드 없이 클래스 내부에 선언됩니다. 각 객체는 생성될 때 이 변수를 위한 독립적인 메모리 공간을 갖으므로, 각 객체는 자신만의 변수 값을 가질 수 있습니다. 하지만 불필요한 메모리 사용을 방지하기 위해 과도한 인스턴스 변수 사용은 지양해야 합니다.
마지막으로, 지역 변수는 메서드, 생성자, 또는 특정 블록 내에서 선언되며, 해당 영역 내에서만 사용됩니다. 이 변수들은 메서드나 생성자가 호출될 때 생성되고 종료될 때 소멸합니다. 지역 변수는 기본적으로 초기화되지 않으며, 사용 전에 반드시 초기화되어야 합니다.
래퍼 클래스(Wrapper Class)에 대해서 설명해 보세요.
래퍼 클래스는 자바에서 기본형 데이터 타입을 객체 형태로 취급하기 위해 사용되는 클래스입니다.
자바가 객체 지향 프로그래밍 언어인 점을 고려할 때, 특정 상황에서 기본형 데이터 타입을 객체로 다루는 것이 필요합니다. 예를 들어, 자바의 컬렉션 프레임워크는 기본형 데이터 타입을 직접 저장할 수 없어, 대신 이들의 래퍼 클래스를 사용합니다. 래퍼 클래스는 기본형 데이터와 관련된 다양한 유틸리티 메서드를 제공하는 동시에, 기본형 데이터와는 달리 null 값을 허용하는 유연성을 갖고 있습니다. 이러한 특성 덕분에 래퍼 클래스는 자바 프로그래밍에서 중요한 역할을 하며, 다양한 상황에서 유용하게 사용됩니다.
오토박싱과 언박싱이란 무엇인지 설명해 보세요.
자바 5부터 도입된 오토박싱과 언박싱 기능은 개발자가 래퍼 클래스 변환 메서드를 직접 사용하지 않아도, 컴파일러가 자동으로 기본형 값과 래퍼 클래스 간의 변환을 처리해 주는 기능입니다.
오토박싱은 기본형 값을 해당하는 래퍼 클래스 객체로 자동 변환하는 과정을 말하며, 반대로 언박싱은 래퍼 클래스 객체를 기본형 값으로 자동 변환하는 것을 의미합니다.
이 기능의 도입으로 코드의 가독성이 크게 향상되었고, 개발자가 변환 메서드를 명시적으로 사용하지 않아도 되기 때문에 코드가 더 간결해졌습니다. 하지만 불필요한 박싱과 언박싱 연산이 반복되면 성능 저하를 초래할 수 있으며, 래퍼 클래스는 null 값을 가질 수 있지만 기본형은 그렇지 않기 때문에, 언박싱 과정에서 NullPointerException이 발생할 위험이 있습니다. 이러한 특징들을 고려하여 적절히 사용하는 것이 중요합니다.
문자열
String 객체를 생성하는 두 가지 주요 방법에 대해 설명해 보세요.
String 객체를 생성하는 방법은 두 가지가 있습니다.
첫 번째로, 큰 따옴표로 묶인 문자열 리터럴을 사용해 String 객체를 생성할 수 있습니다. 이 방법은 생성된 String 객체를 JVM의 String pool에 저장합니다. 만약, 동일한 문자열 리터럴이 이미 String pool에 저장되어 있다면, 새로운 객체를 생성하지 않고 기존 객체의 참조를 재사용할 수 있습니다.
두 번째로, new 키워드를 사용해 String 객체를 명시적으로 생성할 수 있습니다. 이 방법으로 생성된 String 객체는 힙 메모리 영역에 저장합니다. 따라서, 이 방법은 항상 새로운 객체를 생성하게 됩니다.
그래서 일반적으로 문자열 리터럴을 사용하는 방법이 메모리 효율성을 향상 시키기 때문에 권장되지만, 중요한 정보를 다루거나, 자주 변경되는 문자열, 동적으로 생성되거나 변경되는 문자열은 new 키워드를 통해 새로운 String 객체를 생성합니다.
String pool에 대해서 설명해 보세요.
String pool은 JVM의 힙 메모리 내에 위치한 저장소로, 불변성을 지닌 String 객체들을 저장하고 재사용하기 위해 사용됩니다. 이 매커니즘은 중복된 String 객체의 생성을 최소화해 메모리 효율성을 높입니다.
String pool의 사용은 메모리 사용을 최적화하는 데 도움을 주지만, 대규모 문자열 연산이 발생하는 경우 String pool의 검색 및 관리에서 오버헤드가 성능에 영향을 미칠 수 있습니다. 이러한 특징들은 고려해 String pool을 효율적으로 활용하는 것이 중요합니다.
자바에서 String은 불변하다는 것은 무엇을 의미하는지 설명해 보세요.
String의 불변성은 한 번 생성된 String 객체의 값을 변경할 수 없다는 것을 의미합니다. 그래서 어떤 연산을 수행하더라도 기존 String 객체는 유지되며, 변경이 필요한 경우 새로운 String 객체가 생성됩니다. 이러한 특성은 String pool의 구현을 가능하게 하고 메모리 효율성을 향상시킵니다.
또한, String 객체의 불변성은 여러 스레드에 의해 동시에 참조되어도 값이 변경되지 않습니다. 이는 추가적인 동기화가 없어도 스레드 안전성을 제공합니다.
하지만, 대량의 문자열 조작이 필요한 경우에는 매 연산마다 새로운 String 객체가 생성되어 성능상 오버헤드가 발생할 수 있습니다. 이러한 상황에서는 StringBuilder나 StringBuffer와 같은 가변 문자열 클래스 사용이 권장됩니다.
equals와 ‘==’의 차이는 무엇인지 설명해 보세요.
equals() 메서드는 Object 클래스에 정의된 메서드이며, 기본적으로 객체의 참조를 비교합니다. 그러나, 많은 클래스들에서는 equals() 메서드를 오버라이드하여 객체의 내용을 비교하도록 구현합니다. 이렇게 오버라이드된 equals() 메서드는 객체의 값이 실제로 동일한지를 확인하는 데 사용됩니다. 반면, == 연산자는 객체의 참조 자체를 비교합니다. 즉, 두 객체 참조가 메모리 상에서 같은 위치를 가리키는 확인합니다.
String과 StringBuilder, StringBuffer의 차이점은 무엇이며, 문자열의 가변성이 중요한 상황에서는 어떤 것을 사용해야 하는지 그 이유에 대해 설명해 보세요.
String은 불변성을 가집니다. 문자열을 수정할 때마다 기존 객체를 변경하지 않고 새로운 객체를 생성합니다. 그렇기 때문에 문자열 조작이 빈번하면 새 객체의 지속적인 생성은 메모리 낭비와 성능 저하를 일으킬 수 있습니다.
StringBuilder는 문자열의 가변성을 지원하고, 문자열을 조작할 때 기존 객체의 내용을 직접 변경하기 때문에, 메모리 효율성이 향상됩니다. 하지만 동기화를 지원하지 않아 단일 스레드 환경에는 성능이 좋지만, 멀티 스레드 환경에서는 여러 스레드 간의 충돌 위험이 있습니다.
StringBuffer도 문자열의 가변성을 지원하고, StringBuilder와 유사하게 작동합니다. 그러나 StringBuffer는 동기화를 지원하기 때문에 멀티 스레드 환경에서 사용할 수 있습니다. 하지만 이 동기화매커니즘은 단일 스레드 환경에서 성능 오버헤드를 초래할 수 있습니다.
'👨💻 기술면접 > 자바' 카테고리의 다른 글
[기술 면접] Tech Interview - Backend [02-JVM 및 메모리 관리] (0) | 2023.10.05 |
---|