자바의 정석 챕터7 스터디 발표 정리

Louie·2021년 12월 31일

상속

자바에서는 내부적으로 자식 클래스의 인스턴스가 생성되기 전에 부모 클래스의 인스턴스가 먼저 생성된다.
하지만 생성자는 상속을 받지 않기 때문에 자식 클래스의 생성자에서 자동으로 부모 클래스의 기본 생성자를 호출한다.
그런데 부모 클래스의 생성자가 매개변수를 갖고 있다면 자동으로 불러오지 않으며
자식 클래스의 인스턴스를 생성할 때 부모 클래스의 인스턴스가 생성되지 못해서 컴파일 에러가 발생한다.
이러한 이유로 부모 클래스에서 기본 생성자가 없다면 직접 자식 클래스의 생성자에서 super()를 통해 부모 클래스의 생성자를 선언해 줘야한다.
여기서 부모 클래스의 인스턴스가 먼저 생성되기 때문에 생성자의 첫번째 줄에 super()를 넣어줘야 한다.

new 자식클래스() -> 부모클래스 인스턴스 생성 -> 자식클래스 인스턴스 생성

자식클래스() {
    super() //생략
}

추상 클래스와 인터페이스

공통점

  • 인스턴스 생성 불가
  • 자식 클래스가 반드시 추상 메서드를 구현 해야한다.

차이점

  • 인터페이스
    • 다중 상속
    • 접근 제어자는 필드의 경우 상수(public static final), 메서드는 public abstract로 고정되어 있다.
    • default, static 메소드를 통해 구현할 수 있지만 대부분 선언만 한다.
      • 여기서 인터페이스는 기본적으로 public 접근제어자가 붙기 때문에
        default는 접근 제어자가 아닌 인터페이스에서 구현을 한다고 알려주는 키워드입니다.
  • 추상 클래스
    • 단일 상속
    • 접근 제어자를 자유롭게 사용할 수 있다.
    • 공통적인 부분을 구현한 상태에서 확장을 한다고 생각하면 좋을 것 같다.

추상 클래스와 인터페이스 중 무엇을 사용해야 할지 모르겠다면?

  • 자식 클래스가 얼마나 구현해야 되는지에 따라 달라질 것 같다.
  • 예를 들어 person을 부모 클래스이고 man, woman이 자식 클래스라면
    추상 클래스로 선언해서 남자와 여자의 공통된 부분을 구현하고 다르지만 필요한 기능을 추상 메서드로 정의한 뒤
    man과 woman이 추상 메서드를 구현하고 더 필요한 부분을 추가시킬 겁니다.
  • 할인 정책(특정 가격 할인, 가격에 따른 % 할인)이나 리포지토리(MemoryRepository, JdbcRepository) 등 필드의 값이 정해져있고 처음부터 끝까지 모두 구현해야 하는 경우가 있습니다.
  • 이처럼 추상 클래스는 어느정도 구현이 되어있는 상태에서 확장(extends) 하지만
    인터페이스는 해당 기능을 모두 구현시켜야 합니다.

inner class

inner class는 프로젝트의 복잡도가 늘어날 수 있기 때문에
중요하지 않고 한 클래스에서만 사용하는 클래스를 inner class로 정의하면 좋다고 생각이 들었다.
inner class의 종류는 크게 inner class와 inner static class로 나누어 진다.
검색을 통해 이펙티브 자바와 여러 블로그에서 inner class를 inner static class로 정의해야 된다는 것을 알게 되었다.
inner static class로 정의해야 하는 이유

  • inner class는 외부 클래스를 참조 하기 때문에 인스턴스 생성시 시간이 오래걸린다.
  • 외부 인스턴스에 대한 참조가 유지되고 있기 때문에, 가비지 컬렉션이 인스턴스 수거를 못해서 메모리 누수가 생길 수 있다.

추상 클래스

추상 클래스는 추상 메서드가 없어도 추상 클래스로 선언할 수 있다.
다음은 추상 클래스가 인스턴스를 생성하지 못하는 것을 이용한 예제이다.

@Entity
@Getter
@Setter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public abstract class Item {

    @Id
    @GeneratedValue
    @Column(name = "item_id")
    private Long id;

    private String name;
    private int price;
    private int stockQuantity;
    //생략

Item 추상 클래스를 통해 상속받는 자식 클래스들의 공통 부분을 제거하고
다형성을 통해서 데이터베이스에서 조회할 때 사용할 예정이다. (JPA에서는 다형성을 통한 조회가 가능하다.)
여기서 Item의 자식 클래스만 객체를 생성해서 사용할 것 이라면 부모 클래스의 인스턴스를 생성하지 않을 것이다.
만약 부모 클래스의 인스턴스를 생성하고 싶지 않다면 다음과 같이 추상 클래스로 만들자.

static import

static import를 사용하면 불러오는 클래스의 static멤버(변수와 메서드)를 클래스명 없이 접근할 수 있게 해준다.

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class importTests {
    @Test
    void default_import() {
        Assertions.assertThat("기본 import").isEqualTo("기본 import");
        Assertions.assertThat(true).isTrue();
        Assertions.assertThat(false).isFalse();
    }

    @Test
    void static_import() {
        assertThat("static import").isEqualTo("static import");
        assertThat(true).isTrue();
        assertThat(false).isFalse();
    }
}

첫 번째 메소드는 평소처럼 import를 해서 작성한 코드이고
두 번째 메소드는 static import를 사용해서 작성한 코드이다.

테스트 코드에서는 대부분 Assertions 클래스를 사용해서 검증하기 때문에
테스트 코드를 작성할 때 static import를 주로 사용한다.
자주 사용하는 클래스를 static import로 불러오면 코드를 보기 쉽게 작성할 수 있다.
static import는 Intellij에서 alt + Enter를 누르면 나오는 메뉴에서 편리하게 사용할 수 있다.

기본 생성자 + setter를 통한 객체 생성 후 값 입력 방식

객체 생성 시 값을 입력받는 필드는 모두 setter를 정의해줘야 하기 때문에 지양한다.
대신 생성자를 통해 필드에 값을 넣어주거나 빌더 패턴을 사용하는게 좋을 것 같다.

익명클래스

한번만 사용하는것을 객체로 만든다면 클래스 파일의 개수가 증가하게 되면서 프로젝트의 복잡도만 늘어나게 된다.

profile
백엔드 개발자를 준비하고 있는 Louie입니다.

0개의 댓글