TIL

TIL_20220906

번잔중 2022. 9. 6. 21:26

오늘 할 일

☕️ 코드스테이츠 13일차

  • 상속
  • 캡슐화
  • 1일 1커밋
  • 밤 9시 알고리즘 스터디

오늘 배운 것

💊 상속(Inheritance)과 캡슐화(Encapsulation)

객체지향의 네 기둥이라 불리는 상속, 캡슐화, 다형성, 추상화(캡! 상추다!) 중 상속과 캡슐화에 대해 공부했다. 

상속(Inheritance)

기존의 클래스를 재사용하여 새로운 클래스를 작성하는 자바의 문법 요소를 의미한다. extends 키워드를 사용하여 두 클래스 간의 상속 관계를 설정한다.
  • 두 클래스를 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버(필드, 메서드, 이너 클래스)를 하위 클래스와 공유하는 것을 의미한다.
  • 이 두 클래스를 서로 상속 관계에 있다고 하며, 하위 클래스는 상위 클래스가 가진 모든 멤버를 상속받는다.
  • 하위 클래스의 멤버 개수 ≥ 상위 클래스의 멤버 개수
  • “~클래스로부터 확장되었다"는 표현이 역할과 기능을 생각했을 때, 더 적절한 표현이다.
  • 자바에서는 단일 상속(signle inheritance)만을 허용한다. 다중 상속의 경우 인터페이스(interface)라는 문법 요소를 통해 비슷하게 구현할 수 있다.

장점

  • 코드를 재사용하여 보다 적은 양의 코드로 새로운 클래스를 작성할 수 있어 코드의 중복을 제거할 수 있다.
  • 하나의 객체를 여러 모양으로 표현하는 다형적 표현이 가능하다.

예시)

class Animal {
	String name;
	int legs;

	void bite() {
		System.out.println("물었습니다.");
	};
	void bark() {
		System.out.println("짖습니다.");
	};
	void tear() {
		System.out.println("찢습니다.");
	};
}

class Tiger extends Animal { // Animal 클래스로부터 상속, extends 키워드
	String nails;

	void run() { 
		System.out.println("먹이를 쫓습니다.");
	};
}

 

포함 관계

포함(composite)은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조변수를 선언하는 것을 의미한다.

 

포함 관계와 상속 관계를 판별하는 기준

  • ~은 ~이다.(IS-A): 상속
  • ~은 ~을 가지고 있다.(HAS-A): 포함

예시)

  • 상속: SportsCar는 Car이다.(SportsCar는 Car를 가지고 있다. ❌)
  • 포함: Employee는 Address를 가지고 있다.(Employee는 Address이다.❌)
public class Employee {
    int id;
    String name;
    Address address;

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city+ " " + address.country);
    }

    public static void main(String[] args) {
        Address address1 = new Address("서울", "한국"); // 참조 변수 선언
        Address address2 = new Address("도쿄", "일본");

        Employee e = new Employee(1, "김코딩", address1);
        Employee e2 = new Employee(2, "박해커", address2);

        e.showInfo();
        e2.showInfo();
    }
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

// 출력값
1 김코딩
서울 한국
2 박해커
도쿄 일본

 

메서드 오버라이딩

상위 클래스로부터 상속받은 메서드와 동일한 이름의 메서드를 재정의하는 것을 의미한다.
class Vehicle {
    void run() {
        System.out.println("탈것은 달린다!");
    }
}

public class Bike extends Vehicle {
    void run() {
        System.out.println("자전거는 달린다!"); // 메서드 오버라이딩
    }

    public static void main(String[] args) {
        Bike bike = new Bike();
        bike.run();
    }
}

// 출력값
"자전거는 달린다!"

조건

  1. 메서드의 선언부(메서드 이름, 매개변수, 반환타입)이 상위 클래스의 메서드 선언부와 완전히 일치해야 한다.
  2. 접근 제어자의 범위가 상위 클래스의 메서드와 같거나 그보다 더 넓어야 한다.
  3. 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.
public class Main {
    public static void main(String[] args) {
        Bike bike = new Bike(); // 각각의 타입으로 선언 + 각각의 타입으로 객체 생성
        Car car = new Car();
        MotorBike motorBike = new MotorBike();
        
		bike.run();
        car.run();
        motorBike.run();

		Vehicle bike2 = new Bike(); // 상위 클래스 타입으로 선언 + 각각 타입으로 객체 생성
        Vehicle car2 = new Car();
        Vehicle motorBike2 = new MotorBike();

        bike2.run();
        car2.run();
        motorBike2.run();
				
		// 배열로 한번에 관리하기
		Vehicle[] vehicles = new Vehicle[] { new Bike(), new Car(), new MotorBike()};
		for (Vehicle vehicle : vehicles) {
			vehicle.run();
				}
    }
}

class Vehicle {
    void run() {
        System.out.println("Vehicle is running");
    }
}

class Bike extends Vehicle {
    void run() {
        System.out.println("Bike is running");
    }
}

class Car extends Vehicle {
    void run() {
        System.out.println("Car is running");
    }
}

class MotorBike extends Vehicle {
    void run() {
        System.out.println("MotorBike is running");
    }
}

// 출력값
Bike is running
Car is running
MotorBike is running

참조 변수 bike2, car2, motorBike2는 모두 Vehicle 타입이지만 메서드 오버라이딩을 통해 각각의 run() 메서드가 다른 출력값을 보여준다.

 

super 키워드와 super()

this와 this()의 관계와 유사하다. super는 상위 클래스의 객체, super() 메서드는 상위 클래스의 생성자 호출을 의미한다.

공통적으로 모두 상위 클래스의 존재를 상정하며 상속 관계를 전제로 한다.

  • 두 클래스 간에 상속 관계가 있을 때, 하위 클래스가 상위 클래스로부터 특정 변수를 상속받는다. 이 때, 하위 클래스의 인스턴스 변수와 이름이 같은 경우가 있다.
  • 두 개의 같은 이름의 변수를 구분하기 위한 방법이 super 키워드이다.
  • super 키워드가 없다면 자바 컴파일러에서 해당 객체는 자신이 속한 인스턴스 객체의 멤버를 먼저 참조한다.
  • super 키워드를 붙이면 부모의 객체의 멤버 값을 참고할 수 있다.
  • super() 메서드는 생성자 안에서만 사용 가능하고, 반드시 첫 줄에 와야 한다.

⭐ 모든 생성자의 첫 줄에는 반드시 this() 또는 super()가 선언되어야 한다. 만약 없다면 컴파일러가 생성자의 첫 줄에 자동으로 super() 메서드를 삽입해준다. 이 때, 상위 클래스에 기본 생성자가 없으면 에러가 발생한다.

→ 클래스를 만들 때는 자동으로 기본 생성자를 생성하는 것을 습관화하자!!

 

클래스의 정점, Object 클래스

자바의 클래스 상속계층도에서 최상위에 위치한 상위클래스이다. 따라서 자바의 모든 클래스는 Object 클래스로부터 확장된다. 자바 컴파일러는 컴파일링의 과정에서 다른 클래스로부터 아무런 상속을 받지 않는 클래스에 자동적으로 extends Object를 추가하여 Object 클래스를 상속받도록 한다.
class ParentEx {  //  컴파일러가 "extends Object" 자동 추가 

}

class ChildEx extends ParentEx {

}

Object 클래스는 자바 클래스의 상속계층도에 가장 위에 위치하기 때문에 Object 클래스의 멤버들을 자동으로 상속받아 사용할 수 있다.

 

Ojbect 클래스의 대표적인 메서드

메서드명 반환타입 주요 내용
toString() String 객체 정보를 문자열로 출력
equals(Object obj) boolean 등가 비교 연산(==)과 동일하게 스택 메모리값을 비교
hashCode() int 객체의 위치정보 관련. Hashtable 또는 HashMap에서 동일 객체여부 판단
wait() void 현재 쓰레드 일시정지
notify() void 일시정지 중인 쓰레드 재동작

 

캡슐화(Encapsulation)

특정 객체 안에 관련된 속성과 기능을 하나의 캡슐(capsule)로 만들어 데이터를 외부로부터 보호하는 것이다. 내용물이 변하거나 새어나가지 않게 만든 캡슐형 알약과 흡사하다!

캡슐화의 목적

  1. 데이터 보호(data protection): 외부로부터 클래스에 정의된 속성과 기능 보호
  2. 정보 은닉(data hiding): 내부적으로만 사용되는 데이터에 대한 불필요한 외부 노출 방지 

캡슐화의 장점

  • 정보 은닉(data hiding) 및 독립성 확보: 외부로부터 객체의 속성과 기능이 변경되지 못하게 막고, 데이터가 변조된다고 하더라도 다른 객체에는 영향을 주지 않기 때문이다.
  • 효과적인 코드 유지보수: 유지보수와 코드 확장 시에도 오류의 범위를 최소화하기 때문이다.

패키지

특정한 목적을 공유하는 클래스와 인터페이스의 묶음을 의미한다. 자바에서 패키지는 물리적인 하나의 디렉토리(directory)이고, 하나의 패키지에 속한 클래스나 인터페이스 파일은 모두 해당 패키지에 속해 있다.

이 디렉토리(패키지)는 하나의 계층구조를 가지고 있고, 계층 구조간 구분은 점(.)으로 표현된다.

또한 패키지가 있는 경우 소스 코드의 첫 번째 줄에 반드시 package 패키지명이 표시되어야 하고, 만약 패키지 선언이 없으면 이름없는 패키지에 속하게 된다.

// 패키지를 생성했을 때
package practicepack.test; // 패키지 구문 포함. 패키지가 없다면 구문 필요없음

public class PackageEx {

}

패키지 사용 목적

  • 효과적인 관리: 클래스들을 그룹 단위로 묶어서 효과적으로 관리한다. 마치 클래스를 정의할 때 관련있는 속성과 기능을 묶어 데이터들을 효율적으로 관리하는 것과 흡사하다.
  • 클래스의 충돌 방지: 같은 이름의 클래스를 가지고 있다고 해도 다른 패키지에 속해 있다면 클래스끼리 충돌이 일어나지 않는다.

대표적인 패키지 종류

  • java.lang: 자바의 기본 클래스를 모아놓은 패키지
  • java.util: 자바의 확장 클래스를 묶어놓은 패키지
  • java.io 또는 java.nio: 자바의 입출력 관련 클래스를 묶어놓은 패키지

Import문

다른 패키지 내의 클래스를 사용하기 위해 사용한다. 패키지 구문과 클래스문 사이에 작성하는 것이 일반적이다.
// Import문 사용
// import 패키지명.클래스명; -> 하나 혹은 소수의 클래스 사용시
// import 패키지명.*; -> 여러 개의 클래스 사용시

// 패키지1
package practicepack.test;

public class ExampleImport {
		public int a = 10;
		public void print() {
			System.out.println("Import문 테스트")
}

// import문을 사용하지 않는 경우, 다른 패키지 클래스 사용방법
package practicepack.test2; 

public class PackageImp {
		public static void main(String[] args) {
			practicepack.test.ExampleImport example = new practicepack.test.ExampleImport();
		}
}

// import문을 사용하는 경우
package practicepack.test2; 

import practicepack.test.ExampleImp // import문 작성

public class PackageImp {
		public static void main(String[] args) {
			ExampleImp x = new ExampleImp(); // 이제 패키지명을 생략 가능
		}
}

Import문을 사용하면 작성해야 하는 코드의 길이보다 줄어들고 깔끔해진다.

접근 제어자

제어자(Modifier)

클래스, 필드, 메서드, 생성자 등에 부가적인 의미를 부여하는 키워드이다. 국어에서 형용사 역할을 한다고 보면 된다!

자바에서의 제어자는 크게 접근 제어자와 기타 제어자로 구분할 수 있다.

접근 제어자 public, protected, default, private
기타 제어자 static, final, abstract, native, transient, synchronized 등

제어자는 하나의 대상에 대해 여러 제어자를 사용할 수 있지만 접근 제어자는 한 번만 사용할 수 있다.

접근 제어자(Access Modifier)

접근 제어자를 사용하면 클래스 외부로의 불필요한 데이터 노출을 방지(data hiding)할 수 있고, 외부로부터 데이터가 임의로 변경되지 않도록 막을 수 있다. 즉, 접근 제어자가 캡슐화에 중요한 키라는 것이다!

자바의 접근 제어자

접근 제어자 접근 제한 범위
private 동일 클래스에서만 접근 가능
default 동일 패키지 내에서만 접근 가능
protected 동일 패키지 + 다른 패키지의 하위 클래스에서 접근 가능
public 접근 제한 없음

위의 내용을 보면 private에서 public으로 갈수록 제한 범위가 줄어든다. 이중 default는 접근 제어자를 붙이지 않는 경우의 기본 설정을 의미한다.

 

예시) 코드를 통해 알아보는 접근 제어자

package package1; // 패키지명 package1 

//파일명: Parent.java
class Test { // Test 클래스의 접근 제어자는 default
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // 동일 클래스가 아니기 때문에 에러발생!
        System.out.println(p.b);
        System.out.println(p.c);
        System.out.println(p.d);
    }
}

public class Parent { // Parent 클래스의 접근 제어자는 public
    private int a = 1; // a,b,c,d에 각각 private, default, protected, public 접근제어자 지정
    int b = 2;
    protected int c = 3;
    public int d = 4;

    public void printEach() { // 동일 클래스이기 때문에 에러발생하지 않음
        System.out.println(a);
        System.out.println(b);
        System.out.println(c);
        System.out.println(d);
    }
}

// 출력값
2
3
4
package package2; // package2 

//파일명 Test2.java
import package1.Parent;

class Child extends package1.Parent {  // package1으로부터 Parent 클래스를 상속
    public void printEach() {
        // System.out.println(a); // 에러 발생!
        // System.out.println(b);
        System.out.println(c); // 다른 패키지의 하위 클래스
        System.out.println(d);
    }
}

public class Test2 {
    public static void main(String[] args) {
        Parent p = new Parent();

//        System.out.println(p.a); // public을 제외한 모든 호출 에러!
//        System.out.println(p.b);
//        System.out.println(p.c);
        System.out.println(p.d);
    }
}

상기 코드를 그림으로 옮기면 아래와 같다.

  • private: 해당 클래스 내에서만 접근이 가능
  • default: 같은 패키지 내에 있으면 접근 가능
  • protected: 다른 패키지의 하위 클래스까지 접근 가능(상속 관계가 있어야 한다는 것이 전제이다.)
  • public: 자유이용권

getter와 setter 메서드

캡슐화 + 데이터 변경이 필요한 경우에 사용한다. private 접근 제어자가 포함되어 있어도 데이터를 변경할 수 있다. 즉, 캡슐화를 깨지 않으면서 데이터를 변경할 수 있는 방법이다. 두 메서드를 활용하면 데이터를 보호하면서도 의도하는 값으로 값을 변경하여 캡슐화를 보다 효과적으로 달성할 수 있다.

예시) getter와 setter 메서드를 통한 private 접근 제어자의 값 접근 및 변경

public class GetterSetterTest {
    public static void main(String[] args) {
        Worker w = new Worker();
        w.setName("찬붕");

        String name = w.getName();
        System.out.println("근로자의 이름은 " + name);
    }
}

class Worker {
    private String name; // 변수의 은닉화. 외부로부터 접근 불가

    public String getName() { // 멤버변수의 값 
        return name;
    }

    public void setName(String name) { // 멤버변수의 값 변경
        this.name = name;
    }
}

// 출력값
근로자의 이름은 찬붕

setter 메서드

외부에서 메서드에 접근하여 조건에 맞을 경우 데이터 값을 변경가능하게 해준다. 일반적으로 메서드명에 set-을 붙여서 정의한다.

 

getter 메서드

setter 메서드에서 설정한 값을 읽어노는 데에 사용하는 메서드이다. 경우에 따라 객체 외부에서 필드 값을 사용하기에 부적절한 경우가 발생할 수 있는데, 이런 경우에 이런 값을 가공한 이후에 외부로 전달 하는 역할을 한다. 메서드명 앞에 get-을 붙여서 사용한다.

느낀점

  • 학교다니면서 분명 배웠던 내용이고 시험도 봤었는데, 제대로 공부한건 아니었나보다. 오늘 수업 교재를 읽어보는 것이 자바에 한해서는 이해도가 훨씬 높았던 것 같다.
  • 객체지향프로그래밍 심화 단계까지 금새 달려왔는데, 코드를 쳐보면서 조금씩 이해가 갔다. 이번 연휴에는 코드를 많이 작성해보면서 공부하는 것이 목표가 됐다.
  • 1일 1커밋이 생각보다 빡세다. 자바로는 아직 알고리즘 문제를 풀 수 있을만큼 익숙하지가 않다고 느껴서인지 다소 버겁기도 하다. 그래도 어쩌겠습니까 해야지!(견뎌)

내일 할 일

🐳 코드스테이츠 14일차

  • 다형성
  • 추상화
  • 1일 1커밋