개발공부

JAVA쪼렙탈출기: 상속 (Inheritance)

Yuniverse. 2023. 6. 16. 15:03
Python과 SQL만 써본 주니어 분석가. '개발공부를 해보고 싶다', '개발도 공부하긴 해야하는데...'는 말만 한지 어연 1년이 넘어가는 중, 이대로는 안되겠다 싶어 냅다 JAVA 수업 수강에 카드를 긁었다. 쪼렙 중의 쪼렙이 JAVA를 배워나가는 여정을 "JAVA 쪼렙 탈출기"라는 시리즈로 남길 예정이다.

 

상속 ( Inheritance )

  • 기존 class의 내용을 그대로 물려받아서 새로운 class를 정의하는 것
  • 상속을 하더라도 접근제한자는 그대로 유지된다.
  • 자식클래스 객체를 생성할 때, 자식클래스의 생성자에서 super( )를 사용하여 부모클래스의 생성자를 먼저 실행하고,자식클래스의 생성자를 실행한다.

 

Point라는 클래스를 만들고, PointTriple 클래스에서 이를 상속받아보자.

public class Point {
	
	private int px;
	private int py;
	
	public Point() {
		System.out.println("- Point() -");
		this.px = 0;
		this.py = 0;
	}
	
	public Point(int px, int py) {
		System.out.println("- Point(int px, int py) -");
		this.px = px;
		this.py = py;
	}
	
	public int getPx() { return px; }
	public void setPx(int px) {
		this.px = px;
	}
	
	public int getPy() { return py; }
	public void setPy(int py) {
		this.py = py;
	}
	
	public void twoPoint() {
		System.out.println("좌표 X : " + px + " - Y : " + py);
	}

}
public class PointTriple extends Point {
	
	// 상속받아서 Point에 존재하는 px, py는 동일하게 존재하기 때문에 pz만 추가적으로 생성하면 된다.
	private int pz;
	
	public PointTriple() {
		super(); // 부모 클래스의 생성자를 진행해주는 코드. super()를 안 써도 자동으로 실행된다.
		System.out.println("- PointTriple() -");
	}
	
	public PointTriple(int px, int py, int pz) {
		super(px, py);
		System.out.println("- PointTriple(int px, int py, int pz) -");
		this.pz = pz;
	}

	public void triplePoint() {
		// System.out.println("좌표 X : " + px + " - Y : " + py + " - Z : " + pz); Error
		// Error 뜨는 이유: 상속받더라도 접근제한자는 그대로 동작하기 때문에 => private는 바로 쓸 수 없다.
		
		System.out.println("좌표 X : " + getPx() + " - Y : " + getPy() + " - Z : " + pz); // get을 사용한다.
	}
	
}

상속하려면 위의 PointTriple 클래스의 코드처럼 extends [상속할 클래스명] 라고 작성하면 된다.

 

 

public class TestPoint {
	
	public static void main(String[] args) {
		
		Point pA = new Point(10, 20);
		pA.twoPoint();
		
		System.out.println();
		
		PointTriple ptA = new PointTriple();
		// 부모(Point)의 생성자를 먼저 생성하고 나서
		// 자식(PointTriple)의 생성자를 그 다음으로 생성한다.
		
		System.out.println();
		
		PointTriple ptB = new PointTriple(10, 20, 30); 
		// Point 클래스의 기본생성자가 진행된다.
		// -> Point(int px, int py) 생성자가 진행되어야 한다.
		// -> super(px, py)라고 적으면 된다. super()는 this()와 유사하게 작동
		ptB.triplePoint();

	}

}

[결과값]

- Point() -

- PointTriple() -

 

- Point(int px, int py) -

- PointTriple(int px, int py, int pz) -

좌표 X : 10 - Y : 20 - Z : 30

 

 

오버라이드 ( Override )

*오버로딩과 헷갈리지 않도록 주의하자!

  • 상속받은 자식클래스에서 부모클래스의 메서드를 재정의하는 것
  • 메서드의 형태는 부모클래스의 메서드와 동일해야 한다.
  • 부모클래스이 접근제한자보다 더 좁은 범위는 정의할 수 없다. (접근제한자의 범위가 동일하거나 더 넓은 것은 가능하나, 좁아지는 것은 안 된다.)
  • 부모클래스의 final 선언된 메서드는 자식클래스에서 오버라이드할 수 없다.

 

instanceof 연산자

  • 참조 변수가 현재 사용중인 객체 타입을 확인할 때 사용한다.
  • 타입이 같으면 true, 다르면 false
public class Base {
	
	private String item;
	
	public Base() {
		item = "기본무기";
	}
	
	public void baseAttack() {
		System.out.println(item + " 사용");
	}

}
public class UnitA extends Base {
	
	private String unitItem;
	
	public UnitA() {
		unitItem = "방어막";
	}
	
	public void unitAttack() {
		System.out.println(unitItem + " 사용");
	}

}
public class CastingTest {
	
	public static void main(String[] args) {
		
		UnitA userA = new UnitA();
		userA.baseAttack();
		userA.unitAttack();
		
		System.out.println();
		
		// up casting : 자식클래스의 값을 부모클래스에 넣는 것. 자동으로 형변환된다.
		Base base = userA; // UnitA의 객체는 맞지만 Base의 레퍼런스를 가지고 있기 때문에 Base class의 메서드만 사용 가능
		base.baseAttack();
		// base.unitAttack(); Error
		
		System.out.println();
		
		// userA = base; Error
		// up casting일 때는 형변환 시 타입을 지정해줄 필요가 없지만(자동), down casting일 때는 변환 타입을 지정해줘야 한다.
		userA = (UnitA)base;
		userA.baseAttack();
		userA.unitAttack(); // down casting되었기 때문에 이젠 UnitA class의 메서드까지 사용 가능
		System.out.println();
		
		// down casting 보다 안정적인 방법
		if(base instanceof UnitA) { // 먼저 down casting이 되는 조건인지 확인
			userA = (UnitA)base;
		}
		
	}

}

[결과값]

기본무기 사용

방어막 사용

 

기본무기 사용

 

기본무기 사용

방어막 사용

 

 

추상 클래스

  • 상속받을 class에서 구현할 특성을 정의한 class
  • 객체를 생성할 수 없다.

추상 메서드

  • 추상클래스 안에 기능을 정의하지 않은 메서드
  • 상속받는 자식클래스에서 반드시 override해야 한다.
	// 추상클래스
public abstract class Calc {
	
	protected int dataA;
	protected int dataB;
	
		 // 추상메서드
	public abstract void showData(); // 추상메서드는 메서드를 정의할 수 없고, 틀만 만드는 것만 가능하다.
	// public abstract void showData() {} 이런식으로 메서드를 정의하려고 하면 에러남.

}


class Add extends Calc { // 추상클래스를 상속받을 때는 제약조건이 따른다. 무조건 상속x -> 상속 조건: 미완성의 추상메서드를 정의해야 함.
	
	public Add(int a, int b) {
		dataA = a; // protected는 상속받는 클래스에서 사용이 가능한 접근제한자이기 때문에 getter, setter 없이 바로 사용 가능
		dataB = b;
	}
	
	public void showData() {
		// 이렇게 Calc class의 추상메서드를 정의해줘야 에러가 나지 않는다.
		System.out.println(dataA + " + " + dataB + " = " + (dataA+dataB));
	}
	
}


class Sub extends Calc {
	
	public Sub(int a, int b) {
		dataA = a; 
		dataB = b;
	}
	
	public void showData() {
		System.out.println(dataA + " - " + dataB + " = " + (dataA-dataB));
	}
	
}

 

다형성

상속 받은 자식클래스 객체들을 하나의 부모클래스 변수로 사용하는 것

 

public class CalcTest {
	
	public static void main(String[] args) {
		
		Add add = new Add(11, 22);
		add.showData();
		
		System.out.println();
		
		Sub sub = new Sub(33,44);
		sub.showData();
		
		System.out.println();
		
		// Calc calc = new Calc(); Error. 추상클래스는 객체 생성이 불가능
		Calc calc =  null; // 객체 생성은 불가능하지만 변수 생성은 가능
		calc = add; // -> 부모클래스로부터 상속받은 자식클래스의 값을 넘기는 게 가능하다.
		calc.showData(); // 오버라이드가 된 경우엔 이런식으로도 가능하다. (오버라이드 처리가 안 되었다면 불가)
		
		calc = sub;
		calc.showData();
		
		System.out.println();
		
		Calc run = null;
		int a = 123;
		int b = 45;
		char opt = '-';
		
		switch(opt) {
		case '+':
			run = new Add(a, b);
			break;
		case '-':
			run = new Sub(a, b);
			break;
		}
		if(run != null) {
			run.showData();
		}
		
		
	}

}

 

interface

  • 100% 순수한 추상클래스
  • class 대신에 interface를 사용해서 정의한다.
  • interface를 상속받아서 구현할 때에는 implements를 사용한다.
public interface Tool {
	
	// interface 안의 모든 멤버필드는 자동으로 public static final 입니다.
	int A = 1;
	public static final int B = 2;
	
	void testMethod(); // 메서드도 자동으로 추상메서드가 된다. -> 구현하려고 하면 에러
	
	public abstract void abstractMethod();

}

 

도형의 넓이를 구하는 클래스를 생성해보자.

public interface Shape {
	
	int BTN_A = 1;
	int BTN_B = 2;
	
	public double getArea();
	public void drawing();

}
public class Circle implements Shape { // Shape interface 상속
	
	private double radius;
	private final double PI = 3.141592;
	
	public Circle(double radius) {
		this.radius = radius;
	}
	
	public double getArea() {
		return radius * radius * PI;
	}
	
	public void drawing() {
		System.out.println("원 그리기 - 넓이 :  " + getArea());
	}

}
public class Rectangle implements Shape {
	
	private double width;
	private double height;
	
	public Rectangle(double width, double height) {
		this.width = width;
		this.height = height;
	}
	
	public double getArea() {
		return width * height;
	}
	
	public void drawing() {
		System.out.println("사각형 그리기 - 넓이 : " + getArea());
	}

}
import java.util.Scanner;

public class ShapeTest {
	
	public static void main(String[] args) {
		
		Scanner scanner = new Scanner(System.in);
		
		System.out.print("1.사각형  2.원 선택 >> ");
		int select = scanner.nextInt();
		System.out.println();
		
		Shape shape = null;
		switch(select) {
		case 1:
			System.out.println("--- 사각형 ---");
			System.out.print("가로 길이 입력 > ");
			double width = scanner.nextDouble();
			System.out.print("세로 길이 입력 > ");
			double height = scanner.nextDouble();
			
			shape = new Rectangle(width, height);
			break;
		case 2:
			System.out.println("--- 원 ---");
			System.out.print("반지름 입력 > ");
			double radius = scanner.nextDouble();
			
			shape = new Circle(radius);
			break;
		default:
			System.out.println("도형 선택 오류~");
		}
		
		if(shape != null) {
			shape.drawing();
		}
			
	}

}

ShapeTest에서 사각형/원을 선택하고 가로,세로/반지름을 입력하면 Rectangle 클래스와 Circle 클래스에서 각각 공식이 적용되어 넓이 값이 출력되게 된다.