본문 바로가기
☕ Java 웹 프로그래밍/Java

[프로그래머스] Java(자바) 입문 | Part 8. 인터페이스와 다른 형식의 클래스

by 일단연 2023. 5. 6.

 인터페이스 만들기 

인터페이스

  • 서로 관계가 없는 물체들이 상호 작용을 하기 위해서 사용하는 장치나 시스템
  • 구현을 하지 않고 기능들만 선언해서 가지고 있는 것
    • 인터페이스는 자체적으로 타입의 역할은 할 수 있지만, 객체를 생성하진 못함
  • 인터페이스 정의하는 방법
    • 추상 메소드와 상수를 정의할 수 있음
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원입니다.

 

실습 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;
  • 개념
    • 인터페이스에서 선언한 변수는 자동으로 상수가 된다
      • 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; }
  • 예시
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!
      

 

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메소드를 호출할 순 없음

 

실습

  • 문제 설명
    • 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에서 클래스 간 다중상속이 불가능(한 개의 클래스만 상속 가능)하기 때문에 해당 클래스의 구분을 추상 클래스 상속을 통해 해결하고, 할 수 있는 기능들을 인터페이스로 구현
      • 클래스 간 다중상속이 불가능한 이유: 메소드 출처의 모호성
  • 다중상속 가능 여부
    • 클래스는 하나의 추상 클래스만 상속받을 수 있음 (추상 클래스: 다중상속 불가능)
    • 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( );
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( );
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( )메소드를 호출
    • 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();
  }
}