데이터 엔지니어링 과정/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 클래스를 기준으로 구분
      • 하위 클래스일 경우 ➡ 실행 예외 클래스
      • 하위 클래스가 아닌 경우 ➡ 일반 예외 클래스

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)