오늘의하루

[자바의 정석] 상속 & 오버라이딩 & package & import 본문

JAVA

[자바의 정석] 상속 & 오버라이딩 & package & import

오늘의하루_master 2022. 10. 17. 18:39

상속 (Inheritance)

기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것을 말하며 이렇게 작성된 클래스와 기존 클래스는 서로 부모와 자식으로 관계를 맺어지게 됩니다.

  • 자식은 부모의 모든 멤버를 상속받습니다. (단, 생성자 및 초기화 블록은 제외됩니다.)
  • 자식의 멤버개수는 부모보다 적을 수 없습니다.
  • 단일 상속을 원칙으로 한다.
class Point{
    int x;
    int y;
}

class Point3D extends Point{
    int z;
}

Point3D p = new Point3D();
p.x = 1; // OK
p.y = 2; // OK
p.z = 3; // OK

상속 관계

공통 부분은 부모에서 관리하고 개별 부분은 자식에서 관리하게 만들며 부모의 변경은 자식에게 영향을 미치지만 자식의 변경은 부모에게 아무런 영향을 미치지 않는다.

포함 (composite)

한 클래스의 멤버 변수로 다른 클래스를 선언하는 것을 말하며 작은 단위의 클래스를 먼저 만들고 이 들을 멤버 변수로 조합하여 하나의 커다란 클래스를 만든다.

  • 여러 개의 클래스를 상속받고 싶다면 비중이 높은 클래스를 상속관계로 나머지는 포함관계로 작성한다.
class Point{
    int x;
    int y;
}

class Circle{
    Point p = new Point();
    int r;
}

Circle c = new Circle();
c.p.x = 1; // OK
c.p.y = 2; // OK
c.r = 3;   // OK

클래스 간의 관계 결정하는 방법

객체지향을 제대로 활용하기 위해서는 가능한 많은 관계를 맺어주어 재사용성을 높이고 관리하기 쉽게 만들어야 합니다.

상속 관계는 "~은 ~이다"
포함 관계는 "~은 ~을 가지고 있다."

예제로 확인하기

  1. Circle은 Shape이다. (상속 관계)
  2. Circle은 Point를 가지고 있다. (포함 관계)
class Shape{
    String color = "blue";
    void draw(){}
}

class Point{
    int x;
    int y;
    
    Point(){this(0,0);}
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

class Circle extends Shape{ // 상속관계
    Point center; // 포함관계
    int r;
    
    Circle(){
        this(new Point(0,0), 100);
    }
    Circle(Point center, int r){
        this.center = center;
        this.r = r;
    }
}

Circle c = new Circle(new Point(100,50), 30);
System.out.println(c.center.x); // 100
System.out.println(c.center.y); // 50
System.out.println(c.r); // 30

class Triangle extends Shape{
    Point[] p;
    
    Triangle(Point[] p){
        this.p = p;
    }
    
    Triangle(Point p1, Point p2, Point p3){
        p = new Point[]{p1, p2, p3};
    }
}

Point[] p = {
    new Point(100,100),
    new Point(140,50),
    new Point(200,180),
};
Triangle t = new Triangle(p);
System.out.println(t.p[0].x); // 100
System.out.println(t.p[1]); // 배열 주소 반환
System.out.println(t.p[2].y); // 180

Object 클래스 - 모든 클래스의 최고 조상

조상이 없는 클래스는 자동적으로 Object클래스를 상속받게 되며, 모든 클래스는 Object클래스에서 정의된 11개의 메서드를 상속받게 됩니다.

  • toString(), equals(Object obj), hashCode() 등등
    • equals(Object obj)는 문자열을 비교할 때 사용됩니다.

오버라이딩 (Overriding)

오버 라이딩은 부모 클래스로부터 상속받은 메서드의 내용을 상속받은 자식 클래스에 맞게 변경하는 것을 오버 라이딩이라고 합니다.

  • 오버 라이딩과 오버 로딩은 아무런 연관이 없다.
오버로딩(overloading) : 기존에 업는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) : 상속받은 메서드의 내용을 변경하는 것(change)
class Point{
    int x;
    int y;
    String getLocation(){
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point{
    int z;
    String getLocation(){ // Overriding
        return "x : " + x + ", y : " + y + ", z : " + z;
    }
}

오버 라이딩의 조건

오버 라이딩을 하기 위해서는 몇 가지 지켜야 할 조건들이 있다.

  1. 선언부가 같아야 한다.
  2. 접근 제어자를 좁은 범위로 변경할 수 없다.
  3. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
class Parent{
    // 예외를 2개 선언, 접근 제어자 default
    void parentMethod() throws IOException, SQLException{
        // ....
    }
}

class Child1 extends Parent{
    // 예외를 1개 선언, 접근제어자 protected
    protected void parentMethod() throws IOException{
        // ....
    }
}

class Child2 extends Parent{
    // 예외를 2개 선언, 접근제어자 public
    public void parentMethod() throws IOException, SQLException{
        // ....
    }
}

super( )  - 부모의 생성자 호출

부모로부터 상속받은 자식 클래스의 인스턴스를 생성하면 자식 멤버와 부모의 멤버가 합쳐져서 하나의 인스턴스가 생성되는데 자식의 멤버는 자식 클래스에서 생성자를 통해 초기화가 가능하지만 부모의 멤버는 초기화를 할 수 없기 때문에 사용된다.

  • 자식 클래스의 생성자 첫 문장에서 부모의 생성자를 호출해야 한다.
  • 상속 시 부모의 생성자 및 초기화 블록은 상속되지 않는다.
  • Object 클래스를 제외한 모든 클래스의 생성자 첫 줄에는 같은 클래스의 다른 생성자 또는 부모의 생성자를 꼭 호출해야 한다.
    • 그렇지 않을 경우 컴파일러가 자동적으로 "super();"를 생성자의 첫 줄에 삽입한다.
class Point{
    int x;
    int y;
    Point(){this(0,0);}
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

// 위에 코드에서 생략된 생성자 및 최고 조상을 모두 입력하면 아래와 같다.
class Point extends Object{
    int x;
    int y;
    Point(){this(0,0);}
    Point(int x, int y){
        super();
        this.x = x;
        this.y = y;
    }
}

예제를 통해 super( )를 이해하기

class Point{
    int x;
    int y;
    
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
    
    String getLocation(){
        return "x : " + x + ", y : " + y;
    }
}

class Point3D extends Point{
    int z;
    
    Point3D(int x, int y, int z){
        super(x, y); // 부모의 생성자 Point(int x, int y) 호출
        // 만약 생략했다면 어떻게 될까?
        // 정답은 Error!!!!
        // 이유는 컴파일러가 super()를 삽입하는데 부모의 생성자에는 Point()가 없기 때문이다.
        this.z = z;
    }
    
    String getLocation(){ // Overriding
        return "x : " + x + ", y : " + y + ", z : " + z;
    }
}

package와 import

package는 서로 관련된 클래스와 인터페이스들의 묶음을 의미하며 클래스가 물리적으로 클래스 파일(*. class)인 것처럼 패키지는 물리적으로 폴더라고 생각하면 편하다.

  • 패키지는 서브 패키지를 가질 수 있으며 "."으로 구분한다.

클래스의 실제 이름은 패키지 명이 포함되어 있다.

  • 예를 들어 String 클래스의 실제 이름은 java.lang.String이다.

rt.jar는 Java API의 기본 클래스들을 압축한 파일이다.

  • JDK 설치 경로\jre\lib에 위치해 있다.

package의 선언

패키지는 소스파일에 첫 번째 문장으로 단 한번 선언하며 하나의 소스파일에 두 개 이상의 클래스가 포함된 경우 모두 같은 패키지에 속하게 됩니다.

  • 만약 패키지를 선언하지 않았다면 자동적으로 클래스들은 Unnamed Package에 속하게 된다.

import 사용

사용할 클래스가 속한 패키지를 지정하는 데 사용되며 import 문을 사용하면 클래스를 사용할 때 패키지 명을 생략할 수 있다.

  • 단, java.lang 패키지의 클래스는 import 하지 않고도 패키지 명을 생략할 수 있다.
    • 예를 들어 String, Object, System, Thread 등등
class ImportTest{
    java.util.Date today = new java.util.Date();
}

// 위에 코드를 import 문을 사용해서 패키지 명을 생략할 수 있다.
import java.util.Date

class ImportTest{
    Date today = new Date(); // import문을 사용해서 패키지명 생략
    
    void langTest(){
        java.lang.System.out.println("hello java");
        System.out.println("HELLO JAVA"); // java.lang패키지는 import하지 않아도 생략 가능
    }
}

import 문의 선언

import문은 패키지 선언과 클래스 선언의 사이에 선언을 하게 되며 import문은 컴파일 시에 처리되므로 몇 개를 선언하더라도 프로그램 성능에는 아무런 영향을 미치지 않는다.

1. package 문 선언
2. import 문 선언
3. 클래스 선언

import 문을 선언하는 방법에는 2가지가 있다.

1. import.packageName.className;
2. import.packageName.*;

이름이 같은 클래스가 속한 패키지가 있을 경우에는 클래스 앞에 패키지명을 붙여줘야 한다.

import java.sql.*;
import java.util.*;

public class ImportTest{
    public static void main(String[] args){
        // java.util은 생략이 가능하지만 sql 패키지에도 Date 클래스가 있기 때문에
        // 헷갈리는 것을 방지하기 위해 패키지명을 작성해주는게 좋다.
        java.util.Date today = new java.util.Date();
    }
}
Comments