내가 제일 싫어하는 부분이다.
c++에서도 상속에 대해 배웠는데
Java 상속은 또 다르게 생겨먹었다.
먼저 상속에 대해 간단히 말하자면
부모 클래스가 자식 클래스에게 재산을 물려주는 것이다.
상속 형식은
class Childclass[자식 클래스 이름] extends[상속] Parentclass[부모 클래스 이름]
구성되어 있다.
자식 클래스에서는 부모 클래스의 메소드 중에서 필요한 것을 다시 정의할 수 있다.
이것을 오버라이드라고 한다
오버라이딩 조건
1. 메소드의 이름이나 매개 변수, 반환형은 똑같아야 한다.
2. 자식 클래스에는 동일한 메소드가 있어야 한다.
3. 부모 메소드, 자식 메소드 2개가 존재하야 한다.
public class Animal {
public void eat()
{
System.out.println("동물이 먹고 있습니다. ");
}
};
public class Dog extends Animal {
@Override //오버라이드 어노테이션을 붙여 자식클래스에서 오타가 났을 때 오류라고 판단
public void eat()
{
System.out.println("강아지가 먹고 있습니다.");
}
};
public class DogTest {
public static void main(String[] args) {
Dog d = new Dog();
d.eat();
}
};
Dog 클래스는 Animal 클래스와 상속관계에 있고, Dog의 eat()가 Animal의 eat()를 오버라이드 한다.
키워드 super를 사용해 부모 클래스 멤버에 접근할 수 있다.
public class Parent {
public void print() {
System.out.println("부모 클래스의 print() 메소드");
}
}
public class Child extends Parent {
//메소드 오버라이드
public void print() {
super.print(); //부모 클래스의 메소드 호출
System.out.println("자식 클래스의 print() 메소드 ");
}
public static void main(String[] args) {
Child obj = new Child();
obj.print();
}
}
Child 클래스는 Parent 클래스에 상속되어 있고,
super 키워드를 사용해 부모 클래스의 메소드를 호출했다.
부모클래스의 생성자를 호출하는 방식은 2가지가 있다.
1. 명시적인 호출 : super 키워드 사용
2. 묵시적인 호출 : 자식 클래스의 객체가 생성될 때 자동적으로 부모 클래스 생성자 호출
1. 명시적인 호출
class Shape {
public Shape() {
System.out.println("Shape 생성자() ");
}
};
class Rectangle extends Shape {
public Rectangle(){
super(); // 명시적인 호출
System.out.println("Rectangle 생성자()");
}
};
public class Test {
public static void main(String[] args) {
Rectangle r = new Rectangle();
}
};
2. 묵시적인 호출
class Shape {
public Shape() {
System.out.println("Shape 생성자()");
}
};
class Rectangle extends Shape {
public Rectangle() {
System.out.println("Rectangle 생성자()"); //컴파일러가 부모클래스를 자동적으로 호출
}
};
public class Test {
public static void main(String[] args) {
Rectangle r = new Rectangle();
}
};
하지만 묵시적인 호출이 되지 않는 경우도 있다.
class Shape {
public Shape(String msg) { //생성자가 하나라도 정의되어 있으면 기본 생성자 자동으로 추가 안됨
System.out.println("Shape 생성자()" + msg);
}
};
class Rectangle extends Shape {
public Rectangle() { //묵시적 Shape()가 없어서 호출 불가
System.out.println("Rectangle 생성자()");
}
};
public class Test {
public static void main(String[] args) {
Rectangle r = new Rectangle();
}
};
위 오류를 수정하려면
Ractangle() 생성자에 super("test");와 같이 부모 클래스의 생성자를 명시적으로 호출해야 한다.
class Shape {
public Shape(String msg) { //생성자가 하나라도 정의되어 있으면 기본 생성자 자동으로 추가 안됨
System.out.println("Shape 생성자() " + msg);
}
};
class Rectangle extends Shape {
public Rectangle() { //묵시적 Shape()가 없어서 호출 불가
super("test");
System.out.println("Rectangle 생성자()");
}
};
public class Test {
public static void main(String[] args) {
Rectangle r = new Rectangle();
}
};
다음으로 알아볼 것은 추상클래스이다.
추상클래스는 완전하게 구현되어 있지 않은 메소드를 가지고 있는 클래스를 의미한다.
메소드가 미완성 되어 있으므로 추상 클래스는 객체를 생성할 수 없다.
주로 상속 계층에서 추상적인 개념을 나타내기 위한 용도로 사용된다.
추상 클래스의 형식은
public abstract class Animal {
public abstract void move(); //추상 메소드 정의, ;으로 종료됨
};
추상 클래스의 예시를 통해 더 자세히 알아보자.
abstract class Shape { //추상 클래스, 추상 클래스는 객체 생성 불가
int x, y;
public void move(int x, int y) {
this.x = x;
this.y = y;
}
public abstract void draw(); //추상 메소드
};
class Rectangle extends Shape {
int width, height;
public void draw() { // 추상 메소드 구현
System.out.println("사각형 " + "그리기 메소드");
}
};
class Circle extends Shape {
int radius;
public void draw() { //추상 메소드 구현
System.out.println("원 그리기 메소드");
}
};
다음은 상속과 다형성에 대해서 알아보겠다.
다형성은 동일한 메세지에
객체마다 서로 다르게 동작하게 한다.
예를 들면 speak()를 실행하면
강아지는 멍멍 이라고 출력하고
고양이는 야옹이라고 출력해야 한다.
만저, 상향 형변환과 동적 바인딩 개념을 알아야 한다.
상향 형변환 : 부모 클래스 참조 변수로 자식 클래스 객체를 참조할 수 있다.
class A {
A() { }
}
class B {
B() { }
}
public class TypeTest1 {
public static void main(String args[]) {
A a = new B(); // 클래스 A의 참조 변수로 클래스 B의 객체를 참조할 수 없다
}
}
A와 B 클래스의 형태는 매우 비슷하지만
자료형이 서로 다르기 때문에 참조가 불가능하다.
하지만 둘이 상속되어 있는 관계이면 상향 형변환을 통해
class A {
A() { }
}
class B extends A { //B는 A로부터 상속
B() { }
}
public class TypeTest2 {
public static void main(String args[]) {
A a = new B(); // 부모 클래스의 참조변수로 자식 클래스의 객체를 참고할 수 있음
}
}
B는 A로부터 파생될 수 있다.
더 완전한 소스로 상향형변환을 알아보자.
class Shape {
protected int x, y;
}
class Rectangle extends Shape {
private int width, height;
}
class Triangle extends Shape {
private int base, height;
}
class Circle extends Shape {
private int radius;
}
public class ShapeTest {
public static void main(String arg[]) {
Shape s = new Rectangle(); //부모 클래스의 참조변수로 자식 클래스의 객체 가리킬 수 있음
Rectangle r = new Rectangle();
s.x = 0; //Shape 클래스의 필드와 메소드 접근 가능
s.y = 0;
// s.width = 100; s를 통해서는 Rectangle 클래스의 필드와 메소드에 접근 불가
// s.height = 100;
}
}
설명하자면
[Rectangle 객체]
x
y
width
height
x, y는 부모 클래스에서 상속 받은 것이니
Shape s는 x, y에만 접근 가능하고
Rectangle r은 x, y, width, height에 접근 가능하다.
상향 형변환이 있으면 하향 형변환도 있을 것이다.
위에서는 불가능 했던 부모형인 s로 자식의 필드와 메소드에 접근하는 것이
하향 형변환이다.
Rectangle r;
r = new Shape(); //컴파일 오류
그렇다고 서브 클래스 참조 변수로 부모 클래스 객체 참조를 하면
컴파일 오류가 난다.
상향 형변환으로 객체를 참조하는 경우에 한해서
하향 형변환이 가능하다
Rectangle r;
Shape s;
s = new Rectangel();
r = (Recangle)s; //하향 형변환
r->width = 100;
r->height = 100;
동적 바인딩 : 메소드 호출을 실제 메소드의 몸체와 연결하는 것,
자바 가상 머신은 실행 단계에서 적절한 메소드 호출
예제를 통해 동적 바인딩에 대해 알아보자
class Shape {
protected int x, y;
public void draw() {
System.out.println("Shape Draw");
}
}
class Rectangle extends Shape {
private int width, height;
public void draw() {
System.out.println("Rectangle Draw");
}
}
class Triangle extends Shape {
private int base, height;
public void draw() {
System.out.println("Triangle Draw");
}
}
class Circle extends Shape {
private int radius;
public void draw() {
System.out.println("Circle Draw");
}
}
public class ShapeTest {
private static Shape arrayOfShapes[];
public static void main(String arg[]) { //클래스 Shape의 배열을 선언한다
init();
drawAll();
}
public static void init() { //각 원소에 객체를 만들어 대입, 다형성에 의해 배열에 모든 타입 저장 가능
arrayOfShapes = new Shape[3];
arrayOfShapes[0] = new Rectangle();
arrayOfShapes[1] = new Triangle();
arrayOfShapes[2] = new Circle();
}
public static void drawAll() {
for (int i = 0; i < arrayOfShapes.length; i++) {
arrayOfShapes[i].draw();
}
}
}
다형성으로 인해 각 원소가 실제로 가리키고 있는 객체에 따라
서로 다른 draw()가 호출된다.
동적 바인딩의 장점은
만약 여기서 별을 출력하는 것을 추가한다면
시스템에 최소한의 영향을 미치면서 새로운 유형의 객체를 쉽게 추가할 수 있다.
상향 형변환과 동적 바인딩을 함께 활용한 예제를 보겠다.
class Shape {
protected int x, y;
public void draw() {
System.out.println("Shape Draw");
}
}
class Rectangle extends Shape {
private int width, height;
public void draw() {
System.out.println("Rectangle Draw");
}
}
class Triangle extends Shape {
private int base, height;
public void draw() {
System.out.println("Triangle Draw");
}
}
class Circle extends Shape {
private int radius;
public void draw() {
System.out.println("Circle Draw");
}
}
public class ShapeTest {
public static void printLocation(Shape s) {
System.out.println("x=" + s.x + " y=" + s.y);
}
public static void main(String arg[]) { //Shape에서 파생된 모든 클래스의 객체를 다 받을 수 있다
Rectangle s1 = new Rectangle();
Triangle s2 = new Triangle();
Circle s3 = new Circle();
printLocation(s1);
printLocation(s2);
printLocation(s3);
}
}
분량이 너무 넘치는 것 같지만
이게 벼락치기의 결과이다.
다음은 object 클래스에 대해 알아보겠다.
메소드 | 설명 |
Object clone( ) | 객체 자신의 복사본을 생성하여 반환한다. |
boolean equals(Object obj) | obj가 현재 객체와 같은지를 반환한다. |
void finalize( ) | 소멸자, 사용되지 않는 객체가 제거되기 직전에 호출된다. |
class getClass( ) | 실행 시간에 객체의 클래스 정보를 반환한다. |
int hashCode( ) | 객체에 대한 해쉬 코드를 반환한다. |
String toString( ) | 객체를 기술하는 문자열을 반환한다. |
대표적인 object 클래스 3개를 살펴보자
getClass( ) 메소드
class Zen {
private int speed;
//...
}
public class ZenTest {
public static void main(String[] args) {
Zen obj = new Zen();
System.out.println("obj is of type " + obj.getClass().getName()); //객체를 생성한 클래스의 이름을 반환한다
}
}
equals( ) 메소드
public class Car {
private String model;
public Car(String model) {
this.model = model;
}
//String은 Object의 equals를 재정의하여 문자열 비교
public boolean equals(Object obj) {
if (obj instanceof Car)
return model.equals(((Car) obj).model);
else
return false;
}
}
public class CarTest {
public static void main(String[] args) {
Car firstCar = new Car("HMW520");
Car secondCar = new Car("HMW520");
if (firstCar.equals(secondCar)) {
System.out.println("동일한 종류의 자동차입니다.");
} else {
System.out.println("동일한 종류의 자동차가 아닙니다.");
}
}
}
toString( ) 메소드
public class Book {
private String title;
private String isbn;
public Book(String title, String isbn) {
this.title = title;
this.isbn = isbn;
}
@Override
public String toString() {
return "ISBN: " + isbn + "; TITLE: "+title+";";
}
public static void main(String[] args) {
Book myBook = new Book("The Java Tutorial", "0123456");
System.out.println(myBook);
}
}
마지막 종단 클래스이다.
종단 클래스(final class)는 상속을 시킬 수 없는 클래스를 말한다.
public final class MyFinal { }
public class ThisIsWrong extends MyFinal { } //상속 불가
보안상의 이유로 상속이 되지 않는다.
오버라이드도 되지 않는다
class Chess {
enum ChessPlayer { WHITE, BLACK };
...
final ChessPlayer getFirstPlayer() {
return ChessPlayer.WHITE;
}
}
자식 클래스에서 재정의 할 수 없도록 final로 지정한다
드디어 상속 파트 정리가 끝났다.
추가로 정리할 부분은 계속해서 수정하겠다.
'자바학점을 자바줘 (JAVA)' 카테고리의 다른 글
[자바] 람다식, 패키지 (0) | 2022.04.24 |
---|---|
[자바] 인터페이스 (0) | 2022.04.24 |
[자바] 클래스와 메소드 심화 개념 (1) | 2022.04.21 |
[자바] String 클래스 개념 (0) | 2022.04.07 |
[자바] 객체와 클래스 기본 개념 (0) | 2022.04.07 |