[JAVA] 객체지향 프로그래밍2

2023. 1. 3. 21:39Java

 

 

1. 상속(Inheritance)

 

1-1. 상속의 개념

- 상속이란, 기존 클래스의 변수와 메서드를 물려받아 기존 클래스를 재사용한 새로운 클래스를 구성하는 것임.

- 즉, 연관있는 클래스의 공통적인 구성요소를 정의하여 대표하는 클래스(부모 클래스)를 만듦.

- 자식 클래스는 상속을 통해 부모 클래스의 특징을 물려받아 사용함으로써

     1.코드의 중복 제거

     2. 코드의 재사용성

  의 이점을 얻을 수 있음.

 

 

조상 클래스 : 부모 클래스, 상위 클래스, 기반 클래스 
    - 자손 클래스에게 어떠한 영향도 받지 않음

자손 클래스 : 자식 클래스, 하위 클래스, 파생된 클래스
   - 조상 클래스의 모든 멤버를 상속받으므로 항상 조상클래스보다 같거나 큰 멤버를 가짐
   - 단, 상속자와 초기화 블록은 상속되지 않음
   - 상속을 '확장'의 의미로도 볼 수 있기에 extends 키워드를 사용함

 

 

1-2. 상속관계와 포함관계

  • 포함(composite) 관계
    • 한 클래스의 멤버변수로 다른 클래스 타입의 참조변수를 선언하는 것
    • A가 B를 가지고 있으면 A는 B를 포함한다.
  • 상속 관계
    • A가 B이고, B가 A, A2, A3, ...를 통칭하는 말이라면 A는 B를 상속한다.

 

class Circle {
	int x;
    int y;
    int r;
}

class Point {
	int x;
    int y;
}

이를 포함관계로 Point 클래스를 재사용하여 작성하면, 다음과 같이 작성할 수 있음.

class Point{
	int x;
    int y;
}

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

 

 

 

1-3. 단일상속(Single inheritance)

- 한 클래스는 오직 한 클래스만 상속 가능하다는 것임.

- 단일상속을 통해 클래스 간 관계가 명확해지고 코드의 신뢰성이 높아짐.

- A extends B (O)

- A extends B, C (X)

 

 

1-4. Object 클래스

- 모든 클래스의 최상위에 있는 조상클래스

- 다른 클래스로부터 상속 받지 않는 모든 클래스는 자동적으로 Object 클래스로부터 상속받음

 

 

2. 오버라이딩(Overriding)

- 조상 클래스가 갖고 있는 메서드를 자손 클래스가 재정의해 사용하는 것.

- 즉, 조상 클래스에서 정의한 메서드를 자손 클래스가 상속받아 해당 메서드를 재정의해 사용하는 것임.

<오버라이딩의 조건>
1. 접근제어자는 조상클래스의 메서드보다 좁은 범위로 변경할 수 없음
(protected 메서드를 protected나 public으로 설정하는 것은 가능, but private나 default는 불가능함)
2. 조상 클래스보다 많은 에외 선언은 불가능함.
3. 조상 클래스 메서드의 선언부(이름, 매개변수, 반환타입)이 일치해야 함.

 

 

오버라이딩과 오버로딩

구분 오버로딩 오버라이딩
메서드 이름 동일 동일
매개변수, 타입 다름 동일
리턴 타입 상관없음 동일
class Parent{
	void parentMehod(){}
}

class Child extends Parent {
	//오버라이딩
	void parentMethod(){
    	System.out.println("overriding");
    }
    
    //오버로딩
    void parentMethod(String str){
    	System.out.println(str);
    }
}

 

 

2-1. super와 super()

  • super
    • 부모 클래스로부터 상속받은 필드나 메서드를 자식 클래스에서 참조할 때 사용하는 참조변수
    • 부모 클래스의 멤버와 자식 클래스의 멤버명이 같을 경우 'super'를 통해 구별(this 참조변수와 비슷한 기능)
class Parent {
	int x = 10;
}

class Child extends Parent {
	int x = 20;
    
    void method() {
    	System.out.println(x); //자식 인스턴스 변수 x
        System.out.pritnln(super.x); //부모 인스턴스 변수 x
    }
}

public class Main{
	public static void main(String[] args){
    	Child c = new Child();
        c.method; 
        // 20과 10 차례로 출력
    }
}

 

  •  super()
    • 부모 클래스의 생성자 호출시 사용됨
    • 자식 클래스의 객체가 인스턴스화 될 때 기본적으로 부모 클래스의 default 생성자를 호출해야 하는데, 이를 호출하는 것이 super()
    • 자식 클래스 생성자의 첫 줄에서 super() 메서드가 항상 실행되어야 하지만, 편의성을 위해 생략되어도 자동으로 인식됨
    • 즉, 직접 생성자를 정의하거나 생성자 오버라이딩이 되어 있을 경우 올바른 생성자를 super 메서드로 직접 정의해서 호출해야 함
public class Parent {
	private int x;
    
	public Parent(int x){
    	this.x = x;
    }
}

public class Child extends Parent {
	private int y;
    
    public Child(int y){ // super() 기본 생성자를 찾을 수 없어 컴파일 에러 발생
    }
}

위의 예는 컴파일 에러가 발생하므로, 직접 부모 클래스 생성자를 호출하여야 함.

public class Parent {
	private int x;
    
	public Parent(int x){
    	this.x = x;
    }
}

public class Child extends Parent {
	private int y;
    
    public Child(int x){ 
    	super(x);
    }

 

 

 

3. 패키지(package)

 

3-1. 패키지

- 클래스의 묶음으로, 관련있는 클래스 파일을 저장하는 디렉터리임.

- 서로 다른 패키지라면, 같은 이름의 클래스더라도 구별이 가능해 존재하는 것이 가능함(이름 충돌을 피할 수 있음).

 

패키지는 다음과 같이 선언됨.

package 패키지명;

 

 

3-2. import문

- 다른 패키지의 클래스를 사용하려고 할 때, 사용하고자 하는 클래스의 패키지를 명시함.

- package문 다음과 클래스 선언문의 이전에 선언함.

 

더보기

static import

- static 멤버 호출시 클래스 이름 생략이 가능함

import static java.lang.Math.random;
import static java.lang.System.out;

class Main{
	public static void main(String[] args){
    	out.println(random());
    }
}

 

 

 

4. 제어자(modifier)

 

4-1. 제어자

- 클래스와 클래스 멤버의 선언 시 사용하여 부가적인 의미를 부여함.

- 하나의 대상에 대해 여러 제어자 조합을 사용할 수 있음(단, 접근 제어자의 경우 한 가지만 사용 가능함).

접근 제어자 : public, protected, private, (default)
그            외:  static, final, abstract, ...

 

 

4-2. 접근 제어자

- 외부로부터 접근하지 못하도록 제어하여 보호하는 역할(캡슐화)

 

public 제어자

- 어디서나 접근이 가능함(모두 허용).

 

 

default 제어자

- 따로 접근 제어자를 명시하지 않으면 default 제어자임.

- 같은 패키지에 속하면 접근 가능하나, 다른 패키지에서는 접근 불가능함.

 

protected 제어자

- 다른 패키지에서는 접근 불가능하지만, 상속시에는 가능함.

- 이 멤버를 선언한 클래스의 멤버의 경우 접근 가능함.

- 이 멤버를 선언한 클래스가 속한 같은 패키지의 멤버일 경우에 접근 가능함.

- 이 멤버를 선언한 클래스를 상속받은 자식 클래스 멤버일 경우 접근 가능함.

 

 

private 제어자

- 외부에서는 직접 접근할 수 없으며, 공개되어 있지 않음.

- 클래스 내부의 세부적 동작을 구현하는 데 사용됨.

- getter/setter 구현 시 주로 이용됨.

 

 

4-3. 기타 제어자

 

static 제어자

- 동일 클래스 내의 모든 인스턴스가 공유.

- 인스턴스에 관계없이 같은 값을 지님.

 

final 제어자

- 변경할 수 없는 경우 사용함.

- field에 사용시, 값이 변경불가능한 상수(constant).

- 메서드에 사용시, 오버라이딩이 불가능한 메서드.

- 클래스에 사용시, 상속받을 수 없는 클래스.

 

abstract 제어자

- 선언부만 작성하고 실제 수행 내용은 구현하지 않은 추상 메서드를 선언할 때 사용.

 

 

 

5. 다형성

 

5-1. 다형성(ploymorphism)

- 하나의 객체가 여러 자료형 타입을 가질 수 있는 것.

- 부모 클래스의 참조변수로 자식 클래스 타입의 인스턴스를 참조할 수 있음.

- 단, 자식 클래스의 참조변수로 부모 클래스 타입의 인스턴스를 참조할 수는 없음

  (자식 클래스에서 사용할 수 있는 멤버의 개수는 부모 클래스와 같거나 많기 때문임).

class Tv {
	boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel;}
    void channelDown() { --channel;}
}

class CaptionTv extends Tv {
	String text;
    void caption() {}
}
Tv t = new Tv();
CaptionTv c = new CaptionTv();

Tv t2 = new CaptionTv(); // 조상 타입 참조변수로 자손 타입 인스턴스 참조 가능
CaptionTv c2 = new Tv(); //오류, 자식 타입 참조변수로 부모 타입 인스턴스 참조 불가능

 

 

5-2. 참조변수의 형변환

-  서로 상속관계의 클래스에서는 참조변수도 형변환이 가능.

- 자식 클래스에서 부모 클래스로 형변환(업캐스팅)은 생략가능.

- 부모 클래스에서 자식 클래스로 형변환(다운캐스팅)은 반드시 명시해야 함.

 

 

 

업캐스팅

- 자식 클래스가 부모 클래스 타입으로 형변환(캐스팅)되는 것.

- 캐스팅 연산자 괄호 생략 가능.

- 자식 클래스에서만 존재하는 속성과 메서드는 실행 불가능하게 됨(부모 클래스의 멤버만 접근 가능).

- 즉, 업캐스팅 후 메소드 실행 시, 자식 클래스에서 오버라이딩한 메서드가 있다면 부모 클래스에서의 메서드가 아닌 오버라이딩 된 메서드를 실행하게 됨.

 

다운캐스팅

- 업캐스팅과는 반대로 부모 클래스가 자식 클래스 타입으로 캐스팅되는 것.

- 캐스팅 연산자 괄호 생략 불가능.

- 명시적으로 형변환을 지정해야 함.

- 주로 업캐스팅한 객체를 다시 자식 클래스 타입의 객체로 되돌리는 목적을 가짐.

 

class Car{}
class Truck extends Car {}
class FireEngine extends Car{}

Truck t = new Truck();

Car c = (Car) t; //조상 타입으로 형변환, 업캐스팅, 생략 가능
Truck t2 = (Truck) c; // 자손 타입으로 형변환, 다운캐스팅, 생략 불가
FireEngine f = (FireEngine) t; // 에러

 

 

5-3. instance of

-  참조하고 있는 인스턴스의 실제 타입을 알려주는 연산자.

- 어느 클래스 타입인지 판별하여 true/false를 반환함.

-  true일 경우, 검사한 타입으로 형변환이 가능함.

-  false일 경우, 형변환이 불가능하거나, 참조변수 값이 null임.

 

참조변수 instanceof 클래스명
class Parent{}

class Child extends Parent{}
class Brother extends Parent{}

public Class Main{
	public static void main(String[] args){
    
    	Parent p = new Parent();
        System.out.println(p instanceof Parent); //true
        System.out.println(p instanceof Child); // false
        
        Parent p2 = new Child();
        System.out.println(p2 instanceof Parent); //true
        System.out.pritnln(p2 instanceof Child); //true
    }
}

 

 

 

 

6. 추상클래스(abstract class)

 

6-1. 추상클래스

- 구체적이지 않은 추상적인 데이터를 담고 있는 클래스.

- 일반 클래스와 달리 인스턴스화가 불가능한 클래스(인스턴스 생성 불가능).

- 즉, 하나 이상의 추상 메서드(미완성 메서드)를 포함하는 클래스.

- 추상 클래스를 상속받는 자손 클래스는 오버라이딩을 통해 추상 클래스의 추상 메서드를 모두 구현해야 함.

 

 

 

6-2. 추상 메서드

- 추상 메서드란 선언부만 작성하고 구현부는 작성하지 않은 미완성 메서드임.

- 미완성인 구현부는 해당 추상 메서드를 상속받은 클래스에서 구현함.

abstract class Player{ //추상클래스, 추상 메서드를 하나 이상 가짐.
	abstract void play(int pos); //추상 메서드
    abstract void stop();        //추상 메서드
}

abstract class AbstractPlayer extends Player{ //추상 메서드를 모두 완성하지 않아 추상클래스
	void play(int pos){ /*구현*/} //추상 메서드 구현
}

class ChildPlayer extends Player{ //추상 메서드를 모두 완성함
	void play(int pos){ /*구현*/} //추상 메서드 구현
    void stop() {/*구현*/}        //추상 메서드 구현
}

 

  •  

 

 

7. 인터페이스(interface)

 

7-1. 인터페이스

- 일종의 추상클래스로 틀만이 존재함.

- 추상화 정도가 높아 오직 추상메서드와 상수만을 멤버로 가짐.

- 인터페이스의 필드는 상수로만 정의가능함.

- 모든 멤버변수는 public static final이어야 하며, 이를 생략가능함(생략 시 컴파일러가 자동 추가).

- 모든 메서드는 public abstract이어야 하며, 이를 생략가능함.

 

 

7-2. 인터페이스 구현

- 인터페이스는 인스턴스를 생성할 수 없으며, 인터페이스가 자손 클래스에 상속되어 구현될 때 인스턴스 생성가능함.

- 여러개를 다중구현 할 수 있음.

interface Movable {
	void move(int x, int y);
}

interface Attackable {
	void attack(Unit u);
}

class Fighter implements Movable, Attackable{  // 다중 구현 가능
	public void move(int x, int y) {/*구현*/}
    public void attack(Unit u){/*구현*/}
}

- 클래스 상속과 인터페이스 구현도 동시에 가능함.

//상속과 구현 동시 가능
class Fighter extends Unit implements Fightable {
	public void move(int , int y) {/*구현*/}
    public void attack(Unit u) {/*구현*/}
}
인터페이스의 추상 메서드는 public abstract가 생략된 상태이기에, 자식 클래스에서 오버라이딩 시에는 반드시 부모의 메서드보다 넓은 범위의 접근 제어자(public)로 설정해주어야 함.

 

 

7-3. 강한 결합과 느슨한 결합

- 인터페이스를 사용하는 주된 이유 중 하나.

 

강한 결합

- 두 클래스가 직접적 관계일 때.

- 위의 그림에서 A는 B에 의존함(A가 B의 메서드를 사용).

- 클래스 A가  클래스 C를 사용하려면, B에 의존적인 코드를 C에 의존하도록 변경해야 함.

 

 

느슨한 결합

- A는 인터페이스 I에 의존하고 있고, I를 구현한 클래스 B를 사용함.

- 이 때, A가 C를 사용하도록 수정하려면, A는 I에 의존하고 있으므로 I를 구현한 클래스 C 사용시 A의 코드를 변경할 필요가 없음.

 

7-4. 인터페이스 상속

- 클래스는 인터페이스를 상속받을 수 없음.

- 인터페이스는 인터페이스끼리만 상속 가능함.

- 여러 개를 다중 상속할 수 있음.

interface Fightable extends Movable, Attackable { } //다중 상속 가능

- 인터페이스의 최고 조상은 Object 클래스가 아님.

 

 

 

7-4. 인터페이스의 장점

독립적인 프로그래밍의 가능

- 인터페이스를 통해 선언과 구현부를 분리시켜 독립적인 프로그래밍이 가능함.

- 즉, 클래스와 클래스 간 직접적인 관계를 인터페이스를 이용한 간접적 관계로 변경함으로써 서로 다른 클래스 간 미치는 영향이 없어짐(객체간 의존성 줄어듦).

 

 

서로 관계 없는 클래스끼리의 관계 형성

- 아무 관계가 없는 클래스들에게 하나의 인터페이스를 공통적으로 구현하게 하여 관계를 형성할 수 있음.

 

 

개발시간 단축

 

표준화 가능

- 프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 후, 인터페이스를 구현함으로써 일관되고 정형화된 프로그램의 개발이 가능함.

- ex) JDBC, API, ...

 

 

 

 

<참고자료>
자바의 정석(3rd edition) - 도우출판
http://www.tcpschool.com/java/java_modifier_accessModifier
https://scshim.tistory.com/228