Java - 몇 가지 규칙(3)

김형석·2025년 1월 13일

LG CNS AM Inspire Camp

목록 보기
9/18

1. 상속

1-1. 개념

  • 자식 객체는 부모 객체의 타입으로 선언할 수 있다.
  • 자식 객체를 부모 객체의 타입으로 선언한다면, 배열 등을 이용해서 하나의 변수로 관리할 수 있다.
  • 이때 메서드 오버라이딩, 캐스팅 등을 활용한다.

1-2. 메서드 오버라이딩 시 주의할 점

  • 자식 클래스, 메서드의 접근 지정자는 부모 클래스, 메서드의 접근 지정자보다 범위가 넓거나 같아야 한다.

1-2-1. 예시(부모 클래스의 메소드 -> 자식 클래스의 메소드)

  • public -> public(만 가능)
  • protected -> public, protected
  • default -> public, protected, default
  • private -> public, protected, default, private (이론상으로 가능하나, 실제로는 발생하지 않음)
    참고) private 메서드는 선언된 클래스 내부에서만 사용 가능하다. 이는 private 메서드는 상속되지 않으므로, Override 또한 되지 않는다는 것을 의미한다. 즉, 이 경우에는 각 클래스에 동일한 메서드가 별도로 정의되는 것이다.

1-2-2. 접근 지정자를 잘못 지정한다면?

  • Cannot reduce the visibility of the inherited method for ~ 에러가 발생한다.

1-3. 필드의 중복

1-3-1. 인스턴스 필드의 중복

1-3-1-1. 개념

  • 부모 클래스와 자식 클래스에서 같은 이름의 필드를 가지는 경우에 고려되는 문제이다.

1-3-1-2. 어떻게 적용되는가?

  • 자식 클래스는 선언한 타입에 따라 사용하는 필드가 달라진다.
    • 부모 클래스 타입으로 선언하면 부모 클래스의 필드를 사용
    • 자식 클래스 타입으로 선언하면 자식 클래스의 필드를 사용
    • 자식 클래스를 부모 클래스 타입으로 선언한 경우, 다운캐스팅해야 자식 클래스의 멤버를 사용할 수 있다.

1-3-2. 정적 필드의 중복

1-3-2-1. 개념

  • 부모 클래스와 자식 클래스에서 같은 이름의 정적 필드를 가지는 경우에 고려되는 문제이다.
  • 정적 필드는 개별 공간을 가지므로, 오버라이딩이 발생하지 않는다(값이 바뀌지 않는다).
    • 즉, 부모 클래스와 자식 클래스에 동일한 메서드가 선언된다 해도, 두 메서드는 서로 다른(완전히 별도의) 메서드이다.
    • 이 현상을 '숨김' 현상이라고 한다.

1-3-2-2. 어떻게 적용되는가?

  • 정적 필드에 접근하는 권장되는 방법(즉, 클래스명.메서드명() 방법)을 사용하면 항상 참조하는 클래스의 값을 출력한다.
  • 인스턴스 변수를 이용하는 방법(즉, 비권장되는 방법)을 사용하면 인스턴스 필드의 중복과 동일하게 적용된다.
    • 이 경우 static한 방법으로 값에 접근하라는 warning이 발생한다.

1-4. 정적 메서드에 접근하기

  • 정적 메서드 또한 정적 필드처럼 오버라이딩 되지 않는다.
  • 클래스 이름을 이용해서(즉, 권장되는 방법으로) 호출하는 경우, 참조하는 클래스의 메서드를 반환한다.
  • 인스턴스 변수를 이용해서 접근하는 방법(즉, 비권장되는 방법)으로 접근하면 인스턴스 필드의 중복과 동일하게 적용된다.
    • 이 경우 static한 방법으로 메서드에 접근하라는 warning이 발생한다.

2. 최상위 클래스 Object

2-1. 정의

  • Java의 모든 클래스가 상속하는 클래스이다.
  • 생성하는 클래스가 아무것도 상속받지 않는다면 컴파일러는 자동으로 해당 클래스가 Object를 상속하도록 처리한다. -> extends Object를 붙인다.
  • 즉, Java의 모든 클래스는 Object 타입으로 선언할 수 있으며, Object 클래스의 메서드를 선언할 수 있다.
    • 단, Object 클래스의 메서드가 원하는 기능을 제공하지 않을 수 있으므로, 메서드 오버라이드를 해야 할 수 있다.
    • 예시) 동등비교 메서드 equals()는 Object 클래스에서 스택 메모리의 값(즉, 힙 영역의 객체 주소-참조 주소-)을 비교(동등비교)하므로, 실제 값을 비교하려면 오버라이드해서 사용해야 한다.

2-2. 주요 메서드

2-2-1. toString()

  • 객체 정보를 패키지명.클래스명@해시코드 형태의 문자열로 리턴하는 메서드
  • 일반적으로 자식 클래스는 toString()을 오버라이딩해서 문자열 반환을 처리한다.
  • 오버라이드 예시
	@Override
    public String toString() {
    	"출력할 내용";
    }

2-2-2. hashCode()

2-2-2-1. Hash란?

  • 입력을 이용해서 고정 길이의 고유한 값을 추출하는 함수인 '해쉬'를 만드는 함수
  • Hash의 특징
    • 단방향성 - 인증 정보 저장, 인증 처리를 위해 사용
    • 유일성 - 무결성 보장을 위해 사용

2-2-2-2. hashCode()란?

  • 객체의 위치값을 기준으로 생성하는 고유값
  • Hashtable, HashMap 등의 객체에서 동등 비교를 할 때, 이 메서드를 오버라이딩한다.
  • Key-Value 형태로 구성되며, Key는 중복되면 안된다.
    • Key가 중복되면 내용이 덮어씌워진다.
    • Key가 같아보여도 객체로 선언한 문자열처럼 해쉬한 값이 다르다면 별개의 요소로 취급된다.
  • 출력 시 객체 형태로 출력된다.
  • 오버라이드 예시
	@Override
    public int hashCode() {
    	return this.name.hashCode();
    }

3. final 제어자

3-1. 정의

  • 필드, 지역 변수, 클래스 앞에 올 수 있는 제어자
  • 최종적인 값을 의미함

3-2. final 변수

  • final 필드 또는 final 지역변수이다.
  • 선언 방법
    • 선언과 함께 값을 할당
    • 생성자를 이용해서 초기화

3-3. final 메서드

  • 메서드 앞에 final 키워드를 붙인다.
  • 자식 클래스에서 해당 메서드를 오버라이드 할 수 없다.

3-4. final 클래스

  • 클래스 앞에 final 키워드를 붙인다.
  • 상속할 수 없는 클래스이다.

4. abstract 제어자

4-1. 정의

  • 추상 메서드와 추상 클래스를 만드는 제어자이다.

4-2. 추상 메서드

  • abstract 리턴타입 메서드명(매개변수); 로 선언한다.
  • 멤버의 구체적인 내용은 정의하지 않는다.

4-3. 추상 클래스

4-3-1. 특징

  • abstract class 클래스명{ } 로 선언한다.
  • 하나 이상의 추상 메서드를 포함하며, 실제 값이 있는 변수나 정의된 메서드가 존재할 수 있다.
    • 더 나아가서 추상 메서드가 없더라도 추상 클래스가 될 수 있다.
  • 구현되지 않은 부분이 있기 때문에 객체를 실제로 만들 수는 없다.
    • 다시 말해, 생성자를 호출할 수 없다.
  • 추상 클래스를 더 추상화하는 경우 interface가 된다.

4-3-1. 추상 클래스를 사용할 수 있도록 구현하는 방법

  • 추상 클래스를 자식 클래스에 상속해서 구현이 되지 않은 부분을 구현한다.
    • 이를 implement라고 함
  • 추상 클래스를 자식 클래스에 상속하고, 자식 클래스도 추상 클래스로 정의할 수도 있다.
    • 자식 클래스를 구현하는 과정을 필요로 한다.

4-3-2. 추상 클래스의 객체 생성 방법

  • 상속 후 객체를 생성한다
    • 추상 클래스를 구현한 후, 구현한 클래스를 이용해서 객체를 생성한다.
    • 여러 개의 객체를 만들 수 있는 방법이다. - 익명 inner class를 활용한다.
    • 추상 클래스를 new 키워드를 활용해서 구현하고, 생성자 호출 후 중괄호를 열어서 구현해야 하는 부분을 구현한다.
    • 한 개의 객체만 필요로 할 때 사용하는 방법이다.

5. 인터페이스

5-1. 정의

  • 인터페이스는 중요한 객체지향 프로그래밍 요소이다.
  • 추상 인터페이스에서 추상화 수준이 더 높아진 것
  • 모든 필드가 public static final로 정의된다.
  • 모든 메서드는 static, default, public abstract 중 하나를 가진다.
  • 객체로 만들기 위해서는 모든 메서드를 구현해야 한다.
  • 인터페이스는 '명세서'이다.

5-2. 선언 방법

  • class가 아니라, interface 키워드를 사용한다.
  • 필드, 메서드의 제어자 생략 시 컴파일러가 자동으로 제어자를 삽입한다.

5-3. 구현을 위한 상속 시 규칙

5-3-1. 공통 규칙

  • 인터페이스는 다중 상속이 가능하다.
    • 모든 필드가 public static이여서 중복이 발생할 여지가 없기 때문이다.

5-3-2. 클래스에 상속하기

  • implements 키워드를 활용한다.
  • class는 클래스와 인터페이스를 동시에 상속할 수 있다.
    • 동시 상속 시, 클래스를 우선 작성한다.

5-3-3. 인터페이스에 상속하기

  • extends 키워드를 활용한다.
  • 인터페이스는 인터페이스만 상속할 수 있다.

5-3-4. 주의사항

  • 자식 클래스는 반드시 추상 메서드를 구현하거나, 추상 클래스로 정의되어야 한다.
  • 자식 클래스를 구현하는 메서드는 항상 public이다.

5-4. 객체 생성하기

  • 인터페이스를 일반 클래스로 상속해서 객체를 생성한다.
    • 동일 타입 객체를 여럿 만들 수 있다.
  • 익명 inner class를 활용한다.
    • 객체를 한 개만 만들어서 사용할 때 사용할 수 있다.

5-5. 장점

  • 생성되는 객체가 다양해도 코드 자체(즉, 클라이언트 코드 자체)의 변화는 요구되지 않는다.
  • 추상화를 통해 유연한 프로그래밍이 가능해진다.

6. 예외

6-1. 정의

  • 개발자가 해결할 수 있는 오류
  • 연산 오류, 포맷 오류 등을 의미한다
  • 단, 개발자가 처리할 수 없는 오류는 에러라고 하며, JVM 자체의 오류이다.

6-2. 예외 클래스의 상속 구조

  • 모든 예외 클래스는 Object 클래스를 최상위 클래스로 가진다.
  • 예외 관련 최상위 클래스는 Throwable 클래스이다.
  • Throwable 클래스는 에러를 관리하는 Error 클래스와 예외를 관리하는 Exception 클래스를 포함한다.
  • Exception 클래스는 일반 예외 클래스와 실행 예외 클래스를 포함한다.

6-3. 예외의 종류

  • 일반 예외
    • 일반적으로 컴파일 시점에 처리되는 오류를 의미한다.
  • 실행 예외
    • 일반적으로 실행 시점(런타임)에 발생하는 오류를 의미한다.

6-4. 예외 처리

  • 예외 처리는 try-catch-final 구문을 사용한다.
  • catch 구문을 여러 개 사용하는 경우, 에러가 발생하면 이후의 catch 구문은 생략한다.
    • 즉, 작은 오류를 우선 작성하고, 확장해야 한다.
    • 그렇기 때문에 Exception과 같은 일반적인 에러를 가장 마지막에 쓴다.
  • 권장하는 형태는 아니나, |(OR) 연산자를 사용해서 한번에 여러 개의 오류를 처리할 수 있다.
  • finally 구문은 try 블록에서 생성한 리소스를 해제한다.
  • 형태
	try {
    	예외 발생 가능성이 있는 코드
    } catch (예외 클래스명과 참조변수) {
        예외 발생 시 실행할 코드
    } ... {
    } finally {
    	예외 발생 여부와 무관하게 무조건 실행할 코드
    }

6-5. 올바른 예외 처리

  • 발생 가능한 예외를 세분화한다.
  • 다양한 예외를 사용한다면, 발생할 수 있는 순서대로 작성한다.

6-6. 리소스 자동 해제 예외 처리

  • try-with-resource 구문이라고 한다.
  • try 구문의 블럭 내부에 있던 자원 생성 구문(오류가 발생하는지 확인할 구문)을 try(자원 생성 구문) 형태로 작성한다.
  • 기존에는 finally 구문에서 자원을 해제해야 했지만, 이 방법을 사용하면 try 구문이 종료되면 자원이 자동으로 해제된다.
  • 이 기능을 사용하기 위해서는 AutoCloseable 인터페이스를 상속해서 close 메서드를 구현해야 한다.

6-7. 예외 전가(throw)

  • 예외 발생 시 호출한 메서드가 예외를 처리하도록 전달하는 구문이다.
  • 전가된 예외는 최상위 메서드인 main()까지 전달되고, main()에서도 예외가 전가되면 JVM이 직접 예외를 처리한다.
  • 구현
	returnType methodName(Parameter) throws ExceptionClassName {
    	Exception 발생 가능 코드;
    }

6-8. 사용자 정의 예외 클래스

6-8-1. 종류

  • 일반 예외 클래스
    • Exception을 상속받는 클래스이다.
  • 실행 예외 클래스
    • RuntimeException을 상속받는 클래스이다.

6-8-2. 정의 방법

  • Exception 클래스나 RuntimeException 클래스를 상속하고, 생성자를 만든다.
    • 만들어야 하는 생성자 예시
      • 빈 생성자
      • 메시지를 받는 String 매개변수 생성자
  • 사용자 정의 예외 클래스를 사용할 클래스에서 예외 인스턴스를 정의한다.

6-8-3. 주요 메서드

  • e.getMessage()
    • 예외 메시지를 출력하는 메서드
    • System.out.println(e.getMessage());
  • e.printStackTrace()
    • 메서드 실행 중 호출된 메서드를 순서대로 출력한다.
    • 메서드가 호출될 때마다 호출된 메서드가 스택에 남게 되는 것을 이용한다.

0개의 댓글