java, 클래스와 인터페이스의 기본 - 접근제어와 안전한 작성
들어가며
- 이펙티브 자바에서 클래스/메서드 파트를 공부를 공부할 때 자바 기본 지식에 대한 부족함을 느꼈다.
- 신용권 개발자님의 이것이 자바다를 참고하였다.
클래스의 선언과 class 파일의 생성
- 소스파일의 이름과 동일하게 코드에서의 클래스 이름을 정해야 한다. 접근 제어자는 public으로 부여해야 한다.
-
소스파일 내부에 public class 이외에 클래스를 작성할 수 있다. 소스코드로는 한 파일에 저장하더라도 컴파일 시점에서 다른 파일(.class)로 분리 된다.
class PackagePrivateClass
의 경우 package-private 상태이기 때문에 같은 패키지 내부에서 참조 가능하다. - PublicClass.java
public class PublicClass {
}
// 컴파일 시점에서 분리된다.
class PackagePrivateClass{
}
- 아래의 이미지처럼 실제로 분리된다! 신기하다!
정적 초기화 블록
- 정적 필드의 경우 필드 선언과 동시에 초기화 하거나 생성자에서 초기화 한다.
- 추가적으로 아래와 같이 정적 초기화 블록을 사용할 수 있다.
public class StaticInitBlock {
public static final String RANDOM_UUID;
static {
System.out.println("정적 클래스에 대한 초기화 블록이 실행됩니다!");
RANDOM_UUID = UUID.randomUUID().toString();
}
public static void main(String[] args) {
System.out.println("StaticInitBlock.RANDOM_UUID = " + StaticInitBlock.RANDOM_UUID);
}
}
패키지와 import
- 자기 자신을 제외한 클래스에 접근하는 방법은 세 가지이다.
- 메서드 블럭에서 경로와 클래스 이름을 다 명시한다.
- 소스코드 최상단에 import로 경로를 적는다. 메서드 블럭에서 클래스 이름만 작성한다.
- 소스코드 최상단에 import static로 경로를 적는다. 매서드 명만 적을 수 있다.
import java.time.LocalDate;
import static java.time.LocalDateTime.*;
public class ImportTest {
public static void main(String[] args) {
System.out.println("java.time.Instant.now() = " + java.time.Instant.now());
System.out.println("LocalDate.now() = " + LocalDate.now());
System.out.println("now() = " + now());
}
}
접근 제한자와 상속
- 클래스와 그것의 구성요소인 필드, 메서드는 외부 클래스에서의 접근 제한을 할 수 있다.
- 접근 제한이 강한 순서는 다음과 같다 : private - package-private(default) - protected - public
- private은 해당 클래스 내부에서만 사용 가능하다. 상속할 수 없다.
- package-private(default)은 같은 패키지에서만 접근 가능하다.
- protected는 상속한 서브타입만 접근 가능하다.
- protected은 대체로 공개된 api로 이해한다. 상속을 하면 접근 가능하기 때문이다.
- protected는 상속을 통해 변경 및 튜닝이 필요한 부분에 대해서만 제한적으로 사용한다.
- 가능하면 접근 제한자는 private로 사용하며 불가피할 경우 package-private로 사용한다.
- 클래스는 public 혹은 package-private(default) 만 사용 가능하다.
같은 패키지 내의 접근 예시
public class AccessTest {
public static void main(String[] args) {
String publicField = PackagePrivateClass.PUBLIC_FIELD;
String protectedField = PackagePrivateClass.PROTECTED_FIELD;
String packagePrivateField = PackagePrivateClass.PACKAGE_PRIVATE_FIELD;
String privateField = PackagePrivateClass.PRIVATE_FIELD; // 컴파일 에러
}
}
class PackagePrivateClass{
public static final String PUBLIC_FIELD = "hi!";
protected static final String PROTECTED_FIELD = "hi!";
static final String PACKAGE_PRIVATE_FIELD = "hi!";
private static final String PRIVATE_FIELD = "hi!";
}
다른 패키지에서의 접근
- access.a 패키지에 있는 클래스를 접근할 때 다음과 같은 컴파일 결과가 발생한다.
package access.a;
public class AccessTest {
public static final String PUBLIC_FIELD = "hi!";
protected static final String PROTECTED_FIELD = "hi!";
static final String PACKAGE_PRIVATE_FIELD = "hi!";
private static final String PRIVATE_FIELD = "hi!";
}
package access.b;
import access.a.AccessTest;
public class AccessTestTest {
public static void main(String[] args) {
final String protectedField = AccessTest.PROTECTED_FIELD; // 컴파일 오류
final String publicField = AccessTest.PUBLIC_FIELD;
}
}
class AccessExtends extends AccessTest{
public static void main(String[] args) {
final String protectedField = AccessTest.PROTECTED_FIELD; // 상속을 하니 protected 까지 접근 가능하다.
}
}
인터페이스의 구현
- 자바에는 암묵적인 코드가 존재한다. 예를 들면 생성자가 없는 클래스는 컴파일 시 자동적으로 기본 생성자가 작성된다.
- 인터페이스의 모든 메서드는 누락하더라도 public abstract이 암묵적으로 작성된다.
- 필드의 경우 public static final이 암묵적으로 작성된다.
- 리스코프의 원칙에 따라 하위 객체는 상위 객체로 치환될 수 있어야 한다. 그러므로 인터페이스를 오버라이딩한 모든 메서드는 반드시 public 메서드이다.
public interface InterfaceJava8 {
String abc = "abc";
LocalDateTime getDate();
}
public class Main {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
// public static final java.lang.String interfaces.a.InterfaceJava8.abc
final Field abc = InterfaceJava8.class.getDeclaredField("abc");
// public abstract java.time.LocalDateTime interfaces.a.InterfaceJava8.getDate()
final Method getDate = InterfaceJava8.class.getDeclaredMethod("getDate");
}
}
자바 8과 인터페이스 구현
- 자바 8 이전의 모든 메서드는 반드시 abstract 메서드였다.
- 자바 8 이후 인터페이스에서 구현 메서드를 작성할 수 있다.
- default와 static가 그것이며 전자는 인스턴스 메서드이고 후자는 정적 메서드이다.
public interface InterfaceJava8 {
String abc = "abc";
LocalDateTime getDate();
default String defaultMethod(){
return "defaultMethod";
}
static String staticMethod(){
return "staticMethod";
}
}