[혼공자바] 5주차 : 인터페이스
본문 바로가기

혼공 스터디/혼자 공부하는 자바

[혼공자바] 5주차 : 인터페이스

728x90
반응형

 

 

인간의 욕심은 끝이없고 같은 실수를 반복한다...

또 날림.. 나는 자바랑 안 맞는 것일까....?

 

Chap 8  인터페이스

1. 인터페이스

  • 인터페이스(interface) 개발 코드와 객체가 서로 통신하는 접점 역할을 한다.
  • 개발 코드가 인터페이스의 메소드를 호출하면 인터페이스는 객체의 메소드를 호출시킴.
  • 따라서 개발 코드는 객체의 내부 구조를 알 필요가 없고 인터페이스의 메소드만 알고 있으면 됨.
  • 개발 코드가 직접 객체의 메소드를 호출하지 않는 이유?
    • 개발 코드르 수정하지 않고 사용하는 객체를 변경할 수 있도록 하기 위해서
    • 인터페이스는 하나의 객체가 아니라 여러 객체들과 사용이 가능하므로
      어떤 객체를 사용하느냐에 따라서 실행 내용과 리턴값이 다를 수 있다
    • 따라서 개발 코드 측면에서는 코드 변경 없이 실행 내용과 리턴값을 다양화할 수 있다는 장점이 있다

  • 인터페이스는 .java 형태의 소스 파일로 작성되고 컴파일러(javac)를 통해 .class 형태로 컴파일되기 때문에
    물리적 형태는 클래스와 동일하다
  • 그러나 소스를 작성할 때 선언하는 방법이 다름
  • 인터페이스 선언은 class 키워드 대신 interface 키워드 사용

  • public 접근 제한은 다른 패키지에서도 인터페이스를 사용할 수 있도록 해줌
  • 인터페이스는 객체로 생성할 수 없다. 즉, 생성자를 가질 수 없음

[이클립스에서 인터페이스 생성]

  1. Package Explorer 뷰에서 인터페이스를 포함할 패키지를 생성한 후 선택하고,
    [File] - [New] - [Interface] 메뉴를 선택
  2. [New Java Interface] 대화상자가 나타나면 [Name] 입력란에 'RemoteControl'을 입력한 후 [Finish] 버튼을 클릭
  3. RemoteControl 인터페이스의 선언부를 자동으로 만들어 줌

 

  • 클래스의 구성
    • 필드, 생성자, 메소드
  • 인터페이스
    • 상수 필드, 추상 메소드

(1) 상수 필드 선언

  • 인터페이스 객체 사용 방법을 정의한 것이므로
    실행 시 데이터를 저장할 수 있는 인스턴스 또는 정적 필드를 선언할 수 없음
  • 상수 필드(constant field)는 선언이 가능
  • 상수는 인터페이스에 고정된 값으로 실행 시에 데이터를 바꿀 수 없다
상수 필드 선언 RemoteControl.java
package sec01.exam02;


public interface RemoteControl {
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;
}

 

(2) 추상 메소드 선언

  • 인터페이스를 통해 호출된 메소드는 최종적으로 객체에서 실행
  • 추상 메소드는 리턴 타입, 메소드 이름, 매개 변수만 기술되고 중괄호 {}를 붙이지 않는 메소드를 말함.
  • 인터페이스에 선언된 추상 메소드는 모두 public abstract의 특성을 갖기 때문에 
    public abstract를 생략하더라도 컴파일 과정에서 자동으로 붙게 됨

추상 메소드 선언 // RemoteControl.java
package sec01.exam03;


public interface RemoteControl {
//상수
int MAX_VOLUME = 10;
int MIN_VOLUME = 0;


//추상 메소드
void turnOn();
void turnOff();
void setVolume(int volume);
}

2) 인터페이스 구현

  • 개발 코드가 인터페이스 메소드를 호출하면 인터페이스는 객체의 메소드를 호출
  • 객체는 인터페이스에서 정의된 추상 메소드와 동일한 메소드 이름, 매개 타입, 리턴 타입을 가진 실체 메소드를 가지고 있어야 함
  • 이러한 객체를 인터페이스의 구현 객체라고 하고, 구현 객체를 생성하는 클래스를 구현 클래스라고 함.

(1) 구현 클래스

구현 클래스는 보통의 클래스와 동일한데,
인터페이스 타입으로 사용할 수 있음을 알려주기 위해

클래스 선언부에 implements 키워드를 추가하고 인터페이스 이름을 명시함

구현 클래스 // Television.java
package sec01.exam04;


public class Television implements RemoteControl {
//필드
private int volume;

//turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("TV를 켭니다.");
}
//turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("TV를 끕니다.");
}
//setVolume() 추상 메소드의 실체 메소드
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 TV 볼륨: " + this.volume);
}
}
구현 클래스 // Audio.java
package sec01.exam04;


public class Audio implements RemoteControl {
//필드
private int volume;

//turnOn() 추상 메소드의 실체 메소드
public void turnOn() {
System.out.println("Audio를 켭니다.");
}
//turnOff() 추상 메소드의 실체 메소드
public void turnOff() {
System.out.println("Audio를 끕니다.");
}
//setVolume() 추상 메소드의 실체 메소
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재 Audio 볼: " + this.volume);
}
}

[ public 접근 제한 ]

구현 클래스에서 인터페이스의 추상 메소드에 대한 실체 메소드를 작성할 때 주의할 점은

인터페이스의 모든 메소드는 기본적으로 public 접근 제한을 갖기 때문에

public보다 더 낮은 접근 제한으로 작성할 수 없다는 것

  • 구현 클래스가 작성되면 new 연산자로 객체를 생성할 수 있다. 다음 코드는 인터페이스를 사용한 것이 아님

  • 인터페이스로 구현 객체를 사용하려면 다음과 같이 인터페이스 변수를 선언하고 구현 객체를 대입해야한다.
  • 인터페이스 변수는 참조 타입이기 때문에 구현 객체가 대입될 경우 구현 객체의 번지를 저장

인터페이스 변수에 구현 객체 대입 // RemoteControlExample.java
package sec01.exam04;


public class RemoteControlExample {
public static void main(String[] args) {
RemoteControl rc;
rc = new Television();
rc = new Audio();
}
}

 

(2) 다중 인터페이스 구현 클래스

  • 객체는 다음 그림과 같이 다수의 인터페이스 타입으로 사용 가능

  • 인터페이스 A와 인터페이스 B가 객체의 메소드를 호출할 수 있으려면
    객체는 두 인터페이스를 모두 구현해야한다.

인터페이스 // Searchable.java
package sec01.exam05;


public interface Searchable {
void search(String url);
}
다중 인터페이스 구현 클래스 // SmartTelevision.java
package sec01.exam05;


public class SmartTelevision implements RemoteControl, Searchable {
private int volume;

public void turnOn() {
System.out.println("TV를 켭니다.");
}
public void turnOff() {
System.out.println("TV를 끕니.");
}
public void setVolume(int volume) {
if(volume>RemoteControl.MAX_VOLUME) {
this.volume = RemoteControl.MAX_VOLUME;
} else if(volume<RemoteControl.MIN_VOLUME) {
this.volume = RemoteControl.MIN_VOLUME;
} else {
this.volume = volume;
}
System.out.println("현재  TV 볼륨: " + this.volume);
}

public void search(String url) {
System.out.println(url + "을 검색합니다.");
}
}
인터페이스 변수에 구현 객체 대입 // SmartTelevisionExample.java
package sec01.exam05;


public class SmartTelevisionExample {
public static void main(String[] args) {
SmartTelevision tv = new SmartTelevision();

RemoteControl rc = tv;
Searchable searchable = tv;
}
}
1)----------------
TV를 꼅니다.
현재 TV 볼륨: 5
2)----------------
Audio를 켭니다.
현재 Audio 볼륨: 5
3)----------------
Audio를 켭니다.
현재 Audio 볼륨: 5
4)----------------
TV를 꼅니다.
현재 TV 볼륨: 5
 

3) 인터페이스 사용

 

  • 클래스를 선언할 때 인터페이스는 필드, 생성자 또는 메소드의 매개 변수, 생성자 또는 메소드의 로컬 변수로 선언될 수 있음

 

인터페이스 사용 // MyClass.java
package sec01.exam06;


public class MyClass {
// 필드
RemoteControl rc = new Television();


// 생성자
MyClass() {
}


MyClass(RemoteControl rc) {
this.rc = rc;
rc.turnOn();
rc.setVolume(5);
}


// 메소드
void methodA() {
RemoteControl rc = new Audio();
rc.turnOn();
rc.setVolume(5);
}


void methodB(RemoteControl rc) {
rc.turnOn();
rc.setVolume(5);
}
}
인터페이스 사용 // MyClassExample.java
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();
myClass4.methodB(new Television());
}
}
1)----------------
TV를 꼅니다.
현재 TV 볼륨: 5
2)----------------
Audio를 켭니다.
현재 Audio 볼륨: 5
3)----------------
Audio를 켭니다.
현재 Audio 볼륨: 5
4)----------------
TV를 꼅니다.
현재 TV 볼륨: 5

Cat.java Dog.java
package sec01.verify.exam03;


public class Cat implements Soundable {
@Override
public String sound() {
return "야옹";
}
}
package sec01.verify.exam03;


public class Dog implements Soundable {
@Override
public String sound() {
return "멍멍";
}
}

 

2. 타입 변환과 다형성

  • 개발할 때 인터페이스를 사용해서 메소드를 호출하면 구현 객체를 매우 손쉽고 빠르게 교체 가능
  • 프로그램 소스 코드는 변함이 없는데, 구현 객체를 교체하면서 프로그램의 실행결과가 다양해짐

 

  • 1 인터페이스를 이용해서 프로그램을 개발하고
  • 1 인터페이스를 구현한 클래스로 처음에는 A 클래스를 선택했는데,
    테스트를 해보니 A 클래스에 문제가 있다는 걸 알았을 때
    B 클래스와 교체한 후 단 한 줄만 수정해서 프로그램을 재실행할 수 있

1) 자동 타입 변환 (promotion)

  • 구현 객체가 인터페이스 타입으로 변환되는 것
  • 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것

  • 인터페이스 구현 클래스를 상속해서 자식 클래스를 만들었다면
    자식 객체 역시 인터페이스 타입으로 자동 타입 변환 가능

 

 

2) 필드의 다형성

  • 한국 타이어와 금호 타이어는 공통적으로 타이어 인터페이스를 구현했기 때문에
    모든 타이어 인터페이스에 있는 메소드를 가지고 있음
  • 따라서 타이어 인터페이스로 동일하게 사용할 수 있는 교체 가능한 객체에 해당

인터페이스 Tire.java
package sec02.exam01;


public interface Tire {
public void roll();
}
구현 클래스 HankookTire.java
package sec02.exam01;


public class HankookTire implements Tire {
@Override
public void roll() {
System.out.println("한국타이어가 굴러갑니다.");
}
}
구현 클래스 KumhoTire.java
package sec02.exam01;


public class KumhoTire implements Tire {
@Override
public void roll() {
System.out.println("금호타이어가 굴러갑니다.");
}
}
필드 다형성 Car.java
package sec02.exam01;


public class Car {
Tire frontLeftTire = new HankookTire();
Tire frontRightTire = new HankookTire();
Tire backLeftTire = new HankookTire();
Tire backRightTire = new HankookTire();

void run() {
frontLeftTire.roll();
frontRightTire.roll();
backLeftTire.roll();
backRightTire.roll();
}
}
필드 다형성 테스트 CarExample.java
package sec02.exam01;


public class CarExample {
public static void main(String[] args) {
Car myCar = new Car();

myCar.run();

myCar.frontLeftTire = new KumhoTire();
myCar.frontRightTire = new KumhoTire();

myCar.run();
}
}
한국타이어가 굴러갑니다.
한국타이어가 굴러갑니다.
한국타이어가 굴러갑니다.
한국타이어가 굴러갑니다.
금호타이어가 굴러갑니다.
금호타이어가 굴러갑니다.
한국타이어가 굴러갑니다.
한국타이어가 굴러갑니다.

3) 매개 변수의 다형성

  • 자동 타입 변환은 필드의 값을 대입할 때도 발생하지만, 
    주로 메소드를 호출할 때 많이 발생한다.
  • 매개값을 다양화하기 위해서 상속에서는 매개 변수를 부모 타입으로 선언하고
    호출할 때에는 자식 객체를 대입했다.
  • 이번에는 매개 변수를 인터페이스 타입으로 선언하고 호출할 때에는 구현 객체를 대입

 

  • Bus가 구현 클래스라면 다음과 같이 Driver의 drive() 메소드를 호출할 때 Bus 객체를 생성해서 매개값으로 줄 수 있
매개 변수의 인터페이스화 // Driver.java
package sec02.exam02;


public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();

Bus bus = new Bus();
Taxi taxi = new Taxi();

driver.drive(bus);
driver.drive(taxi);
}
}
인터페이스 Vehicle.java
package sec02.exam02;


public interface Vehicle {
public void run();
}
구현 클래스 Bus.java
package sec02.exam02;


public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}

 

 

4) 강제 타입 변환

  • 구현 객체가 인터페이스 타입으로 자동 타입 변환하면, 인터페이스에 선언된 메소드만 사용 가능하다.
  • 인터페이스에는 3개의 메소드가 선언되어 있고 클래스에는 5개의 메소드가 선언되어 있다면
    인터페이스로 호출 가능한 메소드는 3개뿐이다.

  • 강제 타입 변환(casting)
    • 경우에 따라서는 구현 클래스에 선언된 필드와 메소드를 사용해야 할 경우도 발생
    • 이때 다시 구현 클래스 타입으로 변환한 다음, 구현 클래스의 필드와 메소드를 사용 가능

인터페이스 Vehicle.java
package sec02.exam03;


public interface Vehicle {
public void run();
}
구현 클래스 Bus.java
package sec02.exam03;


public class Bus implements Vehicle {
@Override
public void run() {
System.out.println("버스가 달립니다.");
}

public void checkFare() {
System.out.println("승차요금을 체크합니다.");
}
}
강제 타입 변환 VehicleExample.java
package sec02.exam03;


public class VehicleExample {
public static void main(String[] args) {
Vehicle vehicle = new Bus();


vehicle.run();
//vehicle.checkFare(); (x)


Bus bus = (Bus) vehicle;


bus.run();
bus.checkFare();
}
}
버스가 달립니다.
버스가 달립니다.
승차요금을 체크합니다.

5) 객체 타입 확인

객체 타입 확인 Driver.java
package sec02.exam04;


public class Driver {
public void drive(Vehicle vehicle) {
if(vehicle instanceof Bus) {
Bus bus = (Bus) vehicle;
bus.checkFare();
}
vehicle.run();
}
}
객체 타입 확인 DriverExample.java
package sec02.exam04;


public class DriverExample {
public static void main(String[] args) {
Driver driver = new Driver();

Bus bus = new Bus();
Taxi taxi = new Taxi();

driver.drive(bus);
driver.drive(taxi);
}
}
승차요금을 체크합니다.
버스가 달립니다.
택시가 달립니다.

 

6) 인터페이스 상속

  • 인터페이스도 다른 인터페이스를 상속할 수 있다.
  • 인터페이스는 클래스와는 달리 다중 상속을 허용한다.

  • 하위 인터페이스를 구현하는 클래스는 하위 인터페이스의 메소드뿐만 아니라
    상위 인터페이스의 모든 추상 메소드에 대한 실체 메소드를 가지고 있어야한다.

  • 하위 인터페이스로 타입 변환이 되면 상위 및 하위 인터페이스에 선언된 모든 메소드를 사용할 수 있으나,
    상위 인터페이스로 타입 변환되면 상위 인터페이스에 선언된 메소드만 사용 가능하고 하위 인터페이스에 선언된 메소드는 사용할 수 없다.
상위 인터페이스 InterfaceA.java
package sec02.exam05;


public interface InterfaceA {
public void methodA();
}
상위 인터페이스 InterfaceB.java
package sec02.exam05;


public interface InterfaceB {
public void methodB();
}
하위 인터페이스 InterfaceC.java
package sec02.exam05;


public interface InterfaceC extends InterfaceA, InterfaceB {
public void methodC();
}
하위 인터페이스 구현 ImplementationC.java
package sec02.exam05;


public class ImplementationC implements InterfaceC {
public void methodA() {
System.out.println("ImplementationC-methodA() 실행");
}

public void methodB() {
System.out.println("ImplementationC-methodB() 실행");
}

public void methodC() {
System.out.println("ImplementationC-methodC() 실행");
}
}

 

호출 가능 메소드 Example.java
package sec02.exam05;


public class Example {
public static void main(String[] args) {
ImplementationC impl = new ImplementationC();

InterfaceA ia = impl;
ia.methodA();
System.out.println();

InterfaceB ib = impl;
ib.methodB();
System.out.println();

InterfaceC ic = impl;
ic.methodA();
ic.methodB();
ic.methodC();
}
}
ImplementationC-methodA() 실행


ImplementationC-methodB() 실행


ImplementationC-methodA() 실행
ImplementationC-methodB() 실행
ImplementationC-methodC() 실행

 

DataAccessObject.java MySqlDao.java
package sec02.verify.exam03;


public interface DataAccessObject {
public void select();
public void insert();
public void update();
public void delete();
}


















package sec02.verify.exam03;


public class MySqlDao implements DataAccessObject {
@Override
public void select() {
System.out.println("MySql DB에서 검색");
}


@Override
public void insert() {
System.out.println("MySql DB에 삽입");
}


@Override
public void update() {
System.out.println("MySql DB를 수정");
}


@Override
public void delete() {
System.out.println("MySql DB에서 삭제");
}
}
OracleDao.java DaoExample.java
package sec02.verify.exam03;


public class OracleDao implements DataAccessObject {
@Override
public void select() {
System.out.println("Oracle DB에서 검색");
}


@Override
public void insert() {
System.out.println("Oracle DB에 삽입");
}


@Override
public void update() {
System.out.println("Oracle DB를 수정");
}


@Override
public void delete() {
System.out.println("Oracle DB에서 삭제");
}
}
package sec02.verify.exam03;


public class DaoExample {
public static void dbWork(DataAccessObject dao) {
dao.select();
dao.insert();
dao.update();
dao.delete();
}

public static void main(String[] args) {
dbWork(new OracleDao());
dbWork(new MySqlDao());
}
}












Chap 9. 중첩 클래스와 중첩 인터페이스

1. 중첩 클래스와 인터페이스 소개

 

  • 중첩 클래스(nested class)
    • 클래스 내부에 선언한 클래스
    • 두 클래스의 멤버들을 서로 쉽게 접근할 수 있고, 외부에는 불필요한 관계 클래스를 감춰
      코드의 복잡성을 줄일 수 있음
  • 중헙 인터페이스(nested interface)
    • 클래스 내부에 선언한 인터페이스
    • 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위

1) 중첩 클래스

  • 클래스 내부에 선언되는 위치에 따라 두 가지로 분류
  • 멤버 클래스
    • 클래스의 멤버로서 선언되는 중첩 클래스
    • 클래스나 객체가 사용 중이라면 언제든지 재사용이 가능
  • 로컬 클래스
    • 생성자 또는 메소드 내부에서 선언되는 중첩 클래스
    • 메소드를 실행할 때만 사용되고 메소드가 종료되면 없어짐
선언 위치에 따른 분류 선언 위치 설명
멤버 클래스 인스턴스 멤버 클래스 class A {
    class B { ``` }
}
A 객체를 생성해야만 사용할 수 있는 B 클래스
정적 멤버 클래스 class A {
    static class B { ``` }
}
A 클래스로 바로 접근할 수 있는 B 클래스
로컬 클래스 class A {
    void method() {
        class B{ ``` }
    }
}
method()가 실행할 때만 사용할 수 있는 B 클래스
  • 중첩 클래스도 하나의 클래스이기 때문에 컴파일하면 바이트 코드 파일(.class)이 별도로 생성됨.
  • 멤버 클래스일 경우 바이트 코드 파일의 이름은 다음과 같이 결정

  • 로컬 클래스일 경우에는 다음과 같이 $1이 포함된 바이트 코드 파일이 생성

 

(1) 인스턴스 멤버 클래스

  • 인스턴스 멤버 클래스는 static 키워드 없이 중첩 선언된 클래스
  • 인스턴스 멤버 클래스는 인스턴스 필드와 메소드만 선언이 가능하고 정적 필드와 메소드는 선언할 수 없음
  • A 클래스 외부에서 B 객체를 생성하려면 먼저 A 객체를 생성하고 B 객체를 생성해야 함.
  • A 클래스 내부의 생성자 및 인스턴스 메소드에서는 일반 클래스처럼 B 객체를 생성할 수 있음
  • 일반적으로 A 클래스 외부에서 B 객체를 생성하는 일은 거의 없음
  • 대부분 A 클래스 내부에서 B 객체를 생성해서 사용

(2) 정적 멤버 클래스

  • 정적 멤버 클래스는 static 키워드로 선언된 클래스를 말함
  • 정적 멤버 클래스는 모든 종류의 필드와 메소드를 선언할 수 있음
  • A 클래스 외부에서 정적 멤버 클래스 C의 객체를 생성하기 위해 A 객체를 생성할 필요가 없고, C 객체를 생성하면 됨

(3) 로컬 클래스

  • 중첩 클래스는 메소드 내에서도 선언할 수 있음
  • 로컬 클래스는 접근 제한자 및 static을 붙일 수 없음.
  • 로컬 클래스는 메소드 내부에서만 사용되므로 접근을 제한할 필요가 없다.
  • 로컬 클래스 내부에는 인스턴스 필드와 메소드만 선언할 수 있고 정적 필드와 메소드는 선언할 수 없음.
  • 로컬 클래스는 메소드가 실행될 때 메소드 내에서 객체를 생성하고 사용해야함.
중첩 클래스 A.java
package sec01.exam01;


/**바깥 클래스**/
class A {
A() { System.out.println("A 객체가 생성됨"); }

/**인스턴스 멤버 클래스**/
public 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 field1;
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();
}
}
중첩 클래스 객체 생성 Main.java
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.field1 = 3;
c.method1();
A.C.field2 = 3;
A.C.method2();

//로컬 클래스 객체 생성을 위한 메소드 호출
a.method();
}
}
 
A 객체가 생성됨
B 객체가 생성됨
C 객체가 생성됨
D 객체가 생성됨

 

 

 

2) 중첩 클래스의 접근 제한

  • 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때는 제한이 따른다.
  • 메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때도 제한이 따른다.

(1) 바깥 필드와 메소드에서 사용 제한

  • 바깥 클래스에서 인스턴스 멤버 클래스를 사용할 때 제한이 있음
  • 인스턴스 멤버 클래스는 바깥 클래스의 인스턴스 필드의 초기값이나 인스턴스 메소드에서 객체를 생성할 수 있음
  • 정적 필드의 초기값이나 정적 메소드에서는 객체를 생성할 수 없음.
  • 정적 멤버 클래스는 모든 필드의 초기값이나 모든 메소드에서 객체를 생성할 수 있음
바깥 필드와 메소드에서 사용 제한 A.java
package sec01.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 {}
}

(2) 멤버 클래스에서 사용 제한

  • 멤버 클래스가 인스턴스 또는 정적으로 선언됨에 따라 멤버 클래스 내부에서 바깥 클래스의 필드와 메소드에 접근할 때에도 제한이 따름
  • 인스턴스 멤버 클래스 안에서는 바깥 클래스의 모든 필드와 모든 메소드에 접근할 수 있지만,
    정적 멤버 클래스 안에서는 바깥 클래스의 정적 필드와 메소드에만 접근할 수 있고
    인스턴스 필드와 메소드에는 접근할 수 없음.
멤버 클래스에서 사용 제한 A.java
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() {
//field1 = 10;
//method1();


field2 = 10;
method2();
}
}
}

 

(3) 로컬 클래스에서 사용 제한

  • 메소드의 매개 변수나 로컬 변수를 로컬 클래스에서 사용할 때 제한이 있음
  • 로컬 클래스의 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만,
    메소드가 종료되어도 계속 실행 상태로 존재할 수 있음
  • 메소드를 실행하는 스레드와 다르므로 메소드가 종료된 후에도 로컬 스레드 객체는 실행 상태로 존재할 수 있음
로컬 클래스에서 사용 제한 Outter.java
package sec01.exam04;


public class Outter {
//자바 7 이전
public void method1(final int arg) {
final int localVariable = 1;
//arg = 100; (x)
//localVariable = 100; (x)
class Inner {
public void method() {
int result = arg + localVariable;
}
}
}

//자바 8 이후
public void method2(int arg) {
int localVariable = 1;
//arg = 100; (x)
//localVariable = 100; (x)
class Inner {
public void method() {
int result = arg + localVariable;
}
}
}
}

 

(4) 중첩 클래스에서 바깥 클래스 참조 얻기

  • 클래스 내부에서 this는 객체 자신의 참조
  • 중첩 클래스에서 this 키워드를 사용하면 바깥 클래스의 객체 참조가 아니라, 중첩 클래스의 객체 참조가 됨.
  • 따라서 중첩 클래스 내부에서 'this.필드, this.메소드()'로 호출하면 중첩 클래스의 필드와 메소드가 사용됨
  • 중첩 클래스 내부에서 바깥 클래스의 객체 참조를 얻으려면 바깥 클래스의 이름을 this 앞에 붙여주면 됨.
중첩 클래스에서 바깥 클래스 참조 얻기 Outter.java
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();
}
}
}
실행 클래스 OutterExample.java
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-field
Nested-method
Outter-field
Outter-method

 

3) 중첩 인터페이스

  • 중첩 인터페이스는 클래스의 멤버로 선언된 인터페이스를 말함.
  • 인터페이스를 클래스 내부에 선언하는 이유는 해당 클래스와 긴밀한 관계를 맺는 구현 클래스를 만들기 위해서임.
  • 중첩 인터페이스는 인스턴스 멤버 인터페이스와 정적 멤버 인터페이스 모두 가능
  • 인스턴스 멤버 인터페이스는 바깥 클래스의 객체가 있어야 사용이 가능하며,
    정적 멤버 인터페이스는 바깥 클래스의 객체 없이 바깥 클래스만으로 바로 접근 가능
  • 주로 정적 멤버 인터페이스를 많이 사용하는데 UI 프로그래밍에서 이벤트를 처리할 목적으로 많이 활용됨.
중첩 인터페이스 Button.java
package sec01.exam06;


public class Button {
OnClickListener listener;

void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}

void touch() {
listener.onClick();
}

static interface OnClickListener {
void onClick();
}
}
구현 클래스 CallListener.java
package sec01.exam06;


public class CallListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("전화를 겁니다.");
}
}
구현 클래스 MessageListener.java
package sec01.exam06;


public class MessageListener implements Button.OnClickListener {
@Override
public void onClick() {
System.out.println("메시지를 보냅니다.");
}
}
버튼 이벤트 처리 ButtonExample.java
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();
}
}
전화를 겁니다.
메시지를 보냅니다.

[ 선택 미션 ]

 

package sec01.verify.exam05;


public class BackgroundChangeListener implements CheckBox.OnSelectListener {
@Override
public void onSelect() {
System.out.println("배경을 변경합니다.");
}
}

 

2. 익명 객체

  • 이름이 없는 객체를 말한다.
  • 어떤 클래스를 상속하거나 인터페이스를 구현해야만 함.
  • 일반적으로 명시적으로 클래스 이름을 주고 선언하는 방법을 사용

  • 이 경우 부모 클래스 변수는 클래스이름1의 객체를 참조하고, 인터페이스 변수는 클래스이름2의 객체를 참조
  • 그러나 익명 객체를 생성할 때는 다음과 같이 클래스 이름이 없음

  • 이 경우 부모 클래스 변수는 이름이 없는 자식 객체를 참조하고, 인터페이스 변수는 이름이 없는 구현 객체를 참조

 

1) 익명 자식 객체 생성

  • 보모 타입의 필드 또는 변수를 선언하고 자식 객체를 초기값으로 대입하는 경우를 생각해보자
  • 우선 부모 클래스를 상속해서 자식 클래스를 선언한다.
  • 그리고 new 연산자를 이용해서 자식 객체를 생성한 후 부모 타입의 필드 또는 변수에 대입
부모 클래스 Person.java
package sec02.exam01;


public class Person {
void wake() {
System.out.println("7시에 일어납니다.");
}
}
익명 자식 객체 생성 Anonymous.java
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();
}
}
익명 자식 객체 생성 AnonymousExample.java
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 연산자를 이용해 구현 객체를 생성한 후
    인터페이스 타입의 필드 또는 로컬 변수에 대입
인터페이스 RemoteControl.java
package sec02.exam02;


public interface RemoteControl {
public void turnOn();
public void turnOff();
}
익명 구현 객체 생성 Anonymous.java
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();
}
}
익명 구현 객체 생성 AnonymousExample.java
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 클래스 Button.java
package sec02.exam03;


public class Button {
OnClickListener listener;

void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}

void touch() {
listener.onClick();
}

static interface OnClickListener {
void onClick();
}
}
UI 클래스 Window.java
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("메시지를 보냅니다.");
}
});
}
}
실행 클래스 Main.java
package sec02.exam03;


public class Main {
public static void main(String[] args) {
Window w = new Window();
w.button1.touch();
w.button2.touch();
}
}
전화를 겁니다.
메시지를 보냅니다.

 

3) 익명 객체의 로컬 변수 사용

  • 메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때도 제한이 있다.
  • 익명 객체는 메소드 실행이 종료되면 없어지는 것이 일반적이지만,
    메소드가 종료되어도 계속 실행 상태로 존재할 수 있음
  • 문제는 메소드의 매개 변수나 로컬 변수를 익명 객체 내부에서 사용할 때이다.
  • 매개 변수나 로컬 변수는 메소드 실행이 끝나면 스택 메모리에서 사라지기 때문에
    익명 객체에서 지속적으로 사용할 수 없다.
인터페이스 Calculatable.java
package sec02.exam04;


public interface Calculatable {
public int sum();
}
익명 객체의 로컬 변수 사용 Anonymous.java
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; (x)
//arg2 = 20; (x)

//var1 = 30; (x)
//var2 = 30; (x)

Calculatable calc = new Calculatable() {
@Override
public int sum() {
int result = field + arg1 + arg2 + var1 + var2;
return result;
}
};

System.out.println(calc.sum());
}
}
익명 객체의 로컬 변수 사용 AnonymousExample.java
package sec02.exam04;


public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
anony.method(0, 0);
}
}
10

[ 확인 문제 9-2-1 ]

package sec02.verify.exam01;


public class Worker {
public void start() {
System.out.println("쉬고 있습다.");
}
}
package sec02.verify.exam01;


public class Anonymous {
Worker field = new Worker() {
@Override
public void start() {
System.out.println("디자인을 합니다.");
}
};


void method1() {
Worker localVar = new Worker() {
@Override
public void start() {
System.out.println("개발을 합니다.");
}
};
localVar.start();
}

void method2(Worker worker) {
worker.start();
}
}
 
package sec02.verify.exam01;


public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
anony.field.start();
anony.method1();
anony.method2(
new Worker() {
@Override
public void start() {
System.out.println("테스트를 합니다.");
}
}
);
}
}

 

[ 확인 문제 9-2-2 ]

package sec02.verify.exam02;


public interface Vehicle {
public void run();
}
package sec02.verify.exam02;


public class Anonymous {
Vehicle field = new Vehicle() {
@Override
public void run() {
System.out.println("자전거가 달립니다.");
}
};


void method1() {
Vehicle localVar = new Vehicle() {
@Override
public void run() {
System.out.println("승용차가 달립니다.");
}
};
localVar.run();
}

void method2(Vehicle v) {
v.run();
}
}
package sec02.verify.exam02;


public class AnonymousExample {
public static void main(String[] args) {
Anonymous anony = new Anonymous();
anony.field.run();
anony.method1();
anony.method2(
new Vehicle() {
@Override
public void run() {
System.out.println("트럭이 달립니다.");
}
}
);
}
}

 

[ 확인 문제 9-2-3 ]

package sec02.verify.exam03;


public class CheckBox {
OnSelectListener listener;

void setOnSelectListener(OnSelectListener listener) {
this.listener = listener;
}

void select() {
listener.onSelect();
}

static interface OnSelectListener {
void onSelect();
}
}
package sec02.verify.exam03;


public class CheckBoxExample {
public static void main(String[] args) {
CheckBox checkBox = new CheckBox();
checkBox.setOnSelectListener(new CheckBox.OnSelectListener() {
@Override
public void onSelect() {
System.out.println("배경을 변경합니다.");
}
});
checkBox.select();
}
}
728x90
반응형