46강. 인터페이스
47강. 타입 변환과 다형성
48강. 중첩 클래스와 중첩 인터페이스(1) - 중첩 클래스
49강. 중첩 클래스와 중첩 인터페이스(2) - 중첩 클래스의 접근 제한
50강. 중첩 클래스와 중첩 인터페이스(3) - 중첩 인터페이스
46강. 인터페이스
ㅁ 핵심 포인트
- 인터페이스란 객체의 사용 방법을 정의한 타입이다.
(왜 직접 객체를 이용하지 않고 인터페이스를 통해서 객체를 이용하냐)
- 인터페이스를 통해 다양한 객체를 동일한 사용 방법으로 이용할 수 있다.
- 인터페이스를 이용해서 다형성을 구현할 수 있다. // 상속보다도 인터페이스를 이용해 다형성을 더 많이 구현함.
- 개발 코드에서 객체를 직접 이용하는 것이 아니라, 인터페이스를 통해서 이용하고 있다.
개발 코드는 인터페이스만 보고 이용한다. 대신 인터페이스가 객체를 이용하는 형태.
동일한 인터페이스를 통해서 여러 개의 객체를 이용할 수 있다.
- 다형성: 인터페이스, 객체1, 객체2에 다 m1()이라는 메서드가 있는 경우, 개발 코드에서 m1()이라는 메서드를 호출할 때, 인터페이스 뒤에 있는 객체가 객체1이라면 객체1의 m1()이, 객체2라면 객체2의 m1()이 호출된다.
인터페이스 뒤에 어떤 객체가 있느냐에 따라서 다른 m1()이 호출이되고 그 결과 다양한 실행 결과가 나올 수 있게 된다.
ㅁ 인터페이스를 통해 객체를 이용하는 이유는 주로 소프트웨어의 유연성, 확장성, 유지보수성을 높이기 위해서입니다. 이를 구체적으로 설명하면 다음과 같습니다.
1. 느슨한 결합 (Loose Coupling) 인터페이스를 사용하면 클래스 간의 결합도를 낮출 수 있습니다. 느슨한 결합은 한 클래스의 변경이 다른 클래스에 미치는 영향을 최소화하는 것을 의미합니다. 이를 통해 시스템의 한 부분을 수정해도 다른 부분에 영향을 주지 않도록 할 수 있습니다.
2. 다형성 (Polymorphism) 인터페이스를 사용하면 하나의 인터페이스 타입 변수를 통해 여러 구현 클래스를 다룰 수 있습니다. 이는 코드의 유연성과 재사용성을 높입니다.
3. 인터페이스 기반 설계는 시스템의 각 부분을 정의된 인터페이스를 통해 상호작용하게 합니다. 이는 시스템의 구조를 명확히 하고, 각 부분이 독립적으로 개발되고 테스트될 수 있도록 합니다.
4. 코드의 유연성과 확장성. 인터페이스를 사용하면 새로운 기능을 추가할 때 기존 코드를 최소한으로 수정하거나 수정하지 않고도 확장이 가능합니다. 새로운 구현체를 추가하면 인터페이스를 통해 쉽게 교체할 수 있습니다.
5. 테스트 용이성. 인터페이스를 사용하면 가짜 객체(Mock Object)나 스텁(Stub)을 쉽게 만들어 단위 테스트를 수행할 수 있습니다. 이는 테스트 주도 개발(TDD)과 같은 개발 방법론에서 특히 중요합니다.
인터페이스를 통해 객체를 이용하는 것은 느슨한 결합, 다형성, 인터페이스 기반 설계, 코드의 유연성과 확장성, 테스트 용이성 등을 제공하여 소프트웨어의 유지보수성과 확장성을 높이는 데 중요한 역할을 합니다. 이를 통해 개발자는 보다 유연하고 견고한 시스템을 설계할 수 있습니다.
ㅁ 인터페이스(interface)
- 개발 코드는 인터페이스를 통해서 객체와 서로 통신한다.
- 인터페이스의 메서드를 호출하면 객체의 메서드가 호출된다. // 인터페이스의 메서드는 내용이 없다.
- 개발 코드를 수정하지 않으면서 객체 교환이 가능하다.
(개발 코드는 오로지 인터페이스만을 보고 개발을 함. 그럼 인터페이스 뒤쪽의 객체가 어떤 객체로 바뀌더라도 개발 코드가 인터페이스를 사용하는 코드는 바뀌지가 않음. 그래서 쉽게 객체를 교환해서 다른 기능으로 대체를 할 수 있음. )
ㅁ 인터페이스 선언
- ~.java 형태 소스파일로 작성 및 컴파일러를 통해 ~.class 형태로 컴파일된다.
- 클래스와 물리적 파일 형태는 같으나 소스 작성 내용이 다르다.
[public] interface 인터페이스이름 { ... }
- 인터페이스는 객체로 생성할 수 없으므로 생성자를 가질 수 없다.
(인터페이스는 객체 사용 설명서이기 때문에 객체를 만들 목적으로 인터페이스를 작성하는게 아님.)
interface 인터페이스이름 {
//상수
타입 상수이름 = 값; // 이렇게만 선언해도 필드는 상수 필드가 된다.
//추상 메서드
타입 메서드이름(매개변수, ...);
}
- 자바8 이후로 상수와 추상 메서드 외에도 추가적인 멤버들이 생겼지만, 일반적으론 인터페이스에 상수와 추상메서드만 선언해서 사용한다.
- 상수는 한번 값을 저장하면 그 값을 바꿀 수 없는 불변의 특성.
- 클래스에서 상수를 만드려면 static final이란 키워드가 붙는다. 그런데 인터페이스에서는 static final을 붙이지 않아도 static final의 특성을 갖고 있다.
- 상수 필드가 되므로 반드시 값을 지정해야 하고, 이 값을 바꿀 수 없다.
- 추상메서드는 메서드 선언부만 있고 중괄호가 없는 형태. 인터페이스에서는 추상메서드 형태로 메서드를 선언함.
앞에 abstract가 붙어있지 않더라도 붙인 것과 같다.
- 인터페이스는 객체 사용설명서. 객체 사용설명이 바로 이 추상메서드 부분이다. 인터페이스의 메서드를 호출하게 되면 뒤쪽에 있는 객체가 이 메서드를 실행하게 됨.
ㅁ 상수 필드 constant field 선언
- 데이터를 저장할 인스턴스 필드 혹은 정적 필드 선언이 불가능 함.
- 상수 필드만 선언 가능.
[public static final] 타입 상수이름 = 값;
- 인터페이스에서도 이와 동일하게 선언할 수 있음. 근데 인터페이스에서 선언하는 필드는 모두 상수의 특성을 갖고 있기 때문에 [] 부분을 생략해도 괜찮음.
- 상수 이름은 대문자로 작성하되 서로 다른 단어로 구성되어 있을 경우 언더바로 연결하는 것이 관례이다.
public interface RemoteControl {
public int MAX_VOLUME = 10;
}
- 선언할 때 반드시 값을 대입을 해줘야 함. 상수의 초기화는 상수를 선언할 때 반드시 해야 함.
ㅁ 추상 메서드 선언
- 인터페이스의 메서드는 실행 블록이 필요 없는 추상 메서드로 선언.
- 인터페이스를 통해 호출된 메서드는 최종적으로 객체에서 실행.
- 추상 메서드를 선언할 때 public abstract를 선언하지 않아도 숨겨져 있음.
ㅁ 구현 implement 클래스
- 인터페이스에 있는 내용을 구현해서 만든 클래스다.
- 인터페이스에서 정의된 추상 메서드를 재정의해서 실행내용을 가지고 있는 클래스.
- 클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시.
public class 구현클래스이름 implements 인터페이스이름 {
// 인터페이스에 선언된 추상 메서드의 실제 메서드 선언
}
public class Television implements RemoteControl {
// turnOn() 추상 메서드의 실제 메서드
public void turnOn() {
System.out.println("TV를 켭니다.");
}
// turnOff() 추상 메서드의 실제 메서드
public void turnOff() {
System.out.println("TV를 끕니다.");
}
- 인터페이스 RemoteControl이 가지고 있는 추상 메서드를 전부 재정의해야 함.
ㅁ 인터페이스와 구현 클래스 사용 방법
- 인터페이스 변수를 선언하고 구현 객체를 대입.
- 인터페이스는 변수를 선언할 때 타입으로 사용, 구현 클래스는 구현 객체를 생성할 때 사용.
인터페이스 변수;
변수 = 구현객체;
인터페이스 변수 = 구현객체;
ㅁ 다중 인터페이스 구현 클래스
- 객체는 다수의 인터페이스 타입으로 사용 가능. 하나의 객체에 여러 인터페이스.
- 구현 클래스를 작성할 때 인터페이스 하나가 아니라 여러 개를 구현할 수 있음. 그것을 다중 인터페이스 구현 클래스라고 함.
- 인터페이스 A와 B에 있는 추상 메서드들 다 재정의해야 함.
SmartTelevision tv = new SmartTelevision();
RemoteControl r1 = tv;
Searchable r2 = tv;
r1.turnOn();
r1.turnOff();
r1.setVolume(3);
r2.search("10번");
ㅁ 인터페이스 사용
- 인터페이스는 타입으로서 사용되는 곳이면 다 이용가능함.
- 인터페이스는 필드, 매개 변수, 로컬 변수의 타입으로 선언가능.
public class Myclass {
RemoteControl rc = new Television();
Myclass(RemoteControl rc) { // 생성자의 매개값으로 구현 객체 대입.
this.rc = rc; // Myclass mc = new Myclass( new Television() );
}
void methodA() {
RemoteControl rc = new Audio(); // 로컬변수
}
void methodB(RemoteControl rc) { ... } // 메서드의 매개값으로 구현 객체 대입.
// 생성자의 매개변수 타입으로 인터페이스 사용.
// mc.methodB( new Audio() );
}
- 인터페이스가 필드의 타입으로, 생성자와 메서드의 매개변수 타입으로, 로컬변수의 타입으로 다양한 곳에서 사용될 수 있다.
package sec01.exam06;
public class Myclass {
RemoteControl rc = new Television();
Myclass() { }
Myclass(RemoteControl rc) {
this.rc = rc;
rc.turnOn();
rc.setVolume(5);
}
void methodB(RemoteControl rc) {
rc.turnOn();
rc.setVolume(5);
}
void methodA() {
RemoteControl rc = new Audio();
rc.turnOn();
rc.setVolume(5);
}
}
-
package sec01.exam06;
public class MyclassExample {
public static void main(String[] args) {
System.out.println("1)---------------------------------");
Myclass myClass1 = new Myclass();
myClass1.rc.turnOn();
myClass1.rc.setVolume(5);
System.out.println("2)---------------------------------");
Myclass myClass2 = new Myclass(new Audio());
System.out.println("3)---------------------------------");
Myclass myClass3 = new Myclass();
myClass3.methodA();
System.out.println("4)---------------------------------");
Myclass myClass4 = new Myclass();
myClass3.methodB(new Television());
}
}
- 인터페이스(RemoteControl) 1개, 구현 클래스(Television, Audio) 2개, MyClass 1개, 실행 클래스 1개 총 5개.
이전의 예제들은 Myclass가 없었음. 다양하게 인터페이스를 쓸 수 있다는 걸 보여주려고.
ㅁ
- 인터페이스: 객체의 사용 방법을 정의한 타입.
- 상수 필드: 인터페이스의 필드는 기본적으로 public static final 특성을 가짐.
- 추상 메서드: 인터페이스의 메서드는 public abstract가 생략되고 메서드 선언부만 있는 추상 메서드.
- implements: 구현 클래스에는 어떤 인터페이스로 사용 가능한지 기술하기 위해 사용.
- 인터페이스 사용: 클래스 선언 시 필드, 매개변수, 로컬변수로 선언 가능. 구현 객체를 대입.
47강. 타입 변환과 다형성
ㅁ 인터페이스를 가지고 타입 변환과 다형성을 살펴볼 것.
- 인터페이스도 메서드 재정의와 타입 변환이 되므로 다형성을 구현할 수 있다.
- 다형성을 구현하는 중요한 기술적 요소 2가지가 메서드 재정의와 타입 변환.
- 인터페이스도 역시 이 두 개를 제공하기 때문에 다형성을 구현할 수 있다.
ㅁ 인터페이스의 다형성
- 인터페이스의 사용 방법은 동일하지만 구현 객체를 교체하여 프로그램 실행 결과를 다양화.
- 인터페이스는 사용 방법을 정해놓은 것. 인터페이스는 객체의 사용 설명서.
개발 코드에서 인터페이스를 통해서 사용하게 되면 어떤 구현 객체든 사용 방법은 똑같다.
- I는 인터페이스다. I의 구현 객체에 A를 대입해서 i.method1(), i.method2() 이렇게 쓰고 있었는데 A라는 객체 대신에 또 다른 구현 객체인 B를 인터페이스 변수에 대입.
- 이럴 경우에 메서드1과 메서드2를 호출하는 코드는 변함이 없지만 실제로 이 메서드가 실행되는 곳은 B가 됨. 코드는 그대론데 실행 결과가 어떤 객체가 대입되냐에 따라서 다양해질 수 있다.
ㅁ 자동 타입 변환 promotion
- 구현 객체와 (구현 객체의)자식 객체는 인터페이스 타입으로 자동 타입 변환된다.
- 부모 구현 객체가 인터페이스를 구현하고 있기 때문에 그 내용이 그대로 자식도 물려받기 때문에.
ㅁ 필드의 다형성
- Tire는 인터페이스다. 자동차 객체를 생성하고 실제 타이어 자리에는 이 인터페이스를 구현한 구현객체가 장착이 됨.
- 어떤 구현 객체를 장착하냐에 따라서 이 전체 자동차의 실행 성능이 달라짐. 이게 다형성.
- run이라는 메서드를 정의해서 인터페이스 변수들의 roll이라는 메서드를 호출함. 그러면 이 자동차 객체를 만들고 run()을 하게 되면 이 인터페이스에 대입된 구현 객체의 roll들이 실행됨.
- 그런데 객체를 생성하고 나서 frontLeftTire에 금호타이어 객체를 대입하면 한국타이어 대신에 금호타이어의 roll이 실행된다.
- 이렇게 인터페이스로 선언된 필드에는 다양한 구현 객체가 대입될 수 있다. 그래서 인터페이스의 메서드를 호출할 때 다양한 실행결과가 나올 수 있다.
ㅁ 매개변수의 다형성
- Vehicle이라는 인터페이스, 그걸 구현한 Bus라는 구현 클래스, Driver라는 클래스.
- 어떤 구현객체가 매개변수로 들어가냐에 따라서 drive 메서드의 실행 결과가 달라진다.
- 매개변수가 인터페이스이므로 그 자리엔 어떠한 구현 객체든 대입 가능하다.
ㅁ 강제 타입 변환 casting
- 인터페이스에 대입된 구현 객체를 다시 원래 타입으로 변환하는 것.
- 구현 객체가 인터페이스 타입으로 자동 변환하면 인터페이스에 선언된 메서드만 사용 가능.
- RemoteControl이 인터페이스, Television이 구현 클래스.
Television은 인터페이스가 가지고 있는 (추상)메서드를 재정의해야 함. 그리고 자신만의 메서드가 2개 있다.
- Television으로 구현 객체를 만들고 나서 RemoteControl이라고 하는 인터페이스타입 변수에 대입이 되면, 인터페이스에 선언된 3개의 메서드만 호출가능함. (이건 이전까지의 설명)
- Television에 추가로 선언된 2개의 메서드를 사용하고 싶으면, 다시 원래의 Television 객체로 타입 변환을 해줘야 함.
구현 클래스에만 선언된 필드나 메서드를 사용하고 싶은 경우 강제 타입 변환을 해야 함.
Vehicle myVehicle = new Car(); // Vehicle이 인터페이스, Car가 구현 클래스.
if (myVehicle instanceof Car) { // 변수가 특정 타입의 인스턴스인지 확인한 후 캐스팅.
Car myCar = (Car) myVehicle;
myCar.openTrunk(); // Car 클래스의 추가 메서드 호출
myCar.playMusic(); // Car 클래스의 추가 메서드 호출
}
ㅁ 객체 타입 확인 instanceof
- 강제 타입 변환을 할 때 주의할 점은 무조건 강제 타입 변환이 성공하지는 않음.
- 구현 객체가 변환되어 있는지 알 수 없는 상태에서 강제 타입 변환을 할 경우 ClassCastException 발생.
Vehicle vehicle = new Taxi();
Bus bus = (Bus) vehicle; // ClassCastException 발생. (컴파일은 되고 실행할 때 오류)
public void drive(Vehicle vehicle) {
Bus bus = (Bus) vehicle;
bus.checkFare();
- 주로 이런 경우에 발생함. 매개변수로 인터페이스가 선언되어 있으면 매개값으로 어떠한 구현객체든 다 올 수 있는데, 무조건 이렇게 매개값을 Bus로 강제 타입 변환하면 문제가 발생할 수 있다.
- instanceof 연산자로 확인 후 안전하게 강제 타입 변환한다. 상속과 마찬가지로 인터페이스도 이 연산자 사용.
public class Driver {
public void drive(Vehicle vehicle) {
if(vehicle instanceof Bus) { // vehicle 매개변수가 참조하는 객체가 Bus인지 조사
Bus bus = (Bus) vehicle;
bus.checkFare(); // 인터페이스에 없는 메서드.
}
vehicle.run();
}
ㅁ 인터페이스 상속
- 자바의 클래스는 단일 상속만 가능한데, 인터페이스는 다중 상속이 가능함.
public interface 하위인터페이스 extends 상위인터페이스1, 상위인터페이스2 { ... }
- 자바에서 인터페이스는 다른 여러 인터페이스를 상속받을 수 있습니다.
인터페이스a에 메서드a, 인터페이스b에 메서드b, 인터페이스c에 메서드c가 있는 상황에서,
인터페이스c가 인터페이스a와 인터페이스b를 상속받는 경우에,
인터페이스c를 구현한 구현클래스는 메서드a, 메서드b, 메서드c를 전부 구현해야 한다.
- 인터페이스c의 구현클래스를 가지고 객체를 만들게 되면, 그 객체들은 인터페이스c로 타입변환은 당연히 되는 거고, 인터페이스a와 인터페이스b로도 가능하다. (자동 타입변환 = 대입)
ImplementationC impl = new ImplementationC(); // 인터페이스C를 구현한 클래스
impl.methodA(); // 구현 객체는 메서드 전부 사용 가능.
impl.methodB();
impl.methodC();
InterfaceA ia = impl;
ia.methodA(); // 인터페이스 변수로 선언되면 그 인터페이스에 있는 메서드만.
InterfaceB ib = impl;
ib.methodB();
InterfaceC ic = impl;
ic.methodA();
ic.methodB();
ic.methodC();
48강. 중첩 클래스와 중첩 인터페이스(1) - 중첩 클래스
ㅁ 클래스는 긴밀한 관계를 가지는 클래스와 인터페이스를 내부에 선언할 수 있다.
ㅁ 중첩 클래스와 중첩 인터페이스를 쓰는 이유. 바깥 클래스와 중첩 클래스, 중첩 인터페이스가 매우 긴밀한 관계를 가지는 경우. 바깥 클래스가 없으면 중첩 클래스, 중첩 인터페이스가 의미가 없는 경우.
ㅁ 중첩 클래스의 선언위치에 따른 분류
(1) 멤버 클래스
- 클래스의 멤버로서 선언되는 중첩 클래스. (클래스 블록 내에 선언)
- 인스턴스 멤버 클래스: A라는 객체가 있어야만 B를 사용 가능한 중첩 클래스.
- 정적 멤버 클래스: A라는 객체가 없어도 B를 사용 가능한 중첩 클래스. static 키워드로 구분.
(바깥 객체의 필요 여부에 따라 인스턴스 멤버 클래스와 정적 멤버 클래스로 구분.)
- 멤버 클래스로 컴파일하면 아래와 같은 형태의 클래스 파일(바이트코드 파일)이 생성됨.
(2) 로컬 클래스
- 생성자 또는 메서드 (블록)내부에서 선언되는 중첩 클래스.
- 생성자 또는 메서드가 실행될 때만 사용.
- 멤버 클래스로 컴파일하면 아래와 같은 형태의 클래스 파일이 생성됨.
ㅁ 멤버 클래스와 로컬 클래스는 컴파일 시 각각의 특성에 따라 별도의 클래스 파일로 생성됩니다.
(1) 멤버 클래스의 컴파일
-
public class OuterClass {
static class StaticMemberClass { // 정적 멤버 클래스
void display() {
System.out.println("Static Member Class"); }
}
class NonStaticMemberClass { // 비정적 멤버 클래스
void display() {
System.out.println("Non-Static Member Class"); }
}
}
- 컴파일된 클래스 파일 위의 코드를 컴파일하면 다음과 같은 .class 파일들이 생성됩니다.
OuterClass.class - 외부 클래스 파일
OuterClass$StaticMemberClass.class - 정적 멤버 클래스 파일
OuterClass$NonStaticMemberClass.class - 비정적 멤버 클래스 파일
- 클래스 이름 패턴.
정적 및 비정적 멤버 클래스의 클래스 파일 이름은 OuterClass$InnerClassName.class의 형식을 따릅니다.
여기서 $는 외부 클래스와 내부 클래스를 구분하는 역할을 합니다.
(2) 로컬 클래스의 컴파일
-
public class OuterClass {
void myMethod() {
class LocalClass { // 로컬 클래스
void display() {
System.out.println("Local Class"); }
}
LocalClass local = new LocalClass(); local.display();
}
}
- 컴파일된 클래스 파일 위의 코드를 컴파일하면 다음과 같은 .class 파일들이 생성됩니다.
OuterClass.class - 외부 클래스 파일
OuterClass$1LocalClass.class - 로컬 클래스 파일
- 클래스 이름 패턴.
로컬 클래스 파일 이름은 OuterClass$1LocalClass.class와 같이 생성됩니다.
여기서 $1은 로컬 클래스가 선언된 순서를 나타내며, 여러 로컬 클래스가 있을 경우 순차적으로 증가합니다.
ㅁ 인스턴스 멤버 클래스
- 인스턴스 필드와 메서드만 선언 가능하고 정적 필드와 메서드는 선언할 수 없다.
- A 클래스 외부에서는, A 객체가 있어야 B 객체를 사용할 수 있다. 우선적으로 A 객체를 먼저 만들어야 한다.
- A.B는 B의 클래스 이름이다. 이렇게 표현한다.
- 생성자는 a.new B();
ㅁ 정적 멤버 클래스
- static 키워드로 선언된 클래스
- 정적 멤버 클래스에 인스턴스 필드, 인스턴스 메서드, 정적 필드, 정적 메서드 다 선언 가능.
- 정적 필드, 정적 메서드는 사용할 때 풀네임(?)
- 이 C라는 클래스의 이름이 A.C가 됨.
- A 객체가 없어도 C 객체를 쓸 수 있어서, new A.C();로 선언. A객체 선언 안했음.
- 정적 필드와 정적 메서드는 아래와 같이 인스턴스 없이 직접 사용할 수 있습니다.
(A 객체도 C 객체도 필요 없음.)
public class Main {
public static void main(String[] args) {
System.out.println(A.C.field2);
A.C.method2();
}
}
- 정적 필드와 정적 메서드는 클래스 이름으로 바로 접근 가능하며, 클래스 로딩 시점에 메모리에 할당됩니다.
- 인스턴스 필드와 인스턴스 메서드는 정적 멤버 클래스의 인스턴스를 생성한 후에만 접근할 수 있습니다.
ㅁ 로컬 클래스
- 생성자 또는 메서드 내에서 선언된 클래스.
- 접근 제한자 및 static을 붙일 수 없다.
- 인스턴스 필드와 인스턴스 메서드만 선언할 수 있고, 정적 필드와 정적 메서드는 선언 불가능.
- 로컬 클래스를 선언했으면, 이 로컬 클래스를 사용할 수 있는 곳은 이 메서드 안이다.
선언과 동시에 아래에서 사용해야 하는 것.
49강. 중첩 클래스와 중첩 인터페이스(2) - 중첩 클래스의 접근 제한
ㅁ 바깥 필드와 메서드에서 사용 제한
- B와 C의 바깥 클래스(클래스 A)에서 필드, 메소드에서 B, C 타입을 사용하는 경우 어떤 사용 제한이 있는가.
- 인스턴스 필드: 다 가능.
- 인스턴스 메서드: 다 가능. B와 C 타입을 사용해서 객체를 만들 수 있다.
- 정적 필드와 정적 메서드에선 제한이 따름.
- 정적 필드: A클래스의 정적 필드에 B타입의 필드, C타입의 필드를 선언하게 되면, B 같은 경우는 static을 붙일 수 없음.
A 객체가 있어야 B를 사용할 수 있는건데, A 없이도 사용할 수 있는 static을 붙일 수 없다.
C는 A가 없어도 C를 사용할 수 있기 때문에 사용 가능.
- 정적 메서드: A 없이 이 method2를 쓸 수 있기 때문에, 이 안쪽에도 A가 없이도 사용할 수 있는 코드만 와야 함.
- 정리. A가 있어야 사용될 수 있는 필드, 메서드에는 B를 다 사용할 수 있음.
A 객체 없이 사용할 수 있는 static으로 선언된 필드, 메서드에는 B를 사용할 수가 없음.
- C 객체는 A 객체가 없이도 사용 가능함.
- static이 붙은 필드와 메서드들은 A 객체를 생성하지 않아도 사용 가능함.
ㅁ 멤버 클래스 내에서의 사용 제한
ㅁ 로컬 클래스에서의 사용 제한
- 매개변수나 로컬변수를 로컬 클래스에서 사용하려면, 매개 변수나 로컬 변수는 final로 선언되어있어야 함.
(로컬 클래스에서 매개변수와 로컬변수를 사용할 때만 그렇다는 얘기다.)
- 자바 8부터는 final 선언하지 않아도 final 특성 부여되어 있다.
- 매개변수나 로컬변수가 final로 선언되면, 한 번 값이 저장되면 바꿀 수 없음.
값을 바꾸려하면 오류 발생.
매개변수나 로컬변수를 로컬 클래스에서 사용하려면 처음 주어진 값 그대로 사용해야 함.
- 자바 8 이후.
final이 없다고 매개변수나 로컬변수 값을 바꾸면 컴파일 에러가 남.
- 자바 8 이후는 final을 붙여도 되고 안붙여도 final 특성을 가짐.
언제? 로컬 클래스에서 매개변수나 로컬변수를 사용할 때.
- 로컬 클래스가 없다면 매개변수와 로컬변수가 final 특성을 갖지 않음.
ㅁ 중첩 클래스에서 바깥 클래스 참조 얻기
- 경우에 따라서는 중첩 클래스 내부에서 바깥 클래스의 참조를 얻어서 사용할 때도 있음.
- 중첩 클래스도 하나의 클래스이기 때문에 내부에서 this라는 키워드를 사용할 수 있음.
this라는 키워드는 현재 자기 자신의 객체를 얘기함.
중첩 클래스 안에서 this를 사용하면 이 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 a = new Outter();
Outter.Nested b = a.new Nested();
b.print();
}
}
- 실행 결과
Nested - field
Nested - method
Outter - field
Outter - method
- 필드와 메서드 이름이 같아도 상관 없다. 각각 Outter와 Nested 클래스에서 선언된 필드와 메서드이기 때문.
- this는 항상 현재 객체를 참조하는 변수다.
- 중첩 클래스 객체를 선언하려면 new 앞에 컴마 + 바깥 객체가 필요.
50강. 중첩 클래스와 중첩 인터페이스(3) - 중첩 인터페이스
ㅁ 중첩 인터페이스
- (클래스 안에서) 클래스의 멤버로 선언된 인터페이스.
- 중첩 인터페이스를 사용하는 이유는 바깥 클래스와 밀접한 관련이 있는 인터페이스인 경우.
바깥 클래스 없이는 이 인터페이스가 의미가 없는 경우에 사용.
해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위함.
- 주로 UI 프로그래밍에서 이벤트 처리 목적으로 자주 활용함.
class A {
[static] interface I {
void method();
}
}
- 인스턴스 멤버 인터페이스와 정적 멤버 인터페이스 모두 가능함.
- 중첩 인터페이스를 만들 때 앞에 static이 있을 수도 있고, 없을 수도 있음.
static이 없으면 A 객체가 있어야 이 인터페이스를 사용 가능.
static이 있으면 A 객체가 없어도 이 인터페이스를 사용 가능.
- 프로그램을 개발할 때 중첩 인터페이스를 사용해서 개발하는 경우는 드물지만, UI 프로그램, 즉 안드로이드, 자바fx, 스윙 같은 이런 애플리케이션을 만들 때, 그 UI 요소 내부에 선언된 중첩 인터페이스를 가지고 이벤트 처리를 많이 함.
직접 만들어서 쓰기 보다는 이미 제공되는 api 내부에서 중첩 인터페이스가 존재하기 때문에 알고 있어야 함.
- 중첩 인터페이스 사용 방법은 단독으로 인터페이스를 사용하는 경우와 별반 다르지 않음.
인터페이스 사용 방법은 거의 동일하다.
구현 클래스를 만들고, 그 다음 구현 객체를 만들고, 인터페이스 타입 변수나 필드에 대입해서 사용.
ㅁ 예제: UI 프로그램은 아니지만 흉내 내보기. Button이라는 UI 클래스에서 클릭이라는 이벤트가 발생했을 때, 중첩 인터페이스를 이용해서 이벤트를 처리하는 원리.
-
package sec01.exam06;
public class Button {
OnClickListener listener; // 인터페이스에 구현 객체 대입.
void setListener(OnClickListener listener) { // setA 메서드.
this.listener = listener;
}
void click() {
listener.onClick(); // listener에 대입된 구현 객체의 onClick() 메서드 실행.
}
static interface OnClickListener {
void onClick();
}
}
- 구현 클래스
package sec01.exam06;
public class CallListener implements Button.OnClickListener {
// Button에 선언되어 있는 중첩 인터페이스로 구현을 해야 함. Button의 이벤트를 처리하기 위해.
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
- 실행 클래스
package sec01.exam06;
public class ButtonExample {
public static void main(String[] args) {
Button btn = new Button();
Button.OnClickListener i = new CallListener();
btn.setListener(i);
// btn.setListener(new CallListener());
btn.click();
}
}
- 버튼이 클릭되었을 때 click() 메서드가 실행된다고 가정. 버튼을 클릭했을 때 click 메서드가 실행된다.
그런데 메서드 click() 안에 구체적인 실행 내용을 넣어서는 안 됨. 버튼을 사용할 때 그 버튼이 어떤 용도로 사용되는지 결정되기 때문.
- OnClickListener라는 정적 중첩 인터페이스의 용도는, 버튼을 클릭했을 때 버튼의 실행 내용을 갖고 있는 구현 객체를 얻기 위함.
- click() 메서드 안의 구체적인 내용은, 이 버튼을 사용하는 개발자가 작성. 어떻게? 중첩 인터페이스로.
- 중첩 인터페이스는 버튼이 클릭되었을 때 실행될 내용을 갖고 있는 구현 객체를 받기 위해서 선언하는 거임.
// onClick() 메서드를 선언해 놓음.
OnClickListener라는 타입의 필드를 선언해 놓고, 이 필드의 값을 받을 수 있게 setA메서드 작성.
- click() 메서드 안에는 listener.onClick();이 옴.
listener 필드는 OnClickListener라는 인터페이스 타입이기 때문에 onClick() 메서드 호출 가능.
'혼자 공부하는 자바' 카테고리의 다른 글
혼자 공부하는 자바 (56강 ~ 59강) (1) | 2024.06.16 |
---|---|
혼자 공부하는 자바 (51강 ~ 55강) (0) | 2024.06.14 |
혼자 공부하는 자바 (40강 ~ 45강) (0) | 2024.05.31 |
혼자 공부하는 자바 (34강 ~ 39강) (0) | 2024.05.29 |
혼자 공부하는 자바 (27강 ~ 33강) (0) | 2024.05.28 |