백기선님 자바스터디 6주차 : 상속
학습 1) 자바 상속의 특징
개념
- 한 클래스에서 파생된 클래스를
subclass
라고 한다. ( ==derived class
,extended class
,child class
) - 그 대상이 된 클래스를
super class
( ==base class
,parent class
)라고 한다. is -a
관계가 성립한다.- 하위클래스
is a
상위클래스 )
- 하위클래스
- 키워드
extends
를 사용한다.
class Vehicle {
..
}
class Car extends Vehicle {
..
}
- 장점 : 코드 중복 막고, 공통적인 코드 변경에 용이
- 단일상속 : Object을 제외하고 모든 클래스는 단 하나의 슈퍼 클래스가 있다. ( extends A, B 불가), ( c++은 다중상속 허용 )
- 명시적으로 슈퍼클래스가 표시되어있지 않은 클래스는 Object의 하위 클래스다. (extends Object)
- implements를 활용해서 다중 상속을 흉내낼 수 있다. 자바의 정석 참고.
부모클래스의 모든 것을 가져온다?
- 부모 클래스의 public, protected 멤버변수 모든 것을 가져온다. (패키지에 상관없이)
- 하위 클래스가 부모와 같은 패키지에 있다면 package-private 멤버변수도 다 가져올수도 있다. (replace도 가능)
- 생성자는 멤버가 아니므로 상속되지 않는다.
super()
등을 통해 하위클래스에 생성자를 적용할 수 있다. (아래super()
설명)
- superclass의 private 멤버는 상속되지 않는다. 다만 superclass의 public, protected 메소드가 private 멤버에 접근하고 있다면 이것은 subclass에서도 사용할 수 있다.
public class Practice {
public static void main(String[] args) {
Car c = new Car();
c.printPrivateN();
}
}
class Vehicle {
private int n = 1;
public int getPrivateN() {
return this.n;
}
}
class Car extends Vehicle {
public void printPrivateN() {
int n = super.getPrivateN();
System.out.println(n); // 1출력
}
}
- 오버라이딩 메소드는 조상 클래스의 메소드의 하위 타입을 반환할 수 있다. (아래
covariant return type
)
학습 2) super 키워드
부모클래스의 멤버에 접근 할 때
- 자식 클래스에서 부모 클래스를 가리킬 때 사용
- 만약 부모클래스의 메소드를 오버라이딩하고 있다면
super
키워드 통해서 부모클래스의 메소드에 접근 가능 하다 - static 메소드에서 사용 불가
public class Superclass {
public void printMethod() {
System.out.println("Printed in Superclass.");
}
}
public class Subclass extends Superclass {
// overrides printMethod in Superclass
public void printMethod() {
super.printMethod();
System.out.println("Printed in Subclass");
}
}
자식클래스의 생성자에서 사용할 때
- 부모클래스의 생성자를 가리킬 때 사용
super()
orsuper(parameter list)
public MountainBike(int startHeight,
int startCadence,
int startSpeed,
int startGear) {
super(startCadence, startSpeed, startGear);
seatHeight = startHeight;
}
- 자식클래스에서 명시적으로 부모클래스의 생성자를 호출하지 않으면, 자바 컴파일러가 자동적으로 인자 없는 부모클래스의 생성자를 호출한다. (
super()
)- 이 때, 만약 부모클래스에 인자 없는 생성자가 없으면 컴파일 에러가 날 것이다.
- 모든 클래스의 조상인 Object는 인자없는 생성자가 있기 때문에, 부모클래스가 Object라면 이런 문제는 일어나지 않는다.
class Vehicle2 {
int num = 1;
Vehicle2(int num) {
this.num = num;
}
}
class Car2 extends Vehicle2 {
int num;
String name;
Car2(int num, String name) { //컴파일 에러.
this.num = num;
this.name = name;
}
}
- 위에서 Car2의 생성자에서 super에 대한 명시가 없기 때문에 자동적으로
super()
를 호출한다. Vehicle2() 와 같은 인자 없는 생성자가 Vehicle에 없기 때문에 컴파일 에러가 발생한다.
class Vehicle2 {
int num = 1;
Vehicle2(int num) {
this.num = num;
}
}
class Car2 extends Vehicle2 {
int num;
String name;
Car2(int num, String name) { //컴파일 ok.
super(num);
this.name = name;
}
}
- 존재하는 super()를 작성해 줘야 한다.
학습 3) 메소드 오버라이딩
- 상속받은 조상의 메소드를 오버라이딩 하는 것.
- 주의. 오버로딩과 헷갈리지 말기
- 주의. hiding
- static 메서드 오버라이딩이 아니라 hiding
- 오버라이딩은 런타임에 객체를 호출하는데 static은 컴파일 때 올라가기 때문에
- static 메서드 오버라이딩이 아니라 hiding
public class Animal {
public static void testClassMethod() {
System.out.println("The static method in Animal");
}
public void testInstanceMethod() {
System.out.println("The instance method in Animal");
}
}
public class Cat extends Animal {
public static void testClassMethod() {
System.out.println("The static method in Cat");
}
@Override
public void testInstanceMethod() {
System.out.println("The instance method in Cat");
}
public static void main(String[] args) {
Animal animal = new Cat();
Animal.testClassMethod(); //The static method in Animal
animal.testInstanceMethod(); //The instance method in Cat
}
}
오버라이딩 조건
- 메소드의 선언부 (
메소드의 시그니처
: 메소드 이름, 매개변수 타입과 수, 리턴타입) 이 같아야 한다.
class Vehicle3 {
int oil = 100;
public void run() {
oil--;
}
}
class Car3 extends Vehicle3 {
public void run() {
oil -= 2;
}
}
- 접근제어자는 부모 클래스의 접근제어자 보다 좁은 범위로 변경할 수 없다.
class Vehicle3 {
int oil = 100;
public void run() {
oil--;
}
}
class Car3 extends Vehicle3 {
protected void run() { // 컴파일 에러
oil -= 2;
}
}
- 조상 클래스의 메소드보다 많은 수의 예외를 선언할 수 없다.
class Vehicle3 {
int oil = 100;
public void run() throws IndexOutOfBoundsException{
oil--;
}
}
class Car3 extends Vehicle3 {
public void run() throws IndexOutOfBoundsException, IOException { // 컴파일 에러
oil -= 2;
}
}
covariant return type
- 오버라이딩 하면 그 오버라이딩한 조상클래의 메소드의 리턴타입의 하위 클래스를 리턴할 수 있다.
class B extends A {
}
class C {
}
class Vehicle3 {
int oil = 100;
public void run() throws IndexOutOfBoundsException{
oil--;
}
public A getObject() {
return new A();
}
}
class Car3 extends Vehicle3 {
public void run() throws IndexOutOfBoundsException {
oil -= 2;
}
public C getObject() { //컴파일 에러
return new C();
}
class Car3 extends Vehicle3 {
public void run() throws IndexOutOfBoundsException {
oil -= 2;
}
public B getObject() { //이상무
return new B();
}
}
학습 4) 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
- 오버라이딩된 메소드 호출할 때 어떤 메소드를 호출할지를 (컴파일 할 때가 아니라) 런타임 때 결정하는 매커니즘
public class DynamicMethodDispatch {
public static void main(String[] args) {
Fruit fruit = new Fruit();
Fruit peach = new Peach();
fruit.eat(); //"과일은 맛있어"
peach.eat(); //"복숭아는 맛있어"
}
}
class Fruit {
public void eat() {
System.out.println("과일은 맛있어");
}
}
class Peach extends Fruit {
@Override
public void eat() {
System.out.println("복숭아는 맛있어");
}
}
만약 컴파일 할 때 결정하면(참조타입에 의해 결정되므로) peach.eat() 도 "과일은 맛있어"가 되어야 한다.
런타임에 결정하면 JVM이 참조변수에 저장된 객체를 확인하므로 위와 같은 결과가 나온다.
이에 따라 다형성이 지원된다
학습 5) 추상 클래스
abstract
로 선언된 클래스
추상 클래스 개념 이해
- 클래스가 설계도라면 추상 클래스는 미완성 설계도. 구체적인 행동 내용은 부모 입장에서 정의할 수 없으니 자식 클래스에서 이를 구현해야함을 명시하는 것
- 해당 부분 잘 설명해주신 (출처 속 댓글) 출처
안녕하세요,
추상 클래스와 메서드의 개념은 객체지향설계 개념을 좀 더 익히면 이해하기 쉬워질 거라 생각합니다.
객체지향설계에서는 구현해야할 것들을 객체들로 바라본 후, 객체들 간의 연관 관계를 상속(Is-a), 포함(Has-a)로 풀어낸다는 것이 핵심입니다.
추상 개념은, 객체들 간의 관계를 상속 관계로 정립하면서 생기는, 점차 상위의 클래스를 정의하게 되면서 발생하는 "구체적인 행동 내용은 부모 입장에서 정의할 수 없는 것"을 묘사하기 위해 이용하는 것이라 생각하면 될 것 같습니다.
글에서 설명하신 것과 같이 isRelevant()는 자식 시점에서는 그 행동을 구체적으로 정의할 수 있지만 부모
입장에서는 그럴 수 없지요. 게다가 모든 자식 클래스가 반드시 구현해야할 행동입니다. 그렇다면 이 메소드를 부모 단계에서 추상화하여 "내 시점에선 행동을 특정할 수 없지만 자식 시점에선 반드시 구현되어야 하고 구현될 수 있는 행동이다" 라는 것을 설계 시점에서 명시하는 겁니다.
학교나 여느 책에선 해당 이야기를 이런 예를 들어서 설명합니다. 만일 갖가지 도형 객체를 구현해야 하고, 넓이나 부피, 중심 좌표 등을 제공해줄 수 있게 해야한다고 합시다. 그럼 "원", "사각형", "삼각형" 등의 클래스는 추상화하여 "평면도형"이라는 부모 클래스를 정의하고 상속받게 할 수 있을겁니다. "구", "직육면체", "삼각뿔" 등의 클래스는 "입체도형"이라는 부모 클래스를 정의하고 상속받게 할 수 있을거고요.
입체도형과 평면도형 클래스는 다시 추상화하여 "도형"이라는 부모 클래스를 정의하고 상속 받게 설계하면 추상화를 완료하게 됩니다. 도형->평면도형->원 과 같은 형태로요.
넓이()나 부피() 같은 메소드는 최종 자식 클래스에선 구체적으로 구현해줄 수 있지만 평면도형, 입체도형 클래스 시점에선 이를 구현하기 힘들겁니다. 따라서 이들을 추상 메소드로 선언해두고, 자식 클래스에서 실제로 구현해주는거죠. 또 도형 입장에서는 무게중심 혹은 질량중심의 좌표를 담아두기 힘들죠. 하지만 평면도형 클래스에선 무게중심의 좌표를, 입체도형 클래스에선 질량중심의 좌표를 담아둘 수 있을 것입니다.
이런 일련의 과정을 이해하고 몇 번 연습과 실습을 거치게 되면 본인이 맡은 기능을 구현해나가는 도중에 본인이 정의한 클래스들 간의 공통점을 찾아 추상화한 부모 클래스를 만들고 추상 메소드를 추출해나갈 수 있게 될겁니다. 그리고 이게 객체지향설계의 절반 정도 되는 것 같습니다. 그만큼 간단하지만 중요한 개념입니다.
안드로이드가 그런 관점에서는 참고하기 좋은 운영체제입니다. 자바 자체가 객체지향 개념에 잘 기반해 만든 언어인데 안드로이드 역시 자바를 이용하여 각종 API와 기능들을 객체지향에 기반해 잘 설계했거든요.
추상 클래스 특징
-
추상 메소드가 있을 수도 있고 없을 수도 있다.
- 추상메소드는 구현부가 없는 메소드다
abstract void moveTo(double deltaX, double deltaY);
- 여기서, 추상 메소드가 없는 클래스의 의미는 인스턴스화를 막고, 자식 클래스에서 이를 상속해서 부모클래스에 접근할 수 있도록 제한 하는 것
-
추상메소드가 있는 클래스는 반드시 추상클래스어야만 한다.
-
추상클래스의 인스턴스는 만들 수 없다.
- 상속해서 추상 메소드를 구현해야 한다.
-
추상클래스는 하위클래스를 가질 수 있다.
- 보통 abstract의 하위 클래스는 모든 추상 메소드를 구현한다. 그렇지 않으면 그 하위클래스는
abstract
로 선언되어야 한다. - 추상 클래스의 특징을 생각할 때 추상클래스는 일반 클래스인데 추상메소드를 갖고 있는 것이라고 생각하면 편하다.
- 참고. 인터페이스의 메소드는 디폴트나 static으로 선언되지 않으면 전부 abstract이다. 그래서 따로 abstract라고 명시할 필요가 없다.
학습 6) final 키워드
- 값이 오직 한번만 할당될 수 있음을 나타낸다 (변경할 수 없다.
- Final 클래스 : 서브클래스가 만들어질 수 없다.
- 자바 표준 라이브러리 클래스(ex java.lang.String)의 상당수는 final이다
- https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.1.2
- Final 메소드 : 오버라이딩 되거나 서브클래스에 의해 hide 될 수 없다.
public class Base{
public void m1() {...}
public final void m2() {...}
public static void m3() {...}
public static final void m4() {...}
}
public class Derived extends Base
{
public void m1() {...} // OK, overriding Base#m1()
public void m2() {...} // forbidden
public static void m3() {...} // OK, hiding Base#m3()
public static void m4() {...} // forbidden
}
-
Final 변수 : 오직 한번만 초기화될 수 있다. 꼭 선언할 때 초기화될 필요는 없다.
- 변수를 final로 선언한다는 것은 어떤 순간에도 그것이 같은 객체를 가리킨다는 의미다.- 반대로 객체는 자신을 가리키는 변수에 대해 어떤 영향도 받지 않는다.
- 그렇기 때문에 변수가 어떤 객체를 가리킨다고 했을 때 그 안의 멤버 변수 등을 자유롭게 변경될 수 있다.
public class FinalKeyWord { public static final int[] ints = new int[5]; public static final List<Integer> integerList = new ArrayList<>(); public static void main(String[] args) { ints[1] = 5; integerList.add(1); System.out.println(Arrays.toString(ints)); //[0, 5, 0, 0, 0] System.out.println(integerList); //[1] } }
- 만약 이 또한 막고 싶다면 객체의 모든 필드가 final로 선언되어야 한다.
학습 7) Object 클래스
-
모든 클래스의 조상이다.
- java는 단일상속이므로, 부모 클래스가 명시되있지 않으면 extends Object
-
다양한 메서드를 가지고 있다.
-
메서드1) clone() : 특정 클래스를 복제해서 인스턴스를 생성할 때 사용
- 객체를 복제해서 사용하고 싶을 때, 참조 변수를 그대로 가져온다면 객체의 참조값이 같아, 원래 객체를 가져오는 것과 다름 없다. 이 때 clone을 사용한다.
public class Clone { public static void main(String[] args) { Student s = new Student("Chars"); Student m = s; m.changeName("Kim"); System.out.println(s.name); // Kim } } class Student { String name; Student(String name) { this.name = name; } public void changeName(String name) { this.name = name; } }
-
사용방법
- Cloneable을 implements 해서 해당 클래스가 복제 가능한 클래스라는 사실을 JVM에게 알려준다.
- 그리고 예외처리 해줘야 한다.
throws CloneNotSupportedException
public class Clone { public static void main(String[] args) { Student student = new Student("Cheol"); student.toString(); // 가능, 접근제어자 public student.clone() ; // 불가능, 접근제어자 protected } } class Student implements Cloneable { String name; Student(String name) { this.name = name; } }
- 접근제어자가
protected
이므로 Object와 다른 패키지에서 쓰고 싶다면 (일반적으로 이에 해당한다. 우리가 정의한 클래스에서 사용하고 싶다면) clone 다시 써준다.
class Test {
int x, y;
}
class Test2 implements Cloneable {
int a;
int b;
Test c = new Test();
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
public class Main {
public static void main(String args[])
throws CloneNotSupportedException
{
Test2 t1 = new Test2();
t1.a = 10;
t1.b = 20;
t1.c.x = 30;
t1.c.y = 40;
Test2 t2 = (Test2)t1.clone();
t2.a = 100;
t2.c.x = 300;
System.out.println(t1.a + " " + t1.b + " " + t1.c.x
+ " " + t1.c.y); // 10 20 300 40
System.out.println(t2.a + " " + t2.b + " " + t2.c.x
+ " " + t2.c.y); // 100 20 300 40
}
}
-
메서드2) equals() : 오버라이딩해서 동등성 정의
- equals 오버라이딩 할 때 hashCode() 도 같이 오버라이딩 해줘야 한다. : 해시 값을 사용하는 hashMap이나 hashSet에서 우리는 같은 객체로 인식하길 바라는 상황에서도 해시 값이 달라 다른 객체로 인식 할 수도 있기 때문 https://hee96-story.tistory.com/48
-
메서드3) finalize() : 객체가 소멸될 때 호출되기로 약속된 메소드. 오버라이딩 해서 객체가 소멸될 때 일어나야 할 일을 작성 할 수 있다.
- 많은 자바의 전문가가 이 메소드의 사용을 지양한다. 사용될 일 거의 없다.
-
메서드4) getClass() : 클래스 정보를 얻기 위해 사용한다. (getSimpleName(), getSuperclass(), getInterfaces())
- 오버라이드 할 수 없다
void printClassName(Object obj) { System.out.println("The object's" + " class is " + obj.getClass().getSimpleName()); }
-
메서드5) hashCode()
-
메서드6) toString()
출처
- 자바의정석-ch7
- 학습 1) 자바 상속의 특징
- 학습 2) super 키워드
- 학습 3) 메소드 오버라이딩
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
- hiding : https://ohgyun.com/242
- Covariant return type : https://en.wikipedia.org/wiki/Covariant_return_type
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
- 학습 4) Dynamic Method Dispatch
- 학습 5) 추상 클래스
- 학습 6) final
- 학습 7) Object
- clone() https://www.geeksforgeeks.org/clone-method-in-java-2/, 생활코딩
- finalize() 생활코딩
Author And Source
이 문제에 관하여(백기선님 자바스터디 6주차 : 상속), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@bongf/study-java-whiteship-javaStudy-week6저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)