상속 (inheritance) 이란?
기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것.
class Parent{
int age;
}
class Child extends Parent{
String name;
}
상속을 통해 클래스를 관리하면 적은 양의 코드로 새로운 클래스를 작성할 수 있으며 추가 및 변경이 용이해진다.
JAVA에서 상속을 구현하는 방법은 상속받고자 하는 클래스 뒤에 extends를 붙이기만 하면 된다.
상속하는 클래스와 받는 클래스를 다음과 같이 다양한 방법으로 부른다.
- 조상 클래스, 부모(parent) 클래스, 상위(super) 클래스, 기반(base) 클래스
- 자손 클래스, 자식(child) 클래스, 하위(sub) 클래스, 파생(derived) 클래스
JAVA에선 자식 클래스는 부모 클래스의 멤버 변수, 메소드를 상속받아 사용할 수 있다.
"전체 프로그램들을 구성하는 클래스들을 면밀히 설계 분석하여,
클래스간의 상속관계를 적절히 맺어 주는 것이 객체지향 프로그래밍에서 가장 중요한 부분이다."
라고 이야기한다.
객체간의 협력과 책임을 강조하는 내용인 것 같은데 꼭 상속관계여야만 할까? 라는 생각이 드는 부분이기도 하다.
상속관계 vs 포함관계?
class Point{
int x;
int y;
}
class Circle{
Point center;
int radius;
}
Class 간의 관계에는 상속관계가 아닌 포함관계 또한 존재한다.
ex) Circle 클래스가 Point 클래스를 변수로 가지고 있음
클래스간의 관계를 어떻게 결정할 것인가에 대해 책에서는 is a, has a 를 대입해보라고 설명한다.
- 중심은 원이다 (x)
- 원은 중심을 가지고 있다. (o)
하위 클래스 is a 상위클래스일 경우 상속 관계로
상위 클래스 has a 하위클래스일 경우 포함관계로 설정하라고 이야기한다.
그러나 "스프링 입문을 위한 자바 객체지향의 원리와 이해" 책에선 is a 관계를 부정적으로 본다.
Class Car{
String color;
}
// is a 관계 성립
Car myCar = new Car();
내 차는 자동차이다. (o)
is a 관계를 객체와 인스턴스 관계로 오해하기 쉽기 때문에 is a kind of 관계로 하는 것이 명확하다고 이야기한다.
내 차는 자동차의 한 분류이다 (x)
- 객체지향의 상속은 상위 클래스의 특성을 재사용하는 것이다.
- 객체지향의 상속은 상위 클래스의 특성을 확장하는 것이다.
- 객체지향의 상속은 is a kind of 관계를 만족해야 한다.
단일 상속
C++는 한 개 이상의 조상 클래스로 부터 상속을 받는 다중 상속이 가능하다.
그러나 JAVA는 오직 단일 상속만 허용한다.
class Father{
String name = "Dad";
}
class Mother{
String name = "Mom";
}
class Child extends Father, Mother{
}
Child c = new Child();
System.out.println(c.name); //Dad?? Mom??
만약 두 개 이상의 부모 클래스에서 같은 멤버를 상속할 경우 어떤 부모 클래스의 멤버를 참조해야할지 모르게 된다.
이와 같은 다중 상속의 단점 때문에 JAVA는 다중 상속을 과감하게 제거했다.
Object 클래스
Object 클래스는 모든 클래스 상속계층도의 최상위에 있는 조상 클래스이다.
Class를 생성할경우 컴파일러는 자동으로 Object 클래스를 상속받게 해준다.
따라서 Object 클래스의 toString(), equals()와 같은 메서드를 따로 정의하지 않아도 사용할 수 있으며
후술할 다형성 파트에서도 구현에 도움을 줄 수 있다.
오버라이딩이란?
class Parent{
public void say(){
System.out.println("hi");
}
}
class Child extends Parent{
public void say(){
System.out.println("hello");
}
}
Child c = new Child();
c.say(); // "hello"
부모 클래스로부터 상속받은 메서드의 내용을 변경하는 것이다.
자식 클래스에서 부모 클래스와 똑같은 이름의 say() 라는 함수를 재정의했다.
자식 클래스를 생성 후 say() 함수를 호출하면? 재정의된 함수가 실행된다.
조건 :선언부가 조상의 것과 완전히 일치해야함 (이름, 매개변수, 반환타입 모두 동일)
그러나 접근 제어자와 예외는 제한된 조건 하에서만 다르게 변경할 수 있다.
- 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경 불가능
- 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다 → throws 시에 상위 클래스를 참조하는 메소드도 처리가 가능해야한다.
- 인스턴스 메서드를 static으로 또는 그 반대로 변경할 수 없다.
super
자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용하는 참조 변수이다.
super.x; 와 같이 사용하며 super() 를 이용해서 부모 클래스의 생성자를 호출할 수 있다.
하위 클래스는 생성자의 첫 줄에서 반드시 상위 클래스의 생성자를 호출해야한다.
자식 클래스가 조상클래스의 멤버를 사용할 수도 있기 때문이다.
Object 클래스를 제외한 모든 클래스의 생성자에 this() 혹은 super()를 컴파일러는 생성해준다.
이때 컴파일러가 삽입하는 생성자는 기본 생성자이므로 부모 클래스에 기본 생성자 없으면 컴파일 오류가 발생한다.
class Parent{
int x;
public Parent(int x){
this.x = x;
}
}
class Child extends Parent{
int y;
public Child(int x, int y){ //오류 발생
this.x = x;
this.y = y;
}
}
조상 클래스의 멤버변수는 조상클래스의 생성자에 의해 초기화되도록 하는 것이 좋다.
package란?
클래스의 묶음.
클래스 또는 인터페이스를 관련된것끼리 묶어놓아 효율적으로 관리할 수 있도록 한다.
같은 이름의 클래스여도 다른 패키지에 존재할 수 있으므로 라이브러리의 클래스와 이름 충돌 막아준다.
- 클래스 : 물리적으로 하나의 클래스 파일로 존재 (.class)
- 패키지 : 물리적으로 하나의 디렉토리로 존재
하나의 소스파일에는 첫번째 문장으로 단 하나의 패키지 선언만 허용하며
모든 클래스는 반드시 하나의 패키지에 속해야한다.
패키지를 지정하지 않을 경우 자바에서 기본적으로 제공하는 이름없는 패키지(unnamed package)에 포함된다.
패키지는 점을 구분자로 하는 계층구조로 구성 가능하며, 클래스 파일을 포함하는 하나의 디렉토리이다.
패키지의 선언
package 패키지명; 과 같이 선언한다.
패키지명은 대소문자 모두 허용하지만, 클래스명과 구분하기 위해 소문자로 하는 것이 원칙이다.
그러나 패키지 명에 패키지가 존재하는 디렉토리의 절대 경로를 모두 쓰기는 힘들다.
또한 컴파일, 로드시에 패키지위 위치를 찾기 힘드므로 CLASSPATH 라는 이름의 루트 디렉토리를 설정할 수 있다.
; 구분자를 이용해 여러개의 루트 디렉토리도 지정 가능하며 순차적으로 모두 탐색후 제일 먼저 찾은 파일을 사용한다.
JDK는 몇가지 CLASSPATH를 기본으로 제공한다. (\jre\classes, \jre\lib\ext 등등)
import 문
컴파일러에게 소스파일에 사용된 클래스의 패키지에 대한 정보를 제공한다.
import 문은 프로그램 실행 성능에 영향을 끼치지 않는다. 컴파일 시간에만 아주 조금 영향을 끼친다.
import 패키지명; 과 같이 선언하며 package와 다르게 하나의 파일에서 여러번 선언이 가능하다.
같은 패키지 내의 클래스들은 import 없이도 사용이 가능하다.
static import 패키지명.클래스명 을 이용해 static 메소드 사용시 클래스 이름을 생략할 수 있다.
제어자
제어자는 클래스, 변수 또는 메서드의 선언부에 함께 사용하여 부가적 의미를 부여한다.
- 접근 제어자 public, protected, default, private
- 그 외 static, final, abstract, native, volatile 등등
접근 제어자와 접근 범위
- private : 해당 클래스 안에서만 접근 가능
- default : 해당 패키지 안에서만 접근 가능
- protected : 동일 패키지 또는 상속받은 클래스에서 접근 가능
- public : 어디서든 접근 가능
여러 제어자를 함께 사용 가능하나, 접근 제어자는 하나만 사용할 수 있다.
static - 클래스의, 공통적인
- static이 붙었을 경우 static 영역에 저장하여, 모든 인스턴스가 공유한다.
- static 제어자가 붙은 메소드나 변수는 인스턴스 선언 없이도 사용이 가능하다.
- 멤버변수, 메서드, 초기화 블럭 앞에 사용 가능하다.
final - 마지막, 변경할 수 없음
- 변수에 사용시 변경 불가능한 상수로 만든다.
- 메서드 사용시 해당 메서드는 오버라이딩이 불가능하다.
- 클래스에 사용시 해당 클래스는 상속이 불가능하다. (String, Math)
abstract - 추상의, 미완성의
선언부만 작성하고, 실제 수행은 구현하지 않은 추상 메서드, 클래스 선언하는데 사용한다.
추상 클래스는 인스턴스 생성이 불가능하다.
차후 추상 클래스 파트에서 더 자세하게 설명한다.
접근제어자를 이용한 캡슐화?
데이터가 유효한 값을 유지하도록, 외부에서 변화 시키지 못하도록 제한하는 것을 data hiding이라고 하며
객체지향 원리의 캡슐화에 해당한다.
외부에는 불필요한 부분을 감추기 위해 접근 제어자를 사용한다.
생성자의 접근 제어자
생성자에 접근 제어자를 사용함으로서 인스턴스의 생성을 제한할 수 있다.
보통 생성자의 접근제어자는 클래스의 접근제어자와 동일하게 설정하나 다르게 설정하는 것도 가능하다.
private으로 설정하면 외부에서 인스턴스 생성을 막을 수 있으며 싱글톤 패턴을 구현할때 주로 사용한다.
생성자가 private일 경우 다른 클래스의 조상이 될 수 없으므로 super로 불러오기 불가능하며, 보통 final과 같이 사용한다.
다형성
“여러 형태를 가질 수 있는 능력”
한 타입의 참조 변수로 여러 타입의 객체를 참조 할 수 있도록 함으로서 구현한다.
class Parent{
int var = 1;
public String say(){
return "hi";
}
}
class Child extends Parent{
int var = 2;
public void say(){
return "hello";
}
}
Parent c = new Child(); // 가능!
JAVA에선 조상 클래스 타입의 참조변수로 자손 클래스의 인스턴스를 참조할 수 있다.
같은 타입의 참조변수로 참조하는 것과 조상 타입의 참조변수로 참조하는 것의 차이는 무엇일까?
그렇다면 위와 같은 상속관계의 클래스에서 다음과 같은 문장을 실행시키면 어떤 값이 나올까?
Parent a = new Child();
System.out.println(a.var); // 1
System.out.println(a.say()); // 2
Child b = new Child();
System.out.println(b.var); // 3
System.out.println(b.say()); // 4
정답은.... 다음과 같다.
그런데 생각해보면 이상한 부분이 있다. 왜 같은 Child 인스턴스를 만들었는데
- 변수는 참조변수의 타입에 따라 달라지며 (a는 1, b는 2)
- 메서드 함수는 달라지지 않는걸까 (a,b 둘다 hello)
그 이유는 클래스 상속시의 메모리 구조가 다음과 같기 때문이다.
Heap영역에 객체를 생성하면 부모 클래스와 자식 클래스의 인스턴스가 같이 생성된다고 개구리 책은 설명한다.
참조 변수의 타입에 따라 가리키는 인스턴스의 영역이 달라진다.
부모타입의 참조변수가 하위 타입 인스턴스를 참조시에는 부모타입의 멤버만 사용 가능하다.
즉 같은 타입의 인스턴스지만 사용할 수 있는 멤버의 개수가 달라진다.
그러나 메소드를 오버라이딩 했을 경우 자식 클래스의 메소드가 부모 클래스의 메소드까지 덮어버린다.
정확히는 오버라이딩된 메소드가 부모 클래스의 메소드보다 더 높은 우선순위를 가지고 호출된다.
이것이 어떻게 작동하는 것일까?
V Table
짜잔~ 사실 힙 영역에는 메서드 함수가 저장되지 않는다.
이게 갑자기 뭔 소리냐 라고 이야기하겠지만 사실이다.
만약 힙 영역에 객체마다 메서드 함수에 대한 내용을 저장한다면,
100개의 인스턴스를 생성한다면 100개의 메서드가 힙에 올라갈것이다.
그런데 메서드가 인스턴스마다 다르게 동작하는가? 아니다. 모든 인스턴스는 똑같은 메서드를 실행시킨다.
그래서 jvm은 메소드의 구현 주소를 모은 vtable을 만들어 놓는다.
클래스가 로드될때 jvm은 그 클래스의 vtable을 만드는데 ,
부모 클래스의 메소드를 상속할 시 부모 클래스의 vtable 주소를 그대로 사용하며
자식 클래스가 오버라이딩 했을 시 메소드를 vtable에 추가한다.
그리고 힙 영역의 객체 맨 앞에는 vtable의 주소가 들어가 있다.
그래서 객체에서 메서드가 호출되었을 경우 해당 클래스의 vtable로 진입해 가장 가까운 메서드를 호출하는 것이다.
정확하게 vtable이 구현되어있는 방식은 JVM의 종류와 버전에 따라 다르다고 한다.
(오버라이딩 된 메서드가 vtable의 맨 밑으로 들어가는지, 기존 메서드의 주소를 대체하는지는 글마다 다르게 설명한다)
우리가 알아둘 것은 인스턴스 내부에 실제 메소드 구현에 대한 정보를 담고 있는 주소 테이블이 존재한다는 것이고
객체를 상속하고 오버라이딩 할 경우 자식 클래스의 인스턴스는 자식 클래스의 메소드 주소만 접근할 수 있다는 것이다.
https://dataonair.or.kr/db-tech-reference/d-lounge/technical-data/?mod=document&uid=235941
Runtime Data Areas
Runtime Data Areas ㈜엑셈 컨설팅본부 /APM팀 임 대호 Runtime Data Area 구조 Runtime Data Area 는 JVM 이 프로그램을 수행하기 위해 할당 받는 메모리 영역이라고 할 수 있다 . 실제 WAS 성능 문제에 직면했을 때
dataonair.or.kr
메소드의 경우는 이와 같은 참조 테이블의 존재 때문에 오버라이딩시 기존 부모 클래스의 메소드를 완전히 사용할 수 없게 된다.
그러나 멤버 변수의 경우, 힙 영역에 같이 존재하므로, 오버라이딩이 불가능하다.
부모 타입 참조변수로 호출 시 부모 타입 멤버 변수가 호출된다.
class Parent{
public String say(){
return "hi";
}
}
class Child extends Parent{
public void run(){
System.out.println("run run");
}
}
Parent c = new Child(); // 가능!
c.run() // ?????
그렇다면 다음과 같은 코드에서 c.run() 을 호출한다면 어떤 결과가 나올까?
부모 타입의 참조 변수에서 자식 타입에만 구현된 함수를 호출했다.
- 참조변수만 부모타입일 뿐 인스턴스는 자식 타입이므로 vtable에는 자식 타입의 메소드가 존재한다. 따라서 그냥 호출된다
- 프로그램 동작 중 자식 타입의 메소드 호출 시 vtable에 자식 타입의 메소드를 호출 할 수 없어 런타임 에러를 발생시킨다.
- 부모타입의 참조변수가 자식 타입에만 구현된 메소드를 실행하므로 컴파일 에러가 발생한다.
정답은 3번이다.
JVM은 컴파일 시에 변수의 타입과, 메소드를 확인하여 이것이 올바른 메소드인지 검사한다.
Parent 타입의 참조변수에서 run() 메소드를 실행시키면
당연히 Parent 클래스에서는 찾을 수 없는 메소드이므로 컴파일 오류가 발생한다.
vtable, 메소드 오버라이딩에 관한 내용은 컴파일 이후 실행되었을때의 메모리 상황이고
다음과 같은 상황에선 그냥 컴파일 오류가 발생한다.
참조변수의 형변환
참조변수도 형 변환이 가능하다. 단 상속관계에 있는 클래스 사이에서만 가능하다.
자손타입 → 조상타입, 조상타입 →자손타입 모두 가능하다.
- 자손 → 조상 (업캐스팅) : 형변환 생략이 가능
- 조상 → 자손(다운캐스팅) : 형변환 생략 불가능
더 큰 범위에서 작은 범위로의 형변환은 생략이 가능하다.
형변환은 참조변수의 타입을 변환하는 것일 뿐, 인스턴스를 변환하지 않는다.
참조 변수는 인스턴스에 아무런 영향을 끼치지 않는다.
참조하는 인스턴스에서 사용할 수 있는 멤버의 범위만 조절하는 것이다.
instanceof 연산자
class Car{
String name;
}
class FireCar extends Car{
String color = "red";
}
Car myCar = new Car();
Car myFireCar = new FireCar();
System.out.println(myCar instanceof Car); //true
System.out.println(myFireCar instanceof FireCar); //true
System.out.println(myCar instanceof FireCar); //false
System.out.println(myFireCar instanceof Car); //true
참조변수가 참조하는 인스턴스의 실제 타입을 알아보는 연산자이다.
인스턴스의 실제 타입 뿐 아니라 어떤 클래스를 상속받는지도 확인해 볼 수 있다.
true가 나온다면 검사한 타입으로 형변환이 가능하다는 것을 의미하기도 한다.
(null이 들어가면 false가 반환된다.)
개구리 책에서는 instanceof 연산자가 LSP 원칙(리스코프 치환 원칙)을 어기는 코드에서 자주 등장하는 연산자이기에
instanceof 연산자가 나타난다면 냄새 나는 코드가 아닌지(리팩터링의 대상이 아닌지) 다시 확인해 보라는 이야기를 한다.
참조변수와 인스턴스의 연결
조상과 같은 이름의 인스턴스 변수를 중복 정의한다면 조상 타입.변수, 자손타입.변수를 참조할 때 다른 결과가 나온다.
또한 참조변수의 타입에 따라서도 값이 달라진다.
public class Main
{
public static void main(String[] args) {
Parent c = new Child();
System.out.println(c.x); // 1
}
}
class Parent{
int x = 1;
}
class Child extends Parent{
int x = 2;
}
클래스 안에서는 super와 this를 사용해 구분이 가능하다.
매개변수의 다형성
public class Main
{
public static void main(String[] args) {
Book b = new Book("hi");
Car c = new Car("Hello");
printPrice(b); //2000
printPrice(c); //10000
}
public static void printPrice(Product p){
System.out.println(p.getPrice());
}
}
class Product{
int price = 1000;
public int getPrice(){
return this.price;
}
}
class Book extends Product{
String name;
public Book(String name){
this.price = 2000;
this.name = name;
}
}
class Car extends Product{
String name;
public Car(String name){
this.price = 10000;
this.name = name;
}
}
메서드의 매개변수에 다형성을 적용하면, 여러 타입의 인스턴스를 하나의 메서드로 받아올 수 있다.
여러 종류의 객체를 배열로 다루기
조상타입 참조변수로 자손타입 객체를 참조하는 것 가능하므로
조상타입의 참조변수 배열로 여러개의 자손타입 인스턴스를 묶어서 다를 수 있다.
(Object 배열을 생성할 경우 모든 타입의 객체를 받을 수 있다.)
public class Main
{
public static void main(String[] args) {
Product[] products = new Product[3];
products[0] = new Book("book1");
products[1] = new Book("book2");
products[2] = new Car("Car1");
for (Product p : products){
printPrice(p);
}
}
public static void printPrice(Product p){
System.out.println(p.getPrice());
}
}
추상 클래스
책에서는 클래스가 설계도라면 추상 클래스는 미완성 설계도라고 비유한다.
클래스 내에 미완성 메서드를 포함한다는 의미이며
추상클래스로는 인스턴스를 생성하는 것이 불가능하다.
(그러나 추상 클래스도 생성자, 멤버변수와 메서드를 가질 수 있다.)
추상 메서드
선언부만 작성, 구현부는 작성하지 않은 메서드.
추상클래스를 상속받는 자손 클래스는 오버라이딩을 통해 반드시 추상 메서드를 모두 구현해주어야한다.
"상속이 자손클래스를 만드는데 조상 클래스를 사용하는 것이라면,
추상화는 기존의 클래스의 공통부분을 뽑아내서 조상 클래스를 만드는 것" 이라고 이야기한다.
인터페이스란
interface Car{
public void run();
}
class MyCar implements {
public void run(){
System.out.println("부릉부릉");
}
}
MyCar myCar = new MyCar();
myCar.run(); // 부릉부릉
일종의 추상 클래스, 그러나 추상화 정도가 더 높다.
인터페이스는 일반 메서드 또는 멤버변수를 가질 수 없다 (추상메서드와 상수만을 멤버로 가진다)
미완성 설계도도 아닌 기본 설계도라고 비유한다.
모든 멤버변수는 public static final이어야하며, 생략시 컴파일러가 추가해준다.
모든 매서드는 public abstract이며 생략이 가능하다.
(static 메서드와 defauslt 메서드는 JDK1.8부터 예외로 추가되었다.)
인터페이스의 구현
implements라는 키워드를 사용한다.
인터페이스의 모든 추상 메서드를 작성해야한다. 작성하지 않을 시 abstract를 붙여서 추상 클래스로 만들어야한다.
접근 제어자는 상속 시 더 넓은 범위만 지정 가능하다.
인터페이스의 기본 접근 지정자는 public abstract이므로,
접근 지정자를 지정하지 않은 인터페이스의 메소드를 구현할 경우 반드시 public으로 구현해야한다.
인터페이스의 상속과 다중상속
인터페이스는 인터페이스만 상속 가능하며, 다중 상속도 가능하다.
그렇다면 인터페이스는 왜 다중 상속이 가능하냐?
인터페이스의 상수는 static이므로 사용시 앞에 클래스 명을 붙이고,
추상 메서드는 어차피 오버라이딩해야하므로 똑같은 이름의 메서드를 중복 상속해도 문제될 것이 없다.
인터페이스를 이용한 다형성
interface Car{
public void run();
}
class MyCar implements {
public void run(){
System.out.println("부릉부릉");
}
}
Car c = new MyCar();
c.run(); // 부릉부릉
인터페이스 역시 인터페이스 타입의 참조변수로 구현 클래스의 인스턴스를 참조하는 것이 가능하다.
인터페이스 타입으로 형변환 역시 가능하다.
인터페이스 타입의 참조변수는 메서드 호출시 구현한 인스턴스를 제공한다.
리턴타입이 인터페이스일 경우 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다.
인터페이스의 디폴트 메서드와 static 메서드
JDK 1.8 이전엔 허용 안됐다.
(java.util.Collection 관련된 static 메서드가 별도의 Collections 클래스에들어간 것을 볼 수 있다.)
인터페이스에 추상 메서드를 추가할경우 상속받는 모든 클래스에 구현을 추가해줘야한다.
하지만 디폴트 메서드를 추가한다면 모든 클래스에 구현을 추가해줄 필요가 없어진다.
대신 다중 상속시 문제가 생기고 다음과 같이 신경써야한다.
- 여러 인터페이스가 같은 디폴트 메서드 구현 : 구현 클래스에서 오버라이딩 해야한다.
- 디폴트 메서드와 조상 클래스 메서드간의 충돌 : 디폴트 메서드 무시
내부 클래스 (inner class)
클래스 안에 선언되는 클래스이다.
두 클래스가 서로 긴밀한 관계에 있는 경우 사용한다.
서로 쉽게 접근이 가능하며, 외부엔 불필요한 클래스 감추기 가능하다.
종류와 특징
- 인스턴스 클래스 : 멤버 변수 선언위치에 선언, 인스턴스 멤버처럼 다루어진다.
- 스태틱 클래스 : 외부 클래스의 static멤버처럼 사용된다. static 메서드에서 주로 사용한다.
- 지역 클래스 : 메서드나 초기화 블럭에서 선언, 선언된 영역에서만 사용된다.
- 익명 클래스 : 선언과 생성을 동시에 하는 이름없는 클래스, 일회용으로 사용된다.
내부 클래스는 선언위치에따라 선원위치의 변수와 동일한 스코프와 접근성을 가진다.
내부 클래스와 외부 클래스에 선언된 변수 이름이 같으면 this, 외부 클래스명.this로 구별이 가능하다.
static 내부 클래스만 외부의 static 멤버를 사용할 수 있다.
그리고 static 내부 클래스는 외부 클래스의 인스턴스 멤버를 사용하는 것이 불가능하다.
지역 클래스는 지역변수도 사용이 가능하지만 final이 붙은 지역변수만 사용 가능하다.
(메서드가 수행 마친 후에도 지역 클래스가 소멸된 지역변수 참조 가능하기 때문)
class Main {
public static void main(String[] args) {
Animal dog = new Animal() {
public String bark(){
return "멍멍";
}
};
System.out.println(dog.bark()); //멍멍
}
}
class Animal{
public String bark(){
return "동물 울음소리";
}
}
익명 클래스는 클래스의 선언, 객체의 생성을 동시에, 한번에 하나만 생성하는 일회용 클래스이다.
생성자를 가질 수 없으며 하나의 클래스만 상속받거나, 하나의 인터페이스만 구현이 가능하다
'JAVA' 카테고리의 다른 글
JAVA의 정석 정독하기 #9 - java.lang 패키지와 유용한 클래스 (1) | 2023.11.14 |
---|---|
JAVA의 정석 정독하기 #8 - 예외처리 (1) | 2023.11.14 |
JAVA의 정석 정독하기 #6 - Class (1) | 2023.11.11 |
JAVA의 정석 정독하기 #5 - 배열(Array) (0) | 2023.10.29 |
JAVA의 정석 정독하기 #4 - 조건문과 반복문 (0) | 2023.10.22 |