인터페이스 만들기
인터페이스
- 서로 관계가 없는 물체들이 상호 작용을 하기 위해서 사용하는 장치나 시스템
- 구현을 하지 않고 기능들만 선언해서 가지고 있는 것
- 인터페이스는 자체적으로 타입의 역할은 할 수 있지만, 객체를 생성하진 못함
- 인터페이스 정의하는 방법
- 추상 메소드와 상수를 정의할 수 있음
public interface TV {
public int MIN_VOLUME = 0;
public int MAX_VOLUME = 100;
public void on();
public void off();
public void volume(int volume);
public void channel(int channel);
}
- 인터페이스의 모든 필드는 public static final 이어야 함 (상수)
- 인터페이스에서 변수를 선언할 때 final 키워드를 안 써도, 컴파일할 때 자동으로 생성됨
public static final int MAX_VOLUME = 100;
public static final int MIN_VOLUME = 0;
- 인터페이스의 모든 메소드는 public abstact 이어야 함 (추상 메소드)
- 모든 메소드는 컴파일 시에 다음과 같이 자동으로 변경됨
- 추상 클래스에서는 abstract 키워드를 꼭 써야 했는데, 인터페이스에서는 컴파일할 때 abstract 키워드가 자동 생성됨
public abstract void on();
public abstract void off();
public abstract void volume(int value);
public abstract void channel(int number);
** 객체를 만들 때 그 객체가 어떤 기능을 가지는지를 고민해야 함 **
인터페이스 사용하기
- 해당 인터페이스를 구현하는 클래스에서 implements 키워드를 이용
- 접근제한자 class 클래스명 implements 인터페이스명{ }
- 클래스 생성 시 인터페이스를 구현할 때 Interfaces의 Add 버튼을 사용하면 됨
- 아래의 코드가 자동 생성됨
//LedTV는 인터페이스 TV를 구현하겠다
//TV라는 인터페이스가 가진 기능들을 LedTV도 모두 갖게 하겠다
public class LedTV implements TV {
@Override
public void on() {
}
@Override
public void off() {
}
@Override
public void volume(int volume) {
}
@Override
public void channel(int channel) {
}
}
- TV 인터페이스의 메소드를 LedTV에 맞게 수정한 코드
public class LedTV implements TV{
public void on(){
System.out.println("켜다");
}
public void off(){
System.out.println("끄다");
}
public void volume(int value){
System.out.println(value + "로 볼륨조정하다.");
}
public void channel(int number){
System.out.println(number + "로 채널조정하다.");
}
}
- LedTV클래스의 실행 코드
public class LedExam {
public static void main(String[] args) {
TV tv = new LedTV();
tv.on();
tv.off();
tv.volume(20);
tv.channel(10);
}
}
- 인터페이스가 가지고 있는 메소드를 전부 다 구현하지 않는다면, 해당 클래스는 추상클래스가 됨
- 추상클래스는 인스턴스를 만들 수 없음
public class LedTVExam{
public static void main(String args[]){
TV tv = new LedTV();
tv.on();
tv.volume(50);
tv.channel(6);
tv.off();
}
}
- 참조변수의 타입으로 인터페이스를 사용할 수 있음
- TV tv = new LedTV( );
- 이 경우 인터페이스가 가지고 있는 메소드만 사용할 수 있음
- LedTV가 TV 인터페이스에 없는 기능들을 확장해 사용할 경우 그 기능들을 더 사용하고 싶다면 클래스 형변환을 활용 (다운캐스팅 사용해야 함)
- 인터페이스를 사용하는 이유
- 동일한 인터페이스를 구현한다는 것은 클래스 사용법이 같다는 것을 의미
- 예: TV 인터페이스를 구현하는 LcdTV를 만들었다면, 위의 코드에서 new LedTV부분만 new LcdTV로 변경해도 똑같이 프로그램이 동작함
- 클래스는 이러한 인터페이스를 여러 개 구현할 수 있음
실습 1: 인터페이스의 메소드를 구현하는 문제
- 문제 설명
- Meter(택시 미터기)인터페이스는 start와 stop이라는 추상 메소드를 가지고 있습니다. stop메소드는 달린 거리에 해당하는 값(distance)를 매개변수로 받아 요금을 int형으로 반환하는 메소드입니다. Meter인터페이스를 구현하는 택시 클래스를 완성해 보세요. 요금은 distance x 2로 계산하세요.
- Meter 인터페이스
public interface Meter {
public abstract void start();
public abstract int stop(int distance);
}
- Taxi 클래스: Meter 인터페이스를 구현
public class Taxi implements Meter {
// Meter인터페이스의 start와 stop메소드를 구현해야 합니다.
public void start(){
System.out.println("운행을 시작합니다.");
}
public int stop(int distance){
int fee = distance * 2;
return fee;
}
}
- MeterExam 클래스: Taxi를 실행
// 아래는 실행을 위한 코드입니다. 수정하지 마세요.
public class MeterExam {
public static void main(String[]args){
Taxi taxi = new Taxi();
boolean a = Meter.class.isInstance(taxi);
if(a!=true){
System.out.println("Taxi클래스는 Meter인터페이스를 구현해야 합니다.");
}
else if(taxi.stop(200)!=400){
System.out.println("stop(200)의 값은 400이어야 합니다.");
}
else{
System.out.println("정답입니다. [제출]을 누르세요.");
}
}
}
프로그래머스 실습 창에서 MeterExam 클래스의 실행 결과가 출력되지 않는 게 아쉬워 이클립스에서 수정한 코드
- Meter 인터페이스
public interface Meter {
public void start();
public int stop(int distance);
}
- Taxi 클래스
public class Taxi implements Meter {
@Override
public void start() {
System.out.println("운행을 시작합니다.");
}
@Override
public int stop(int distance) {
int fee = distance * 2;
return fee;
}
}
- MeterExam 클래스
public class MeterExam {
public static void main(String[] args) {
Taxi taxi = new Taxi();
boolean a = Meter.class.isInstance(taxi);
System.out.println(a);
taxi.start();
int fee = taxi.stop(200);
System.out.println("요금은 " + fee + "원입니다.");
}
}
- 결과
- true
운행을 시작합니다.
요금은 400원입니다.
- true
실습 2: 인터페이스에서 변수를 선언하면 자동으로 상수가 되는 개념 문제
- 문제 설명
- 인터페이스에서 정의한 변수는 모두 상수이기 때문에 변경할 수 없습니다. 때문에 다음 코드를 실행하면 TaxiExam.java의 4번째 줄에서 "cannot assign a value to final variable BASE_FARE"라는 에러가 발생합니다. 오류를 수정하고 문제를 해결해 보세요.
- 해설
- 인터페이스에서 선언한 변수는 자동으로 상수가 돼서 변경할 수 없는데 MeterExam에서 변경했기 때문에 오류가 발생함
- public interface Meter{ public int BASE_FARE = 3000;
- 인터페이스가 아니라 클래스로 옮겨주면 변경 가능해지기 때문에 오류 해결됨
- public class Taxi implements Meter{ public int BASE_FARE = 3000;
- 인터페이스에서 선언한 변수는 자동으로 상수가 돼서 변경할 수 없는데 MeterExam에서 변경했기 때문에 오류가 발생함
- 개념
- 인터페이스에서 선언한 변수는 자동으로 상수가 된다
- final 키워드를 붙이지 않아도 컴파일할 때 자동으로 생성됨
- 인터페이스에서 선언한 변수는 자동으로 상수가 된다
- Taxi 클래스
public class Taxi implements Meter{
//원래 Meter 인터페이스에 있던 변수인데 값을 변경하고 싶어서 Taxi 클래스로 옮김
//기본요금
public int BASE_FARE = 3000;
public void start(){
System.out.println("운행을 시작합니다.");
}
public int stop(int distance){
int fare = BASE_FARE + distance * 2;
System.out.println("운행을 종료합니다. 요금은 " + fare + "원 입니다.");
return fare;
}
}
- Meter 인터페이스
public interface Meter{
public abstract void start();
public abstract int stop(int distance);
}
- TaxiExam 클래스
public class TaxiExam{
public static void main(String []args){
Taxi taxi = new Taxi();
//인터페이스에서 선언한 변수는 상수라 변경하지 못하는데
//밑의 코드에서 변경했기 때문에 에러 발생
//'cannot assign a value to final variable BASE_FARE'
taxi.BASE_FARE = 2500;
//Meter 인터페이스에 있던 변수 BASE_FARE를 Taxi 클래스로 옮겼기 때문에 에러 발생하지 않음 (값 변경 가능)
}
}
인터페이스의 default 메소드와 static 메소드
인터페이스의 default 메소드와 static 메소드
- JAVA 8이 등장하면서 인터페이스에 대한 정의가 몇 가지 변경됨
- 인터페이스를 구현하는 클래스에서는 인터페이스의 추상 메소드를 모두 구현해야 하기 때문에 인터페이스에 메소드를 추가하면 구현체 클래스의 변경사항이 너무 많아져 문제 발생
- 위의 문제를 해결하기 위해 JAVA 8에서는 인터페이스에서 default 메소드와 static 메소드에서 추상 메소드를 기본으로, default 메소드와 static 메소드도 사용할 수 있게 됨
인터페이스에 default메소드가 생겨난 배경
- 기존의 인터페이스
- 기능에 대한 구현보다는 기능에 대한 선언에 초점을 맞춰 사용하기 때문에, 실제 코드를 구현한 로직은 포함될 수 없음
- 추상 메소드만 존재할 수 있었고 인터페이스를 상속받는 클래스에서 직접 모든 추상 메소드를 구현해야만 했음
- 기존의 인터페이스를 이용해 구현된 클래스를 쓰는 상황에서 인터페이스를 보완해야 함
- 요구사항이 추가되면서 인터페이스에 추가적으로 구현하거나 필수적으로 존재해야 하는 추상 메소드가 생김 > 해당 인터페이스의 구현체 클래스 모두가 그 메소드를 구현해야 함
- 인터페이스와 해당 인터페이스를 구현한 클래스와의 호환성이 떨어짐 (하위 호환성 저하)
- 이럴 경우 default 메소드를 이용하면 인터페이스의 기본 구현을 그대로 상속하므로 인터페이스에 자유롭게 새로운 메소드를 추가할 수 있음 > 하위 호환성을 유지한 채 API를 바꿀 수 있음
- 참고1 / 참고2
인터페이스에서의 default 메소드
- 인터페이스에 있는, 로직을 구현하는 메소드
- 메소드 앞에 default 예약어를 붙이고, 구현부 { }가 있어야 함
- 형태
- 인터페이스에서 메소드를 default키워드로 선언
- 예: default int exec(int i, int j){ return i + j; }
- 인터페이스에서 메소드를 default키워드로 선언
- 예시
public interface Calculator {
public int plus(int i, int j);
public int multiple(int i, int j);
default int exec(int i, int j){
*//default로 선언함으로 메소드를 구현할 수 있음*
return i + j;
}
}
*//Calculator인터페이스를 구현한 MyCalculator클래스*
public class MyCalculator implements Calculator {
@Override
public int plus(int i, int j) {
return i + j;
}
@Override
public int multiple(int i, int j) {
return i * j;
}
}
public class MyCalculatorExam {
public static void main(String[] args){
Calculator cal = new MyCalculator();
int value = cal.exec(5, 10);
System.out.println(value);
}
}
- 기능
- default 메소드가 있는 인터페이스를 구현하는 클래스는 인터페이스의 default 메소드를 오버라이딩할 수 있음
- 장점
- 인터페이스에 추상 메소드를 추가하면 모든 구현체 클래스에 해당 메소드를 구현해야 하는 문제점 해결 (추가 변경을 막을 수 있음)
- OCP(Open-Close-Principle, 개방 폐쇄 원칙)을 지킬 수 있음
- 객체지향 설계 5대 원칙 중 하나
- 소프트웨어 요소(컴포넌트, 클래스, 모듈, 함수)는 확장에는 개방(Open)되어야 하지만 변경에는 폐쇄(Close)되어야 한다는 의미
- 기존의 코드가 변경되지 않고 기능 확장을 해야 함
default 메소드 간 충돌 (참고)
- default 메소드 간 충돌 상황
- 여러 인터페이스의 default 메소드 간의 충돌
- 여러 인터페이스를 구현한 클래스의 default 메소드끼리 이름이 같은 경우
- default 메소드는 추상 메소드가 아니기 때문에 자동으로 구현되지 않고, 해당 인터페이스를 구현한 클래스가 객체로 생성된 경우에만 호출 가능
- 같은 이름의 default 메소드를 갖는 인터페이스들이 상속관계일 때의 우선순위
- 서브 인터페이스(B)가 우선권을 가짐
public interface A { default void run() { System.out.println("A : run!"); } } public interface B extends A { default void run() { System.out.println("B : run!"); } } public class C implements B, A { } new C().run(); // B : run!
- 해결방법: 충돌이 발생하는, 인터페이스를 구현하는 클래스에서 default 메소드를 재정의(오버라이딩)해야 함
- default 메소드와 상위 클래스 내 메소드 간의 충돌
- 인터페이스의 default 메소드 이름과 상위 클래스의 메소드 이름이 같을 때의 우선순위
- 클래스나 부모클래스에서 정의한 메소드가 default 메소드보다 우선권을 가짐
public interface A { default void run() { System.out.println("A : run!"); } } public interface B extends A { default void run() { System.out.println("B : run!"); } } public class C implements A, B { public void run() { System.out.println("C : run!"); } } new C().run(); // C : run!
- 인터페이스의 default 메소드 이름과 상위 클래스의 메소드 이름이 같을 때의 우선순위
- 여러 인터페이스의 default 메소드 간의 충돌
static메소드
public interface Calculator {
public int plus(int i, int j);
public int multiple(int i, int j);
default int exec(int i, int j){
return i + j;
}
public static int exec2(int i, int j){ *//static 메소드*
return i * j;
}
}
*//인터페이스에서 정의한 static메소드는 반드시 인터페이스명.메소드 형식으로 호출해야 함*
public class MyCalculatorExam {
public static void main(String[] args){
Calculator cal = new MyCalculator();
int value = cal.exec(5, 10);
System.out.println(value);
int value2 = Calculator.exec2(5, 10); *//static메소드 호출*
System.out.println(value2);
}
}
- 인터페이스에 static 메소드를 선언함으로써, 인터페이스를 이용해 간단한 기능을 가지는 유틸리티성 인터페이스를 만들 수 있게 됨
- 재정의 불가능
- 인터페이스명으로만 호출 가능
- 인터페이스의 static 메소드 호출방법
- 인터페이스명.static메소드명( );
- 예: Calculator.exec2(5, 10);
- 참조변수로 static메소드를 호출할 순 없음
- 인터페이스명.static메소드명( );
실습
- 문제 설명
- Taxi클래스는 Meter인터페이스를 구현하고 있습니다. Meter인터페이스를 살펴보면 start,stop이외에도 default메소드인 afterMidnight이라는 메소드가 추가되었는데요. afterMidnight은 default로 선언되어 있기 때문에 Taxi에서 별도로 구현하지 않더라도 에러가 발생하지는 않습니다. 우선 [실행]을 눌러서 확인해 보세요. 그리고 Taxi에서 afterMidnight을 오버라이드 한 다음 [제출]해 보세요.
- 해설
- 일반 클래스가 인터페이스의 default 메소드를 오버라이딩할 땐, default 키워드를 제거해야 함
- Meter 인터페이스
public interface Meter{
public void start();
public int stop(int distance);
public default void afterMidnight(){
System.out.println("자정이 넘었습니다. 할증이 필요한 경우 이 메소드를 오버라이드 하세요.");
}
}
- Taxi 클래스
public class Taxi implements Meter{
public void start(){
System.out.println("택시가 출발합니다.");
}
public int stop(int distance){
int fare = distance * 2;
System.out.println("택시가 도착했습니다. 요금은 "+fare+"입니다.");
return fare;
}
public void afterMidnight(){
System.out.println("자정이 넘었습니다. 할증 요금이 부가됩니다.");
}
}
- TaxiExam 클래스
public class TaxiExam{
public static void main(String[] args){
Taxi taxi = new Taxi();
taxi.start();
taxi.afterMidnight();
taxi.stop(10);
}
}
+ 추상 클래스와 인터페이스 비교
* 강의를 듣다가 추상 클래스면 충분할 것 같은데 인터페이스를 쓰는 이유를 알 수가 없어서 공부하다가 보충한 내용입니다.
추상클래스의 개념
- 클래스 내 하나 이상의 추상 메소드를 가지고 있거나 abstract로 정의된 클래스
- 클래스 안에 추상 메소드가 하나라도 있다면 abstract 클래스명으로 표기되어야 함
- abstact와 final 키워드를 동시에 표기할 순 없음
- 추상 클래스 안엔 필드, 생성자, 추상 메소드, 일반 메소드 모두가 존재할 수 있음
- 추상클래스의 접근지정자는 어떤 것이나 가능
- 추상 클래스는 먼저 상속을 통해 자식 클래스를 만들고, 만든 자식 클래스에서 추상 클래스의 모든 추상 메소드를 오버라이딩하고 나서야 비로소 자식 클래스의 인스턴스를 생성할 수 있게 됨
- 추상 클래스를 상속받은 클래스는 추상 클래스가 갖고 있는 추상 메소드를 반드시 구현해야 함
- 추상 클래스를 상속받았는데 추상 클래스가 갖고 있는 추상 메소드를 구현하지 않으면 해당 자식클래스도 추상 클래스가 됨
- But, 추상클래스에서 추상클래스를 상속받는다면 모두 구현하지 않아도 됨
- 추상클래스에서도 인터페이스를 구현할수 있는데 이때는 구현하지 않고 그냥 놔둘 수 있음
인터페이스의 개념
- 안이 비어있는 메소드들의 형태들만 써놓은 것이며, 구현하는 클래스들에서 해당 메소드들의 내용을 구현해서 가져야하는 메소드들의 집합
- 인터페이스를 구현하는 클래스는 인터페이스가 갖고 있는 추상 메소드를 반드시 구현해야 함
- 모든 메소드가 추상 메소드 (JAVA 8부터는 default 메소드와 static 메소드로 일반 메소드를 구현할 수 있음)
- 다중상속: 하나의 클래스가 여러 인터페이스는 구현 할 수 있음
- 여러 인터페이스를 구현한 클래스는 인터페이스 타입으로 형변환 되는 경우 해당 인터페이스에 선언된 메서드만 사용 가능
- 예: Sell 인터페이스와 이를 구현한 Customer클래스 - Sell sell = new Customer( ); > Customer클래스는 Sell 인터페이스의 메소드만 사용 가능
- 여러 인터페이스를 구현한 클래스는 인터페이스 타입으로 형변환 되는 경우 해당 인터페이스에 선언된 메서드만 사용 가능
- 인터페이스 간의 상속 (참고)
- 다중 상속이 가능하고 구현 코드의 상속이 아니므로 타입 상속 이라고 함
- extends 키워드 사용
- 인터페이스명 extends 인터페이스명{ }
- 클래스 상속과 인터페이스 구현 함께 쓰기
- 실무에서 프레임워크나 오픈소스와 함께 연동되는 구현을 하면, 클래스 상속과 인터페이스의 구현을 함께 사용하는 경우가 많음
- 형태: 접근제한자 class 클래스명A extends 클래스명B implements 인터페이스명C{ }
- 클래스A가 클래스B를 상속받고, 클래스B는 인터페이스C를 구현하는 관계
- 인터페이스의 장점
- 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능
- 클래스의 작성과 인터페이스의 구현을 동시에 진행할 수 있으므로, 개발 시간을 단축할 수 있음
- 클래스와 클래스 간의 관계를 인터페이스로 연결하면, 클래스마다 독립적인 프로그래밍이 가능
추상 클래스와 인터페이스의 공통점
- 추상 메소드를 사용할 수 있음
- 추상 클래스/인터페이스를 상속받은 클래스는 추상 클래스/인터페이스가 갖고 있는 추상 메소드를 반드시 구현해야 함
- 인스턴스를 만들 수 없지만, 상속받은 클래스를 통하면 인스턴스화가 가능함
- 추상 클래스: 추상클래스명 ab = new 클래스명( );
- 인터페이스: 인터페이스명 ab = new 클래스명( );
추상 클래스와 인터페이스의 차이점 (참고)
- 상속/구현할 때 쓰는 키워드
- 추상 클래스: 상속받는클래스명 extends 추상클래스명{ }
- 인터페이스: 구현하는클래스명 implements 인터페이스명{ }
- 구성요소
- 추상 클래스는 필드, 생성자, 추상 메소드, 일반 메소드를 가짐
- 인터페이스는 상수와 추상 메소드만을 포함
- 사용의도
- 추상 클래스; is a “-이다”
- 그 추상 클래스를 상속받아 기능을 이용하고 확장시키는 게 존재 목적
- 인터페이스: has a “-을 할 수 있는” > 이 때문에 이름에 -able이 들어가기도 함
- 함수의 구현을 강제하기 위해 추상 메소드(메소드의 껍데기)를 가짐 > 구현을 강제함으로써 해당 인터페이스를 구현한 객체들에 대해 동일한 동작을 보장
- Java에서 클래스 간 다중상속이 불가능(한 개의 클래스만 상속 가능)하기 때문에 해당 클래스의 구분을 추상 클래스 상속을 통해 해결하고, 할 수 있는 기능들을 인터페이스로 구현
- 클래스 간 다중상속이 불가능한 이유: 메소드 출처의 모호성
- 추상 클래스; is a “-이다”
- 다중상속 가능 여부
- 클래스는 하나의 추상 클래스만 상속받을 수 있음 (추상 클래스: 다중상속 불가능)
- But, 인터페이스는 여러 개를 구현할 수 있음 (인터페이스: 다중상속 가능)
- 공통으로 필요한 기능의 사용 여부
- 인터페이스의 한계: 모든 클래스가 인터페이스를 사용해 기본 틀을 구성한다면 공통 기능도 모든 클래스에서 오버라이딩해야 하는 불편함 존재
- 해결방법: 공통 기능이 필요하다면 추상 클래스에 일반 메소드를 작성하고, 자식 클래스가 그 추상 클래스를 상속해 일반 메소드를 사용
- 추상 클래스의 한계: Java에선 하나의 클래스만 상속 가능 (클래스 간 다중상속 불가능)
- 해결방법: 각각 다른 추상 클래스를 상속하는 여러 클래스에 공통된 기능이 필요하면 그 기능을 인터페이스로 작성해 구현
- 예: Creature 클래스를 상속받는 Animal클래스와 Human클래스 아래의 Rabbit클래스(수영 불가능)와 Brian클래스(수영 가능)
- swim 기능이 필요한데 수영 가능 여부가 다르니 Animal클래스와 Human클래스에 추상 메소드를 만들면 Rabbit클래스와 Brian클래스 모두가 추상 클래스를 상속해야 해서 수영 못하는 Rabbit도 수영을 할 수 있는 게 돼버림
- 추상 클래스 대신 인터페이스로 swim 기능을 따로 선언해 수영 가능한 Brian클래스만 그 인터페이스를 상속하면 됨 > 가독성 상승, 유지보수 편리해짐
- 정리
- 추상클래스 사용 시기: 상속 관계를 쭉 타고 올라갔을 때 같은 조상클래스를 상속하는데 기능까지 완벽히 똑같은 기능이 필요한 경우 추상 클래스 사용
- 인터페이스 사용 시기: 상속 관계를 쭉 타고 올라갔을 때 다른 조상클래스를 상속하는데 같은 기능이 필요할 경우 인터페이스 사용
- 인터페이스의 한계: 모든 클래스가 인터페이스를 사용해 기본 틀을 구성한다면 공통 기능도 모든 클래스에서 오버라이딩해야 하는 불편함 존재
내부클래스
클래스 안에 선언된 클래스
- 어느 위치에 선언하느냐에 따라서 4가지 형태가 있음
-
- 인스턴스 클래스 2. static 클래스 3. 지역 클래스 4. 익명 클래스
-
1. 인스턴스(instance) 클래스 (=중첩클래스)
- 클래스 안에 인스턴스 변수, 즉 필드를 선언하는 위치에 선언되는 경우
- 객체 생성 및 사용 방법
- 외부클래스인 InnerExam1의 객체를 만든 후
- 형태: 외부클래스 참조변수 = new 외부클래스( );
- 내부클래스(인스턴스 클래스)인 Cal 객체를 생성해 사용
- 형태: 외부클래스.인스턴스클래스 참조변수 = 참조변수.new 인스턴스클래스( );
- 예: InnerExam1.Cal cal = t.new Cal( );
- 형태: 외부클래스.인스턴스클래스 참조변수 = 참조변수.new 인스턴스클래스( );
- 외부클래스인 InnerExam1의 객체를 만든 후
public class InnerExam1{
class Cal{
int value = 0;
public void plus(){
value++;
}
}
public static void main(String args[]){
InnerExam1 t = new InnerExam1();
InnerExam1.Cal cal = t.new Cal();
cal.plus();
System.out.println(cal.value);
}
}
2. static 클래스 (=정적 중첩 클래스)
- 내부 클래스가 static으로 정의된 경우
- 필드 선언할 때 static한 필드로 선언한 것과 같음
- 객체 생성 및 사용 방법
- 외부클래스의 객체를 먼저 생성할 필요 없이(static한 필드니까) 바로 static클래스의 객체를 생성
- 형태: 외부클래스.static클래스 참조변수 = new 외부클래스.static클래스( );
- 예: InnerExam2.Cal cal = new InnerExam2.Cal( );
- 외부클래스의 객체를 먼저 생성할 필요 없이(static한 필드니까) 바로 static클래스의 객체를 생성
public class InnerExam2{
static class Cal{
int value = 0;
public void plus(){
value++;
}
}
public static void main(String args[]){
InnerExam2.Cal cal = new InnerExam2.Cal();
cal.plus();
System.out.println(cal.value);
}
}
3. 지역(local) 클래스 (=지역 중첩 클래스)
- 메소드 안에 클래스를 선언한 경우
- 메소드 안에서만 해당 클래스를 이용할 수 있음
- 객체 생성 및 사용 방법
- 외부클래스의 객체를 먼저 생성
- 형태: 외부클래스 참조변수 = new 외부클래스( );
- 예: InnerExam3 t = new InnerExam3( );
- 외부클래스 참조변수로 메소드를 호출
- 형태: 외부클래스참조변수.메소드;
- 예: t.exec();
- 메소드 안에서 지역클래스의 객체 생성
- 형태: 지역클래스 참조변수 = new 지역클래스( );
- 예: Cal cal = new Cal( );
- 외부클래스의 객체를 먼저 생성
public class InnerExam3{
public void exec(){
class Cal{
int value = 0;
public void plus(){
value++;
}
}
Cal cal = new Cal();
cal.plus();
System.out.println(cal.value);
}
public static void main(String args[]){
InnerExam3 t = new InnerExam3();
t.exec();
}
}
4. 익명클래스 (=익명 중첩 클래스)
- 다른 내부 클래스와는 달리, 이름을 가지지 않는 클래스
- 프로그램에서 일시적으로 한 번만 사용되고 버려지는 일회용 클래스
- 외부클래스를 상속받는 클래스가 해당 외부클래스에서만 사용되고 다른 클래스에서는 사용되지 않음
- 클래스의 선언과 동시에 객체를 생성하므로 단 하나의 객체만을 생성
- 익명클래스가 아닌 보통의 경우, 특정 클래스의 자원을 상속받아 재정의해 사용하기 위해서는 먼저 자식클래스를 만들고 상속(extends) 후에 객체 인스턴스 생성 및 초기화를 통해 가능
- 생성자를 선언할 수도 없으며, 오직 단 하나의 클래스를 상속받거나 단 하나의 인터페이스를 구현할 수 있을 뿐
- 용도: 매우 제한적 (일회성 오버라이딩 용)
- 구현해야 하는 메소드가 매우 적은 클래스를 구현할 때 사용
- 추상 클래스를 상속받는 클래스를 만들 필요가 없을 때 사용 (상속 없이 인스턴스화)
- 어느 메소드에서 부모 클래스의 자원을 상속받아 재정의해 사용할 자식 클래스가 한번만 사용되고 버려질 경우
- 주의할 점
- 익명 클래스의 외부에서 사용 가능한 메소드: 익명 클래스에서 오버라이딩한 메소드
- 익명 클래스에서 새로 정의한 메소드는 외부에서 사용 불가능 (익명 클래스 내에서만 호출 가능)
- 익명클래스가 인스턴스로 생성됐을 때 그 인스턴스는 별도의 클래스가 아니라 클래스를 상속받거나 인터페이스를 구현하는 익명 클래스. 부모 클래스/인트턴스 자체에는 새로 정의한 메소드가 선언되지 않았으므로 사용 불가능 (다형성의 법칙)
- 보통 기존의 부모 클래스를 상속한 자식 클래스에서는 부모 클래스의 메소드를 재정의할 뿐만 아니라 자신만의 새로운 메소드를 만들어 사용할 수도 있음
- 익명 클래스도 내부 클래스이기 때문에, 외부의 지역 변수를 이용하려 할 때 내부 클래스의 제약을 받음
- 내부 클래스에서 가져올 수 있는 외부 변수는 오직 final 상수
- 익명 클래스의 외부에서 사용 가능한 메소드: 익명 클래스에서 오버라이딩한 메소드
- 객체 생성 및 사용 방법
- 1) 클래스를 선언하는 동시에 객체를 생성
- 형태: 추상클래스 참조변수 = new 추상클래스( ){ 메소드 };
- 생성자 다음에 중괄호 열고 닫고( { } )가 나오면, 해당 생성자 이름에 해당하는 클래스를 상속받는 이름 없는 객체를 만든다는 것을 뜻함
- 괄호 안에는 메소드를 구현하거나 메소드를 추가할 수 있음
- 예: 이렇게 생성된 이름 없는 객체를 action이라는 참조변수가 참조하도록 하고, exec( )메소드를 호출
- 1) 클래스를 선언하는 동시에 객체를 생성
-
- 2) 익명클래스 밖에서 메소드 호출
- 형태: 추상클래스명.메소드명( );
- 2) 익명클래스 밖에서 메소드 호출
//추상클래스 Action
public abstract class Action{
public abstract void exec();
}
//추상클래스 Action을 상속받은 클래스 MyAction
public class MyAction extends Action{
public void exec(){
System.out.println("exec");
}
}
//익명 클래스 사용
//MyAction을 사용하지 않고 Action을 상속받는 익명 클래스를 만들어서 사용하도록 수정
public class ActionExam{
public static void main(String args[]){
Action action = new Action(){
public void exec(){
System.out.println("exec");
}
};
action.exec();
}
}
//익명 클래스 사용 X
//MyAction을 사용하는 클래스 ActionExam
public class ActionExam{
public static void main(String args[]){
//Action클래스는 추상클래스라 객체 생성이 안 돼서 추상클래스를 상속받은 MyAction클래스로 간접 생성
Action action = new MyAction();
action.exec();
}
}
'☕ Java 웹 프로그래밍 > Java' 카테고리의 다른 글
[프로그래머스] Java(자바) 입문 | Part 10. 연습문제 (약수의 합) (0) | 2023.05.10 |
---|---|
[프로그래머스] Java(자바) 입문 | Part 9. 예외처리 (0) | 2023.05.09 |
[프로그래머스] Java(자바) 입문 | Part 7. 상속 (2) | 2023.05.03 |
[프로그래머스] Java(자바) 입문 | Part 6. 클래스 다듬기 (0) | 2023.05.03 |
[프로그래머스] Java(자바) 입문 | Part 5. 클래스와 객체 (0) | 2023.05.02 |