7. 객체지향 프로그래밍 II

1. 상속

  1. 상속의 정의와 장점 상속이란 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것.
    적은 양의 코드로 새로운 클래스를 작성할 수 있다.
    코드를 공통적으로 관리할 수 있기 때문에 코드의 추가 및 변경이 용이하다
    

<예제>

class Parent {
    ...
}


class Child extends Parent {
    ...
}
  • Child 클래스는 Parent 클래스의 모든 멤버를 상속 받기 때문에 Child 클래스는 Parent 클래스를 포함한다고 할 수 있다. 그러나 Child 클래스에 변수를 추가해도 Parent 클래스는 아무런 영향을 받지 않는다.
class Parent {
    int a;
}
멤버변수 => int a

class Child extends Parent {
    ...
}
멤버변수 => int a

class Child extends Parent {
    int b;
}
멤버변수 => int a, b

*주의사항
1. 생성자와 초기화 블럭은 상속되지 않으며 멤버만 상속된다.
2. Child 클래스의 멤버 개수는 Parent 클래스보다 같거나 많다.
  • 하나의 Parent 클래스를 여러개의 Child 클래스가 상속 받았을 때 각각의 Child 클래스 간에는 아무런 관계도 성립되지 않는다. => Parent 클래스만 같기 때문에, Parent 클래스의 멤버(변수 메소드)는 동일하게 상속받아 사용하지만 Child 클래스에서 추가한 멤버에 대해서는 사용할 수 없으므로..

따라서 Child 클래스들에 공통적으로 사용될 멤버가 있다면 Parent 클래스에 생성하는 것이 좋다.

  • 상속 관계를 이용하는 이유 중 하나는 다음과 같다.

    같은 내용의 코드를 한 곳에서 관리함으로써 코드의 중복이 줄어든다. 
    => 코드의 중복이 많아지면 유지보수가 어렵고 일관성을 유지하기 어렵다.
    
  • Parent 클래스를 따로 인스턴스를 생성하지 않아도 Child 클래스의 인스턴스를 생성하면 Parent 클래스의 멤버도 함께 생성되기 때문에 Parent 클래스의 멤버들을 사용할 수 있다.

  1. 클래스간의 포함관계 (클래스간의 관계 - 포함관계) 상속 이외에도 클래스를 재사용하는 다른 방법은 포함 관계를 맺어 주는 것이다.
class Point {
    int x;
    int y;
}

class Circle {
    Point p = new Point();
    int z;
}
  1. 클래스간의 관계 결정하기 상속으로 클래스를 작성하거나 포함 관계를 맺어 주는데 있어 애매한 경우가 있는데 이럴 경우 아래와 같이 확인한다.
상속 관계 : ~은 ~이다.
포함 관계 : ~은 ~을 가지고 있다.
원(Circle), 점(Point)
원은 점이다.
원은 점을 가지고 있다.
  1. 단일상속(Single Inheritance) 자바에서는 단일 상속만 허용한다.
class TVCR extends TV, VCR {
    ...
}

단일 상속이 하나의 Parent 클래스만 상속받을 수 있기 때문에 다중 상속에 비해 불편한 점도 있겠지만 클래스 간 관계가 더 명확해지고 코드를 더욱 신뢰할 수 있게 만들어 준다는 점에서 다중상속에 비해 유리하다. (인터페이스로 비슷하게 구현 가능, 비중이 높은 클래스 하나만 상속관계로, 나머지는 포함관계로 한다.)

  1. Object 클래스 - 모든 클래스의 조상 Object 클래스는 모든 클래스 상속계층도의 최상위에 있는 Parent 클래스이다. 어떤 클래스에서 다른 클래스를 상속 받지 않는 경우 컴파일러는 자동적으로 extends Object를 추가하여 Object 클래스를 상속받도록 한다.
class Test (extends Object) {
    ...
}

그동안 toString()이나 qeuals(Object o)와 같은 메소드를 별도로 정의하지 않고도 사용할 수 있었던 이유는 이 메소드들이 Object 클래스에 정의된 것들이기 때문이다.

2. 오버라이딩

  1. 오버라이딩이란? Parent 클래스로부터 상속받은 메소드의 내용을 변경하는 것을 오버라이딩이라고 한다.
class Point {
    int x;
    int y;

    String getLocation() {
        return "x :" + x + ", y :" + y;
    }
}

class Point3D extends Point {
    int z;

    String getLocation() {
        return "x :" + x + ", y :" + y  + ", z :" + z;
    }
}
  1. 오버라이딩의 조건 오버라이딩은 상속받은 메소드의 내용만 새로 작성하는 것이므로 선언부는 Parent 클래스의 것과 완전히 일치해야 한다.
Parent 클래스의 메소드 및 Child 클래스의 메소드는
1. 이름이 같아야 한다.
2. 매개변수가 같아야 한다.
3. 반환타입이 같아야 한다.

요약하자면 선언부가 서로 일치해야 한다는 것이지만 접근 제어자와 예외는 아래의 제한된 조건 하에서 변경할 수 있다.

1. 접근 제어자는 Parent 클래스의 메소드보다 좁은 범위로 변경할 수 없다.
    접근 제어자 : public, protected, default, private
    (그 외 제어자 : static, final, abstract, native, transient, synchronized, volatle, strictfp)

2. Parent 클래스의 메소드보다 많은 수의 예외를 선언할 수 없다.

Class Parent {
    void testMethod() throws IOException, SQLException {
        ...
    }
}

class Child {
    void testMethod() throws IOException { (o)
    void testMethod() throws Exception { (x)
        ...
    }
}

3. 인스턴스 메소드를 static 메소드로 또는 그 반대로 변경할 수 없다.
  1. 오버로딩 vs 오버라이딩
  2. 오버로딩 : 기존에 없는 새로운(new) 메소드를 정의하는 것
  3. 오버라이딩 : 상속받은 메소드의 내용을 변경하는 것(modify)
class Parent {
    void parentTest() {
        ...
    }
}

class Child {
    void parentTest() {}        - 오버라이딩
    void parentTest(int a) {}   - 오버로딩, 상속받은 메소드와 달리 매개변수 int a 추가
  1. super child 클래스에서 parent 클래스의 상속받은 멤버를 참조하는데 사용되는 참조변수. 덧 상속받은 parent 클래스의 변수도 child 클래스의 변수이므로 this를 사용할 수 있지만 상속받은 변수와 같은 child 변수가 있을 경우 구별을 위해 사용. 다만 static 메소드에서는 사용할 수 없다.
  2. super() - 조상(부모) 클래스의 생성자 super()는 생성자이며 this()는 같은 클래스 내 다른 생성자를 호출할 때 사용되지만 super()는 Parent 클래스의 생성자를 홏루할 때 사용된다.
Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에는 생성자 this()또는 super()를 호출해야 한다. 그렇지 않을 경우 컴파일러가 자동적으로 super();를 삽입한다.

Parent 클래스를 상속받으면 Parent 클래스의 멤버를 상속받아 사용할 수 있지만 멤버변수를 사용하기 전 Parent 클래스의 멤버변수를 Parent 클래스의 생성자로 초기화한다.

3. package와 import

  1. 패키지(package)
  2. 패키지란 클래스의 묶음으로 클래스 또는 인터페이스를 포함시킬 수 있으며 서로 관련된 클래스들끼리 그룹 단위로 묶어 놓음으로서 효율적으로 관리할 수 있다.
  3. 같은 이름의 클래스라도 서로 다른 패키지에 있는 경우 존재하는 것이 가능하며, 클래스의 실제 이름은 패키지명을 포함한다.
  4. 클래스가 물리적으로 하나의 클래스파일(.class) 인 것처럼 패키지는 물리적으로 하나의 디렉토리이다. 따라서 패키지에 속한 클래스는 해당 패키지 디렉토리에 존재하는 클래스 파일이어야 한다.
하나의 소스파일에는 첫 번째 문장으로 단 한 번의 패키지 선언만을 허용한다.
모든 클래스는 반드시 하나의 패키지에 속해야 한다.
패키지는 점(.)을 구분자로 하여 계층구조로 구성할 수 있다.
패키지는 물리적으로 클래스 파일(.class)을 포함하는 하나의 디렉토리이다.
  1. 패키지의 선언
package 패키지명;
  • 패키지 선언문은 반드시 소스파일에서 주석과 공백을 제외한 첫 번째 문장이어야 하며 단 한번만 선언될 수 있다.
  • 선언된 클래스나 인터페이스는 선언된 패키지에 속하게 된다.
  • 패키지명은 대소문자를 모두 허용하지만 클래스명과의 구분을 위해 소문자로 한다.
  • 패키지를 선언하지 않으면 기본적으로 제공하는 이름없는 패키지(unnamed package) 떄문이다.
  1. import문
  2. 소스코드 작성 시 다른 패키지의 클래스를 사용하려면 패키지명이 포함된 클래스 이름을 사용해야 한다.
  3. 코드 작성 전 import문으로 사용하고자 하는 클래스의 패키지를 미리 명시하여 소스코드에서는 사용되는 클래스 이름만을 사용할 수 있다.(패지지명 생략)

  4. import문의 선언

  5. import문은 package문 다음에 클래스 선언문 이전에 위치해야 한다. import문은 한 소스파일에 여러 번 선언할 수 있다.

일반적인 파일 구성

1. package문
2. import문
3. 클래스 선언
  • 클래스 이름 지정 대신 '' 사용 시 지정된 패키지에 속하는 모든 클래스를 import하여 패키지명 없이 사용할 수 있다. (클래스명을 지정하는 것과 ''을 사용하는 것은 실행 시 성능상의 차이는 전혀 없다고 함...)
  • '*'을 쓸 경우 하위 패키지의 클래스까지 포함하는 것은 아니므로 주의한다.
  • 같은 패키지 내의 클래스들은 import 하지 않아도 패키지명을 생략할 수 있다.

  • static import문

  • import문과 마찬가지로 static import문을 사용하면 static 멤버를 호출할 때 클래스 이름을 생략할 수 있다.

4. 제어자(modifier)

  1. 제어자란? 제어자는 클래스 변수 또는 메소드의 선언부에 함께 사용되어 부가적인 의미를 부여하며 접근 제어자 및 그 외의 제어자로 나눌 수 있다.
접근 제어자 : public, protected, default, private
그 외 제어자 : static, final, abstract, native, transient, synchronized, volatile, strictfp
  • 클래스나 멤버변수와 메소드에 주로 사용되며 하나의 대상에 대해 여러 제어자를 조합하여 사용하는 것이 가능하다
  • 접근 제어자는 한 번에 네 가지 중 하나만 사용할 수 있다.
  • 제어자간 순서는 관계 없지만 주로 접근 제어자를 제일 왼쪽에 놓는다.
  1. static - 클래스의, 공통적인 '클래스의 또는 '공통적인' 의미.
  2. 멤버변수 메소드 초기화 블럭 등에 사용된다.
  3. 인스턴스 멤버를 사용하지 않는 메소드는 static을 붙여서 static 메소드로 선언하는 것을 고려해 보도록 하자. (인스턴스를 생성하지 않고도 호출이 가능해서 더 편리하고 속도도 더 빠르다.)

  4. final - 마지막의, 변경될 수 없는 '마지막의' 또는 '변경될 수 없는' 의미.

  5. 변수에 사용되면 값을 변경할 수 없는 상수가 된다.
  6. 메소드에 사용되면 오버라이딩을 할 수 없다.
  7. 클래스에 사용되면 다른 클래스에서 상속할 수 없다.
  8. 클래스 메소드 멤버변수 지역변수 등에 사용된다.
  9. final이 붙은 변수는 상수이므로 일반적으로 선언과 초기화를 동시에 하지만 인스턴스변수의 경우 생성자에서 초기화 되도록 할 수 있다.

  10. abstract - 추상의, 미완성의 '미완성' 의미.

  11. 메소드의 선언부만 작성하고 실제 수행내용은 구현하지 않은 추상 메소드를 선언하는 사용.
  12. 클래스 및 메소드 등에 사용된다.
  13. 추상 클래스는 아직 완성되지 않은 메소드가 존재하므로 인스턴스를 생성할 수 없다.

  14. 접근 제어자(access modifier) 멤버 또는 클래스에 사용되어 해당하는 멤버 또는 클래스를 외부에서 접근하지 못하도록 제한하는 역할을 한다.

  15. 접근 제어자가 지정되어 있지 않다면 접근 제어자는 default.

접근 제어자의 범위 나열

public > protected > default > private
  • public은 접근 제한이 전혀 없다.
  • protected는 패키지에 관계 없이 상속관계에 있는 Child 클래스 내로만 제한한다.
  • default는 같은 패키지내 클래스 내로만 제한한다.
  • private은 같은 클래스 내로만 제한한다.

접근 제어자를 사용하는 이유

접근 제어자는 클래스 내부에 선언된 데이터를 보호하기 위해서 사용한다. 
외부에는 불필요한 내부적으로 사용되는 부분을 감추기 위해
  1. 제어자(modifier)의 조합

제어자를 조합할 때 주의점

1. 메소드에 static과 abstract를 함께 사용할 수 없다.
2. 클래스에 abstract과 final을 동시에 사용할 수 없다.
3. abstract메소드의 접근 제어자가 private일 수 없다.
4. 메소드에 private과 final을 같이 사용할 필요는 없다. (클래스에서는 상속할 수 없는 클래스임을 알리기 위해 사용하는 것을 추천)

5. 다형성(polymorphism)

다형성이란?

  • 여러가지 형태를 가질 수 있는 능력"
  • 상속을 통해 발생하며, 부모클래스 타입의 참조 변수로 자식클래스의 인스턴스를 참조할 수 있는것을 다형성이라 한다.
class Phone {
}

class iPhone extends Phone {
}

class AndroidPhone extends Phone {
}

Phone phone = new Phone();
iPhone iphone = new iPhone();
AndroidPhone androidPhone = new AndroidPhone();

// success
Phone phone1 = new iPhone();
Phone phone2 = new AndroidPhone();

// compile error
iPhone iphone1 = new Phone();
AndroidPhone androidPhone1 = new Phone();
iPhone iphone2 = new AndroidPhone();
AndroidPhone androidPhone2 = new iPhone();

참조변수의 형변환

  • 기본형 변수와 마찬가지로 참조타입의 변수도 형변환이 가능함.
  • 상속관계에 있는 클래스간의 형변환만 가능
  • 형제 관게의 형변환은 불가
class Phone {
}

class iPhone extends Phone {
}

class AndroidPhone extends Phone {
}


Phone phone = new Phone();
iPhone iphone = new iPhone();
AndroidPhone androidPhone = new AndroidPhone();

// Up-Casting
// 캐스팅 연산자 생략 가능
Phone castedPhone1 = iphone;
Phone castedPhone2 = androidPhone;

// Down-Casting
// 캐스팅 연산자 생략 불가
// Runtime Error : ClassCaseException
iPhone castedPhone3 = (iPhone)phone;
AndroidPhone castedPhone4 = (AndroidPhone)phone;

// Compile Error
iPhone castedPhone5 = (iPhone)androidPhone;
AndroidPhone castedPhone6 = (AndroidPhone)iphone;

instanceof 연산자

  • 참조 변수가 참조하고 있는 인스턴스의 타입을 알아보기 위해 사용
  • 주로 조건문과 함께 사용되며, instanceof 연산자 우항에 타입 확인을 위한 클래스명 등을 지정
class Phone {
}

class iPhone extends Phone {
}

class AndroidPhone extends Phone {
}

Phone phone = new Phone();
Phone iphone = new iPhone();
Phone androidPhone = new AndroidPhone();

boolean isPhoen = phone instanceof Phone;
boolean isiPhone = iphone instanceof iPhone;
boolean isAndroidPhone = androidPhone instanceof AndroidPhone;

boolean isTypeOfPhone1 = iphone instanceof Phone;
boolean isTypeOfPhone2 = androidPhone instanceof Phone;
  • 부모클래스/추상클래스/인터페이스 역시 instanceof 연산자에 의해 구분 가능
  • instanceof는 그럼 왜 쓸까? 바로 Down-Casting을 통해 형변환에 의해 숨겨진 멤버에 접근하기 위함
  • instanceof 연산자의 속도는 문제 없을까?
  • getClass()class를 통해 직접 비교하는 방법도 있음

참조변수와 인스턴스의 연결

  • 부모클래스와 자식클래스에 같은 이름의 멤버변수와 메소드가 있으면?
class Parent {
    String name = "parent";
    void print() {
        System.out.println("print parent");
    }
}
class Child extends Parent {
    String name = "child";
    void print() {
        System.out.println("print child");
    }
}

Parent parent = new Child();
Child child = new Child();

parent.name; // "parent"
child.name; // "child"

parent.print(); // "print parent"
child.print(); // "print child"
  • 상속 관계에서 부모와 자식에 중복된 이름을 사용하지 말자

매개변수의 다형성 & 여러 종류의 객체를 배열로 다루기

  • 다형성을 매개변수의 인자로 사용할 수 있음
  • 다형성을 통해 배열로 다룰 수 있음
class Beverage {
    int price;
}
class Coffee extends Beverage {
    // blah blah
}
class Juice extends Beverage {
    // blah blah
}
class Tea extends Beverage {
    // blah blah
}
class Order {
    Beverage
    List<Beverage> beverages = new ArrayList<Beverage>();
    void add(Beverage beverage) {
        beverages.add(beverage);
    }
    int price() {
        // blah blah
    }
}
  • 다형성을 통한 로직의 구현은 자바세계에서 빈번한 일

6. 추상클래스(abstract class)

추상클래스란?

  • 추상 클래스는 미완성 설계도(미완성 설계도란 표현은 적절치 못한 것 같음)
  • 미완성 메서드를 포함하고 있음
abstract class UnfinishedSymphony {
    void play();
}
  • 미완성 설계도를 통해 제품을 만들 수 없듯이 인스턴스 생성 불가
new UnfinishedSymphony(); // compile error
  • 클래스의 완성을 자식클래스에 위임
class FinishedSymphony extends UnfinishedSymphony {
    void play() {
        // blah blah
    }
}

추상메서드(abstract method)

  • 메서드의 구현부가 없는 것이 추상메서드
  • 메서드의 구현을 자식 클래스에게 위임
  • 메서드 선언 시 abstract 키워드를 앞에 붙여주고, 메서드의 본문인 {} 생략하고 세미콜론으로 마무리
abstract 메서드선언;
  • 만일 추상메서드가 선언된 클래스상속받은 클래스추상메서드 구현을 하지 않았다면 추상클래스가됨
// 추상클래스와 추상메서드
abstract class Phone {
    abstract void call();
}

// 추상클래스를 상속받아 추상메서드 구현
class iPhone extends Phone{
    void call() {
        // blah blah
    }
}
// 추상클래스를 상속받았지만, 추상메서드를 구현하지 않아 추상클래스로 선언
abstract class SmartPhone extends Phone {
}

추상클래스의 작성

  • 추상화 : 클래스간의 공통점을 찾아 공통의 조상을 만드는 작업
  • 구체화 : 상속을 통해 클래스를 구현/확장하는 작업
abstract class Vehicle {
    void start() {
        // blah
    }
    void stop() {
        // blah
    }
       abstract void move();
}
class Bike extends Vehicle {
    void move() {
        // blah
    }
}
class Airplane extends Vehicle {
    void move() {
        // blah
    }
}
  • 그런데 추상메서드나 추상클래스를 사용하지 않고 구현하면 어떻게 될까?
class Vehicle {
    void start() {
        // blah
    }
    void stop() {
        // blah
    }
       void move() {
        // blah
    }
}
class Bike extends Vehicle {
    override void move() {
        super.move();
        // blah
    }
}
class Airplane extends Vehicle {
    override void move() {
        super.move();
        // blah
    }
}
  • 추상메서드나 추상클래스를 사용하지 않고 구현하는것은 사실 큰 차이가 없다
  • 개인적으로 추상클래스 대신 인터페이스를 선호

7. 인터페이스(interface)

인터페이스란?

  • 일종의 추상클래스
  • 인터페이스는 추상클래스에 비해 일반 메서드멤버변수를 가질 수 없다

인터페이스의 작성

  • interface 키워드와 함께 인터페이스 이름 지정
  • 멤버변수는 선언할 수 없지만, static final 상수는 선언할 수 있다
  • 메서드 선언 시 public, abstract는 생략 가능
    interface 인터페이스이름 {
     public static final 타입 상수이름 = 리터럴;
     public abstract 메서드선언;
    }
    

인터페이스의 상속

  • 클래스 상속과 마찬가지로 extends 키워드를 통해 인터페이스간의 상속도 가능
  • 인터페이스간의 상속은 다중상속 가능
interface Phone {
    // blah
}
interface Touchable {
    // blah
}
interface SmartPhone extends Phone, Touchable {
    // blah
}

인터페이스의 구현

  • 클래스에 implements 키워드를 통해 인터페이스 구현을 위임할 수 있다
  • 인터페이스 구현은 추상메서드를 구현할 때와 동일한 룰이 적용됨
    abstract class Phone {
     void call();
    }
    interface Touchable {
     void touch(int x, int y);
    }
    class iPhone extends Phone implements Touchable {
     void call() {
         // blah
     }
     void touch(int x, int y) {
         // blah
     }
    }
    

인터페이스를 이용한 다중상속

  • 클래스 상속 시 extends 키워드를 사용해 상속받을 클래스를 지정. 이때 하나의 클래스만 지정이 가능
  • 인터페이스는 상속(위임) 시 implements 키워드를 사용해 구현을 위임받을 인터페이스 지정. 여러 인터페이스 지정 가능
interface Touchable {
    void touch(int x, int y);
}
interface Callable {
    void request(string host);
}
class SmartPhone implements Touchable, Callable {
    void touch(int x, int y) {
        // blah
    }
    void request(string host) {
        // blah
    }
}

인터페이스를 이용한 다형성

  • 인터페이스 역시 클래스 상속 시 생기는 다형성과 동일한 룰을 따름
  • 클래스 상속을 통한 다형성인터페이스를 통한 다형성은 어떤 차이가 날까? "인터페이스의 장점"에서 살펴보자

인터페이스의 장점

  • 개발 시간을 단축 시킬 수 있다
    • 인터페이스를 통해 프로토콜만 맞추고, 따로 작업할 수 있다
  • 표준화가 가능하다
    • 인터페이스를 잘 만들면 표준화가 가능
    • 인터페이스의 변경은 hell
  • 서로 관계없는 클래스들에게 관계를 맺어줄 수 있다
    • 관계를 잘 맺어줄 수 있다
  • 독립적인 프로그래밍이 가능하다
    • 클래스의 상속을 통한 구현보다 클래스 간의 의존성을 낮춘다.
    • Loose Coupling

인터페이스의 이해

  • 인터페이스의 장점 중 클래스 간의 의존성이 어떻게 낮아지는지에 대한 이야기

디폴트 메서드와 static 메서드

  • 디폴트 메서드와 static 메서드를 인터페이스에서 사용 가능(JDK 1.8)
interface 인터페이스이름 {
    default 메서드선언 {
        // 구현
    }
}
  • 인터페이스는 변경 시 인터페이스에 대한 구현이 위임된 모든 클래스에 영향을 준다. 디폴트 메서드는 이러한 문제를 해결해줌. 그런데 남용하면 인터페이스의 장점이 사라진다.
  • 디폴트 메서드는 클래스에서 오버라이딩 가능

8. 내부 클래스(inner class)

내부 클래스란?

  • 클래스의 선언이 클래스 내부에 선언된 경우
  • 언제 사용할까?
    • 새롭게 추가할 클래스가 특정 클래스 내부에서 사용되는 경우
    • 새롭게 추가할 클래스가 특정 Scope에 자주/쉽게 접근할 필요가 있는 경우
    • 새롭게 추가한 클래스와 특정 클래스가 같은 듯 같지 않은 경우

내부 클래스의 종류와 특징

  • 내부 클래스에는 4가지 종류가 있음
    • 인스턴스 클래스 : 멤버변수 선언 위치에 클래스가 선언된 경우
    • 스태틱 클래스 : 멤버변수 선언 위치에 static과 함께 선언된 경우
    • 지역 클래스 : 메서드나 초기화 블럭에 선언되며, 선언된 영역 내부에서만 사용 가능
    • 익명 클래스 : 클래스의 선언과 객체의 생성을 통시에 하는 이름없는 클래스

내부 클래스의 선언

// 인스턴스 클래스 vs 스태틱 클래스
class OuterClass {
    class InstanceClass {
    }
    static class StaticClass {
    }
    public void sayGoodbye() {
        class English {
            public static final String farewell = "Bye bye";
            public void sayGoodbye() {
                System.out.println(farewell);
            }
        }
        English english = new English();
        english.sayGoodbye();
    }

}

// 인스턴스 클래스
OuterClass outerClass = new OuterClass();
OuterClass.InstanceClass instanceClass = outerClass.new InstanceClass();

// 스태틱 클래스
OuterClass.StaticClass staticClass =
     new OuterClass.StaticClass();

// 지역 클래스
outerClass.sayGoodbye();

내부 클래스의 제어자와 접근성

  • 내부 클래스의 제어자는 외부 클래스의 접근 제어자의 영향을 동일하게 받는다
  • abstract, final과 같은 제어자도 사용할 수 있으며, private, protected 같은 접근 제어자나 interface 등도 함께 사용할 수 있다
class Greeting {
    public void english() {
        interface HelloThere {
           public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            static public void greet() {
                System.out.println("Hello " + name);
            }
        }
        EnglishHelloThere.greet();
    }

익명 클래스(anonymous class)

  • 이름이 없는 클래스로 일회용 클래스
  • 이름이 없기 때문에 인스턴스 생성을 위해 상위 클래스의 이름이나 구현할 인터페이스의 이름을 사용해 생성자 호출
    new 상위클래스이름() {
    }
    new 인터페이이름() {
    }
    
  • 이벤트 핸들링 작업에서 자주 사용된다
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloWorld extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });

        StackPane root = new StackPane();
        root.getChildren().add(btn);
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }
}