ABCD

상속과 추상 클래스 본문

PROGRAMMING LANGUAGE/JAVA

상속과 추상 클래스

카메사마 2024. 6. 9. 12:20

*부모 클래스=super 클래스=기본 클래스

자식클래스=sub 클래스=파생 클래스=확장 클래스

 

🐢 상속: 부모 클래스가 존재하고 부모 클래스가 가지고 있는 필드와 메서드를 자식 클래스들이 물려받아 사용하는 것

--> 공통적인 부분을 하나로 묶어서 관리하자

공통적인 특징을 가지고 부모 클래스를 만들고 더 specific하게 세부적으로 고유한 특징은 클래스로 따로 만들어서 상속을 준다.

 

기능적인 측면에서는 상속을 하거나 안하거나 동일하지만 같은 것을 다 따로따로 정의하면 유지, 보수 시 번거롭게 모두 수정해야하는 상황이 생길 수 있음! (기술적인 측면에서는 동일하지만, 개발하는 측면에서의 유지, 보수의 편의성이 더 극대화되었다. 객체 지향 페러다임에 충실한 프로그램 설계)

 

    - 자식 클래스는 부모 클래스에서 물려받은 멤버를 그대로 사용하거나 변경할 수 있고 새로운 멤버를 추가할 수 있다.

    --> 자식 클래스가 대체로 부모 클래스보다 속성이나 동작이 더 많다.

 

🐢 상속은 'is a'관계

      공통적인 grouping을 할 수 있는 특징

    cf) has a: 소유관계 --> 소유관계는 상속이 될 수 없다.

 

🐢 상속의 선언

    extend 키워드 사용: class 자식클래스명 extends 부모클래스명{}

 

! 다중상속은 안됨

cf) C++은 다중 상속이 가능하다. 헷갈리지말고 잘 사용하기!

 

🐢 메서드 오버라이딩: 물려받은 메서드를 자식 클래스에게 맞도록 수정하는 것

--> 부모 클래스에서 상속받은 필드. 메서드가 어떤 것들은 자식 클래스의 고유한 특성을 반영해서 수정될 필요가 있을 수 있다.

 

🐢 메서드 오버라이딩의 규칙

    - 부모 클래스의 메서드와 동일한 시그니처를 사용하기. 반환 타입까지 동일해야 한다. (이름, return type 등)

    - 부모 클래스의 메서드보다 접근 범위를 더 좁게 수정할 수 없다. --> 접근지정자 범위를 부모클래스보다 더 좁게 수정하는 것이 불가능하다.

 

🐢 오버라이딩이 불가능한 경우

    - 상속 자체가 안되는 경우

        - private메서드: 부모 클래스 전용이므로 자식 클래스에게 상속되지 않는다.

        - static메서드: 클래스 소속이므로 자식 클래스에 상속되지 않는다.

 

    - 상속은 되지만 오버라이딩이 안되는 경우

        - final메서드: final메서드는 더 이상 수정할 수 없으므로 자식 클래스가 오버라이딩할 수 없다.

        --> 오버라이딩의 개념 자체가 메서드를 물려받은 후에, 그 내용을 수정하는 것이기 때문에 수정을 할 수 있어야 오버라이딩이 될 수 있다.

        --> final은 상수를 지정할 때 사용하는 키워드이다. 더 이상 메서드 내용을 수정하지 않겠다는 의미로 쓰인다.

 

🐢 어노테이션

@override
// 위와 같이 오버라이딩을 한 메서드임을 표시하는 부분을 어노테이션이라고 한다.
// 어노테이션을 한 후 그 아래에 오버라이딩한 메서드를 사용하면 된다.

 

🐢 부모 클래스의 멤버 접근

    - 자식 클래스가 메서드를 오버라이딩하면 자식 객체는 부모 클래스의 오버라이딩된 메서드를 숨긴다.

    ! 이때 부모 클래스의 메서드를 덮어쓰는 overwrite와는 다름을 확실히 해두자

    --> 그 숨겨진 메서드를 호출하려면 super 키워드를 사용한다.

    --> super: 현재 객체에서 부모 클래스의 참조

 

🐢 메서드 오버라이딩과 메서드 오버로딩

! 이름이 비슷하여 헷갈리기 쉬우니 표를 통해 제대로 구분하도록 하자

비교요소 메서드 오버라이딩 메서드 오버로딩
메서드 이름 동일 동일
매개변수 동일 다름
반환타입 동일 관계없음
상속관계 필요 필요없음
예외와 접근범위 제약있음 제약없음
바인딩 동적 바인딩 정적 바인딩

- 동적 바인딩: 호출할 메서드를 실행 중에 결정

- 정적 바인딩: 호출할 메서드를 컴파일할 때 결정

 

🐢 패키지: 클래스 파일을 하나의 패키지 단위로 묶어서 관리하기 위한 수단으로 파일 시스템의 폴더를 이용한다. (클래스들이 묶여서 특정 패키지에 속한다.)

    --> 프로그램 작성할 때 필요한 여러가지 기본적인 메서드들을 이미 자바언어로 다 짜놓은 것

        : 기본적으로 제공해주는 라이브러리 (default package)

 

🐢 패키지의 장점

    - 패키지마다 별도의 이름공간(namespace)이 생기기 때문에 클래스 이름의 유일성 (중복x)을 보장한다.

    ! 서로 다른 두 패키지 package1과 package2가 동일한 클래스 A를 포함하고 있을 때, 패키지의 이름이 다르므로 각각에 속해있는 클래스 A는 서로 다른 클래스로 취급된다.

    - 클래스를 패키지 단위로도 제어할 수 있기 때문에 좀 더 세밀하게 접근을 제어할 수 있다.

 

🐢 대표적인 패키지

    - java.lang: import문을 선언하지 않아도 자동으로 import되는 자바의 기본 클래스를 모아 둔 것

    - java.awt: 그래픽 프로그래밍에 관련된 클래스를 모아 둔 것

    - java.io: 입출력과 관련된 클래스를 모아 둔 것

 

🐢 패키지 선언

    주석문을 제외하고 반드시 첫 라인에 위치

    cf) 명령어를 사용하여 패키지 폴더 생성도 가능하다.

        1. javac -d . 클래스명.java

        javac: 컴파일 명령어

        -d: 패키지 폴더를 생성하는 옵션

        .: 현재 폴더를 의미

 

        2. java 패키지이름.클래스이름

        ! 이때 클래스이름에는 확장자가 없어야 한다.

 

🐢 패키지 사용

- 여러 클래스가 묶여서 하나의 패키지가 되는데 이 패키지들끼리 조합하여 하나의 프로그램을 만든다.

- 다른 패키지에 들어있는 것을 사용하려면 패키지 이름을 명시적으로 적어서 접근한다.

 

🐢 import문: 패키지의 경로를 미리 컴파일러에게 알려주는 문장

import 패키지이름.클래스;			// 패키지 안에 있는 특정 패키지만 쓰려할 때
import 패키지이름.*;			// 패키지 안에 있는 모든 클래스들을 import 시키려할 때(와일드 카드)

 

🐢 정적 import문: 패키지 단위로 import하지 않고 패키지 경로와 정적 메서드를 함께 import

--> 일반 import문: 클래스 안에 있는 메서드를 사용하려면 객체 생성하고나서 사용

정적 import문은 정적 메서드를 불러오기 때문에 객체 생성을 하지 않고도 사용가능하다.

// 예시
import static java.util.Arrays.sort;

public class Main{
	public static void main(String[] args){
    	int[] data={4,6,2,1};
    	sort(data);				// 객체 생성하지 않고 사용
        // Arrays.sort(data);		// 일반의 경우
    }
}

 

🐢 자식 클래스와 부모 생성자

    - 자식 생성자를 호출하면 부모 생성자도 자동으로 호출된다. (상속을 받아서 사용하려면 일단 부모 클래스 객체가 존재해야 하기 때문이다.)

    - 자식 생성자는 첫 행에 부모 생성자 호출 코드가 있다. (없다면 컴파일러가 알아서 기본 생성자로 super(); 코드를 추가한다.)

    ! 자식 클래스의 디폴트 생성자가 호출될 때는 부모 클래스의 디폴트 생성자가 호출된다. 이때, 이미 부모 클래스에 따로 직접 구현한 생성자가 있다면 부모 클래스에서 디폴트 생성자는 호출되지 않는다. 그러므로 오류가 발생한다.

    ! 자식 클래스의 따로 직접 구현한 생성자가 호출될 때는 부모 클래스에서 따로 직접 구현한 생성자 (자식 클래스의 생성자와 인자가 같은)가 호출된다. 이때, 부모 클래스에는 자식 클래스의 생성자와 인자가 같은 생성자가 없거나, 디폴트 생성자가 호출된다면 오류가 발생한다.

 

🐢 상속과 접근 제어

    - 접근 지정자의 접근 범위

        public: 어떤 클래스든 해당 멤버에 접근할 수 있다.

        protected: 같은 패키지 내의 클래스와 해당 클래스를 상속받은 외부 패키지의 클래스에서 접근할 수 있다.

        package-private: 같은 패키지 내의 클래스들만 접근할 수 있다. (default)

        --> 접근 지정자 생성 안하면

        private: 오직 같은 클래스 내에서만 접근할 수 있다.

접근 지정자 동일 클래스 동일 패키지 자식 클래스 다른 패키지
public O O O O
protected O O O X
없음 (package-private) O O X X
private O X X X

! private멤버는 자식 클래스에 상속되지 않는다.

! 클래스 멤버는 어떤 접근 지정자로도 지정할 수 있지만, 클래스는 protected와 private으로 지정할 수 없다.

 

🐢 final 클래스: 더 이상 상속될 수 없는 클래스

    - 대표적인 final 클래스로는 String 클래스

    ! final 클래스는 상속을 받을 수는 있지만, 상속을 해줄 수는 없다.

 

🐢 final 메서드: final 클래스는 클래스 내부의 모든 메서드를 오버라이딩할 수 없다. 특정 메서드만 오버라이딩하지 않도록 하려면 final 메서드로 선언한다.

 

🐢 타입 변환과 다형성 (polymorphism)

    - 객체의 타입 변환

        - 참조 타입 데이터도 기초 타입 데이터처럼 타입 변환이 가능하다.

        - 상속 관계일 경우만 타입 변환이 가능하다.

        - 기초 타입처럼 자동 타입 변환과 강제 타입 변환이 있다.

 

🐢 자동 타입 변환 --> 시야를 좁히는 것은 가능하다. (더 넓은 시야를 가진 객체에서 좁은 시야를 가진 객체로 타입 변환이 가능하다.)

🐢 강제 타입 변환 --> 시야를 넓히는 것 불가능하다. (확장이 되지 않는다.)

 

🐢 추상 메서드: 메서드의 시그니처는 제공하지만 메서드의 구현부는 제공하지 않는 메서드이다.

abstract 반환타입 메서드이름();
// abstract로 추상 메서드라는 것을 나타낸다.
// 메서드의 본체가 없고, 항상 세미콜론으로 끝나야한다.

 

🐢 추상 클래스

    --> 객체화시키지 못한다. 객체를 생성할 수 없다.

    - 보통 하나 이상의 추상 메서드를 포함하지만 없을 수도 있다.

    - 주로 상속 계층에서 자식 멤버의 이름을 통일하기 위해 사용한다.

    ! 필드, 이름을 일치시킬 수 있는 용도로 사용한다. (개발 시 유지, 보수가 편하다.)


🐢 상속과 추상 클래스 예시

// 예시1

package main;

class Circle {
	private void secret() {
		System.out.println("비밀이다.");
	}

	protected void findRadius() {
		System.out.println("반지름이 10.0센티이다.");
	}

	public void findArea() {
		System.out.println("넓이는 (π*반지름*반지름)이다.");
	}
}

class Ball extends Circle {
	private String color;

	public Ball(String color) {
		this.color = color;
	}

	public void findColor() {
		System.out.println(color + " 공이다.");
	}

	public void findVolume() {
		System.out.println("부피는 4/3*(π*반지름*반지름*반지름)이다.");
	}
}

public class Main {
	public static void main(String[] args) {
		Circle c1 = new Circle();
		Ball c2 = new Ball("빨간색");

		System.out.println("원 :");
		c1.findRadius();
		c1.findArea();

		System.out.println("\n공 :");
		c2.findRadius();
		c2.findColor();
		c2.findArea();
		c2.findVolume();
	}
}
// 예시2

package main;

class Circle {
	private void secret() {
		System.out.println("비밀이다.");
	}

	protected void findRadius() {
		System.out.println("반지름이 10.0센티이다.");
	}

	public void findArea() {
		System.out.println("면적은 (π*반지름*반지름)이다.");
	}
}

class Ball extends Circle {
	private String color;

	public Ball(String color) {
		this.color = color;
	}

	public void findColor() {
		System.out.println(color + " 공이다.");
	}

	public void findArea() {
		System.out.println("넓이는 4*(π*반지름*반지름)이다.");
	}

	public void findVolume() {
		System.out.println("부피는 4/3*(π*반지름*반지름*반지름)이다.");
	}
}

public class Main{
	public static void main(String[] args) {
		Circle c1 = new Circle();
		Ball c2 = new Ball("빨간색");

		System.out.println("원 :");
		c1.findRadius();
		c1.findArea();

		System.out.println("\n공 :");
		c2.findRadius();
		c2.findColor();
		c2.findArea();
		c2.findVolume();
	}
}
// 예시3

package main;

class Circle {
	private void secret() {
		System.out.println("비밀이다.");
	}

	protected void findRadius() {
		System.out.println("반지름이 10.0센티이다.");
	}

	public void findArea() {
		System.out.println("면적은 (π*반지름*반지름)이다.");
	}
}

class Ball extends Circle {
	private String color;

	public Ball(String color) {
		this.color = color;
	}

	public void findColor() {
		System.out.println(color + " 공이다.");
	}

	public void findArea() {
		findRadius();

		super.findArea();

		// super.secret();		// secret은 지금 보면 private으로 되어있기 때문에 자식 클래스에서 접근할 수 없다.

		System.out.println("넓이는 4*(π*반지름*반지름)이다.");
	}

	public void findVolume() {
		System.out.println("부피는 4/3*(π*반지름*반지름*반지름)이다.");
	}
}

public class Main{
	public static void main(String[] args) {
		Circle c1 = new Circle();
		Ball c2 = new Ball("빨간색");

		System.out.println("원 :");
		c1.findRadius();
		c1.findArea();

		System.out.println("\n공 :");
		c2.findRadius();
		c2.findColor();
		c2.findArea();
		c2.findVolume();
	}
}
// 예시4

package main;

class Animal {
	public Animal(String s) {
		System.out.println("동물 : " + s);
	}
}

class Mammal extends Animal {
	public Mammal() {
		// super();
		super("원숭이");
		System.out.println("포유류 : 원숭이");
	}

	public Mammal(String s) {
		super(s);
		System.out.println("포유류 : " + s);
	}
}

public class Main {
	public static void main(String[] args) {
		Mammal ape = new Mammal();
		Mammal lion = new Mammal("사자");
	}
}
// 예시5

package main;

class Good {
}

class Better extends Good {
}

final class Best extends Better {
}
class NumberOne extends Best {}

public class Main {
	public static void main(String[] args) {
		new Best();
	//	new NumberOne();
	}
}
// 예시6

package main;

class Chess {
	enum ChessPlayer {
		WHITE, BLACK
	}

	final ChessPlayer getFirstPlayer() {
		return ChessPlayer.WHITE;
	}
}

class WorldChess extends Chess {
	ChessPlayer getFirstPlayer() {}
}

public class Main {
	public static void main(String[] args) {
		WorldChess w = new WorldChess();
		w.getFirstPlayer();
	}
}
// 예시7

package main;

abstract class Shape {
	double pi = 3.14;

	abstract void draw();

	public double findArea() {
		return 0.0;
	}
}

class Circle extends Shape {
	int radius;

	public Circle(int radius) {
		this.radius = radius;
	}

	public void draw() {
		System.out.println("원을 그리다.");
	}

	public double findArea() {
		return pi * radius * radius;
	}
}

public class Main {
	public static void main(String[] args) {
		// Shape s = new Shape();

		Circle c = new Circle(3);
		c.draw();
		System.out.printf("원의 넓이는 %.1f\n", c.findArea());

	}
}

'PROGRAMMING LANGUAGE > JAVA' 카테고리의 다른 글

패키지와 API 문서  (0) 2024.09.07
열거 타입  (0) 2024.06.10
예외처리  (0) 2024.06.09
인터페이스  (0) 2024.06.09