[JAVA] 객체 지향 프로그래밍 Ⅱ 정리 - (1) : 관계

DongGyu Jung·2022년 1월 19일
0

자바(JAVA)

목록 보기
12/60
post-thumbnail

🏃‍♂️ 들어가기 앞서..

본 게시물은 스터디 활동 중에 작성한 게시물로 자바의 정석-기초편 교재를 학습하여 정리하는 글입니다.
※ 스터디 Page : 〔투 비 마스터 : 자바〕

*해당 교재의 목차 순서와 구성을 참고하여 작성하며
각 내용마다 부족할 수 있는 내용이나 개인적으로 궁금한 점은
추가적인 검색을 통해 채워나갈 예정입니다.



📚 상속

기존 클래스를 재사용하여 새로운 클래스를 작성하는 것

  • 보다 적은 양의 코드로 새로운 클래스 작성 가능 ( "재사용성" 제고 )
  • 공통적인 관리 가능 ▶ 코드 추가 및 변경 용이 ( "생산성" & "유지보수" 기여 )

상속 구현 방법은 간단하다.
entends 키워드만 기억하자.

extends
" 새로 작성하는 클래스의 이름 뒤 "에
상속받고자 하는 클래스의 이름과 함께 써주면 된다.

class Parent {  } // 기존에 존재하는 클래스 (Child class가 상속받을 클래스)
class Child extends Partent {
    // Parent class 상속받은 상태
    // 새호운 Child 클래스에서의 새로운 코드들
}

위 코드에서의
두 클래스 (Parent & Child )의 관계를
" 서로 상속 관계에 있다. "라 표현하고

상속해주는 클래스를 < " 상위클래스 " / " 조상클래스 " / " 부모클래스 " >등으로 부르고
상속받는 클래스를 < " 하위클래스 " / " 자손클래스 " / " 자식클래스 " > 등으로 부른다.
( 클래스타원으로 표현하고 상속관계화살표로 표시하는 상속관계를 그림으로 표현한 것을 "상속계층도(class hierarchy)"라고 한다.
→ 자손 클래스 타원 내부에 조상 클래스가 포함되어 있는 형태이다.)

[ 자손 클래스 ]는
조상클래스의 모든 멤버를 상속받기 때문에
항상 조상클래스보다 같거나 큰 멤버를 가지게 되지만
( 단 ❗,상속자 & 초기화 블럭은 상속되지 않는다. )

[ 조상 클래스 ]는
자손 클래스에게 어떠한 영향도 받지 않기 때문에
자손 클래스가 변하더라도 변화가 없다.
( 그래서 " 상속 받는 것 "을 조상클래스를 " 확장 "한다는 의미로 볼 수도 있기 때문에 extends키워드를 사용하는 것이다. )

🧲 클래스 간의 관계

※ " 상속(inheritance) "관계 vs " 포함(composite) "관계

앞서 살펴봤던 상속 이외에도 재사용할 수 있는 방법이 더 있는데
그것이 바로
클래스 간 서로 " 포함 관계 "를 맺어주는 것이다.

포함관계를 맺어준다라 함은
한 클래스의 멤버변수
" 다른 클래스 타입의 참조변수를 선언하는 것 "이다.

즉, 한 클래스를 작성할 때
다른 클래스를 멤버변수로 선언해서 사용하는 것인데
예를 들어보자면

// 원을 만드는 class
class Circle {  
    int x ; // x좌표
    int y ; // y좌표
    int r ; // 반지름
}
// 좌표(점) class
class Point {  
    int x ; // x좌표
    int y ; // y좌표
}

Circle클래스와 Point클래스에서 int xint y를 공통적으로 사용한다.
공통적으로 사용하는 부분만 사용하는 Point클래스를
Circle클래스에 포함시키는 형태라고 생각하면 된다.

class Point {  
    int x ; // x좌표
    int y ; // y좌표
}
class Circle {  
    Point p = new Point() ; // x좌표 & y좌표 
    int r ;
}

이렇게 말이다.

이렇게 단위별로 여러 개의 클래스를 작성한 후,
클래스 간 필요에 따라
포함관계로 서로 재사용하면 간결하고 쉽게 클래스를 작성할 수 있다.

💡 클래스 간 관계 결정 꿀Tip 💡

  • 범위를 생각해보자
    • " ~은 ~이다. " (is) ▶ 상속(inheritance)관계
    • " ~은 ~을 가지고 있다. " (has) ▶ 포함(composite)관계

※ 단일 상속 (single inheritance)

( ❗ C++이라는 또 다른 객체지향언어의 경우는 여러 조상클래스로부터 상속받는 다중상속(multiple inheritance)가 가능하지만 JAVA는 단일상속 허용한다. )

단일 상속만 가능하다는 것은
쉽게 말해 extends 클래스a, 클래스b 같은 형태의 상속이 불가능하다는 것이다.

💡 " 다중상속 "의 장단점 💡

장점

  • 복합적인 기능을 가진 클래스 쉽게 작성

단점

  • 클래스 간 관계 너무 복잡
  • 서로 다른 클래스에서 상속받은 멤버간의 이름이 같은 경우, 구별할 수 없다.
    (static메서드라면 클래스 이름 붙여서 구분할 수 있지만 인스턴스 메서드라면 답 없음.)

→ 다중상속에 비해 불편할 수 있지만
클래스 간 관계가 명확해지고
코드의 신뢰성이 높아지기 때문에

" Java는 단일상속만을 허용한다. "


※ 모든 클래스의 조상 - " Object클래스 "

" 모든 클래스 상속계층도의 최상위 에 있는 조상클래스 "

말 그대로 모든 클래스들 중 가장 최상위에 위치하고 있는 클래스로

< 다른 클래스로부터 상속받지 않는 모든 클래스들 >은
자동적으로 Object클래스로부터 상속받게 한다.

이게 무슨 강압적인 행위이냐 싶을 수 있지만
이름은 낯설겠지만
어디에나 있지만 어디에서도 볼 수 없던 그런 존재이다.
toString()메서드와 equals(Object o) 메서드등을 많이 봤을 것이다.

자동으로 상속되기 때문에
별 다른 작업을 하지 않아도 위 메서드들을 아무렇지 않게 사용했던 이유가
Object클래스의 정의된 멤버였기 때문이다.

※ 오버라이딩(overriding) ?

* 필자 블로그의 『오버로딩 & 오버라이딩 게시글』 참고


🔖 "super" 과 "super()"

헤드라인을 보고
어라? 어디서 본 듯한 비교인데 싶을 것이다.

그렇다.
이전 『객체 지향 프로그래밍 Ⅰ 정리 - (3)』에서 봤던
this와 this()의 차이점을 알아봤을 때와 유사하다.

여기서도 그 분류기준이 유사하다.
먼저 super에 대해 알아보자.

◎ 참조변수 - super

super
자손클래스에서
" 조상 클래스로부터 상속받은 멤버를 참조하는데 사용하는 참조변수 "이다.

멤버변수와 지역변수의 이름이 같을 때,
this을 통해 구별했던 것 처럼

class SuperTest {
    public static void main(String args[]) {
        Child c = new Child();
        c.method();
    }
}
/* 출력
x=20
this.x=20
super.x=10
*/

class Parent { int x= 10 ; } // super.x
class Child extends Parent {
    int x = 20 ; // this.x
    
    void method() {
        System.out.println("x=" + x) ; Child클래스에서 선언한 x : 20
        System.out.println("this.x=" + this.x) ; // 자신(Child클래스)의 멤버 x : 20
        System.out.println("super.x=" + super.x) ; // Parent 클래스의 x : 10
    }
}

상속받은 멤버와 자신의 멤버의 이름이 같을 때,
super를 통해서 구별한다.

모든 인스턴스 메서드에는
지역변수로서 thissuper가 존재한다.

class SuperTest2 {
    public static void main(String args[]) {
        Child c = new Child();
        c.method();
    }
}
/* 출력
x=10
this.x=10
super.x=10
*/

class Parent { int x= 10 ; } // x를 조상클래스에서만 -> super.x로도, this.x로도 사용됨.
class Child extends Parent {
    // 상속받은 x를 건드리지 않고
    void method() {
        System.out.println("x=" + x) ; // 10
        System.out.println("this.x=" + this.x) ; // 10
        System.out.println("super.x=" + super.x) ; // 10
        // x == this.x == super.x 
    }
}

사실상 이 둘은
근본적으로 같다고 볼 수 있고
" 조상의 멤버 "와 "자신의 멤버"를 구별하는데 사용된다는 점에서 차이가 있는 것이다.

◎ 조상의 생성자 - super()

여기서도 괄호()의 효과는 대단했다!
this()처럼 super()생성자의 역할을 한다.

this()가 같은 클래스의 다른 생성자를 호출한다면
super()은 조상의 생성자를 호출한다.
( ❗ 생성자는 상속되지 않는다. )

class Point {  
    int x ; // x좌표
    int y ; // y좌표
    
    // 메서드 추가
    Point(int x, int y) {
        this.x = x ;
        this.y = y ;
    }
}

class Point3D extends Point {
    int z ;
    
    Point3D(int x, int y, int z) {
    // this를 통해 새롭게 초기화 하는 것도 틀리진 않지만 
    // 이 방법으로 초기화하기보단 super()생성자를 통해 초기화하는 것이 바람직하다.
        //this.x = x ; // 조상멤버 "초기화"
        //this.y = y ; // 조상멤버 "초기화"
        super(x, y) ; // Point(int x, int y) 호출
        this.z = z ;
    }
}


📂 패키지 ( package )

= 클래스 묶음
( 물리적으로 소속 클래스들이 묶여있는 하나의 디렉토리 )

  • 클래스 또는 인터페이스를 포함시킬 수 있다.
  • < 서로 관련된 클래스들 > 끼리 그룹 단위로 묶어놓아 클래스를 효율적으로 관리할 수 있다.

" 같은 이름의 클래스 "이더라도
" 서로 다른 패키지 "에 존재할 수 있기 때문에

각 개발자들이 자신만의 패키지 체계를 유지함으로서
다른 개발자의 클래스 라이브러리의 클래스&이름이 충돌나는 것을 피할 수 있다.

사실 클래스의 진짜 이름(full name)
단순히 클래스 이름이 아니라
패키지명이 포함된 이름이다.

String 클래스를 한 번 보자.
이 클래스의 실제이름은
" java.lang.String "이다.
" java.lang패키지에 속한 String 클래스 "라는 의미이다.
( lang 패키지java 패키지의 하위 패키지인 것)

그래서 만약 String이라는 같은 이름의 클래스가 있더라도
다른 패키지에 속해있다면 문제 없이 구별 가능한 것이다.

◎ 선언

놀라지마라.
package 패키지명

끝이다.
...
..

그냥 "클래스"나 "인터페이스의 소스파일(.java) 맨 위"에
저 한 줄만 적으면 된다.
( ❗ 반드시 [주석&공백 제외] 첫 번째 문장이어야 하며 소스파일에 단 한번만 선언될 수 있다. )

대소문자 구분은 가능하나
class 명과 구분하기 쉽도록 소문자를 사용하는 것이 원칙이다.

또한,
모든 클래스
반드시 하나의 패키지에 포함되어야 한다.


?
여기서 잠깐!
""" 여태까지 우리는 패키지 선언을 한 적없는데 어떻게 된거지..? """
라는
의문이 들 수 있다.

우리에게 여태 소스파일을 작성할 때 문제 없었던 이유는
자바에서
기본 제공하는 " 이름없는 패키지 (unnamed package) "에
자동으로 속할 패키지를 지정하지 않은 클래스소속시킨다.
( ❗ 간단한 프로그램은 지정하지 않아도 큰 문제가 없지만 "큰 프로젝트"나 Java API 등과 같은"클래스 라이브러리" 를 작성할 땐
미리 패키지를 구성하여 적용해야한다.)

🔊 클래스 패스(classpath)

JVM이나 Java 컴파일러
" 사용자정의 클래스 "와 " 패키지의 위치 "를 지정해주는 파라미터이다.

쉽게 말해
Java가 클래스를 찾아 사용을 해야하는데
" 클래스들이 어디 있는지 위치를 지정해주는 값 " 이라고 할 수 있다.

Java파일을 컴파일하고 class를 실행시킬 때,

[ class가 있는 위치 ]에서
해당 클래스를 실행하는 명령어를 입력하면 클래스패스를 별도로 지정하지 않아도 되지만
[ 다른 경로 ]에서 실행시킨다면
클래스패스를 지정해주어야 해당 위치를 찾아 실행시킬 수 있다

그렇기 때문에
패키지의 루트 디렉토리를 클래스패스에 포함시켜야 한다.
( *루트 디렉토리는 클래스가 소속된 패키지의 상위 디렉토리이다. )

[Windows] 제어판 - 시스템 - 고급 시스템 설정 - 환경변수 - 새로 만들기 ▶ 《변수이름》에 'CLASSPATH' 입력 & 《변수 값》에 루트 디렉토리 path 입력

여러 개의 경로를 " ;구분자 "를 통해 클래스패스에 지정할 수 있다.

.jar파일 사용 ★
: java 클래스 생성시,
jar 파일에 포함하여 생성할 수도 있는데,

SET CLASSPATH=C:\ (루트 디렉토리 Path);(루트 디렉토리 Path)\xxx.jar

위 코드와 같이 jar를 생성하고
해당 jar의 위치클래스패스에 지정하면,
위치에서 해당 클래스를 실행해도
" 클래스패스 위치에서 해당 클래스를 찾아 실행을 하게 된다. "


import

소스 코드를 작성하면서
다른 패키지의 클래스를 사용하려고 할 때,

원래 " 패키지명이 포함된 클래스 이름 "을 사용해야 하는데
매번 붙여서 작성하는 것은 비효율적이다.

그래서
코드를 작성하기 이전
사용하고자 하는 클래스의 패키지
import으로 미리 명시하여
코드 내에서 사용할 때
패키지명을 생략하고 사용할 수 있다.

/* 실행 시 성능상의 차이는 전혀 없다 */
import 패키지명.클래스명 ; //방법 1
import 패키지명.* ; // 방법 2 _ 전체(*) 클래스 import

《 작성 위치
" package문 다음 " & " 클래스 선언문 이전 "
( ❗ package문과 달리 한 소스파일 내에 여러 번 선언될 수 있다. )

static import

위에서 알아본 import문보다 더 깊은 import라고 생각하면 이해하기 쉽다.

패키지명 생략을 넘어
static import문을 사용하면
static 멤버를 호출할 때
" 클래스 이름 생략 "이 가능해진다.
( 【특정 클래스의 static 멤버를 자주 사용하는 경우】 효과적 )

/* 아래와 같이 간략하게 작성 가능 */
import static java.lang.Math.random ; // Math클래스 내 모든 static 메서드
import static java.lang.System.out ; // System.out

class Test {
    public static void main(String args[]) {
        // System.out.println(Math.random()) ;
        out.println(random());
        // System.out.println("Math.PI : " + Math.PI) ;
        out.println("Math.PI : " + Math.PI);
    }
}

0개의 댓글