[Java] - abstract class, Interface, Generic

chancehee·2022년 12월 8일
0

자바

목록 보기
8/12
post-thumbnail

0. 글을 작성하는 이유

Java에서 다형성 및 여러 이점을 제공하는 고마운 친구들이 있습니다.
추상 클래스, 인터페이스, 제너릭 3인방을 공부하며 장점들을 흡수하여 코드를 작성하고자 하여 글을 작성합니다.

1. abstract class(추상 클래스)

하나 이상의 추상 메서드를 포함한 클래스입니다.

[특징]

  • 단 하나 이상의 추상 메서드만 포함하면 되며 생성자, 일반 메서드도 포함 가능합니다.
  • 추상 클래스와 인터페이스의 다른 점은 인터페이스는 추상 메서드만 포함하나, 추상 클래스는 하나의 추상 메서드만 포함하면 추상 클래스가 됩니다.
  • 추상 클래스는 객체를 생성할 수 없습니다. (= abstract 키워드를 붙인 이유)
  • 상속 전용의 클래스입니다.(부모의 추상 메서드를 자식이 반드시 Overriding 해줘야 합니다.)

[예시]

abstract class Pet{
	public abstract void eat();
}

[장점]

  • "자식 클래스에서 반드시 재정의가 되어야 된다"는 점에서 다형성을 보장합니다.
  • 구현의 강제를 통해 프로그램의 안정성을 향상 시킵니다.
  • interface에 있는 메서드 중 구현할 수 있는 메서드를 구현해 개발의 편의를 지원합니다.

2. 추상 메서드

함수 선언만 되어있고 구현부가 없는 메서드입니다.

[특징]

  • 자식 클래스에서 구현이 반드시 이뤄줘야 하기 때문에 private 접근제한자는 사용할 수 없습니다.

[예시]

public abstract void eat();

3. Interface(인터페이스)

다른 클래스를 작성할 때 기본이 되는 틀을 제공하며, 다른 클래스 사이의 중간 매개 역할까지 담당하는 일종의 추상 클래스를 의미합니다. (다중 상속의 이점)

[특징]

  • [오리지널 인터페이스] 최고 수준의 추상화 단계: 일반 메서드는 모두 abstract 형태입니다.
  • JDK 8 에서 default method와 static method가 추가되었습니다.
  • 모든 멤버변수는 public static final이며 생략 가능합니다.
  • 모든 메서드는 Public abstract이며 생력 가능합니다.
  • 클래스와 마찬가지로 인터페이스도 extends를 이용해 상속이 가능합니다.
  • 클래스와 다르게 다중 상속이 가능합니다.
  • 추상 메서드와 상수만을 포함합니다.

[예시]

public interface PET {
	public static final int MALE = 1;
    int FEMALE = 2; // 생략 가능
    
    public abstract void eat();
    void run(); // 생략 가능 
}

[장점]

  • 서로 상속의 관계가 없는 클래스들에게 인터페이스를 통한 관계 부여로 다형성 확장이 가능합니다.(느슨한 결합)
  • 독립적인 프로그래밍으로 개발 기간을 단축할 수 있습니다.
  • 대규모 프로젝트 개발 시 일관되고 정형화된 개발을 위한 표준화가 가능합니다.

4. default method

인터페이스에 선언 된 구현부가 있는 일반 메서드입니다.

[특징]

  • 접근 제한자는 public만 가능합니다.(생략 가능)
  • 인터페이스에 있는 메서드의 구현부 작성이 가능합니다.
  • default method가 추가된 이후, 추상클래스의 활용도가 줄어들었습니다.

[예시]

public interface PET {
	public static final int MALE = 1;
    int FEMALE = 2; // 생략 가능
    
    public abstract void eat();
    void run(); // 생략 가능 
    
    // 시나리오: 펫이 진화해서 기본적으로 날 수 있습니다. ㄷㄷ 
    default void fly() {
    	System.out.print("날아 오르기");
    }
}

[필요성]
1. 기존에 interface 기반으로 동작하는 라이브러리의 interface에 추가해야 하는 기능이 발생
2. 기존 방식으로라면 모든 구현체들이 추가되는 메서드를 Override 해야 합니다.
3. default 메서드는 abstract가 아니므로 반드시 구현 해야 할 필요는 없습니다.

[default 메서드의 충돌] = [다이아몬드 문제]

  • JDK 1.8 부터 default method가 생기면서 동일한 이름을 갖는 구현부가 있는 메서드가 충돌할 수 있습니다.
  • method 우선 순위
    - super class의 메서드 우선: super class가 구체적인 메서드를 갖는 경우 default method는 무시됩니다.
    - interface 간의 충돌: 하나의 interface에서 default method를 제공하고 다른 interface에서도 같은 이름의 메서드(default 유무와 무관)가 있을 때 sub class는 반드시 Override 해서 충돌을 해결합니다.

5. static method

interface에 선언된 static method 입니다.

[특징]

  • 일반 static 메서드와 마찬가지로 별도의 객체가 필요 없습니다.
  • 구현제 클래스 없이 바로 인터페이스 이름으로 메서드에 접근해서 사용 가능합니다.

[예제]

interface Pet {
	static void petInfo() {
    	System.out.println("pet을 사랑으로 돌봐주세요~~.");
    }
}

public class StaticMethodTest {
	public static void main(String[] args) {
    	Pet.petInfo(); // 인터페이스의 이름으로 메서드에 접근합니다.
    }
}

6. Generics

클래스 또는 인터페이스 등을 선언 시 <>에 타입 파라미터 표시하는 것을 말하며, 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법입니다.

[타입 파라미터]

  • 특별한 의미의 알파벳 보다는 단순히 임의의 참조형 타입을 말합니다.
  • T: reference Type
  • E: Element
  • K: Key
  • V: Value

[와일드 카드]

  • Generic Type 객체를 할당 받을 때 와일드카드(?)로 사용이 가능합니다.
  • 구체적인 타입 대신 사용합니다. (뭐가 들어올지 모를 때)
  • Spring에서 반환타입으로 ResponseEntity<?>로 사용한 경험이 있는데, 사실 ?로 사용하기 보다는 타입을 명시해 주는 것이 코드 유지보수에 더 좋은 것으로 알고있습니다.

[특징]

  • 변수 쪽과 생성 쪽의 타입은 반드시 같아야 합니다.

[예시]

public class Generic<T> { // T는 임의의 타입입니다.
	private T something;
    
    // 기본 생성자 
    public Generic() {}
    
    public Generic(T something) {
    	super();
        this.something = something;
    }
    
    public setSomething(T something) {
    	this.something = something;
    }
}

// int, long, double ... 등 의 타입을 담고 싶다면??
public class NumberBox<T extends Number> {
	public void addSomethings(T... ts) {
    	double d = 0;
        for (T t : ts) {
        	d += t.doubleValue();
        }
        System.out.println("총 합: " + d);
    }
}

public class GenericTest {
	public static void main(String[] args) {
    	Generic<String> sbox = new Generic<>();
        sbox.setSomething("Hello"); // compile 시점에 타입에 대한 체크가 발생합니다!! (런타임이 아니라 ㄷㄷ)
        
        NumberBox<Number> numBox = new NumberBox<>();
        numBox.addSomethings(1.5, 2, 3L);
    }
}

[장점]

  • 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있습니다.
  • 클래스 외부에서 타입을 지정해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없습니다.(관리하기 용이)
  • 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아집니다.

7. 결론

Java에서는 유지보수성이 높은 코드 작성을 위한 많은 도구들이 있습니다.
결국, 개발자는 이런 좋은 도구들을 잘 활용하여 최적의 코드를 작성하면됩니다.😎

0개의 댓글