데이터 엔지니어링 과정/java
[23일차] 중첩 클래스와 중첩 인터페이스
오리는짹짹
2023. 1. 25. 17:32
목차
1. 중첩 클래스와 중첩 인터페이스 소개
2. 익명 객체
3. 예외 클래스
1. 중첩 클래스와 중첩 인터페이스 소개
0. 시작하기 전에
- 중첩 클래스
: 클래스 내부에 선언한 클래스- 장점
① 두 클래스의 멤버들을 서로 쉽게 접근 가능
② 외부에는 불필요한 관계 클래스를 감춤으로써 코드의 복잡성을 줄일 수 있음
- 장점
- 중첩 인터페이스
: 클래스 내부에 선언한 인터페이스
➡ 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해 클래스 내부에 선언
1. 중첩 클래스
- 분류 클래스 내부에 선언되는 위치에 따라 나누어짐
- 멤버 클래스 : 클래스의 멤버로서 선언되는 중첩 클래스
➡ 클래스나 객체가 사용 중이라면 언제든지 재사용 가능
👀 바이트 코드 $ - 로컬 클래스 : 생성자 또는 메소드 내부에서 선언되는 중첩 클래스
➡ 메소드를 실행할 때만 사용되고 메소드가 종료되면 없어짐
👀 바이트 코드 $1
- 멤버 클래스 : 클래스의 멤버로서 선언되는 중첩 클래스
- 인스턴스 멤버 클래스
- static 키워드 없이 중첩 선언된 클래스
- 인스턴스 필드와 메소드만 선언가능
정적 필드와 메소드는 선언 불가
- 정적 멤버 클래스
- static 키워드로 선언된 클래스
- 모든 조율의 필드와 메소드 선언 가능
- 로컬 클래스
- 메소드 내에서 선언된 중첩 클래스
- 접근 제한자(public, private) 및 static 붙일 수 없음
🐰 메소드 내부에서 사용되므로 접근을 제한할 필요가 없음!
➡ 내부에 인스턴스 필드와 메소드만 선언할 수 있고 정적 필드와 메소드는 선언 불가 - 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용
- 비동기 처리를 위해 스레드 객체 만들 때 사용
- 💻 중첩 클래스 & 중첩 클래스 객체 생성
package sec01.exam01;
public class A {
/**바깥 클래스**/
A() {System.out.println("A 객체가 생성됨");}
/** 인스턴스 멤버 클래스 **/
class B {
B() { System.out.println("B 객체가 생성됨");}
int field1;
//static int field2;
void method1() {}
//static void method2() {}
}
/** 정적 멤버 클래스**/
static class C {
C() { System.out.println("C 객체가 생성됨");}
int filed1;
static int field2;
void method1() {}
static void method2() {}
}
void method() {
/**로컬 클래스**/
class D {
D() {System.out.println("D 객체가 생성됨");}
int field1;
//static int field2;
void method1() { }
//static void method2() { }
}
D d = new D();
d.field1 = 3;
d.method1();
}
}
package sec01.exam01;
public class Main {
public static void main(String[] args) {
A a = new A();
//인스턴스 멤버 클래스 객체 생성
A.B b = a.new B();
b.field1 = 3;
b.method1();
//정적 멤버 클래스 객체 생성
A.C c = new A.C();
c.filed1 = 3;
c.method1();
A.C.field2 = 3;
A.C.method2();
//로컬 클래스 객체 생성을 위한 메소드 호출
a.method();
}
}
>>> A 객체가 생성됨
>>> B 객체가 생성됨
>>> C 객체가 생성됨
>>> D 객체가 생성됨
2. 중첩 클래스의 접근 제한
- 바깥 필드와 메소드에서 사용 제한
- 인스턴스 멤버 클래스
- 바깥 클래스의 인스턴스 필드의 초기값이나 인스턴스 메소드에서 객체 생성 가능
- 정적 필드의 초기값이나 정적 메소드에서는 객체 생성 불가 - 정적 멤버 클래스
- 모든 필드의 초기값이나 모든 메소드에서 객체 생성 가능
- 인스턴스 멤버 클래스
package sex01.exam02;
public class A {
//인스턴스 필드
B field1 = new B();
C field2 = new C();
//인스턴스 메소드
void method1() {
B var1 = new B();
C var2 = new C();
}
//정적 필드 초기화
//static B field3 = new B();
static C field4 = new C();
//정적 메소드
static void method2() {
//B var1 = new B();
C var2 = new C();
}
//인스턴스 멤버 클래스
class B {}
//정적 멤버 클래스
static class C {}
}
- 멤버 클래스에서 사용 제한
- 인스턴스 멤버 클래스 내부
- 바깥 클래스의 모든 필드와 모든 메소드에 접근 가능 - 정적 클래스 내부
- 바깥 클래스의 정적 필드와 메소드에서만 접근 가능
- 인스턴스 필드와 메소드에는 접근 불가
- 인스턴스 멤버 클래스 내부
package sec01.exam03;
public class A {
int field1;
void method1() {}
static int field2;
static void method2() {}
class B {
void method() {
field1 = 10;
method1();
field2 = 10;
method2();
}
}
static class C {
void method() {
//filed1 = 10;
//method1();
field2 = 10;
method2();
}
}
}
- 로컬 클래스에서 사용 제한
- 로컬 클래스의 객체는 메소드 실행이 종료되면 없어지는 게 일반적
- 메소드가 종료되어도 계속 실행 상태로 존재 가능
➡ 매개 변수나 로컬 변수를 final로 선언
package sex01.exam04;
public class Outter {
//자바7 이전
public void method1(final int arg) {
final int localVariable = 1;
//arg = 100;
//localVariable = 100;
class Ineer {
public void method() {
int result = arg + localVariable;
}
}
}
//자바 8 이후
public void method2(int arg) {
int localVariable = 1;
//arg = 100;
// localVariable = 100;
class Inner {
public void method() {
int result = arg + localVariable;
}
}
}
}
- 로컬 클래스에서 바깥 클래스 참조 얻기
- 중첩 클래스 내부에서 'this.필드.this.메소드()'로 호출하면 중첩 클래스의 필드와 메소드 사용
- 바깥클래스.this.필드
바깥클래스.this.메소드()
➡ 중첩 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근하기 위한 코드
package sec01.exam05;
public class Outter {
String field = "Outter-field";
void method() {
System.out.println("Outter-method");
}
class Nested {
String field = "Nested-field";
void method() {
System.out.println("Nested-method");
}
void print() {
System.out.println(this.field);
this.method();
System.out.println(Outter.this.field);
Outter.this.method();
}
}
}
package sec01.exam05;
public class OutterExample {
public static void main(String[] args) {
Outter outter = new Outter();
Outter.Nested nested = outter.new Nested();
nested.print();
}
}
>>> Nested-method
>>> Outter-field
>>> Outter-method
3. 중첩 인터페이스
- 중첩 인터페이스
: 클래스의 멤버로 선언된 인터페이스- 👀 인터페이스를 클래스 내부에 선언하는 이유?
➡ 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위하여 - 인스턴스 멤버 인터페이스와 정적 멤버 인터페이스 모두 가능
- 인스턴스 멤버 인터페이스는 바깥 클래스의 객체가 있어야 사용 가능
- 정적 멤버 인터페이스는 바깥 클래스의 객체 없이 바깥 클래스만으로 바로 접근 가능
👀 주로 정적 멤버 인터페이스를 많이 사용하는데 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 활용
- 👀 인터페이스를 클래스 내부에 선언하는 이유?
- 💻 중첩 인터페이스 & 구현 클래스 & 버튼 이벤트 처리
package sec01.exam06;
public class Button {
OnClickListener listener;
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
void touch() {
listener.onClick();
}
static interface OnClickListener {
void onClick();
}
}
package sec01.exam06;
public class CallListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
package sec01.exam06;
public class MessageListener implements Button.OnClickListener{
@Override
public void onClick() {
System.out.println("메시지를 보냅니다.");
}
}
package sec01.exam06;
public class ButtonExample {
public static void main(String[] args) {
Button btn = new Button();
btn.setOnClickListener(new CallListener());
btn.touch();
btn.setOnClickListener(new MessageListener());
btn.touch();
}
}
2. 익명 객체
0. 익명 객체
- 익명 객체
- 정의: 이름이 없는 객체
- 조건 : 어떤 클래스를 상속하거나, 인터페이스를 구현해야만 함
1. 익명 자식 객체 생성
- 자식 클래스를 명시적으로 선언하는 이유
: 어디서건 이미 선언된 자식 클래스로 간단히 객체를 생성해서 사용할 수 있기 때문
➡ 재사용성이 높다! - 자식 클래스가 재사용되지 않고, 오로지 특정 위치에서 사용할 경우
➡ 익명 자식 객체 생성 - 생성 방법
부모클래스 [필드|변수] = new 부모클래스(매개값, ...) {
//필드
//메소드
};
🐰 하나의 실행문이므로 끝에는 세미콜론(;)을 반드시 붙인다!
- 💻 부모 클래스
package sec02.exam01;
public class Person {
void wake() {
System.out.println("7시에 일어납니다.");
}
}
- 💻 익명 자식 객체 생성
package sec02.exam01;
public class Anonymous {
//필드 초기값으로 대입
Person field = new Person() {
void work() {
System.out.println("출근합니다.");
}
@Override
void wake() {
System.out.println("6시에 일어납니다.");
work();
}
};
void method1() {
//로컬 변수값으로 대입
Person localVar = new Person() {
void walk() {
System.out.println("산책합니다.");
}
@Override
void wake() {
System.out.println("7시에 일어납니다.");
walk();
}
};
//로컬 변수 사용
localVar.wake();
}
void method2(Person person) {
person.wake();
}
}
package sec02.exam01;
public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
//익명 객체 필드 사용
anony.field.wake();
//익명 객체 로컬변수 사용
anony.method1();
//익명 객체 매개값 사용
anony.method2(
new Person() {
void study() {
System.out.println("공부합니다.");
}
@Override
void wake() {
System.out.println("8시에 일어납니다.");
study();
}
}
);
}
}
>>> 6시에 일어납니다.
>>> 출근합니다.
>>> 7시에 일어납니다.
>>> 산책합니다.
>>> 8시에 일어납니다.
>>> 공부합니다.
2. 익명 구현 객체 생성
- 구현 클래스를 명시적으로 선언하는 이유
: 어디서건 이미 선언된 구현 클래스로 간단히 객체를 생성해서 사용할 수 있기 때문
➡ 재사용성이 높다! - 구현 클래스가 재사용되지 않고, 오로지 특정 위치에서 사용할 경우
➡ 익명 구현 객체 생성 - 생성 방법
부모클래스 [필드|변수] = new 인터페이스() {
//인터페이스에 선언된 추상 메소드의 실체 메소드 선언
//필드
//메소드
};
🐰 추가로 필드와 메소드를 선언할 수 있지만, 실체 메소드에서만 사용이 가능하고 외부에서는 사용이 불가!
- 💻 인터페이스
package sec02.exam02;
public interface RemoteControl {
public void turnOn();
public void turnOff();
}
- 💻 익명 구현 객체 생성
package sec02.exam02;
public class Anonymous {
//필드 초기값으로 대입
RemoteControl field = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("TV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("TV를 끕니다.");
}
};
void method1() {
//로컬 변수값으로 대입
RemoteControl localVar = new RemoteControl() {
@Override
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
};
//로컬 변수 사용
localVar.turnOn();
}
void method2(RemoteControl rc) {
rc.turnOn();
}
}
package sec02.exam02;
public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
//익명 객체 필드 사용
anony.field.turnOn();
//익명 객체 로컬 변수 사용
anony.method1();
//익명 객체 매개값 사용
anony.method2(
new RemoteControl() {
@Override
public void turnOn() {
System.out.println("SmartTV를 켭니다.");
}
@Override
public void turnOff() {
System.out.println("smartTV를 끕니다.");
}
}
);
}
}
>>> TV를 켭니다.
>>> Audio를 켭니다.
>>> SmartTV를 켭니다.
- 💻 UI 클래스
package sec02.exam03;
public class Button {
OnClickListener listener;
void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
void touch() {
listener.onClick();
}
static interface OnClickListener {
void onClick();
}
}
package sec02.exam03;
public class Window {
Button button1 = new Button();
Button button2 = new Button();
//필드 초기값으로 대입
Button.OnClickListener listener = new Button.OnClickListener() {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
};
Window() {
button1.setOnClickListener(listener);
button2.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick() {
System.out.println("메세지를 보냅니다.");
}
});
}
}
- 💻 실행 클래스
package sec02.exam03;
public class Main {
public static void main(String[] args) {
Window w = new Window();
w.button1.touch();
w.button2.touch();
}
}
>>> 전화를 겁니다.
>>> 메세지를 보냅니다.
3. 익명 객체의 로컬 변수 사용
- 💻 인터페이스
package sec02.exam04;
public interface Calculatable {
public int sum();
}
- 💻 익명 객체의 로컬 변수 사용
package sec02.exam04;
public class Anonymous {
private int field;
public void method(final int arg1, int arg2) {
final int var1 = 0;
int var2 = 0;
field = 10;
//arg1 = 20;
//arg2 = 20;
//var1 = 30;
//var2 = 30;
Calculatable calc = new Calculatable() {
@Override
public int sum() {
int result = field + arg1 + arg2 + var1 + var2;
return result;
}
};
System.out.println(calc.sum());
package sec02.exam04;
public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
anony.method(0, 0);
}
}
>>> 10
3. 예외 클래스
0. 시작하기 전에
- 예외 : 사용자의 잘못된 조작 또는 개발자의 잘못된 코딩으로 인해 발생하는 프로그램 오류
1. 예외와 예외 클래스
- 일반 예외 (=컴파일러 체크 예외)
- 프로그램 실행 시 예외가 발생할 가능성이 높기 때문에 자바 소스를 컴파일하는 과정에서 해당 예외 처리 코드가 있는지 검사
➡ 없다면 컴파일 오류 발생
- 프로그램 실행 시 예외가 발생할 가능성이 높기 때문에 자바 소스를 컴파일하는 과정에서 해당 예외 처리 코드가 있는지 검사
- 실행 예외 (=컴파일러 넌 체크 예외)
- 실행 시 예측할 수 없이 갑자기 발생하기 때문에 컴파일하는 과정에서 예외 처리 코드가 있는지 검사 x
- 예외 클래스
- RuntimeException 클래스를 기준으로 구분
- 하위 클래스일 경우 ➡ 실행 예외 클래스
- 하위 클래스가 아닌 경우 ➡ 일반 예외 클래스
- RuntimeException 클래스를 기준으로 구분
2. 실행 예외
- 컴파일러가 체크하지 않기 때문에 오로지 개발자의 경험에 의해서 예외 처리 코드를 작성해야 함
- NullPointerException
- 객체 참조가 없는 상태에서 객체 접근 연산자인 도트(.)를 사용했을 때 발생
➡ 객체가 없는 상태에서 객체를 사용하려 해서 예외 발생
- 객체 참조가 없는 상태에서 객체 접근 연산자인 도트(.)를 사용했을 때 발생
package sec01.exam01;
public class NullPointerExceptionExample {
public static void main(String[] args) {
String data = null;
System.out.println(data.toString());
}
}
>>> Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toString()" because "data" is null
at chap10/sec01.exam01.NullPointerExceptionExample.main(NullPointerExceptionExample.java:6)
- ArrayIndexOutOfBoundsException
- 배열에서 인덱스 범위를 초과할 경우 발생
package sec01.exam02;
public class ArrayIndexOutOfBoundsException {
public static void main(String[] args) {
String data1 = args[0];
String data2 = args[1];
System.out.println("args[0]: "+data1);
System.out.println("args[1]: "+data2);
}
}
>>> Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
at chap10/sec01.exam02.ArrayIndexOutOfBoundsException.main(ArrayIndexOutOfBoundsException.java:5)
package sec01.exam03;
public class ArrayindexOutofBoundsException {
public static void main(String[] args) {
if (args.length == 2) {
String data1 = args[0];
String data2 = args[1];
System.out.println("args[0]: "+ data1);
System.out.println("args[1]: "+ data2);
} else {
System.out.println("두 개의 실행 매개값이 필요합니다.");
}
}
}
>>> 두 개의 실행 매개값이 필요합니다.
- NumberFormatException
- 숫자로 변환될 수 없는 문자를 숫자로 변환하려고 할 때 발생
package sec01.exam04;
public class NumberFormatExceptionExample {
public static void main(String[] args) {
String data1 = "100";
String data2 = "a100";
int value1 = Integer.parseInt(data1);
int value2 = Integer.parseInt(data2);
int result = value1 + value2;
System.out.println(data1 + "+" + data2 + "=" + result);
}
}
>>> Exception in thread "main" java.lang.NumberFormatException: For input string: "a100"
at java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
at java.base/java.lang.Integer.parseInt(Integer.java:665)
at java.base/java.lang.Integer.parseInt(Integer.java:781)
at chap10/sec01.exam04.NumberFormatExceptionExample.main(NumberFormatExceptionExample.java:9)
- ClassCastException
- 타입 변환이 잘못 됐을 때 발생
package sec01.exam05;
public class ClassCastException {
public static void main(String[] args) {
Dog dog = new Dog();
changeDog(dog);
Cat cat = new Cat();
changeDog(cat);
}
public static void changeDog(Animal animal) {
//if (animal instanceof Dog) {
Dog dog = (Dog) animal;
//}
}
}
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
>>> Exception in thread "main" java.lang.ClassCastException: class sec01.exam05.Cat cannot be cast to class sec01.exam05.Dog (sec01.exam05.Cat and sec01.exam05.Dog are in module chap10 of loader 'app')
at chap10/sec01.exam05.ClassCastException.changeDog(ClassCastException.java:14)
at chap10/sec01.exam05.ClassCastException.main(ClassCastException.java:9)