Java Virtual Machine(자바 가상 기계)

OS에 종속받지 않고 CPU 가 Java를 인식, 실행할 수 있게 하는 가상 컴퓨터이다.

Java Byte Code를 OS에 맞게 해석 해주는 역할.

Java compiler는 .java 파일을 .class 라는 Java byte code로 변환.

컴파일

💡 여기서 Java compiler는 JDK를 설치하면 bin 에 존재하는 javac.exe를 말한다. (즉, JDK에 Java compiler가 포함되어 있다는 소리이다.)

Byte Code 는 기계어가 아니기 때문에OS에서 바로 실행되지 않는 데 JVM은 OS가 ByteCode를 이해할 수 있도록 해석

 

터미널 에서 test.java 파일이 있는 디렉토리에서

명령어 :  javac test.java 입력

 

실행하는 방법

.class 파일이 위치한 곳으로 이동 후 java <.class 파일 이름> 을 입력해 실행시킨다.

명령어 : java test 입력

 

바이트코드란 무엇인가

자바 바이트 코드(Java bytecode)란 자바 가상 머신이 이해할 수 있는 언어로 변환된 자바 소스 코드를 의미한다.

자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 자바 바이트 코드라고 불리고 있다.

이러한 자바 바이트 코드의 확장자는 .class이다.

자바 바이트 코드는 자바 가상 머신만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있다.

 

JIT 컴파일러란 무엇이며 어떻게 동작하는가

JIT 컴파일(just-in-time compilation) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다.

 

JIT는 인터프리터 방식과 정적 방식을 혼합한 방식이다.

 

인터프리터 방식은 프로그램을 실행할 때마다 컴퓨터가 알아 들을 수 있는 언어로 변환하는 작업을 수행한다.

간편하기는 하지만 성능이 매우 느릴 수 밖에 없다.바이트 코드를 읽어 기계어를 생성 하는데, 코드가 실행되는 과정에서 실시간으로 일어나며 전체코드의 필요한 부분만 변환한다.

 

하지만 정적 컴파일 방식은 변환 작업을 딱 한번만 한다. 언어로 변환하는 작업을 미리 실행한다.

기본적으로 인터프리터에 의해 수행되지만, 필요한 코드의 정보는 캐시에 담아두었다가(메모리에 담아두었다가 재사용 하게 된다. 정적 컴파일 방식 처럼)

 

자바 소스코드 -> 자바컴파일러 -> 바이트코드 -> JVM -> 기계어 -> 하드웨어 및 OS의 과정을 거친다.

 

여기서 JVM -> 기계어로 변환되는 부분을 JIT에서 수행한다.

 

JIT를 사용하면 반복적으로 수행되는 코드는 매우 빠른 성능을 보이지만, 반대로 처음에 시작할 때에는 변환 단계를 거쳐야 하므로 성능이 느린 단점이 있다.

 

 

JVM 구조

  • 클래스 로더(Class Loader)
  • 실행 엔진(Execution Engine)
    • 인터프리터(Interpreter)
    • JIT 컴파일러(Just-in-Time)
    • 가비지 콜렉터(Garbage collector)
  • 런타임 데이터 영역 (Runtime Data Area)

클래스 로더
JVM 내로 클래스 파일(*.class)을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다.
런 타임시 동적으로 클래스를 로드하고 jar 파일 내 저장된 클래스들을 JVM 위에 탑재한다.
즉, 클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크한는 역할을 한다.

 

실행 엔진

클래스를 실행시키는 역할이다.

클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키고, 이것은 실행 엔진에 의해 실행된다.

자바 바이트 코드(*.class)는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다.

  인터프리터(interpreter)

  자바 컴파일러에 의해 변환된 바이트 코드를 읽고 한 줄씩 기계어로 해석하는 역할을 하는 것이 자바 인터프리터   (interpreter)이다.

 

  원래 JVM에서는 인터프리터 방식만 사용하다가 성능 이슈가 발생해서 JIT 컴파일러를 추가해서 성능을 끌어올렸다. 현      재는 컴파일과 인터프리터 방식을 병행해서 사용한다.

 

 JIT(Just-In-Time)

 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 해당 더 이   상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식이다.

 

💡 컴파일 임계치(Compile Threshold)
 - JIT 컴파일러가 메소드가 자주 사용되는 지 체크하는 방식으로 컴파일 임계치를 사용합니다. JIT 컴파일러가 내부적으   로 메서드가 호출될 때마다 호출 횟수를 카운팅하고 그 횟수가 특정 수치를 초과할 때 캐싱해서 이후에는 JIT 컴파일이     트리거된다.

 

프로그램이 실행 중인 런타임 중에 여러번 호출되는 메소드들을 미리 만들어 둔 해석본을 이용해서 컴파일하는 역할을 하는 것이 JIT 컴파일러이다.

 

 가비지 컬렉터(garbage collector)

 가비지 컬렉터(garbage collector)는 메모리 관리를 자동으로 해줘서 개발자가 비즈니스 로직에 더 집중할 수 있게 도와준   다.

 

Runtime Data Area

Runtime Data Area는 JVM이 프로그램을 수행하기 위해 OS로부터 할당받는 메모리 영역이다.

WAS의 성능에 문제가 발생했을 때, 대부분 이 영역들이 원인이 된다.

 

Runtime Data Area는 5가지로 구분된다.

PC Register  JVM stack   Native Method stack  Heap  Method Area

 좌측 3개의 영역은 Thread별로 생성되고,  우측 2개의 영역은 모든 Thread가 공유한다.

 

PC Register

Thread가 시작될 때 생성되며 생성될 때마다 생성되는 공간으로, 스레드마다 하나씩 존재한다.

Thread가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 수행 중인 JVM 명령의 주소를 갖는다.

  💡 스레드(thread)란?

   스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미

   모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다.

 

 JVM 스택 영역

 Thread의 Method가 호출될 때 수행 정보(메소드 호출 주소, 매개 변수, 지역 변수, 

 연산 스택)가 Frame 이라는 단위로 JVM stack에 저장된다.

 그리고 Method 호출이 종료될 때 stack에서 제거된다.

 

 Native method stack

 자바 프로그램이 컴파일되어 생성되는 바이트 코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는   영역.

 JAVA가 아닌 다른 언어로 작성된 코드를 위한 공간.

 Java Native Interface를 통해 바이트 코드로 전환하여 저장하게 된다.

 일반 프로그램처럼 커널이 스택을 잡아 독자적으로 프로그램을 실행시키는 영역

 

Method Area (= Class Area = Static area)

Class Loader가 적재한 클래스(또는 인터페이스)에 대한 메타데이터 정보가 저장된다.

이 영역에 등록된 class만이 Heap에 생성될 수 있다.

사실 Method Area는 논리적으로 Heap에 포함된다.

더 구제적으로는 Heap의 PermGen이라는 영역에 속한 영역인데, 

Java 8 이후로는 Metaspace라는 OS가 관리하는 영역으로 옮겨지게 된다.

  Runtime Constant Pool

 스태틱 영역에 존재하는 별도의 관리영역.

 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 수행한다.

Type Information - Type(class or interface)의 전체 이름
- Type의 직계 하위 클래스 전체 이름
- Type의 class/interface 여부
- Type의 modifier (public / abstract / final)
- 연관된 interface 이름 리스트
Field Information

- Field Type
- Field Modifier
(public / private / protected / static / final / volatile / transient)
Method Information - Constructor를 포함한 모든 Method의 메타 데이터를 저장
Runtime Constant Pool - Type, Field, Method로의 모든 레퍼런스를 저장
- JVM은 이 영역을 통해 실제 메모리 상의 주소를 찾아 참조
Class Variable - static 키워드로 선언된 변수를 저장
- 기본형이 아닌 static 변수는 레퍼런스 변수만 저장되고 
실제 인스턴스는 Heap에 저장됨
- 클래스를 사용하기 이전에 이 변수들은 미리 메모리를 할당받음

Heap 영역

객체를 저장하는 가상메모리 공간. new 연산자로 생성되는 객체와 배열을 저장한다.

Class Area(Static Area)에 올라온 클래스들만 객체로 생성할 수 있다.(당연함)

힙은 세 부분으로 나뉘어 진다.

 

 Permanent Generation

  직역하면 영구적인 세대이다.

  생성된 객체들의 정보의 주소값이 저장된 공간이다. 클래스 로더에 의해 load되는 Class, Method 등에 대한 Meta 정보가    저장되는 영역이고 JVM에 의해 사용된다.
  Reflection을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다.

    💡 Reflection이란?
    객체를 통해 클래스의 정보를 분석해 내는 프로그래밍 기법
    구체적인 클래스 타입을 알지 못해도, 컴파일된 바이트 코드를 통해
    역으로 클래스의 정보를 알아내어 사용할 수 있다는 뜻이다.

 

   New/Young 영역

    이곳의 인스턴스들은 추후 가비지 콜렉터에 의해 사라진다.
    생명 주기가 짧은 “젊은 객체”를 GC 대상으로 하는 영역이다.

    여기서 일어나는 가비지 콜렉트를 Minor GC 라고 한다.

        Eden: 객체들이 최초로 생성되는 공간

       Survivor 0, 1: Eden에서 참조되는 객체들이 저장되는 공간

   Old 영역

    이곳의 인스턴스들은 추후 가비지 콜렉터에 의해 사라진다.
    생명 주기가 긴 “오래된 객체”를 GC 대상으로 하는 영역이다.
    여기서 일어나는 가비지 콜렉트를 Major GC 라고 한다. Minor GC에 비해 속도가 느리다.

    New/Young Area에서 일정시간 참조되고 있는, 살아남은 객체들이 저장되는 공간이다.

 

JRE와 JDK 차이점

JRE(Java Runtime Environment)란?

JRE란 번역하면 자바 실행환경으로 자바 프로그램을 실행하는데 필요한 것이다.

즉, 자바 프로그램을 실행시키는데는 문제가 없지만 자바 프로그램을 코딩할 때 jdk가 아니라 jre를 사용하면 문제점이 생길 수 있다.

예를 들어 컴파일이 정상적으로 되지 않을 수도 있다.

 

 

JDK(Java Development Kit)란?

번역하면 자바 개발 키트이다. 간단하게 설명하면 자바를 개발하는데 필요한 기능들이 들어간 것이다.

여기에는 물론 자바를 실행하는데 필요한 jre도 포함되어 있어서 jdk를 다운로드 받으면 jre 또한 포함되어 있다.

자바 프로그램 개발을 위해서는 바로 이 jdk를 다운로드 받아 자바 기능을 사용하고 컴파일 해야하는 것이다.

 

 

즉, 정리해보면 자바 프로그램을 실행시키는데 필요한 것이 바로 jre이고 자바 프로그램을 개발하는데 필요한 것이 jdk이다.

jdk를 다운로드 받으면 jre도 포함되어 있어 개발한 자바 프로그램을 실행시키는 것까지 가능하다.

'JAVA > 참고 개인공부' 카테고리의 다른 글

핵클 협력사 세션.  (0) 2022.10.04
인터페이스  (0) 2022.09.30

트랜잭션(Transaction)이란?

트랜잭션은 데이터베이스의 상태를 변환시키는 하나의 논리적 기능을 수행하기 위한 작업의 단위 또는 한꺼번에 수행되어야할 일련의 연산들을 의미한다.

 

트랜잭션은 작업의 완전성을 보장해준다. 즉, 논리적인 작업 셋을 모두 완벽하게 처리하거나 또는 처리하지 못할 경우에는 원 상태로 복구해서 작업의 일부만 적용되는 현상이 발생하지 않게 만들어주는 기능이다. 사용자의 입장에서는 작업의 논리적 단위로 이해를 할 수 있고 시스템의 입장에서는 데이터들을 접근 또는 변경하는 프로그램의 단위가 된다.

 

트랜잭션은 SELECT, UPDATE, INSERT, DELETE와 같은 연산을 수행하여 데이터베이스의 상태를 변화시키는 작업의 단위다.

 

트랜잭션의 특징(ACID)

  • Atomicity(원자성)
    • 트랜잭션이 데이터베이스에 모두 반영되던지, 아니면 전혀 반영 되지 않아야 한다.
    • 트랜잭션 내의 모든 명령은 반드시 완벽히 수행되어야 하며, 모두가 완벽히 수행되지 않고 어느 하나라도 오류가 발생하면 트랜잭션 전부가 취소되어야 한다.
  • Consistency(일관성)
    • 트랜잭션의 작업 처리 결과가 항상 일관성이 있어야 한다.
    • 시스템이 가지고 있는 고정요소는 트랜잭션 수행 전과 수행 완료 후의 상태가 같아야 한다.
  • Isolation(독립성)
    • 둘 이상의 트랜잭션이 동시에 실행되고 있을 경우 어떤 하나의 트랜잭션이라도 다른 트랜잭션의 연산에 끼어들수 없다.
    • 수행 중인 트랜잭션은 완전히 완료될 때까지 다른 트랜잭션에서 수행 결과를 참조 할 수 없다.
  • Durability(지속성)
    • 트랜잭션이 성공적으로 완료되었을 경우, 결과는 영구적으로 반영되어야 한다.

트랜잭션의 Commit과 Rollback

Commit

commit 연산은 하나의 트랜잭션이 성공적으로 끝났고, 데이터베이스가 일관성있는 상태에 있을 때 하나의 트랜잭션이 끝났음을 알려주기 위해 사용하는 연산이다. 

Rollback

Rollback 연산은 하나의 트랜잭션 처리가 비정상적으로 종료되어 데이터베이스의 일관성을 깨뜨렸을 때, 이 트랜잭션의 일부가 정상적으로 처리되었더라도 트랜잭션의 원자성을 구현하기 위해 이 트랜잭션이 행한 모든 연산을 취소(Undo)하는 연산이다.

 

트랜잭션의 상태

Transaction State

 

  • 활동(Active) : 트랜잭션의 활동 상태. 트랜잭션이 실행중이며 동작중인 상태를 말한다.
  • 부분완료(Partially Committed) : 트랜잭션의 Commit 명령이 도착한 상태. 트랜잭션의 commit 이전 sql문이 수행되고 commit만 남은 상태를 말한다.
  • 완료(Committed) : 트랜잭션 완료 상태. 트랜잭션이 정상적으로 완료된 상태를 말한다.
  • 실패(Failed) : 트랜잭션 실패 상태. 트랜잭션이 더 이상 정상적으로 진행될 수 없는 상태를 말한다.
  • 취소(Aborted) : 트랜잭션 취소 상태. 트랜잭션이 취소되고 트랜잭션 실행 이전 데이터로 돌아간 상태를 말한다.

부분완료와 완료의 차이점

commit요청이 들어오면 상태는 부분완료 상태가 된다. 이후 commit을 문제없이 수행 할 수 있다면 완료 상태로 전이되고, 만약 오류가 발생하면 실패 상태가 된다. 즉, 부분완료는 commit요청이 들어왔을 때를 말하며, 완료 상태는 commit을 정상적으로 완료한 상태를 말한다.

트랜잭션을 사용할 때 주의할 점

트랜잭션은 꼭 필요한 최소의 코드에만 적용하는 것이 좋다. 즉 트랜잭션의 범위를 최소화하라는 의미다.

일반적으로 데이터베이스 커넥션은 개수가 제한적이다.

그런데 각 단위 프로그램이 커넥션을 소유하는 시간이 길어진다면 사용 가능한 여유 커넥션의 개수는 줄어들게 된다.

그러다 어느 순간에는 각 단위 프로그램에서 커넥션을 가져가기 위해 기다려야 하는 상황이 발생할 수도 있는 것이다.

출처: https://code-lab1.tistory.com/51 [코드 연구소:티스토리]

'JAVA > 참고 개인공부' 카테고리의 다른 글

JVM 개념 및 구조  (0) 2022.12.09
인터페이스  (0) 2022.09.30

인터페이스는 왜 필요한가?

다음은 어떤 동물원 사육사가 하는 일이다.

난 동물원의 사육사이다.
육식동물이 들어오면 난 먹이를 던져준다.
호랑이가 오면 사과를 던져준다.
사자가 오면 바나나를 던져준다.

이런 케이스를 코드로 담아보자. 다음과 같이 Animal, Tiger, Lion, Zookeeper 클래스를 작성하자.

Sample.java

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

class Tiger extends Animal {
}

class Lion extends Animal {
}

class ZooKeeper {
    void feed(Tiger tiger) {  // 호랑이가 오면 사과를 던져 준다.
        System.out.println("feed apple");
    }

    void feed(Lion lion) {  // 사자가 오면 바나나를 던져준다.
        System.out.println("feed banana");
    }
}

public class Sample {
    public static void main(String[] args) {
        ZooKeeper zooKeeper = new ZooKeeper();
        Tiger tiger = new Tiger();
        Lion lion = new Lion();
        zooKeeper.feed(tiger);  // feed apple 출력
        zooKeeper.feed(lion);  // feed banana 출력
    }
}

이전 챕터에서 보았던 Dog 클래스와 마찬가지로 Animal을 상속한 Tiger와 Lion이 등장했다. 그리고 사육사 클래스인 ZooKeeper 클래스를 위처럼 정의 하였다. ZooKeeper 클래스는 호랑이가 왔을 때, 사자가 왔을 때 각각 다른 feed 메소드가 호출된다.

 

프로그램을 실행하면 다음과 같은 결과가 출력될 것이다.

feed apple
feed banana

자, 이제 다음을 생각 해 보자.

동물원에 호랑이와 사자뿐이라면 ZooKeeper 클래스는 더이상 할일이 없겠지만 악어, 표범등이 계속 추가된다면 ZooKeeper는 육식동물이 추가될 때마다 매번 다음과 같은 feed 메소드를 추가해야 한다.

아래 추가한 메소드는 실제로 코드에 적용하지 말고 눈으로만 보자.

(... 생략 ...)

class ZooKeeper {
    void feed(Tiger tiger) {
        System.out.println("feed apple");
    }

    void feed(Lion lion) {
        System.out.println("feed banana");
    }

    public void feed(Crocodile crocodile) {
        System.out.println("feed strawberry");
    }

    public void feed(Leopard leopard) {
        System.out.println("feed orange");
    }
}

(... 생략 ...)

이렇게 육식동물이 추가 될 때마다 feed 메소드를 추가해야 한다면 사육사(ZooKeeper)가 얼마나 귀찮겠는가? 이런 어려움을 극복하기 위해서는 인터페이스의 도움이 필요하다.

 

인터페이스 작성하기

다음과 같이 코드 상단에 육식동물(Predator) 인터페이스를 추가하자.

interface Predator {
}

class Animal {
    String name;

    void setName(String name) {
        this.name = name;
    }
}

(... 생략 ...)

위 코드와 같이 인터페이스는 class가 아닌 interface 라는 키워드를 이용하여 작성한다.

※ 인터페이스는 클래스와 마찬가지로 Predator.java와 같은 단독 파일로 저장하는 것이 일반적인 방법이다. 여기서는 설명의 편의를 위해 Sample.java 파일의 최상단에 작성하였다.

그리고 Tiger, Lion 클래스는 작성한 인터페이스를 구현하도록(Implements) 수정하자.

(... 생략 ...)

class Tiger extends Animal implements Predator {
}

class Lion extends Animal implements Predator {    
}

(... 생략 ...)

인터페이스 구현은 위와같이 implements 라는 키워드를 사용한다.

이렇게 Tiger, Lion 클래스가 Predator 인터페이스를 구현하게 되면 ZooKeeper 클래스의 feed 메소드를 다음과 같이 변경 할 수 있다.

변경전

(... 생략 ...)

class ZooKeeper {
    void feed(Tiger tiger) {
        System.out.println("feed apple");
    }

    void feed(Lion lion) {
        System.out.println("feed banana");
    }
}

(... 생략 ...)

변경후

(... 생략 ...)

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed apple");
    }
}

(... 생략 ...)

feed 메소드의 입력으로 Tiger, Lion을 각각 필요로 했지만 이제 이것을 Predator라는 인터페이스로 대체할 수 있게 되었다. tiger, lion은 각각 Tiger, Lion의 객체이기도 하지만 Predator 인터페이스의 객체이기도 하기 때문에 위와같이 Predator를 자료형의 타입으로 사용할 수 있는 것이다. 상속에서 공부했던 IS-A 관계가 인터페이스에도 마찬가지로 적용된다. "Tiger is a Predator", "Lion is a Predator"가 성립된다.

  • tiger - Tiger 클래스의 객체, Predator 인터페이스의 객체
  • lion - Lion 클래스의 객체, Predator 인터페이스의 객체

※ 이와같이 객체가 한 개 이상의 자료형 타입을 갖게되는 특성을 다형성(폴리모피즘)이라고 하는데 이것에 대해서는 "다형성" 챕터에서 자세히 다루도록 한다.

이제 어떤 육식동물이 추가되더라도 ZooKeeper는 feed 메소드를 추가할 필요가 없다. 다만 육식동물이 추가 될 때마다 다음과 같이 Predator 인터페이스를 구현한 클래스를 작성하기만 하면 되는 것이다.

class Crocodile extends Animal implements Predator {
}

※ Crocodile 클래스는 실제 코드에 적용하지 말고 눈으로만 보자.

눈치가 빠르다면 이제 왜 인터페이스가 필요한지 감을 잡았을 것이다. 보통 중요 클래스를 작성하는 입장이라면 (여기서는 ZooKeeper가 중요한 클래스이다) 클래스의 구현체와 상관없이 인터페이스를 기준으로 중요 클래스를 작성해야만 한다. 구현체(Tiger, Lion, Crocodile,...)는 늘어날수 있지만 인터페이스(Predator)는 하나이기 때문이다.

인터페이스의 메소드

자, 그런데 위 ZooKeeper 클래스에 약간의 문제가 발생했다. 아래의 ZooKeeper클래스의 feed 메소드를 보면 호랑이가 오던지, 사자가 오던지 무조건 "feed apple" 이라는 문자열을 출력한다. 사자가 오면 "feed banana" 를 출력해야 하지 않겠는가!

(... 생략 ...)

class ZooKeeper {
    public void feed(Predator predator) {
        System.out.println("feed apple");  // 항상 feed apple 만을 출력한다.
    }
}

(... 생략 ...)

이번에도 인터페이스의 마법을 부려보자.

Predator 인터페이스에 다음과 같은 메소드를 추가 해 보자.

interface Predator {
    String getFood();
}

(... 생략 ...)

getFood 라는 메소드를 추가했다. 그런데 좀 이상하다. 메소드에 몸통이 없다?

인터페이스의 메소드는 메소드의 이름과 입출력에 대한 정의만 있고 그 내용은 없다. 그 이유는 인터페이스는 규칙이기 때문이다. 위에서 설정한 getFood라는 메소드는 인터페이스를 implements한 클래스들이 구현해야만 하는 것이다.

인터페이스에 위처럼 메소드를 추가하면 Tiger, Lion 등의 Predator 인터페이스를 구현한 클래스들에서 컴파일 오류가 발생할 것이다. 오류를 해결하려면 다음처럼 Tiger, Lion 클래스에 getFood 메소드를 구현해야 한다.

 

(... 생략 ...)

class Tiger extends Animal implements Predator {
    public String getFood() {
        return "apple";
    }
}

class Lion extends Animal implements Predator {
    public String getFood() {
        return "banana";
    }
}

(... 생략 ...)

※ 인터페이스의 메소드는 항상 public으로 구현해야 한다.

Tiger, Lion 클래스의 getFood 메소드에 육식동물의 먹이인 "apple", "banana"를 각각 리턴하도록 작성했다. 이렇게 getFood 메소드를 추가하면 컴파일 오류가 해결될 것이다.

이제 ZooKeeper 클래스도 다음과 같이 변경이 가능하다.

(... 생략 ...)

class ZooKeeper {
    void feed(Predator predator) {
        System.out.println("feed "+predator.getFood());
    }
}

(... 생략 ...)

feed 메소드가 "feed apple" 을 출력하던 것에서 "feed "+predator.getFood()를 출력하도록 변경되었다. predator.getFood()를 호출하면 Predator 인터페이스를 구현한 구현체(Tiger, Lion)의 getFood() 메소드가 호출된다.

그리고 프로그램을 실행해 보자. 원하던 데로 다음과 같은 결과값이 출력되는 것을 확인할 수 있을 것이다.

feed apple
feed banana

05-07 인터페이스 - 점프 투 자바 (wikidocs.net)

'JAVA > 참고 개인공부' 카테고리의 다른 글

JVM 개념 및 구조  (0) 2022.12.09
핵클 협력사 세션.  (0) 2022.10.04

+ Recent posts