금요일엔 정말 중요한걸 배웠다. 바로 상속. Inheritance.
상속에 대해서 알아보자.
상속 (Inheritance)
class 자식클래스이름 extends 부모클래스이름 {
// 내용
}
위와 같이 사용한다.
@Override 어노테이션 - 오버라이딩 유효성 확인
extends 가 뒤에 없을 경우 - extends Object 생략된 것.
extends 뒤에는 딱 한개의 클래스만 올 수 있음.
다음과 같이 상속을 extend로 선언하여 사용하면, 부모클래스에 선언된 필드/메소드를 선언없이 사용할 수 있다.
@Override 어노테이션은 오버라이드가 됐는지 확인할 수 있는 코드로, 상속받은 메소드를 오버라이드했을 경우 오류가 발생하지 않고, 오버라이드 메소드가 아닐 경우 오류가 발생한다. @Override는 써도되고, 안써도 된다.
우리가 지금까지 extends를 모르고 사용하지 않았었다. 즉 extends를 생략시켰었다.
상속 선언부(extends ~~~)가 없을 경우, extends Object 구문이 생략된 것이다.
자바 상속모델의 최상위 클래스가 Object 인 것이다. 그러므로 모든 클래스는 Object의 메소드를 물려받아 사용할 수 있다.
간단한 코드로 상속을 이해해보자.
public class InheritanceBasicMain{
public static void main(String[] args){
Parent parent = new Parent();
Child child=new Child();
child.name="자식";
child.age=15;
child.say();
System.out.println(child.information());
child.doGame();
}
}
class Parent{
String name;
int age;
public void say() {
System.out.println("내가 애비다.");
}
public String information() {
return "name = "+this.name+", age = "+this.age;
}
}
class Parent{
String name;
int age;
public void say() {
System.out.println("내가 애비다.");
}
public String information() {
return "name = "+this.name+", age = "+this.age;
}
}
다음을 출력하면
제가 자식입니다.
name=자식, age=15
자식이 LOL을 한다.
이렇게 출력된다.
하지만, public say(String str)로 쓰면 제가 자식입니다. 대신 "내가 애비다"가 출력된다. (대신 @Override 쓰면 오류발생하므로 위에 지워줘야함)
즉, 부모에 선언된 public void say()를 사용하게 된 것이다.
하지만 '제가 자식입니다'가 출력되는 것을 보아 똑같이 선언하여 또 다르게도 사용할 수 있음을 알 수 있다.
또한 자식 클래스에서 새로운 doGame 메소드를 만들어 사용할 수 있다는 것을 알 수 있다.
Parent와 Child의 구조 )
이제 Parent에 다른 class를 상속하고, 또 Child에도 상속을 해보자.
class OtherChild extends Parent{
}
class GrandChild extends Child{//extends 뒤에는 딱 한개의 class만 올 수 있음.
}
아래에 Parent를 상속받은 다른 클래스를 하나 추가하고, Child를 상속받은 클래스는 GrandChild로 했다.
main을 수정해보자.
public class InheritanceBasicMain {
public static void main(String[] args) {
Parent parent = new Parent();
Child child=new Child();
child.name="자식";
child.age=15;
child.say();
System.out.println(child.information());
child.doGame();
GrandChild grandChild = new GrandChild();
grandChild.name="손자";
grandChild.age=3;
grandChild.say(); //child의 say
System.out.println(grandChild.information());
grandChild.doGame();
OtherChild otherChild = new OtherChild();
otherChild.say(); //parent의 say . child와 무관함
// otherChild.doGame(); //형제클래스 선언 메소드 사용 불가
}
}
출력은 다음과 같다. otherChild에서 형제클래스의 메소드인 doGame을 출력하려고 하면 오류가 발생한다. 같은 부모클래스를 상속받은 형제클래스 간에는 관련이 없음을 의미한다.
그리고 결과와 위의 사진을 통해서 Child가 grandChild의 상속을 받았음을 알 수 있다. (Child와 Parent의 field가 뜸)
마지막으로, 위에서 언급했었던 Parent뒤에 아무것도 없을 경우 Object를 상속받는 것이라고 했는데, 이 역시 확인해 볼 수 있다.
parent. 을 누르면 Object의 메소드들이 나온다.
즉 위에서 만든 코드는 다음과 같은 구조를 가진다.
Object가 Parent에 상속, Parent가 Child에 상속, Child가 GrandChild에 상속.
이렇게 기본적인 상속을 이해해 보았다.
점점 기본적인 개념부터 어려워지고 있다.ㅠㅠ
상속의 특징
1. 부모의 필드와 메소드는 자식클래스에서 선언 없이 사용이 가능하다.
2. 부모클래스의 생성자와 초기화블럭은 상속이 안된다. (자식 클래스에서 직접 선언해서 사용해야 한다.)
3. 부모의 private 필드 / 메소드는 상속은 되지만 , 직접 접근은 불가능하다.
반대로 부모필드가 protected인 경우 직접 접근이 가능하다.
3-1) private 필드의 setting 은 부모생성자를 호출해서 처리한다.
3-2) public setter를 이용해서 접근하여 사용한다.
private는 상속은 되지만, 접근은 불가능하다
4. 모든 클래스는 Object클래스의 후손으로, Object메소드를 사용 혹은 재작성 할 수 있다.
-> 1, 4 는 위에서 확인되었으나 2,3은 확인되지 않았다. 아래의 새로운 클래스를 만들어 이해해보자.
Desktop, Tv, SmartPhone 상품에 대한 정보를 다루는 클래스를 만들어보자.
상품 브랜드, 코드, 상품명, 가격 은 셋다 전부 사용되는 필드이다. 그러므로 이에 대한 부모클래스인 Product를 만든다.
Product는 부모클래스로 필드가 상속될 예정이다.
public class Product {
private String brand;
private String productCode;
private String productName;
private int price;
//constructor
public Product() {}
public Product(String brand, String productCode, String productName, int price) {
this.brand = brand;
this.productCode = productCode;
this.productName = productName;
this.price = price;
}
//getter & setter
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getProductCode() {
return productCode;
}
public void setProductCode(String productCode) {
this.productCode = productCode;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
//print Information
public String getProductInfo() {
return brand+", "+productCode+", "+productName+", "+price;
}
@Override
public String toString() {
return brand+", "+productCode+", "+productName+", "+price;
}
}
package kh.java.inheritance.product.after;
import kh.java.inheritance.product.after.parent.Product;
public class Tv extends Product {
private String resolution; // 화질 FHD, UHD
private int size; // size inch
public Tv() {}
public Tv(String brand, String productCode, String productName, int price, String resolution, int size) {
super(brand, productCode, productName, price);
this.resolution = resolution;
this.size = size;
}
public String getResolution() {
return resolution;
}
public void setResolution(String resolution) {
this.resolution = resolution;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getTvInfo() {
return "Tv[" + getProductInfo() + ", " + resolution + ", " + size + "]";
}
@Override
public String toString() {
return "Tv[" + super.toString() + ", " + resolution + ", " + size + "]";
}
}
public class SmartPhone extends Product {
private String os;
private String carrier; // 통신사 정보
public SmartPhone() {}
public SmartPhone(String brand, String productCode, String productName, int price, String os, String carrier) {
super(brand, productCode, productName, price);
this.os = os;
this.carrier = carrier;
}
public String getOs() {
return os;
}
public void setOs(String os) {
this.os = os;
}
public String getCarrier() {
return carrier;
}
public void setCarrier(String carrier) {
this.carrier = carrier;
}
public String getSmartPhoneInfo() {
return "SmartPhone[" + getProductInfo() + ", " + os + ", " + carrier + "]";
}
@Override
public String toString() {
return "SmartPhone[" + super.toString() + ", " + os + ", " + carrier + "]";
}
}
public class Desktop extends Product {
private String os;
private String monitor;
private String keyboard;
private String mouse;
public Desktop() {}
public Desktop(String brand, String productCode, String productName, int price, String os, String monitor,
String keyboard, String mouse) {
// 부모생성자 호출
super(brand, productCode, productName, price);
this.os = os;
this.monitor = monitor;
this.keyboard = keyboard;
this.mouse = mouse;
}
public String getOs() {
return os;
}
public void setOs(String os) {
this.os = os;
}
public String getMonitor() {
return monitor;
}
public void setMonitor(String monitor) {
this.monitor = monitor;
}
public String getKeyboard() {
return keyboard;
}
public void setKeyboard(String keyboard) {
this.keyboard = keyboard;
}
public String getMouse() {
return mouse;
}
public void setMouse(String mouse) {
this.mouse = mouse;
}
public String getDesktopInfo() {
return "Desktop[" + this.getProductInfo() + ", "
+ os + ", " + monitor + ", " + keyboard + ", " + mouse + "]"; // 1
// return "Desktop[" + this.getBrand() + ", " + this.getProductCode()
// + ", " + this.getProductName() + ", " + this.getPrice() + ", "
// + os + ", " + monitor + ", " + keyboard + ", " + mouse + "]"; // 2
}
@Override
public String toString() {
return "Desktop[" + super.toString() + ", "
+ os + ", " + monitor + ", " + keyboard + ", " + mouse + "]";
}
}
데스크탑은 Product를 물려받는다. 하지만 기본생성자 public Desktop() {}를 쓰지 않을 경우 오류가 발생하기 때문에 꼭 써줘야 한다 ----------------------------------------------------------------------------------(2번 특징)
이때 필드는 데스크탑에서만 쓰일 필드를 써주고, 생성자는 기본생성자와 모든 필드가 써있는 생성자를 쓴다.
이때 부모생성자에 있는 요소들은 super()를 통해 써준다.
* 만약 Product에서 필드를 protected로 썼다면, this.brand=brand;로 직접 접근이 가능하다.
super ) 부모생성자 호출시 유의사항
1) 첫번째 줄에 단 한번만 사용 가능
2) this(), super() 중 하나만 사용할 것.
그리고 아랫줄에 getDesktopInfo()를 주목하자.
Desktop의 정보를 출력할 때, 두가지 방법이 존재한다.
1) productInfo () 사용하기.
2) this.getBrand()로 접근해서 값 얻어오고 return안에 반환하여 사용하기.
1의 경우, Product 클래스에 미리 써둔
이 코드를 이용하는 것이다. (매우 편리)
2의 경우, this.getBrand(), this.getProductCode(), this.getProductName()...등 getter로 값을 얻어와서 return값 안에 넣어 반환하는 방법이다.
Product에 getProductInfo를 써뒀기 때문에, 굳이 2번 방법을 사용할 것 같지는 않다.
이를 통해서 3번 특징을 확인할 수 있다.
이제 실행할 Main함수를 작성해보자.
public class ProductAfterMain {
public static void main(String[] args) {
Desktop desktop = new Desktop("삼성", "samsung-1234", "삼성점보데스크탑", 1_000_000, "Windows11", "어떤모니터", "어떤키보드", "어떤마우스");
System.out.println(desktop.getDesktopInfo());
System.out.println(desktop);
SmartPhone smartPhone = new SmartPhone("샤오미", "xioami-8888", "샤오미폰", 800_000, "안드로이드", "SK");
Tv tv = new Tv("LG", "lg-9898", "울트라아이드샤프TV", 5_000_000, "UHD", 80);
System.out.println(smartPhone.getSmartPhoneInfo());
System.out.println(tv.getTvInfo());
}
}
그러면 결과는 다음과 같이 나온다.
Desktop [삼성, samsung-1234, 삼성점보데스크탑, 1000000, Windows11, 어떤모니터, 어떤키보드, 어떤마우스]
SmartPhone [샤오미, xioami-8888, 샤오미폰, 800000, 안드로이드, SK, ]
Tv [LG, lg-9898, 울트라아이드샤프TV, 5000000, UHD, 80]
점을 찍는 클래스 Point를 작성하고, Point를 통해 원을 그리는 클래스 Circle을 작성한다.
넓이와 도형을 그리는 메소드를 가진 부모클래스 Shape클래스도 작성한다.
public class Shape {
private double area; //넓이
public Shape() {}
public Shape(double area) {
this.area=area;
}
public void setArea(double area) {
this.area=area;
}
public double getArea() {
return area;
}
public void draw() {
System.out.println("도형을 그린다.");
}
}
public class Point {
private int x;
private int y;
public Point() {}
public Point(int x, int y) {
this.x=x;
this.y=y;
}
public int getX() {
return x;
}
public void setX() {
this.x=x;
}
public int getY() {
return y;
}
public void setY() {
this.y=y;
}
@Override
public String toString() {
return String.format("(%d, %d)", x,y);
}
}
public class Circle extends Shape{
private Point center;
private int r;
public Circle() {
this(new Point(200,200),50); //기본 원 생성
}
public Circle(Point center, int r) {
super();
this.center=center;
this.r=r ;
}
public Point getCenter() {
return center;
}
public void setCenter(Point center) {
this.center = center;
}
public int getR() {
return r;
}
public void setR(int r) {
this.r = r;
}
@Override
public void draw() {
System.out.printf("중심점 (%d,%d)에 반지름이 %d인 원을 그린다.%n",center.getX(),center.getY(),r);
}
@Override
public String toString() {
return "Circle [center = "+center+", r = "+r+"]";
}
}
그리고 실행하는 코드 ShapeMain을 작성한다.
public class ShapeMain{
public static void main(String[] args){
Circle c1=new Circle();
Point point=new Point(40,40);
c1.setCenter(point);//new Point(40,40)은 Circle과 별개로 작동.여기서 설정한 값임.
c1.setR(25);
c1.draw(); //중심점 (50,50)에 반지름이 25인 원을 그린다.
Circle c2=new Circle();
c2.draw();
Circle c3=new Circle(point,200);
c3.draw();
System.out.println(c3);//뒤에 toString()붙이나 안붙이나 똑같이 호출됨.
//getClass().getName() + "@" + Integer.toHexString
}
실행결과
중심점 (40,40)에 반지름이 25인 원을 그린다.
중심점 (200,200)에 반지름이 50인 원을 그린다.
중심점 (40,40)에 반지름이 200인 원을 그린다.
Circle [center = (40, 40), r = 200]
이 코드들에서 연관관계를 살펴보자.
is - a 상속관계 (Generalization)
class Circle extends Shape의 경우.
-> 원은 하나의 도형이다. is - a 상속관계
Circle is a Shape.
원 클래스는 도형 클래스를 물려받고, 이는 is-a 상속관계이다.
UML다이어그램에서는 Generalization, 일반화관계 라고도 한다.
has - a 포함관계(Association)
원은 점을 가지고 있다. has-a 포함관계
Circle has a Point.
포인트클래스에서 원을 물려받거나 하는 것은 아니지만, 원 클래스에서는 Point 클래스가 사용된다.
UML다이어그램에서는 Association, 연관관계라고도 한다.
연관관계는 두개로 나뉘는데,
- Aggregation 약집합 : Circle이 Point 객체의 생명주기를 결정하지 않는 경우
(다른 사용자에게 쓰일 수 있는 경우에는 약집합)
- Composition 강집합 : Circle이 Point 객체의 생명주기를 결정하는 경우
이다.
다음 그림을 참고하자.
오버라이딩(Overriding)
오버라이딩이란, 자식클래스에 부모의 메소드를 재작성해서 사용하는 것을 의미한다.
1. 메소드 이름, 매개변수 선언부, 리턴타입이 모두 동일해야한다.
2. 접근제한자는 개방적인 방향으로만 수정이 가능하다.
* private -> default -> protected -> public 방향인데, private는 직접접근이 되지 않아 오버라이드 불가능하고, default는 쓰는 경우가 희박하다. 주로 protected -> public 방향으로 진행된다.
3. 부모메소드가 던지는 예외를 제거하거나 개수를 줄일 수 있다.
4. 부모메소드를 오버라이드 한 경우에도 해당 메소드 안에서 super키워드로 호출이 가능하다.
5. private, final메소드는 재작성 할 수 없다. (final class는 상속이 아예 불가능하다.)
4의 super 키워드로 호출하는 것은 Desktop class에서 맨마지막 메소드에서 확인할 수 있다.
점점 내용이 더 어려워지고 설명해서 쓰기도 어려워지고 있다. ㅠㅠ
내가 이해를 잘 못하는건지...?
다음 시험이 걱정된다 ㅋㅋㅋㅋㅋㅋㅋ큐ㅠㅠ