클래스 상속과 객체
- 슈퍼 클래스 객체와 서브 클래스의 객체는 별개이다.
- 서브 클래스 객체는 슈퍼 클래스 멤버를 포함한다.
- 서브 클래스는 슈퍼 클래스의 private 멤버 외 모든 멤버를 접근할 수 있다.
상속 선언과 객체
1. 상속 선언 - extends 키워드 사용
class ColorPoint extends Point { ... }
2. 서브 클래스 객체 생성
Point p = new Point();
ColorPoint cp = new ColorPoint(); // 서브 클래스 객체 생성
특징
- 자바에서는 클래스의 다중 상속을 지원하지 않는다.
- 자바의 모든 클래스는 자바에서 제공하는 Object 클래스를 자동으로 상속받도록 컴파일된다.
상속과 생성자
서브 클래스와 슈퍼 클래스의 생성자 호출 및 실행
class A {
public A() {
System.out.println("생성자A");
}
}
class B extends A {
public B() {
System.out.println("생성자B");
}
}
class C extends B {
public C() {
System.out.println("생성자C");
}
}
public class ConstructorEx {
public static void main(String[] args) {
C c;
c = new C();
}
}
- 서브 클래스의 객체가 생성될 때, 서브 클래스의 생성자와 슈퍼 클래스의 생성자가 모두 실행된다.
- 이때 슈퍼 클래스의 생성자가 먼저 실행된다.
- 서브 클래스의 생성자가 먼저 호출되지만, 더 나중에 실행된다.
서브 클래스에서 슈퍼 클래스 생성자 선택
1. 슈퍼 클래스의 기본 생성자가 묵시적으로 선택
class A2 {
public A2() {
System.out.println("생성자A");
}
public A2(int x) {
System.out.println("생성자A2");
}
}
class B2 extends A2 {
public B2() {
System.out.println("생성자B");
}
}
public class ConstructorEx2 {
public static void main(String[] args) {
B2 b;
b = new B2(); // 생성자 호출
}
}
- 서브 클래스의 기본 생성자에 대해 컴파일러는 자동으로 슈퍼클래스의 기본 생성자와 짝을 맺는다.
class A3 {
public A3() {
System.out.println("생성자A");
}
public A3(int x) {
System.out.println("매개변수생성자A");
}
}
class B3 extends A3 {
public B3() {
System.out.println("생성자B");
}
public B3(int x) {
System.out.println("매개변수생성자B");
}
}
public class ConstructorEx3 {
public static void main(String[] args) {
B3 b;
b = new B3(5);
}
}
- 서브 클래스의 매개 변수가 있는 생성자도 슈퍼 클래스의 기본 생성자와 짝을 이룬다.
2. super()를 이용하여 명시적으로 슈퍼 클래스의 생성자 선택
class A4 {
public A4() {
System.out.println("생성자A");
}
public A4(int x) {
System.out.println("매개변수생성자A" + x);
}
}
class B4 extends A4 {
public B4() {
System.out.println("생성자B");
}
public B4(int x) {
super(x); // 첫 줄에 와야 함
System.out.println("매개변수생성자B" + x);
}
}
public class ConstructorEx4 {
public static void main(String[] args) {
B4 b;
b = new B4(5);
}
}
업캐스팅과 instanceof 연산자
- 캐스팅: 타입 변환 -> 업캐스팅 / 다운캐스팅업
업캐스팅
Person p;
Student s = new Student();
p = s; // 업캐스팅
- 서브 클래스 객체가 슈퍼 클래스 타입으로 변환되는 것
- 슈퍼 클래스의 레퍼런스가 서브 클래스 객체를 가리키도록 치환되는 것
- 업캐스팅한 레퍼런스로는 슈퍼 클래스의 멤버만 접근할 수 있다.
다운캐스팅
Student s = (Student)p; // 다운캐스팅, (Student)의 타입 변환을 반드시 지정
- 업캐스팅된 서브 클래스 객체를 다시 원래대로 되돌리는 것
instanceof 연산자와 객체 구별
객체레퍼런스 instanceof 클래스타입
// instance 연산자 활용 예제
class Person { }
class Student extends Person { }
class Researcher extends Person { }
class Professor extends Researcher { }
public class InstanceOfEx {
static void print(Person p) {
if (p instanceof Person)
System.out.print("Person ");
if (p instanceof Student)
System.out.print("Student ");
if (p instanceof Researcher)
System.out.print("Researcher ");
if (p instanceof Professor)
System.out.print("Professor ");
System.out.println();
}
public static void main(String[] args) {
System.out.print("new Student() -> ");
print(new Student());
System.out.print("new Researcher() -> ");
print(new Researcher());
System.out.print("new Professor() -> ");
print(new Professor());
}
}
- 레퍼런스가 가리키는 객체가 어떤 클래스 타입인지 구분하기 위해 사용
- '레퍼런스'가 가리키는 객체가 해당 '클래스 타입'이면 true, 아니면 false
메소드 오버라이딩
class A {
void f() {
System.out.println("A의 f() 호출");
}
}
class B extends A {
void f() { // A의 f()를 오버라이딩
System.out.println("B의 f() 호출");
}
}
- 서브 클래스에서 슈퍼 클래스에 선언된 메소드를 중복 작성하여 슈퍼 클래스에 작성된 메소드를 무력화시킴.
- 그러면 슈퍼 클래스의 레퍼런스를 이용하든 서브 클래스의 레퍼런스를 이용하든 항상 서브 클래스에 오버라이딩한 메소드가 실행된다.
- 서브 클래스에 오버라이딩한 메소드는 반드시 슈퍼 클래스에 작성된 메소드의 이름, 리턴 타입, 매개 변수 리스트가 모두 같도록 작성
오버라이딩의 목적, 다형성 실현
- 오버라이딩은 슈퍼 클래스에 선언된 메소드를, 동일한 이름으로 각 서브 클래스에서 필요한 내용으로 새로 구현하는데 있다.
- 오버라이딩은 상속을 통해 '하나의 인터페이스에 서로 다른 내용 구현'이라는 객체 지향의 다형성을 실현하는 도구이다.
class Shape { // 도형의 슈퍼 클래스
public void draw() {
System.out.println("Shape");
}
}
class Line extends Shape {
public void draw() { // 메소드 오버라이딩
System.out.println("Line");
}
}
class Rect extends Shape {
public void draw() { // 메소드 오버라이딩
System.out.println("Rect");
}
}
class Circle3 extends Shape {
public void draw() { // 메소드 오버라이딩
System.out.println("Circle");
}
}
public class MethodOverridingEx {
static void paint(Shape p) { // Shape을 상속받은 모든 객체들이 매개변수로 넘어올 수 있음
p.draw(); // p가 가리키는 객체에 오버라이딩한 draw() 호출. 동적 바인딩
}
public static void main(String[] args) {
Line line = new Line();
paint(line); // Line의 draw() 실행. "Line" 출력
// 다음 호출들은 모두 paint() 메소드 내에서 Shape에 대한 레퍼런스 p로 업캐스팅됨
paint(new Shape()); // Shape의 draw() 실행. "Shape" 출력
paint(new Line()); // 오버라이딩된 메소드 Line()의 draw() 실행, "Line" 출력
paint(new Rect()); // 오버라이딩된 메소드 Rect()의 draw() 실행, "Rect" 출력
paint(new Circle3()); // 오버라이딩된 메소드 Circle()의 draw() 실행, "Circle" 출력
}
}
동적 바인딩
- 실행할 메소드를 컴파일 시에 결정하지 않고 실행 시에 결정하는 것
- 자바에서는 동적 바인딩을 통해 오버라이딩된 메소드가 항상 실행되도록 보장한다.
public class SuperObject {
protected String name;
public void paint() {
draw();
}
public void draw() {
System.out.println("Super Object");
}
public static void main(String[] args) {
SuperObject a = new SuperObject();
a.paint();
}
}
public class SuperObject {
protected String name;
public void paint() {
draw();
}
public void draw() {
System.out.println("Super Object");
}
}
public class SubObject extends SuperObject {
public void draw() {
System.out.println("Sub Object");
}
public static void main(String [] args) {
SuperObject b = new SubObject();
b.paint();
}
}
오버라이딩과 super 키워드
- 서브 클래스에서 super 키워드를 이용하면 정적 바인딩을 통해 슈퍼 클래스의 멤버에 접근할 수 있다.
super.슈퍼클래스의멤버
@Override
- 오버라이딩하는 메소드 앞에 붙임
- 컴파일러에게 오버라이딩이 정확한지 확인하도록 지시
오버로딩(overloading) vs 오버라이딩(overriding)
비교 요소 | 메소드 오버로딩 | 메소드 오버라이딩 |
선언 | 같은 클래스나 상속 관계에서 동일한 이름의 메소드 중복 작성 | 서브 클래스에서 슈퍼 클래스에 있는 메소드와동일한 이름의 메소드 재작성 |
관계 | 동일한 클래스 내 혹은 상속 관계 | 상속 관계 |
목적 | 이름이 같은 여러 개의 메소드를 중복 선언하여 사용의 편리성 향상 | 슈퍼 클래스에 구현된 메소드를 무시하고 서브클래스에서 새로운 기능의 메소드를 재정의하고자 함 |
조건 | 메소드 이름은 반드시 동일함. 메소드의 인자의 개수나 인자의 타입이 달라야 성립 | 메소드의 이름, 인자의 타입, 인자의 개수, 리턴 타입 등이 모두 동일하여야 성립 |
바인딩 | 정적 바인딩. 컴파일 시에 중복된 메소드 중 호출되는 메소드 결정 | 동적 바인딩. 실행 시간에 오버라이딩된 메소드 찾아 호출 |
추상 클래스
추상 메소드
- abstract 키워드와 함께 원형만 선언되고, 코드는 작성되지 않은 메소드
abstract public String getName(); // 추상 메소드
abstract public String fail() { return "Good Bye"; } // 추상 메소드 아님. 컴파일 오류
추상 클래스
- 추상 메소드를 최소 한 개 이상 가지고 abstract로 선언된 클래스
- 추상 메소드가 없어도 abstract로 선언한 클래스
// 추상 메소드를 가진 추상 클래스
abstract class Shape { // 추상 클래스 선언
public Shape() { ... }
public void edit() { ... }
abstract public void draw(); // 추상 메소드 선언
}
// 추상 메소드 없는 추상 클래스
abstract class Jcomponent { // 추상 클래스 선언
String name;
public void load(String name) {
this.name = name;
}
}
- 추상 메소드를 가지고 있으면 반드시 추상 클래스로 선언되어야 한다.
- 추상 클래스의 인스턴스를 생성할 수 없다.
Jcomponent p; // 오류 없음. 추상 클래스의 레퍼런스 선언
p = new JComponent(); // 컴파일 오류. 추상 클래스의 인스턴스 생성 불가
Shape obj = new Shape(); // 컴파일 오류. 추상 클래스의 인스턴스 생성 불가
추상 클래스의 상속
- 추상 메소드를 가진 추상 클래스를 상속받는 서브 클래스는 자동으로 추상 클래스가 된다.
- 그러므로 서브 클래스에 abstract를 붙여 추상 클래스임을 명시해야 함.
abstract class A { // 추상 클래스
abstract public int add(int x, int y); // 추상 메소드
}
abstract class B extends A { // 추상 클래스. 추상 메소드 add()를 상속받기 때문
public void show() { System.out.println("B"); }
}
추상 클래스의 구현
- 서브 클래스에서 슈퍼 클래스의 모든 추상 메소드를 오버라이딩하여 실행 가능한 코드로 구현하는 것
- 추상 클래스를 구현한 서브 클래스는 인스턴스를 생성할 수 있다.
class C extends A { // 추상 클래스 구현. C는 정상 클래스
@Override
public int add(int x, int y) { return x+y; } // 추상 메소드 구현. 오버라이딩
public void show() { System.out.println("C"); }
}
추상 클래스의 목적
- 상속을 위한 슈퍼 클래스로 활용하기 위한 것
- 추상 클래스는 추상 메소드를 통해 서브 클래스가 구현할 메소드의 원형을 알려주는 인터페이스의 역할
- 서브 클래스의 다형성을 실현
abstract class Calculator {
public abstract int add(int a, int b); // 두 정수의 합을 구하여 리턴
public abstract int subtract(int a, int b); // 두 정수의 차를 구하여 리턴
public abstract double average(int[] a); // 정수 배열의 평균 리턴
}
public class GoodCalc extends Calculator {
@Override
public int add(int a, int b) { // 추상 메소드 구현
return a+b;
}
@Override
public int subtract(int a, int b) { // 추상 메소드 구현
return a-b;
}
@Override
public double average(int[] a) { // 추상 메소드 구현
double sum = 0;
for (int i=0; i<a.length; i++)
sum += a[i];
return sum/a.length;
}
public static void main(String[] args) {
GoodCalc c = new GoodCalc();
System.out.println(c.add(2, 3));
System.out.println(c.subtract(2, 3));
System.out.println(c.average(new int [] {2, 3, 4}));
}
}
인터페이스
interface PhoneInterface { // 인터페이스 선언
public static final int TIMEOUT = 10000; // 상수 필드. public static final 생략 가능
public abstract void sendCall(); // 추상 메소드. public static abstract 생략 가능
public abstract void receiveCall(); // 추상 메소드. public static abstract 생략 가능
public default void printLogo() { // 디폴드 메소드는 public 생략 가능
System.out.println("** Phone **");
} // 디폴트 메소드
}
- interface 키워드를 사용하여 선언
- 5 종류의 멤버로 구성되며, 필드(멤버 변수)를 만들 수 없다.
· 상수, 추상 메소드, default 메소드, private 메소드, static 메소드
· 상수: public static final 속성이며, 속성은 생략 가능
· 추상 메소드: public abstract 속성이며, 속성은 생략 가능
· default 메소드: 접근 지정 public으로 고정
· private 메소드: 인터페이스 내의 다른 메소드에서만 호출 가능
· static 메소드: 접근 지정이 생략되면 public이며, private으로 지정 가능
· default, private, static 메소드들은 코드가 작성되어 있어야 한다.
- 인터페이스는 객체를 생성할 수 없다.
인터페이스 상속
- 상속을 통해 기존 인터페이스에 새로운 규격을 추가한 새로운 인터페이스를 만든다.
interface MobilePhoneInterface extends PhoneInterface {
void sendSMS(); // 추상 메소드
void receiveSMS(); // 추상 메소드
}
- 이렇게 함으로써, MobilePhoneInterface 인터페이스는 총 6개의 멤버를 가지게 된다.
- 인터페이스는 콤마로 연결하여 2개 이상의 인터페이스를 다중 상속할 수 있다.
interface MusicPhoneInterface extends PhoneInterface, MP3Interface {
...
}
인터페이스 구현
- implements 키워드를 사용하여 인터페이스의 모든 추상 메소드를 구현한 클래스를 작성하는 것
class SamsungPhone implements PhoneInterface { // 인터페이스 구현
// PhoneInterface의 모든 추상 메소드 구현
public void sendCall() { System.out.println("띠리리리링"); }
public void receiveCall() { System.out.println("전화가 왔습니다."); }
// 메소드 추가 작성
public void flash() { System.out.println("전화기에 불이 켜졌습니다."); }
}
'Language > Java' 카테고리의 다른 글
[Java] 클래스와 객체 (0) | 2022.02.11 |
---|---|
[Java] 배열 / 예외처리 (0) | 2022.02.10 |
[Java] 자바 기본 프로그래밍 (프로그램 구조, 데이터 타입, 키 입력) (0) | 2022.02.05 |