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. 추상 클래스를 사용할 수 있도록 구현하는 방법
- 추상 클래스를 자식 클래스에 상속해서 구현이 되지 않은 부분을 구현한다.
- 추상 클래스를 자식 클래스에 상속하고, 자식 클래스도 추상 클래스로 정의할 수도 있다.
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. 종류
- 일반 예외 클래스
- 실행 예외 클래스
- RuntimeException을 상속받는 클래스이다.
6-8-2. 정의 방법
- Exception 클래스나 RuntimeException 클래스를 상속하고, 생성자를 만든다.
- 만들어야 하는 생성자 예시
- 빈 생성자
- 메시지를 받는 String 매개변수 생성자
- 사용자 정의 예외 클래스를 사용할 클래스에서 예외 인스턴스를 정의한다.
6-8-3. 주요 메서드
- e.getMessage()
- 예외 메시지를 출력하는 메서드
- System.out.println(e.getMessage());
- e.printStackTrace()
- 메서드 실행 중 호출된 메서드를 순서대로 출력한다.
- 메서드가 호출될 때마다 호출된 메서드가 스택에 남게 되는 것을 이용한다.