▩ 목 차 ▩
1. 클래스 안의 클래스
1-1. Static nested 클래스의 특징
1-2. 내부클래스와 익명클래스
1-3. Nested 클래스의 특징은 중요하니 꼭 알자.
1-4. 정리
■ 1. 클래스 안의 클래스 ■
자바에서는 클래스 안에 클래스가 들어갈 수 있다. 이러한 클래스를 "Nested 클래스"라고 부른다.
==> "Nested 클래스"가 존재하는 이유는 코드를 간단하게 표현하기 위함이다.
"Nested 클래스"는 자바 기반의 UI 처리를 할 때 사용자의 입력이나, 외부의 이벤트에 대한 처리를 하는 곳에서 가장 많이 사용된다.
"Nested 클래스"는 선언한 방법[static]에 따라 "Static nested 클래스"와 내부 클래스"로 구분된다.
내부 클래스는 다시 두 가지로 나뉘는데,
이름이 있는 내부 클래스는 "로컬(혹은 지역) 내부 클래스"라고 하고, 이름이 없는 클래스를 "익명 내부 클래스"라고 부른다.
줄여서 "내부 클래스"와 "익명 클래스"로 부른다.
[EX]
package part16;
public class PublicClass {
}
class JustNotPubicClass{
}
PublicClass라는 클래스와 JustNotPublicClass가 하나의 .java 파일에 선언되어 있다.
이 파일의 이름은 public한 클래스의 이름을 따라서 PublicClass.java라고 정해야만 한다.
여기서 JustNotPublicClass는 이름 그대로 pulbic한 클래스가 아닐 뿐이지 이 클래스는 내부 클래스가 아니다.
Nested 클래스를 만드는 이유
- 한곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 필요가 있을 때 - Static Nestd 클래스를 사용하는 이유
- 캡슐화가 필요 할 때(예를 들어 A라는 클래스에 private 변수가 있다. 이 변수에 접근하고 싶은 B라는 클래스를 선언하고, B클래스를 외부에 노출시키고 싶지 않을 경우에 여기에 속한다.) 즉, 내부 구현을 감추고 싶을 때를 말한다. - 내부 클래스를 사용하는 이유
- 소스의 가독성과 유지보수성을 높이고 싶을 때
■ 1-1. Static nested 클래스의 특징
내부 클래스는 감싸고 있는 외부 클래스의 어떤 변수도 접근할 수 있다. 심지어 private로 선언된 변수까지도 접근 가능하다. 하지만 static nested 클래스는 static이라 선언된 인스턴스 변수만 사용 가능하다.
==> 이름 그대로 Static하기 때문이다.
[EX] - static nested 클래스 생성
package part16;
public class OuterOfStatic {
static class StaticNeted{
private int value=0; //1
public int getValue() { //2
return value;
}
public void setValue(int value) {
this.value=value;
}
}
}
위의 코드에서 주석코드 처리된 곳을 보면
- OuterOfStatic이라는 클래스를 선언했다.
- OuterOfStatic 내부에 static으로 선언된 StaticNected라는 클래스가 선언되어 있다. 그 안에 선언된 메소드의 내용들은 간단하게 인스턴스 변수의 값을 지정하고 조회하는 작업을 수행한다.
위에 있는 StaticNected라는 클래스의 객체 생성은 어떻게 할까? 예제를 보며 이해해보자.
[EX] - StaticNected 클래스 객체 사용 및 메소드 사용
package part16;
public class NestedSample {
public static void main(String[] args) {
NestedSample sample = new NestedSample();
sample.makeStaticNestedObject();
}
public void makeStaticNestedObject() {
OuterOfStatic.StaticNeted staticNested = new OuterOfStatic.StaticNeted();
staticNested.setValue(3);
System.out.println(staticNested.getValue());
}
}
Static Nested 클랫를 만들었을 때 객체 생성은 클래스 파일 이름처럼 중간에 $를 쓰는 것이 아니라 감싸고 있는 클래스 이름 뒤에 .(점)을 찍고 쓰면 된다. [ 객체를 생성한 이후에 사용하는 방법은 일반 클래스와 동일하다. ]
왜 이리 귀찮게 Static nested 클래스를 만들까?
==> 클래스를 묶기 위해서다.
[ 예를들어, 만약 학교를 관리하는 School이라는 클래스를 만들고, 대학을 관리하는 UniverSity라는 클래스를 만들었을때를 생각해보자. 이때 Student라는 클래스를 만들면 School의 학생인지 University의 학생인지가 불분명해진다. 하지만 만약 School 내에 static nested 클래스인 Student를 만든다면, 이 클래스의 용도가 보다 명확해진다. 물론 이 경우에 SchoolStudent라는 식의 클래스를 만들어도 되겠지만, 필요하다면 이렇게 만들 수도 있다는 말이다. 또한 Shcool.Student 클래스는 School 클래스에 만들었기 때문에 University 클래스에서는 사용할 수가 없다. ]
[EX] - University 클래스의 예
public class University {
static class Student {
}
}
[EX] - School 클래스의 예
public class School {
static class Student {
}
}
즉, 겉으로 보기에는 유사하지만, 내부적으로 구현이 달라야 할 때 이와 같이 static nested 클래스를 사용한다.
■ 1-2. 내부클래스와 익명클래스
Static nested 클래스와 내부 클래스와의 차이는 겉으로 보기에는 그냥 static을 쓰느냐 쓰지 않느냐의 차이만 있을 뿐이다.
예시를 보며 차이를 확인하자.
[EX] - 내부클래스
package part16;
public class OuterOfInner {
class Inner{
private int value=0;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value=value;
}
}
}
앞절의 예제에서 StaticNested와 Inner 클래스의 내부 내용은 동일하다. 하지만, Inner 클래스의 선언부에는 static 선언이 없다
==> 그렇기에 Inner 클래스의 객체를 생성하는 방법은 static Nested와 다르다.
[EX] - Inner클래스의 객체 생성
package part16;
public class InnerSample {
public static void main(String[] args) {
InnerSample sample = new InnerSample();
sample.makeInnerObject();
}
public void makeInnerObject() {
OuterOfInner outer = new OuterOfInner();
OuterOfInner.Inner inner = outer.new Inner();
inner.setValue(3);
System.out.println(inner.getValue());
}
}
위 코드를 보게 되면 내부 클래스의 객체를 생성한 다음에 사용하는 방법은 차이가 없다. 하지만 객체를 생성하는 방법에 차이가 있다.
- Inner 클래스의 객체를 생성하기 전에는 먼저 Inner 클래스를 감싸고 있는 OuterOfInner라는 클래스의 객체를 만들어야만 한다.
[ 여기서는 outer라는 객체다. ] - 그리고 이 outter 객체를 통해서 Inner 클래스의 객체를 만들어 낼 수 있다.
Inner 클래스의 객체를 만드는 방법이 복잡한데도 왜 만들고 쓸까?
==> 캡슐화 때문이다. 하나의 클래스에서 어떤 공통적인 작업을 수행하는 클래스가 필요한데 다른 클래스에서는 그 클래스가 전혀 필요가 없을 때 이러한 내부 클래스를 만들어 사용한다. 내부 클래스는 GUI 관련 프로그램을 개발할 때 가장 많이 사용한다.
[ *GUI : Graphic User Interface의 약자로 사용자 화면용 애플리케이션을 의미하다. ]
GUI에서 내부 클래스들이 많이 사용되는 부분은 리스너 라는 것을 처리할 때다. 사용자가 버튼을 클릭하거나, 키보드를 입력할 때는 모두 인벤트 라는 것이 발생하게 된다. 어떤 버튼이 눌렀을 때 해야하는 작업을 해야 하는 작업을 하기 위해서 내부 클래스를 만들어 사용하게 된다.
그런데, 하나의 애플리케이션에서 어떤 버튼이 눌렸을 때 수행해야 하는 작업은 대부분 상이하다.
==> 그러니, 하나의 별도 클래스를 만들어 사용하는 것보다는 내부 클래스를 만드는 것이 훨씬 편하다.
내부 클래스를 만드는 것보다도 더 간단한 방법은 "익명 클래스"를 만드는 것이다.
익명이라는 것은 영어로 Anonymous라고 하고, 자바에서 익명 클래스는 말 그대로 이름이 없는 클래스다.
간단한 버튼을 처리하는 예제를 통해서 익명 클래스에 대해 알아보자 [ GUI는 제공하지는 않고 그냥 객체만 생성하는 예제다. ]
[EX] - 버튼 로직 생성
package part16;
public class MagicButton {
public MagicButton() {
}
private EventListener listener;
public void setListener(EventListener listener) {
this.listener = listener;
}
public void onClickProcess() {
if(listener!=null) {
listener.onClick();
}
}
}
[EX] - EventListener
package part16;
public interface EventListener {
public void onClick();
}
[EX] - 내부 클래스를 별도로 만들고 사용( 내부 클래스를 이용하여 사용하려는 인터페이스를 선언하고 구현하여 그것을 사용하는 것임)
package part16;
public class AnonymousSample {
public static void main(String[] args) {
AnonymousSample sample = new AnonymousSample();
sample.setButtonListener();
}
private void setButtonListener() {
MagicButton button = new MagicButton();
MagicButtonListener listener = new MagicButtonListener();
button.setListener(listener);
button.onClickProcess();
}
class MagicButtonListener implements EventListener{
public void onClick() {
System.out.println("Magic Button Clicked!!!");
}
}
}
위의 코드들을 살펴보자.
- 우선 단순하게 선언되어 있는 EventListener 인터페이스를 선언한다.
- MagicButton 클래스는 간단하게 구성되어 있고, setListener()메소드에서는 매개 변수로 EventListener 인터페이스의 객체를 받고 인스턴스 변수인 listener에 넣어준다. onClickProcess()메소드에서는 인스턴스 변수인 listener가 null이 아니면 lister의 onClick()메소드를 출력한다. [ onClick() 메소드는 setListener()메소드에서 매개변수로 넘겨받은 EventListener의 객체에 담겨있는 onClick()메소드를 출력하는 것이다.]
- AnonymousSample 클래스는 간단하게 구성되어 있고, setButtonListener()메소드에서는 MagicBtton 클래스의 객체를 생성한다. 또한 MagicButtonListener 클래스의 객체를 생성한다. 그 후 MagicBtton의 객체에서 setListener()메소드를 불러오고 거기에 넣은 매개변수를 전에 만든 MagicButtonListener의 객체를 넣어준다. 그 후 MagicBtton의 객체의 onClickProcess()메소드를 불러온다. [ 미리 EventListener 인터페이스를 구현한 MagicButtonListener 클래스를 만든다. EventListener의 인터페이스에는 onClick()메소드가 있기 때문에 onClick()메소드를 구현해줘야 한다. 또한 MagicButtonListener 클래스는 AnonymousSample 클래스 내부에 존재한다. ]
==> 실제로는 GUI에서는 이 예제처럼 별도로 onCLickProcess() 메소드를 실행하지는 않는다.
화면이 클릭되었을 때 MagicButtonListener에 있는 onClick()메소드가 수행되게 된다. 여기서는 실제 해당 메소드가 수행되게 된다.
[ 여기서는 실제 해당 메소드가 수행되는지를 확인하고자 이 메소드를 호출한 것이다. ]
방금 사용한 것과 같이 내부 클래스로 MagicButtonListener 클래스를 별도로 만들 수 있다.
다른 방법을 통해 익명 클래스를 만들 수도 있다.
[EX] - 익명 클래스 만들고 사용2(객체 재사용 불가능)
public void setButtonListenerAnonymous() {
MagicButton button = new MagicButton();
button.setListener(new EventListener() {
@Override
public void onClick() {
// TODO Auto-generated method stub
System.out.println("Magic Button Clicked !!!");
}
});
button.onClickProcess();
}
익명 클래스를 다른 방법으로 사용한 위의 코드를 살펴보자.
setListener() 메소드를 보면 new EventListener()로 생성자를 호출한 후 바로 중괄호를 열었다.
그리고 그 중괄호 안에는 onClick() 메소드를 구현한 후 중괄호를 닫았다. 이렇게 구현한 것이 바로 "익명 클래스"다.
클래스에는 이름이 없지만, onClick()과 같은 메소드가 구현되어 있다.
이렇게 클래스를 구현 할 때 조심해야 하는 것은 괄호를 닫는 것이다. [ 겉으로 보기에는 클래스를 선언한 것이지만, 실제로는 setListener()메소드를 호출하는 과정내에 익명 클래스가 있는 것이기 때문에 소괄호를 닥고 세미콜론을 해줘야만 한다. ]
setListener()메소드가 호출되어 onClick()메소드가 호출될 필요가 있을 때 그 안에 구현되어 있는 내용들이 실행된다.
그런데 이렇게 구현했을 때에는 클래스 이름도 없고, 객체 이름도 없기 때문에 다른 클래스나 메소드에서는 참조할 수 없다.
==> 그래서 만약, 객체를 해당 클래스내에서 재사용하려면, 아래와 같이 객체를 생성한 후 사용하면 된다.
[EX] - 익명 클래스 만들고 사용3(객체 재사용 가능)
public void setButtonListenerAnonymousObject() {
MagicButton button = new MagicButton();
EventListener listener = new EventListener() {
@Override
public void onClick() {
System.out.println("Magic Button CLicked !!!");
}
};
button.setListener(listener);
button.onClickProcess();
}
위의 코드를 보면 EventListener 인터페이스를 바로 객체로 만들고 구현해야할 onClick()메서드를 바로 구현한 것을 볼 수 있다.
여기서 객체(listener 라는 객체)를 만들었기 때문에 재사용이 가능하다.
내부 클래스를 만들면 편할 것 같은데 왜 자바에서는 복잡하게 익명 클래스라는 것을 제공할까?
==> 클래스를 만들고, 그 클래스를 호출하면 그 정보는 메모리에 올라간다. 즉, 클래스를 많이 만들수록 메모리는 많이 필요해지고 애플리케이션을 시작할 때 더 많은 시간이 소요된다. 따라서, 자바에서는 이렇게 간단한 방법으로 객체를 생성할 수 있도록 해놓았다.
[ 클래스 한 개 더 만든다고 해서 애플리케이션 시작 시간이 1초씩 더 걸리는 것으 아니지만 , 줄일 수 있으면 줄이는 것이 좋다는 것이다. ]
- 익명 클래스나 내부 클래스는 모두 다른 클래스에서 재사용할 일이 없을때 만들어야 한다.
- 익명 클래스를 사용함으로써 코드의 가독성이 높아질 수도 있지만, 그 반대의 경우도 생길 수 있기 때문에 너무 남용하지는 말자.
■ 1-3. Nested 클래스의 특징은 중요하니 꼭 알자.
지금까지 클래스의 선언 방식(static)에 따라 Static nested클래스와 내부클래스로 나뉘고, 일반적인 내부 클래스와 익명 클래스가 있다는 것을 배웠다. 이 클래스들에는 특징이 있기 때문에 사용하려면 알고 있어야 하는 사항은 참조 가능한 변수들이다. 예제를 보고 익히자.
[EX] - Neted 클래에 참조 가능한 변수들
package part16;
public class NestedValueReference {
public int publicInt=0;
protected int protectedInt=1;
int justInt=2;
private int privateInt=3;
static int staticInt=4;
static class StaticNested{
public void setValue() {
staticInt=14;
}
}
class Inner{
public void setvalue() {
publicInt=20;
protectedInt=21;
justInt=22;
privateInt=23;
staticInt=24;
}
}
public void setValue() {
EventListener listener = new EventListener() {
@Override
public void onClick() {
publicInt=30;
protectedInt=31;
justInt=32;
privateInt=33;
staticInt=34;
}
};
}
}
예제에 있는 것과 같이 Static Nested 클래스에서는 감싸고 있는 클래스의 static 변수만 참조할 수 있다.
==> Static Nested 클래스가 static으로 선언되어 있기 때문에 부모 클래스에 satic하지 않은 변수를 참조할 수는 없다.
내부 클래스(Inner 클래스)와 이경 클래스는 감싸고 있는 클래스의 어떤 변수라도 참조 할 수 있다.
반대로 감싸고 있는 클래스에서 Static Nested 클래스의 인스턴스 변수나 내부 클래스의 인스턴스 변수로의 접근하는 것은 가능할까 ?
==> 물론 가능하다.
[EX] - 감싸는 클래스에서 Static Nested 클래스와 내부 클래스의 변수 접근
package part16;
public class ReferenceAtNested {
static class StaticNested{
private int saticNestedInt=99;
}
class Inner {
private int innerValue=100;
}
public void setValue(int value) {
StaticNested nested = new StaticNested();
nested.saticNestedInt = value;
Inner inner = new Inner();
inner.innerValue = value;
}
}
위의 코드를 보면 알 수 있듯이 감싸는 클래스에서 Static Nested 클래스와 내부 클래스의 객체를 생성한 후 그 값을 참조하는 것은 가능하다. 그 값이 private라고 할지라도 모두 접근할 수 있다.
■ 1-4. 정리
자신이 만든 클래스의 내용을 숨기려고, 너무 많은 Neted 클래스를 선언하여 사용하는 것은 좋지 않다.
Nested 클래스를 사용하는 것이 어떻게 보면 가독성이 증가할 수도 있지만, 어떻게 보면 오히려 가독성이 감소될 수도 있다.
따라서, 꼭 필요한 경우에만 적절하게 사용하는 것이 좋다.
'JAVA > 자바의신 1' 카테고리의 다른 글
18장 기본 문법은 거의 다 배웠으니 정리해보자 (0) | 2022.09.13 |
---|---|
17장 어노테이션이란? (0) | 2022.09.13 |
15장 String (0) | 2022.09.10 |
14장 다 배운 것 같지만, 예외라는 중요한 것이 있어요.(예외 처리 try-catch) (1) | 2022.09.09 |
13장 인터페이스와 추상클래스, enum (0) | 2022.09.09 |