[자바의 정석 기초편] 객체지향개념 2

JEREGIM·2023년 1월 18일
0

📌상속(Inheritance)

기존의 클래스로 새로운 클래스를 작성하는 것(코드의 재사용)

두 클래스를 부모와 자식으로 관계를 맺어주는 것

class Parent() {} // 부모 관계
class Child extends Parent {} // 자식 관계

자손은 조상(부모의 부모)의 모든 멤버를 상속받는다.(생성자, 초기화블럭 제외)

자손의 멤버 개수는 조상보다 적을 수 없다.(같거나 많다)

자손의 변경은 조상에 영향을 미치지 않는다.

class Parent {
	int age;
}

class Child extends Parent {
	void play() {
    	System.out.println("놀자~");
    }
}

class Point {
	int x;
    int y;
}

1. class Point3D extends Point{      2. class Point3D {
	   int z;								int x;
}											int y;
											int z;
                                        }
  • 상속 받은 1번 Point3D만 부모 클래스 Point의 영향을 받는다.

Point3D p = new Point3D();

  • 두 Point3D 클래스의 구조는 동일하다.

📌포함(Composite)

클래스의 멤버로 참조변수를 선언하는 것

class Point {
	int x;
    int y;
}

1. class Circle {        		  2. class Circle {
	 Point p = new Point();          	int x;
     int r; 							int y;
}   									int r;
									 }
  • 1번 클래스 Circle이 Point를 포함하고 있다.(Circle과 Point는 포함 관계)
    Circle c = new Circle();
  • 두 Circle의 멤버 개수는 동일하지만(x, y, r) 구조가 다르다.
  • 1번이 포함 관계가 있는 클래스 간의 구조

작은 단위의 클래스를 만들고, 이들을 조합해서 클래스를 만든다.


📌클래스 간의 관계 결정

상속 관계 : ~은 ~이다(is-a)
포함 관계 : ~은 ~을 가지고 있다(has-a)

  • 포함 관계가 90% 정도 쓰인다. 잘 모르겠을땐 포함 관계로 설정
  • 상속은 꼭 필요할 때 사용

📌단일 상속(Single Inheritance)

Java는 단일 상속(하나의 부모만 허용)만을 허용한다.

  • 비중이 높은 클래스 하나만 상속 관계로, 나머지는 포함 관계로 한다.
class Tv {
	boolean power;
    int channel;
    
    void power() { power = !power; }
    void channelUp() { ++channel; }
    void channelDown() { --channel; }
}

class DVD {
	boolean power;
    
    void power() { power = !power; }
    void play() {/*내용 생략*/}
    void stop() {/*내용 생략*/}
    void rew() {/*내용 생략*/}
    void ff() {/*내용 생략*/}
}

class TvDVD extends Tv {
	DVD dvd = new DVD();
    
    void play() {
    	dvd.play();
    }
    void stop() {
    	dvd.stop();
    }
    void rew() {
    	dvd.rew();
    }
    void ff() {
    	dvd.ff();
    }
  • Tv 클래스를 상속 관계로, DVD 클래스는 포함 관계로 설정
  • DVD 객체를 만들고 객체 사용을 위한 메서드들을 호출

📌Object 클래스 - 모든 클래스의 조상

부모가 없는 클래스는 자동적으로 Object 클래스를 상속받게 된다.

모든 클래스는 Object 클래스에 정의된 11개의 메서드를 상속받는다.

  • toString(), equals(Object obj), hashCode() 등등
Circle c = new Circle();
System.out.println(c.toString());
System.out.println(c); // c.toString() 과 c 는 같은 값을 출력한다.

📌오버라이딩(Overriding)

상속 받는 조상의 메서드를 자신에 맞게 변경하는 것

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

class Point3D extends Point {
	int z;
    
    String getLocation() { // 오버라이딩, 선언부는 변경 불가
    	return "x= " +x+", y= "+y+", z= "+z; // 내용(구현부)만 변경 가능
    }
}
  • 오버라이딩은 선언부는 변경 불가
  • 내용(구현부)만 변경 가능

오버라이딩의 조건

1. 선언부(반환타입, 메서드 이름, 매개변수 목록)가 조상 클래스의 메서드와 일치해야 한다.
2. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
3. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.


📌오버로딩 vs 오버라이딩

오버로딩(Overloading) : 기존에 없는 새로운 메서드(이름이 같은)를 정의하는 것(new)
오버라이딩(Overriding) : 상속 받은 메서드의 내용을 변경하는 것(modify)

class Parent {
	void parentMethod() {}
}

class Child extends Parent {
	void parentMethod() {}      // 오버라이딩
    void parentMethod(int i) {} // 오버로딩
    
    void childMethod() {}      // 메서드 정의
    void childMethod(int i) {} // 오버로딩
    void childMethod() {}      // 중복정의, 에러
    
}

📌참조변수 super

객체 자신을 가리키는 참조변수, 인스턴스 메서드(생성자)내에만 존재

조상의 멤버를 자신의 멤버와 구별할 때 사용

  • ≒ 참조변수 this : iv와 lv 구별에 사용
class Ex7_2 {
	public static void main(String[] args) {
		Child c = new Child();
		c.method();
	}
}

class Parent { int x=10; } // super.x

class Child extends Parent {
	int x=20; // this.x

	void method() {
		System.out.println("x=" + x);
		System.out.println("this.x=" + this.x);
		System.out.println("super.x="+ super.x);
	}
}

결과
x=20
this.x=20
super.x=10

  • class Parent { int x=10; } : 조상의 멤버 x = super.x
  • 자손 클래스 Child 의 멤버 x = this.x

📌super() - 조상의 생성자

조상의 생성자를 호출할 때 사용

조상의 멤버는 조상의 생성자를 호출해서 초기화

class Point {
	int x, 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) {
    	super(x, y); // 조상클래스의 생성자 Point(int x, int y)를 호출
        this.z = z;
    }
}
  • super(x, y); : 조상클래스의 생성자 Point(int x, int y)를 호출

생성자의 첫 줄에 반드시 생성자를 호출해야 한다.

  • 그렇지 않으면 컴파일러가 생성자의 첫 줄에 super(); 삽입
class Point {
	int x, 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.x = x; this.y = y; this.z = z; }
}

public class PointTest {
	public static void main(String[] args) {
    	Point3D p3 = new Point3D(1, 2, 3);
    }
}    
  • Point3D p3 = new Point3D(1, 2, 3); 에서 에러 발생

Point(int x, int y) { this.x = x; this.y = y; }
Point3D(int x, int y, int z) { this.x = x; this.y = y; this.z = z; }
생성자의 첫 줄에 생성자를 호출하지 않아서 자동으로 첫 줄에 super(); 추가

Point(int x, int y) {
	super(); // Object();
	this.x = x; 
	this.y = y;
}
Point3D(int x, int y, int z) {
	super(); // Point(); 호출
	this.x = x; 
	this.y = y;
    this.z = z;
}
  • Point(); 를 호출하는데 Point 클래스에는 기본 생성자가 없기 때문에 여기서 에러가 나는 것이다.

해결 방법 2가지

  1. Point 클래스에 기본 생성자 추가
class Point {
	int x, y;
    
    Point() {} // 기본 생성자 추가
    Point(int x, int y) { this.x = x; this.y = y; }
}
  1. Point3D 클래스의 생성자를 변경
Point3D(int x, int y, int z) {
	super(x, y); // Point(int x, int y); 호출
    this.z = z;
}
  • 2번의 코드가 맞는 코드이다. 2번의 경우 Point 클래스에 기본 생성자를 추가해주지 않아도 되지만, 그래도 기본 생성자는 필수로 추가해주는게 좋다.

📌제어자(Modifier)

클래스와 클래스의 멤버(멤버변수, 메서드)에 부가적인 의미 부여

  • 접근 제어자 : public, protected, (default), private
  • 그 외 : static, final, abstract, native, transient, synchronized, volatile, strictfp

하나의 대상에 여러 제어자를 같이 사용 가능(접근 제어자는 하나만)
접근 제어자를 제일 왼쪽에 쓰는게 좋다.

static - 클래스의, 공통적인

사용 대상 : 멤버변수, 메서드

class StaticTest {
	static int width = 200;  // 클래스 변수(cv)
    static int height = 100; // 클래스 변수(cv)
    
    static { // 클래스 초기화 블럭
    	// static 변수의 복잡한 초기화 수행
    }
    
    static int max(int a, int b) { // 클래스 메서드(static 메서드)
    	return a + b;
    }
}
  • static 메서드 내에서는 인스턴스 멤버(iv, im) 사용 불가
  • static 변수와 static 메서드는 객체 생성 없이 사용 가능

final - 마지막의, 변경될 수 없는

사용 대상 : 클래스, 메서드, 멤버변수, 지역변수

final class FinalTest { // 조상이 될 수 없는 클래스
	final inx Max = 10; // 값을 변경할 수 없는 변수(상수)
    
    final void getMaxSize() { // 오버라이딩할 수 없는 메서드
    	final int LV = Max;
        return Max;
    }
}
  • final로 지정된 클래스는 다른 클래스의 조상이 될 수 없다.
    (대표적인 final 클래스 : String, Math 클래스 등)
  • final로 지정된 메서드는 오버라이딩을 통해 재정의 될 수 없다.
  • 변수 앞에 final이 붙으면, 값을 변경할 수 없는 상수가 된다.

abstract - 추상의, 미완성의

사용 대상 : 클래스, 메서드

abstract class AbstractTest {
	abstract void move();
}

AbstractTest a = new AbstractTest(); // 에러
  • 추상 메서드 : 구현부가 없는 메서드, 미완성 메서드
  • 추상 클래스 : 추상 메서드를 포함한 클래스, 미완성 클래스
  • 추상 클래스는 인스턴스(객체) 생성 불가
    -> 추상 클래스를 상속받아서 완전한 클래스를 만든 후 객체 생성 가능

📌접근 제어자(Access modifier)

private : 같은 클래스 내에서만 접근 가능

(default) : 같은 패키지 내에서만 접근 가능(아무것도 쓰지 않는 것)

protected : 같은 패키지 + 다른 패키지의 자손 클래스에서 접근 가능

public : 접근 제한 x

  • 클래스 앞에는 public, (default) 2가지만 붙일 수 있다.
  • 멤버(변수, 메서드) 앞에는 4가지 모두 붙일 수 있다.

📌캡슐화와 접근 제어자

접근 제어자를 사용하는 이유
-> 외부로부터 데이터를 보호하기 위해서(캡슐화)

public class Time {
	public int hour, minute, second; // 접근 제한 x
}

Time t = new Time();
t.hour = 25; // 멤버변수에 직접 접근	
  • hour(시간)의 범위는 0~23 인데 범위 외의 값이 들어가는걸 보호할 수 없다.
class Time {
    private int hour, minute, second;

    public void setHour(int hour) { 
        if(isNotValidHour(hour)) return;
        this.hour = hour;
    }

    private boolean isNotValidHour(int hour) {
        return hour < 0 || hour > 23;
    }

    public int getHour() { return hour; }
}

public class TimeTest {
    public static void main(String[] args) {
        Time t = new Time();
        t.setHour(21);
        System.out.println(t.getHour());
        t.setHour(210);
        System.out.println(t.getHour());
    }
}

private int hour, minute, second;
외부에서 접근하지 못하도록 private로 지정

public void setHour(int hour) { 
        if(isNotValidHour(hour)) return; 
        this.hour = hour;
    }
  • 메서드를 public 으로 설정하여 간접 접근 허용
  • 매개변수 hour의 범위가 유효한지 확인하기 위한 메서드 생성
private boolean isNotValidHour(int hour) {
        return hour < 0 || hour > 23;
}

접근 제어자의 범위는 가능한 최소한으로 설정(private)
-> 나중에 코드를 수정하고 확인할 때 접근 제어자의 범위만 확인하면 되기 때문에 시간을 절약할 수 있다.

0개의 댓글