자바에서 Inner 클래스는 다른 클래스 내에 선언된 클래스를 의미합니다. Inner 클래스는 외부 클래스의 멤버로 간주되며, 외부 클래스의 인스턴스와 직접적으로 상호 작용할 수 있습니다.
Inner 클래스는 외부 클래스 내에서만 사용되며, 외부 클래스의 멤버에 대한 접근 권한을 가지고 있습니다.
이러한 특성 때문에 Inner 클래스는 외부 클래스와 강한 결합을 가지며, 외부 클래스의 내부 구현을 보완하거나 보호하는 데 사용될 수 있습니다.
1 . 내부 클래스의 종류:
인스턴스 내부 클래스 (Instance Inner Class): 외부 클래스의 인스턴스와 관련된 내부 클래스로, 외부 클래스 인스턴스 생성 시 생성됩니다.
정적 내부 클래스 (Static Inner Class): 외부 클래스와 연결되지만, 인스턴스 생성과는 독립적으로 사용할 수 있는 내부 클래스입니다.
지역 내부 클래스 (Local Inner Class): 메서드나 블록 내부에서 선언되는 내부 클래스로, 해당 블록 내에서만 유효합니다.
익명 내부 클래스 (Anonymous Inner Class): 이름이 없는 내부 클래스로, 인터페이스나 추상클래스를 구현하거나 확장한 클래스를 생성할 때 사용됩니다.
인스턴스 내부 클래스와 지역 내부 클래스를 가장 많이 쓴다.
2 . 접근성:
Inner 클래스는 외부 클래스의 멤버이기 때문에, 외부 클래스의 private 멤버에도 접근할 수 있습니다.
반대로, 외부 클래스는 Inner 클래스의 private 멤버에도 접근할 수 있습니다.
3 . 인스턴스 생성:
Inner 클래스의 인스턴스를 생성하기 위해서는 먼저 외부 클래스의 인스턴스를 생성한 후, 그 인스턴스를 사용하여 Inner 클래스의 인스턴스를 생성합니다.
Static Inner 클래스의 경우, 외부 클래스의 인스턴스 없이도 직접적으로 인스턴스를 생성할 수 있습니다.
public class OuterClass {
private int outerData;
public OuterClass(int outerData) {
this.outerData = outerData;
}
public void outerMethod() {
System.out.println("Outer Method");
}
public class InnerClass {
private int innerData;
public InnerClass(int innerData) {
this.innerData = innerData;
}
public void innerMethod() {
System.out.println("Inner Method");
System.out.println("Outer Data: " + outerData);
outerMethod();
}
}
public static void main(String[] args) {
OuterClass outerObj = new OuterClass(10);
OuterClass.InnerClass innerObj = outerObj.new InnerClass(20);
innerObj.innerMethod();
}
}
- 위의 코드에서 OuterClass는 외부 클래스로, InnerClass는 내부 클래스입니다.
- OuterClass에는 outerData라는 private 멤버 변수와 outerMethod()라는 메서드가 있습니다.
- InnerClass에는 innerData라는 private 멤버 변수와 innerMethod()라는 메서드가 있습니다.
메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에, OuterClass.InnerClass의 인스턴스를 생성합니다. 이렇게 내부 클래스의 인스턴스를 생성하기 위해서는 외부 클래스의 인스턴스가 먼저 필요합니다. 마지막으로 내부 클래스의 인스턴스에서 innerMethod()를 호출하여 결과를 출력합니다.
Inner 클래스는 외부 클래스의 멤버에 접근할 수 있으므로, Inner 클래스의 메서드에서 외부 클래스의 멤버에 접근하거나 수정하는 것도 가능합니다. 이를 통해 내부 클래스는 외부 클래스의 구현을 보완하거나 외부 클래스의 정보에 접근할 수 있습니다.
public class OuterClass {
private int outerData;
private InnerClass innerObj;
public OuterClass(int outerData) {
this.outerData = outerData;
}
public void outerMethod() {
System.out.println("Outer Method");
}
public void createInnerObject() {
innerObj = new InnerClass(20);
innerObj.innerMethod();
}
public class InnerClass {
private int innerData;
public InnerClass(int innerData) {
this.innerData = innerData;
}
public void innerMethod() {
System.out.println("Inner Method");
System.out.println("Outer Data: " + outerData);
outerMethod();
}
}
public static void main(String[] args) {
OuterClass outerObj = new OuterClass(10);
outerObj.createInnerObject();
}
}
위의 코드에서 OuterClass에는 innerObj라는 필드가 추가되었습니다.
이 필드는 InnerClass의 객체를 참조하는 역할을 합니다.
createInnerObject() 메서드 내에서는 InnerClass의 객체를 생성하고 innerObj 필드에 대입합니다.
이렇게 외부 클래스의 메서드에서 내부 클래스의 객체를 생성하여 필드에 대입할 수 있습니다.
메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에 createInnerObject()를 호출하여 내부 클래스의 객체를 생성하고 필드에 대입합니다.
이 코드를 실행하면 OuterClass의 createInnerObject() 메서드에서 InnerClass의 객체를 생성하고 해당 객체의 innerMethod()를 호출하여 결과를 출력합니다. 또한, 외부 클래스의 필드인 innerObj를 통해 내부 클래스의 객체에 접근할 수 있습니다.
정적(Static) 클래스는 다양한 상황에서 다양한 목적으로 사용될 수 있습니다. 주요 목적 및 사용 사례는 다음과 같습니다:
1 . 유틸리티 클래스 : 정적 클래스는 주로 유틸리티 기능을 제공하는 데 사용됩니다. 예를 들어, Math 클래스는 자바의 내장 정적 클래스로, 수학 연산과 관련된 메서드를 제공합니다. 이러한 유틸리티 기능은 객체 지향 프로그래밍에서 자주 사용되는 기능을 모듈화하고, 코드 재사용성을 높이는 데 유용합니다.
2 . 네임스페이스 분리 : 정적 클래스는 클래스의 네임스페이스를 확장하여 별도의 이름공간을 제공합니다. 이는 클래스 이름 충돌을 방지하고, 서로 관련된 기능을 그룹화하여 코드를 구조화하는 데 도움을 줍니다. 예를 들어, 여러 유틸리티 클래스를 정적 클래스로 구현하면, 각 클래스의 이름을 분리하여 구분되는 기능을 제공할 수 있습니다.
3 . Factory Method : 정적 클래스는 팩토리 메서드 패턴을 구현하는 데 사용될 수 있습니다. 팩토리 메서드 패턴은 객체의 생성을 서브클래스에 위임하는 방식으로, 객체를 생성하기 위한 별도의 클래스나 메서드를 제공합니다. 이때 정적 클래스는 팩토리 메서드를 구현하고, 객체 생성을 담당하는 역할을 수행할 수 있습니다.
4 . Helper 클래스 : 정적 클래스는 다른 클래스의 보조 역할을 수행하는 Helper 클래스로 사용될 수 있습니다. 이는 주로 객체의 생성, 변환, 유효성 검사 등과 같은 작업을 수행하는 메서드를 정적으로 제공하는 클래스입니다. Helper 클래스를 사용하면 관련된 기능을 한 곳에 모아놓고 재사용성을 높일 수 있습니다.
5 . 상수 클래스 : 정적 클래스는 상수를 정의하는 데 사용될 수 있습니다. 상수 값들을 정적 필드로 선언하여 다른 클래스에서 사용할 수 있게 하거나, 열거 타입(enum)을 사용하여 상수 값을 그룹화할 수도 있습니다.
정적 클래스는 위와 같은 목적으로 사용되며, 코드의 구조화와 모듈화를 도와주고 재사용성을 높여주는 기능을 제공합니다. 하지만 모든 클래스를 정적 클래스로 정의하는 것은 적절하지 않으며, 사용 시에는 해당 클래스의 목적과 적합성을 고려하여 적절하게 활용하는 것이 중요합니다.
public class OuterClass {
private static int outerField;
public static void outerMethod() {
// Static Inner 클래스의 인스턴스 생성
InnerClass inner = new InnerClass();
inner.innerMethod();
}
public static class InnerClass {
private int innerField;
public void innerMethod() {
outerField = 10; // 외부 클래스의 정적 멤버 접근
innerField = 20; // 내부 클래스의 멤버 접근
System.out.println("Outer Field: " + outerField);
System.out.println("Inner Field: " + innerField);
}
}
public static void main(String[] args) {
// Static Inner 클래스의 인스턴스 생성
OuterClass.InnerClass inner = new OuterClass.InnerClass();
inner.innerMethod();
}
}
- OuterClass는 외부 클래스이고, InnerClass는 정적 내부 클래스입니다.
- InnerClass는 OuterClass의 멤버로 선언되었으며, outerMethod()에서 InnerClass의 인스턴스를 생성하고 innerMethod()를 호출합니다.
- innerMethod()에서는 외부 클래스의 정적 멤버인 outerField와 내부 클래스의 innerField에 접근하여 값을 출력합니다.
정적 내부 클래스는 외부 클래스의 인스턴스에 의존하지 않고 직접적으로 인스턴스를 생성할 수 있습니다.
따라서, main() 메서드에서도 OuterClass.InnerClass의 형태로 정적 내부 클래스의 인스턴스를 생성하고 innerMethod()를 호출할 수 있습니다.
public class OuterClass {
private static int outerData = 10;
private int instanceData = 20;
public static void outerMethod() {
System.out.println("Outer Method");
}
public void instanceMethod() {
System.out.println("Instance Method");
}
public static class StaticInnerClass {
private int innerData;
public StaticInnerClass(int innerData) {
this.innerData = innerData;
}
public void innerMethod() {
System.out.println("Inner Method");
System.out.println("Outer Data: " + outerData);
outerMethod();
// 정적 내부 클래스에서는 외부 클래스의 인스턴스 멤버에 직접 접근할 수 없습니다.
// System.out.println("Instance Data: " + instanceData); // 컴파일 에러
// 정적 내부 클래스에서는 외부 클래스의 인스턴스 메서드를 직접 호출할 수 없습니다.
// instanceMethod(); // 컴파일 에러
}
}
public static void main(String[] args) {
OuterClass outerObj = new OuterClass();
OuterClass.StaticInnerClass innerObj = new OuterClass.StaticInnerClass(30);
innerObj.innerMethod();
}
}
- OuterClass는 외부 클래스로, StaticInnerClass는 정적 내부 클래스입니다.
- OuterClass에는 outerData라는 정적 멤버 변수와 outerMethod()라는 정적 메서드가 있습니다.
- StaticInnerClass에는 innerData라는 인스턴스 멤버 변수와 innerMethod()라는 메서드가 있습니다.
- StaticInnerClass의 innerMethod()에서는 OuterClass의 정적 멤버 변수인 outerData에 접근하고 outerMethod()를 호출합니다.
정적 내부 클래스에서는 외부 클래스의 정적 멤버 변수와 메서드에 접근할 수 있습니다.
메인 메서드에서는 OuterClass의 인스턴스를 생성한 후에, OuterClass.StaticInnerClass의 인스턴스를 생성합니다. 이렇게 정적 내부 클래스의 인스턴스를 생성할 때는 외부 클래스의 인스턴스가 필요하지 않습니다. 마지막으로 정적 내부 클래스의 인스턴스에서 innerMethod()를 호출하여 결과를 출력합니다.
public class OuterClass {
private int outerField;
public void outerMethod() {
int localVariable = 10;
// 지역 내부 클래스 정의 및 인스턴스 생성
class LocalInnerClass {
public void innerMethod() {
outerField = 20; // 외부 클래스의 멤버 접근
System.out.println("Outer Field: " + outerField);
System.out.println("Local Variable: " + localVariable);
}
}
LocalInnerClass inner = new LocalInnerClass();
inner.innerMethod();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
- OuterClass는 외부 클래스이고, LocalInnerClass는 지역 내부 클래스입니다.
- outerMethod()에서는 LocalInnerClass를 지역 변수로 선언하고, 해당 클래스의 인스턴스를 생성하여 innerMethod()를 호출합니다.
- innerMethod()에서는 외부 클래스의 멤버인 outerField와 지역 변수인 localVariable에 접근하여 값을 출력합니다.
지역 내부 클래스는 메서드나 블록 내에서 선언되는 클래스로, 선언된 블록 내에서만 유효합니다. 따라서, outerMethod() 내에서만 LocalInnerClass의 인스턴스를 생성하고 사용할 수 있습니다.
이를 통해 지역 내부 클래스는 외부 메서드의 지역 변수에 접근하거나 메서드의 동작을 보완하는 용도로 사용될 수 있습니다.
로컬 내부 클래스(Local Inner Class)는 메서드 내부에 정의되는 내부 클래스로, 특정 메서드에서만 사용할 수 있습니다. 로컬 내부 클래스는 다음과 같은 경우에 사용될 수 있습니다:
1 . 캡슐화와 정보 은닉 : 메서드 내에서 로컬 내부 클래스를 정의하면, 해당 클래스는 메서드 내부에서만 접근 가능합니다. 이로써 클래스의 범위를 제한하여 정보 은닉과 캡슐화를 달성할 수 있습니다. 메서드 외부에서는 로컬 내부 클래스에 직접 접근할 수 없으므로, 클래스의 내부 구현을 감추고 메서드의 로직을 단순화할 수 있습니다.
2 . 메서드 지역 변수와의 상호작용 : 로컬 내부 클래스는 정의된 메서드 내부에서만 접근 가능한 로컬 변수에 쉽게 접근할 수 있습니다. 이를 통해 메서드 내부에서 사용되는 데이터에 접근하고 조작하는 용도로 로컬 내부 클래스를 활용할 수 있습니다.
3 . 콜백 구현 : 로컬 내부 클래스는 이벤트 처리나 콜백 구현에 활용될 수 있습니다. 메서드 내에서 인터페이스를 구현하는 로컬 내부 클래스를 정의하여 이벤트 핸들링 등의 작업을 수행할 수 있습니다.
로컬 내부 클래스는 메서드 내에서 선언되고 사용되므로, 해당 메서드가 호출되기 전까지는 로컬 내부 클래스에 대한 인스턴스를 생성할 수 없습니다. 로컬 내부 클래스는 메서드 내에서 지역 변수처럼 동작하며, 메서드의 실행이 끝나면 로컬 내부 클래스의 인스턴스도 사라지게 됩니다.
로컬 내부 클래스는 클래스의 범위를 제한하고 캡슐화를 통해 코드의 가독성과 유지보수성을 높일 수 있는 유용한 기능입니다. 하지만 필요에 따라 적절하게 사용해야 하며, 클래스의 범위를 제한하는 것이 코드의 명확성과 유연성을 개선하는 데 도움이 되는지 신중하게 고려해야 합니다.
public class OuterClass {
private int privateField;
public OuterClass() {
privateField = 10;
}
public void outerMethod() {
InnerClass inner = new InnerClass();
inner.innerMethod();
}
public class InnerClass {
public void innerMethod() {
int value = privateField; // 외부 클래스의 private 멤버에 접근
System.out.println("Private Field: " + value);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
- OuterClass는 외부 클래스이고, InnerClass는 Inner 클래스입니다.
- OuterClass의 privateField는 private으로 선언되었지만, InnerClass에서 접근하여 값을 읽을 수 있습니다.
- innerMethod()에서 privateField 값을 출력하여 확인할 수 있습니다.
- Inner 클래스는 외부 클래스의 멤버에 접근할 수 있기 때문에, private 멤버를 포함한 모든 멤버에 접근이 가능합니다.
이는 Inner 클래스가 외부 클래스의 내부 구현을 보완하거나 외부 클래스의 정보에 접근할 수 있는 강력한 기능을 제공합니다.
아래는 외부 클래스가 Inner 클래스의 private 멤버에 접근하는 예제 코드입니다
public class OuterClass {
private int outerField;
public OuterClass() {
outerField = 10;
}
public void outerMethod() {
InnerClass inner = new InnerClass();
inner.innerMethod();
}
private class InnerClass {
private int innerField;
private void innerMethod() {
innerField = 20;
outerField = 30; // 외부 클래스의 private 멤버에 접근
System.out.println("Inner Field: " + innerField);
System.out.println("Outer Field: " + outerField);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.outerMethod();
}
}
- OuterClass는 외부 클래스이고, InnerClass는 Inner 클래스입니다.
- InnerClass의 innerMethod()에서 outerField는 OuterClass의 private 멤버인데도 접근하여 값을 수정할 수 있습니다.
- innerMethod()에서 innerField와 outerField 값을 출력하여 확인할 수 있습니다.
외부 클래스는 Inner 클래스의 private 멤버에 접근할 수 있으므로, Inner 클래스의 private 멤버에 대한 수정 및 조회도 가능합니다.
이를 통해 외부 클래스는 Inner 클래스의 구현 세부 사항에 접근하거나 조작할 수 있습니다.
public class Singleton {
private static Singleton instance;
private Singleton() {
// private 생성자
}
//synchronized : 임계 영역 여러 쓰레드에서 가져다 쓰라고
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
public class Singleton {
private Singleton() {
// private 생성자
}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
public static synchronized Singleton getInstance(){}
의 코드는 Singleton 디자인 패턴을 구현한 예시입니다.
Singleton은 애플리케이션에서 단 하나의 인스턴스만을 생성하고, 이후에는 그 인스턴스를 공유하여 사용하는 패턴입니다.
해당 코드는 getInstance() 메서드를 호출하여 Singleton 인스턴스를 반환하는 정적 메서드입니다.
이 메서드는 public으로 선언되어 외부에서 접근할 수 있으며, synchronized 키워드로 동기화되어 여러 스레드가 동시에 접근하는 것을 방지합니다.
메서드의 반환 타입은 Singleton 클래스의 타입이며, 이는 하나의 인스턴스만을 반환하기 때문에 항상 동일한 객체를 반환합니다.
Singleton 디자인 패턴은 전공자들이 많이 사용하는 디자인 패턴 중 하나입니다.
이 패턴을 사용하면 인스턴스를 여러 개 생성하지 않고도 전역적인 접근이 필요한 상황에서 객체를 사용할 수 있습니다. 이를 통해 메모리 사용량을 줄이고, 객체의 일관성을 유지할 수 있습니다.
Thread Safety : getInstance() 메서드는 여러 스레드가 동시에 접근할 수 있으므로, 동시에 println을 사용하면 출력 결과가 서로 섞일 수 있습니다. 이는 디버깅이나 로깅 시에 문제를 야기할 수 있습니다.
Performance : println은 I/O 연산을 수행하므로 상대적으로 느린 작업입니다. getInstance() 메서드 내에서 println을 사용하면 불필요한 I/O 작업이 반복적으로 수행되어 성능 저하를 가져올 수 있습니다.
Separation of Concerns : Singleton 클래스는 인스턴스 생성에 초점을 맞춘 것이므로, 출력과는 관련이 없는 역할을 수행합니다. 따라서 getInstance() 메서드 내에서 println을 사용하는 것은 개념적으로 부적절한 것으로 간주됩니다. 대신, 클라이언트 코드에서 필요한 경우에 출력을 처리하는 것이 더 적합합니다.