* 이 글은 제가 공부하기 위해 최대한 사실에 입각해 내용을 적으려고 하지만 일부 내용들이 정확하지 않을 수 있습니다.
혹시나 잘못된 부분이 있으면 너그럽게 이해해주시고 피드백 부탁드려요!
8장 상속
8.1 클래스상속
이미 만들어져 있는 클래스로 상속을 해주는 클래스를 부모 클래스, 또는 상위 클래스라고 부른다. 반대로 부모 클래스로부터 기존 변수나 메서드들을 그대로 물려받는 즉, 상속을 받는 클래스를 자식 클래스 또는 하위 클래스라고 부른다.
상속의 정의 방법은
class 자식클래스 extends 부모클래스 { .... // 코드 작성 } |
위와 같이 클래스를 정의한다.
상속의 특징
1. 단일 상속만 가능 - 자식클래스는 하나의 부모 클래스에서만 상속 받을 수 있다.
2. 자식 클래스를 객체로 생성할 때, 부모 클래스가 먼저 객체화 된다.
3. 모든 클래스는 Object 클래스로부터 시작하는 상속관계의 하위 객체이다. - 모든 클래스의 가장 상위 클래스는 Object 클래스이다.
package chapter08;
public class Phone {
String name;
String color;
String company;
void call() {
System.out.println("전화를 건다.");
}
void receive() {
System.out.println("전화를 받다");
}
}
package chapter08;
public class SmartPhone extends Phone {
public void installApp() {
System.out.println("앱 설치");
}
}
package chapter08;
public class SmartPhoneMain {
public static void main(String[] args) {
Phone p = new Phone();
p.name = "전화기";
p.company = "현대";
p.color = "화이트";
System.out.println("Phone 출력");
System.out.println(p.name);
System.out.println(p.company);
System.out.println(p.color);
p.call();
p.receive();
SmartPhone sp = new SmartPhone();
sp.name = "갤럭시";
sp.company = "삼성";
sp.color = "블랙";
System.out.println("SmartPhone 출력");
System.out.println(sp.name);
System.out.println(sp.company);
System.out.println(sp.color);
sp.call();
sp.receive();
sp.installApp();
}
}
super
super는 자식 객체에서 부모 객체를 가리키는 참조변수이다.
super. 변수명(); super. 메서드명(); |
super. 을 사용하게 되면 자신이 속한 클래스에서 찾지 않고 상위 클래스에서만 멤버를 찾게 된다.
또 하나의 형태는 super()인데, 관호가 붙어서 메서드처럼 사용하고, 부모 객체의 생성자를 생성할 때 사용한다. 앞 챕터에서 배웠던 this와 super 모두 static 메서드에서는 사용할 수 없다. 따라서 main() 메서드 내에서도 사용할 수 없다.
package chapter08;
public class Programmer extends Emp {
void work() {
System.out.println("개발업무 합니다.");
}
void goWork() {
System.out.print("개발자 ");
super.goWork();
}
// void goWork() { // 재귀호출
// goWork();
// }
}
super()
super라는 키워드에 메서드처럼 ()가 붙어 있는데, 바로 부모 객체의 생성자를 의미한다. 이 super()로 부모 객체의 생성자를 알 수 있는데, 아래 예제와 같이 부모 클래스의 생성자에 매개변수가 잇는 경우 자식 클래스의 생성자에서 반드시 super()로 부모 생성자를 실행해줘야한다.
package chapter08;
public class SuperEx2 {
}
class Parent2 {
// public Parent2() {
// // TODO Auto-generated constructor stub
// super(); // this를 제외하고는 super를 다 가지고 있음
// }
String name;
Parent2(String name) {
super();
this.name = name;
}
}
class Child2 extends Parent2 {
public Child2() {
super(null);
}
}
8.2 메서드 재정의
상속관계에서 부모 클래스의 메서드를 자식 클래스가 변경해서 정의하는 것을 메서드 재정의(overriding)이라고 한다.
상속관계에서도 동일한 이름의 메서드를 자식 클래스가 똑같이 생성했다면 부모클래스의 메서드보다 우선적으로 적용된다.
매서드 재정의가 가능하려면 첫번째, 부모 클래스의 메서드와 자식 클래스의 메서드의 선언부가 동일해야 한다. 다르면 메서드 재정의가 아니라 오버로딩이 되기 때문이다.
두번째는 자식 클래스의 재정의된 메서드의 접근 제한자가 부모 클래스의 메서드 접근제한자보다 사용 범의가 같거나 커야한다.
예를 들어 부모 클래스의 메서드가 private이라면 자식 클래스의 재정의된 메서드가 public이 가능하지만, 반대로 부모 클래스의 메서드가 public이라면 재정의된 메서드는 private으로 선언이 불가능하다.
package chapter08;
public class Car {
String color;
String name;
public void go() {
System.out.println("전진");
}
void back() {
System.out.println("후진");
}
}
package chapter08;
public class Taxi extends Car {
public void go() {
System.out.println("미터기를 켜고 전진");
}
}
package chapter08;
public class TaxiMain {
public static void main(String[] args) {
Car taxi = new Taxi(); // 선언될 타입이 조상것을 적어도 가능 : 다형성
taxi.go();
System.out.println(taxi);
// 결과값 chapter08.Taxi@28a418fc 패키지.클래스 >> new Taxi로 만들어져
Taxi t2 = (Taxi)taxi; // taxi의 인스턴스인지 실행해봐야 알수 있음.
// 택시 타입인지 카 타입인지 먼저 알수 없음 컴파일러가.
// 상속 관계이기 때문에 형변환은 가능한거
// 컴파일 : 문법 검수
Car car = new Car();
//Taxi t3 = (Taxi)car;
// 참조자료형 형변환을 하려고 인스턴스가 조상?
// Phone p = (Phone)new Car();
// 관계 없는 것은 절대 안됨.
Car c = t2;
Object o = new int[5];
System.out.println(((int[])o).length);
// 문법적으로 알수 없음,
// 조상 타입 자손 인스턴스 대입 가능 외워야함
}
}
오버라이딩 vs 오버로딩
구분 | 오버라이딩 | 오버로딩 |
관계 | 상속 관계 | 같은 클래스 |
메서드명 | 동일 | 동일 |
매개변수 | 동일 | 다름 |
리턴타입 | 동일 | 상관없음 |
접근제한 | 같거나 넓은 범위 | 상관없음 |
8.3 다형성
다형성 : 선언 타입이 조상이고, 인스턴스가 자손일 때라고 기억하면 된다.
객체지향 프로그래밍 언어에서의 다형성에 대한 개념을 이해하려면 자료형과 상속 관계를 이해하고 있어야 하는데, 간단히 한마디로 정리하면, "하위 클래스 객체를 상위 클래스 자료형으로 변환이 가능하다."
우리가 변수와 자료형을 배울 때 작은 범위의 자료형은 큰 범위의 자료형으로는 자동형변환이 되고, 큰 범위의 자료형을 작은 범위의 자료형으로 변환하려면 강제형변환을 해줘야한다고 했다. int는 double로 자동형변환이 되지만, double을 int로 형변환하려면 강제형변환을 해야 하는 것이다.
매개변수의 다형성
객체 변수에 다양한 타입의 값을 대입할 때도 다형성 개념을 적용하지만, 메서드의 매개변수에서도 다형성 개념을 많이 사용한다. 메서드 입장에서는 다양한 자료형을 매개변수로 받기 위해서 메서드를 정의할 때 매개변수의 자료형을 상위 클래스 타입으로 지정하는 것이다.
8.4 상속관계에서 접근제한자
앞에서 접근제한자 public, protected, default, private 네 가지를 배웠는데, public은 아무데서나 자유롭게, private는 자신의 클래스 내에서만(개인적인) 사용가능하다고 했다. default와 protected가 패키지내에서만 사용가능하다는 공통점이 있다. 그 중 protected가 상속 관계와 관련이 있다.
8.5 추상클래스
추상적이다라는 단어의 의미는 대상을 추려서 나타낸 것을 말한다. 구체적이다라는 말과 반대되는 말이다. 영어로는 abstract라고 한다.
구현부가 없고, 선언부만 가지고 있는 메서드(추상메서드)가 하나라도 있으면 이 클래스는 추상 클래스가 되어야한다. 이 추상 클래스는 new 연산자를 사용해서 객체화할 수 없으며,(있긴함.) 부모클래스로만 사용된다. 상속받는 자식 클래스는 부모 클래스의 메서드 중 추상 메서드가 있다면 이 추상메서드를 반드시 구현해야한다. 구현한다는 말은 구현부(몸통)이 빠져있는 추상메서드의 구현부를 채워준다는 이야기다. 즉 메서드를 재정의(오버라이딩)해야한다.
접근제한자 abstract 리턴타입 메서드명(매개변수); |
package chapter08;
public class Abstract {
public static void main(String[] args) {
C c = new C();
c.a();
c.b();
c.c();
B b = new C(); // ? 조상타입으로 선언했기 때문에 자손 인스턴스 선언 가능
b.a();
b.b();
b.c(); // 컴파일 시와 문법적을 볼 때 구분할 수 있어야함.
B b2 = new B() { // 클래스 구간
@Override
void c() {
// TODO Auto-generated method stub
System.out.println("annoy c()");
}
};
b2.a();
b2.b();
b2.c();
}
}
abstract class A {
abstract void a(); // 구현할 필요가 없기 때문에 구현부가 없는것
//
abstract void b();
abstract void c();
}
abstract class B extends A {
void a() {
System.out.println("B.a()");
}
void b() {
System.out.println("B.b()");
}
}
class C extends B {
@Override
void c() {
// TODO Auto-generated method stub
System.out.println("C.c()");
}
}
package chapter08;
public class ShapeEx {
public static void main(String[] args) {
//System.out.println(File.separator);
System.out.println("사각형의 넓이 : " + Rect.area(3,5) + " cm^2");
System.out.println("사각형의 둘레 : " + Rect.perimeter(3,5) +" cm");
System.out.println("삼각형의 넓이 : " + Tri.area(3,5) + " cm^2");
System.out.println("삼각형의 둘레 : " + Tri.perimeter(3,5) +" cm");
System.out.println("원의 넓이 : " + Circle.area(5) + " cm^2");
System.out.println("원의 둘레 : " + Circle.perimeter(5) +" cm");
System.out.println("육면체의 부피 : " + Cube.vloume(3,4,5) + " cm^3");
System.out.println("육면체의 겉넓이 : " + Cube.outerArea(3,4,5) + " cm^2");
System.out.println("삼각기둥의 부피 : " + Prizm.vloume(3,4,5) + " cm^3");
System.out.println("삼각기둥의 겉넓이 : " + Prizm.outerArea(3,4,5) + " cm^2");
System.out.println("원기둥의 부피 : " + Cylinder.vloume(5,3) + " cm^3");
System.out.println("원기둥의 겉넓이 : " + Cylinder.outerArea(5,3) + " cm^2");
// Shape[] shape = new Shape[6];
}
}
// Shape(도형)
// Rect(사각형), Tri(직각삼각형), Circle(원) // 기능 : 둘레, 넓이를 구해야함
// ↑ 수평적 공통점이 뭐가 있을까?
// Cube(육면체), Prizm(삼각기둥), Cylinder(원기동) 클래스 // 부피, 겉넓이
// ↑x,y,z
// 생성자 빡빡하게 써야함.
// 기본 생성자 X
// 도형의 넓이를 구하시오?? 라는 말은 없음
// 여섯개의 클래스는 기본생성자 없어야함.
// x,y,z 를 다 받을 수 있는 생성자만 써야함
// abstract를 쓰던 안쓰던 상관없음
// 여섯개의 배열을 가진 배열,
// 여섯개의 겉넓이 부피, 둘레 넓이를 구야함.
// Math.PI 써보기
// 수직적, 수평적 관계 >> 누구는 2차원 누구는 3차원이기 때문
// 포함도 잘 써야함, 상속과 포함관계 is has???
// 이 클래스들을 어떻게 할 것인지
// 수식은 쉬움,
class Shape {
static int x; // 밑변1
static int y; // 밑변2
static int z; // 높이
static int r; // 반지름
static int PI = 3;
}
// 도형과 사각형, 삼각형, 원은 수직적 관계
class Rect extends Shape{
// 사각형 넓이와 둘레
// 넓이 : a * b
// 둘레 : 2(a+b)
static int area(int x, int y) {
return x*y;
}
static int perimeter(int x, int y) {
return 2 * (x+y);
}
}
class Tri extends Shape{
// 직각삼각형 넓이와 둘레
// 넓이 : (a*b) / 2
// 둘레 : a+b + math.sqrt(a^2 + b^2)
static int area(int x, int y) {
return (x*y) / 2;
}
static int perimeter(int x,int y) {
return x+y + (int)Math.sqrt(x*x +y*y);
}
}
class Circle extends Shape{
// 원의 넓이와 둘레
// 넓이 : PI * r * r
// 둘레 : 2 * PI * r
static int area(int r) {
return PI * r * r;
}
static int perimeter(int r) {
return 2 * PI * r;
}
}
// 도형과 육면체, 삼각기둥, 원기둥은 수직적 관계
class Cube extends Shape{
// 육면체 부피와 겉넓이
// 부피 : a*b*c
// 겉넓이 : 2((a*b) + (a*c) + (b*c))
static int vloume(int x, int y, int z) {
return Rect.area(x, y)*z;
}
static int outerArea(int x, int y, int z) {
return 2*(Rect.area(x, y)+Rect.area(x, z)+Rect.area(y, z));
}
}
class Prizm extends Shape{
// 삼각기둥
// 부피 : ((a*b)/2)*c
// 겉넓이 : 밑넓이 *2 + 옆넓이
static int vloume(int x, int y, int z) {
return (Tri.area(x, y)/2)*z;
}
static int outerArea(int x, int y, int z) {
return 2*Tri.area(x, y) + Tri.perimeter(x,y)*z;
}
}
class Cylinder extends Shape{
// 원기둥의 부피와 겉넓이
// 부피 : PI * r* r * 높이
// 겉넓이 : 2*PI*r^2 + 2*PI*r*h = 2*PI*r(r+h)
static int vloume(int r, int z) {
return Circle.area(r)*z;
}
static int outerArea(int r, int z) {
return Circle.perimeter(r)*(r+z);
}
}
그럼 추상 메서드, 추상 클래스를 왜 만드는 것일까? 위 예제도 마찬가지지만 추상클래스나 추상메서드 없이도 프로그램을 구현할 수 있는데, 굳이 클래스를 하나 더 만들면서 추상클래스를 만들까?
첫번째로 클래스를 설계할 때 변수와 메서드의 이름을 공통적으로 적용시키기 위함이다. 유사한 특성을 가진 클래스들을 모아 공통 변수나 메서드의 이름을 통일 시켜 각 클래스에 맞게 재정의 할 수 있다.
두번째는 중복 소스들을 줄일 수 있다. 상속관계는 기본적으로 모든 변수, 메서드를 물려받기 때문에 개발을 줄일 수 있다.
세번째는 다형성의 개념을 적용시킬 수 있어, 소스의 수정이나 변경사항이 있을 때, 전체를 변경하거나 바꾸는 것이 아니라 부품 교체하듯이 특정 클래스만 새 클로스로 바꾸면 쉽게 수정이 가능하다.
8.6 객체를 배열로 처리
package chapter08;
// Product : price
// Computer 200, TV 100, Audio 50
public class ProductEx {
public static void main(String[] args) {
Buyer buyer = new Buyer();
buyer.buy(new Computer());
buyer.buy(new Computer());
buyer.buy(new Computer());
// buyer.buy(new Computer());
// buyer.buy(new Computer());
buyer.summary();
//
//System.out.println("".getClass().getSimpleName());
//System.out.println("".getClass().getName());
//new Object().toString();
System.out.println(new Computer());
System.out.println(new Tv());
System.out.println(new Audio());
}
}
class Product {
int price;
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
class Computer extends Product {
public Computer() {
price = 200;
}
}
class Tv extends Product {
public Tv() {
price = 100;
}
}
class Audio extends Product {
public Audio() {
price = 50;
}
}
class Buyer {
int money = 1000;
Product[] cart = new Product[10];
int cnt;
void buy(Product product) {
if(money < product.price) {
System.out.println("잔액이 부족합니다.");
return;
}
cart[cnt++] = product;
money -= product.price;
}
void summary() {
// 잔액 : XXXX 제품의 합계 : XXXX
int sum = 0;
String cartText = "";
for(int i =0; i<cnt; i++) {
sum += cart[i].price;
cartText += cart[i] + " ";
}
//money = money - sum;
// money -= sum; 두번 빼게되는 것임
System.out.println("잔액 : " + money + "제품의 합계 : " + sum);
System.out.println("장바구니 : [" + cartText + " ] ");
// 현재 장바구니에는 [xxx, xxx,xxx]가 있습니다.
}
}
8.7 final 제어자
변수 앞에 사용되었던 final과 마찬가지로 클래스와 메서드 앞에 final이라는 키워드를 사용할 수 있다. 여기서도 역시 마지막이라는 의미를 생각하면 된다.
먼저 final 클래스는 상속이 불가능한 클래스이다. 즉 다른 클래스의 부모(상위) 클래스가 될 수 없다. 대표적인 final 클래스로는 String, Math 등의 클래스가 있다.
public class SubClass extends String { // 사용불가 ... // 코드작성 } |
final 메서드는 재정의(오버라이딩)가 불가능한 메서드이다. 부모(상위) 클래스에서 해당 메서드를 상속 받는 자식 메서드들이 변경하지 못하도록 하기 위해 final 키워드를 사용한다.
package chapter08;
public class FinalMethod {
void method() {
// 재정의가 가능한 메서드
}
final void finalMethod() {
// 재정의가 불가능한 메서드
}
}
class SubFinalMethod extends FinalMethod {
void method() { // 재정의 가능
System.out.println("method() 재정의");
}
void finalMethod() { // 재정의 불가
System.out.println("finalMethod() 재정의");
}
}
'Full Stack > JAVA' 카테고리의 다른 글
[풀스택과정] JAVA 9장 인터페이스 (1) | 2023.01.22 |
---|---|
[풀스택과정] JAVA 8장 연습문제 (1) | 2023.01.20 |
[풀스택과정] JAVA 7장 연습문제 (1) | 2023.01.18 |
[풀스택과정] JAVA 7장 클래스(3) (1) | 2023.01.17 |
[풀스택과정] JAVA 7장 클래스(2) (1) | 2023.01.16 |