컴파일 & 실행
자바의 경우 다음과 같은 과정으로 진행된다.
코드 작성 > 컴파일 > 실행
코드를 작성하고 콘솔에서 컴파일 및 실행을 하면 된다.
컴파일은 javac
명령어를 사용하고 실행은 java
명령을 사용한다.
개발자가 작성한 코드를 컴퓨터가 이해할 수 있도록 엮어주는 작업이 컴파일이다.
.java
확장자의 소스를 컴파일하면 .class
확장자를 가진 파일이 생성되어 디스크에 저장된다.
.class
파일은 바이너리 파일로 되어있고, 컴파일하는 프로그램을 컴파일러라고 부르며,
자바에서는 java.exe
프로그램이 그 역할을 수행한다. (Mac이나 Linux에서는 javac
)
컴파일시 코드에 규칙을 지키지 않았다면, 오류를 내면서 컴파일이 되지 않는다.
컴파일을 마친 클래스 파일은 JVM에서 읽어서 운영체제에서 실행된다.
JIT 컴파일러
Just In Time의 약자
프로그램 실행을 보다 빠르게 하기 위한 기술이고 명칭은 컴파일러지만 실행시에 적용된다.
JIT 컴파일러는 프로그램의 성능에 영향을 주는 지점에 대해서 지속적으로 분석한다.
분석된 지점은 부하를 최소화하고 높은 성능을 내기 위한 최적화의 대상이 된다.
JIT 컴파일러를 사용한다는 것은?
언제나 자바 메서드가 호출되면 바이트코드를 컴파일하고 실행 가능한 네이티브 코드로 변환한다는 의미다.
매번 JIT 컴파일하면 성능 저하가 심하므로 최적화 단계를 거치게 된다.
javac
컴파일러는 소스코드를 바이트코드로 변환 (.class
)
자바 프로그램을 실행할 때 JVM은 항상 바이트코드로 시작,
동적으로 기계에 의존적인 코드로 변환한다.
JIT은 애플리케이션에서 각각의 메서드를 컴파일할 만큼 시간적 여유가 없어서
모든 코드는 초기에 인터프리터에 의해서 시작되고 해당 코드가 충분히 많이 사용될 경우에 컴파일할 대상이 된다.
- 인터프리터
Java 인터프리터는 javac 명령으로 자바 프로그램을 자바 바이트코드로 컴파일하고,
Java 인터프리터가 한 줄씩 해석하여 기계어로 변역한다.
- 컴파일
고급언어로 작성된 프로그램을 목적 프로그램(컴퓨터가 읽을 수 있는) 번역 후
링킹(Linking) 작업을 통해 실행 프로그램을 생성한다.
- 링킹(Linking)
컴파일이 끝나면 나눠져 있는 Object 파일들이나 다른 파일(라이브러리)를 엮어주는 작업이다.
HotSpot
JVM에서 이 작업은 각 메서드에 있는 카운터를 통제되며, 메서드에 두 개의 카운터가 존재한다.
- 수행 카운터 (invocation counter)
메서드를 시작할 때마다 증가한다.
- 백에지 카운터 (backedge counter)
높은 바이트코드 인덱스에서 낮은 인덱스로 컨트롤 흐름이 변경될 때마다 증가하며
메서드에 루프가 존재하는지 확인할 때 사용된다.
backedge counter는 invovation counter 보다 컴파일 우선순위가 더 높다.
이 카운터들이 인터프리터에 의해 증가될 때마다 한계치에 도달했는지 확인하고 도달한 경우 인터프리터는 컴파일을 요청한다.
OSR
HotSpot VM은 OSR(On Stack Replacement)라는 특별한 컴파일도 수행한다.
OSR은 인터프리터에서 수행한 코드 중 오랫동안 루프가 지속되는 경우
중간에 컴파일해야 남은 반복을 빠르게 수행할 수 있기 때문에 사용된다.
최적화되지 않은 코드가 수행되고 있는 것을 발견하면 인터프리터에 두지 않고 컴파일된 코드로 변경한다.
참조 자료형
생성자는 몇 개까지 만들 수 있을까?
자바는 여러 생성자를 가질 수 있다. 100개가 되도 상관 없다.
이러한 이유는 다음 예를 통해 알아보자.
자바 패턴 중에서 DTO 라는 것이 있다. (Data Transfer Object)
어떤 속성을 갖는 클래스를 만들고 그 속성들을 쉽게 전달하기 위해 DTO라는 것을 만든다.
비슷한 클래스로 VO라는 것도 있다.
VO는 Value Object의 약자로 DTO와 형태는 동일하지만, VO는 데이터를 담아두기 위한 목적이고
DTO는 데이터를 다른 서버로 전달하기 위한 것이 목적이다.
접근 제어자
public
누구나 접근 가능
protected
같은 패키지 내에 있거나 상속받은 경우에만 접근 가능
package-private
같은 패키지 내에 있을 때만 접근 가능
private
해당 클래스 내에서만 접근 가능
상속
public class Child extends Parent
extends는 “확장하다.”는 뜻이다.
extends Parent
는 “Parent 클래스를 확장” 한다는 말이다.
이렇게 확장하면 부모 클래스에서 선언되어 있는 public
및 protected
로 선언되어 있는
모든 변수와 메서드를 내가 갖고 있는 것처럼 사용할 수 있다.
즉, 다른 패키지에 선언된 부모 클래스의 접근 제어자가 없거나
private
로 선언된 것들은 자식 클래스에서 사용할 수 없다.
Child child = new Child();
child.printName();
위 코드에서 Parent 클래스의 메서드를 호출하지도 않았는데,
확장한 클래스인 Child의 생성자를 호출하니 자동으로 부모 클래스의 기본 생성자가 호출된다.
기본 생성자란 매개변수가 아무것도 없는 생성자를 의미한다.
- 부모 클래스에서는 기본 생성자를 만들어 놓는 것 이외에는 상속을 위해 해야하는 작업은 없다.
- 자식 클래스의 생성자가 호출되면 자동으로 부모 클래스의 매개변수 없는 생성자가 실행된다.
- 자식 클래스에서는 부모 클래스에 있는
public
,protected
로 선언된
모든 인스턴스 및 클래스 변수와 메서드를 사용할 수 있다.
상속과 생성자
만약 부모 클래스에 기본 생성자가 없다면 어떻게 될까?
// public Patrent() {
// System.out.println("Parent constructor");
// }
기본 생성자를 주석 처리 했다.
다시 실행해보면, Parent constructor 메시지가 출력되지 않고 정상적으로 처리된다.
문제가 없다고 생각할 수도 있지만 부모 클래스에 매개변수를 받는 메서드가 있을 경우에는 문제가 생긴다.
매개변수가 있는 생성자를 만들었을 경우, 기본 생성자는 자동으로 만들어지지 않는다.
해결 방법은 두 가지다.
(1) 부모 클래스에 “매개변수가 없는” 기본 생성자를 만든다.
(2) 자식 클래스에서 부모 클래스의 생성자를 명시적으로 지정하는 super()
을 사용한다.
명시적으로 지정하지 않아도 컴파일시 자동으로 super()
가 추가된다.
메서드 Overriding
상속 광계를 보다 유연하게 활용하기 위한 메서드 Overriding에 대해 알아보자.
자식 클래스에서 부모 클래스에 있는 메서드와 동일하게 선언하는 것을 “메서드 Overriding”이라고 한다 .
- 접근 제어자
- 리턴 타입
- 메서드 이름
- 매개변수 타입 및 개수
위 목록 모두 동일해야만 메서드 Overriding 이라고 부른다.
Overriding된 메서드의 접근 제어자는 부모 클래스에 있는 메서드와 달라도 되지만,
접근 권한이 확장되는 경우에만 허용된다.
접근 권한이 축소될 경우 컴파일 에러가 발생한다.
Overloading
Overloading(오버로딩)과 헷갈릴 수 있다.
- Overloading(오버로딩): 확장
- Overriding(오버라이딩): 덮어씀
오버로딩은 같은 이름의 메서드 여러 개를 가지면서 매개변수의 유형과 개수가 다르도록 하는 기술이다.
형변환
Parent obj = new Child(); // OK!
Child obj2 = new Parent(); // Fail!
왜 첫번째 코드는 되는데 두번째 코드는 안될까?
Child 클래스에서 Parent 클래스에 있는 메서드와 변수 사용이 가능하다.
반대로 Parent 클래스에서는 Child 클래스에 있는 모든 메서드와 변수들을 사용할 수 없다.
정확하게는 Parent 클래스에서 Child 클래스의 모든 메서드, 변수를 사용할 수도 있고 그렇지 않을 수도 있다.
만약 Child 클래스에 추가된 메서드나 변수가 없으면 가능할 수도 있다.
하지만 자바 컴파일러에서는 자식 객체를 생성할 때 부모 생성자를 사용하면 안된다고 못을 박는다.
명시적으로 형변환(casting)을 한다고 알려줘야만 한다.
Child child = (Child) new Parent();
위 코드는 Parent cannot be cast to Child Exception이 발생한다.
Parent p = new Parent();
Child c = new Child();
Parent p2 = c;
Child c2 = p;
분명 마지막 라인에서 컴파일 에러가 발생할 것이다.
“incompatible types”라는 에러가 뜨며 컴파일 되지 않는다.
그 이유는 parent 객체는 Child 클래스에 선언되어 있는 메서드나 변수를 완전히 사용할 수 없기 때문이다.
컴파일 오류만을 피하려면 다음과 같이 형변환을 해야만 한다.
Child c2 = (Child)p;
“너 이제 Child 클래스인 것처럼 행동해!” 라고 명시적으로 선언해주는 것이다.
컴파일은 정상적으로 수행되지만, 예외가 발생한다.
p가 실제로 Parent 클래스의 객체이므로 컴파일 오류는 넘겼지만
실행시에는 “얘는 원래 Parent 클래스의 객체라서 못쓰겠는데요..” 라는 예외가 발생한 것이다.
Child c = new Child();
Parent p2 = c;
Child c2 = (Child) p2;
위 코드는 아무런 문제 없이 실행될 것이다.
왜 아무 문제 없이 실행될까?
Parent p2 = child;
p2는 child를 대입한 것이다. 그리고 child는 Child 클래스의 객체다.
p2로 겉모습은 Parent 클래스의 객체인 것처럼 보이지만,
실제로는 Child 클래스의 객체이기 때문에 p2를 Child 클래스로 형변환해도 전혀 문제 없다.
Parent[] parentArray = new Parent[3];
parentArray[0] = new Child();
parentArray[1] = new Parent();
parentArray[2] = new Child();
위 코드에서 문제는 없다.
그런데 타입이 Parent인지 Child인지 어떻게 구분할 수 있을까?
이럴때는 instanceof
예약어를 사용하면 된다.
Polymorphism
Polymorphism은 다형성이다.
형태가 다양하다. > 어떤 형태가 다양하다는 걸까?
예) Parent, Child, ChildOther 클래스가 있다.
Child와 ChildOther은 Parent 클래스를 상속받았다.
Parent parent1 = new Parent();
Parent parent2 = new Child();
Parent parent3 = new ChildOther();
parent1.printName();
parent2.printName();
parent3.printName();
전부 Parent 타입으로 선언하고 각 객체의 printName()
메서드를 호출했다.
위 코드의 결과는 상이하다.
이유는 선언시에는 모두 Parent 타입으로 선언했지만,
실제로 호출된 메서드는 생성자를 사용한 클래스에 있는 것이 호출된다.
“형변환을 하더라도 실제 호출되는 것은 원래 객체에 있는 메서드가 호출된다.”
라는 것이 바로 다형성이고 Polymorphism이다.
자식 클래스에서 할 수 있는 일들
생성자 관련
-
자식 클래스의 생성자가 호출되면 자동으로 부모 클래스의 매개변수가 없는 기본 생성자가 호출된다.
-
명시적으로
super()
라고 지정할 수도 있다.
변수 관련
-
부모 클래스에 private로 선언된 변수를 제외한 모든 변수가 자신의 클래스에 선언된 것처럼 사용할 수 있다.
-
부모 클래스에 선언된 변수와 동일한 이름을 가지는 변수를 선언할 수도 있다.
(그러나 이렇게 덮어쓰는 것은 권장하지 않는다.) -
부모 클래스에 선언되어 있지 않는 이름의 변수를 선언할 수 있다.
메서드 관련
-
변수처럼 부모 클래스에 선언된 메서드들이 자신의 클래스에 선언된 것처럼 사용할 수 있다.
-
부모 클래스에 선언된 메서드와 동일한 시그니처를 사용함으로써 메서드를 Overriding할 수 있다.
-
부모 클래스에 선언되어 있지 않은 이름의 새로운 메서드를 선언할 수 있다.
인터페이스와 추상클래스, enum
interface, abstract
자바에서 .class
파일을 만들 수 있는 것에는 클래스에만 있는 것이 아니다.
interface와 abstract 클래스도 있다.
final
상속과 관련해서 알아야 하는 또 하나의 예약어는 final이다.
final은 클래스, 메서드, 변수에 선언할 수 있다.
final 클래스
public final class FinalClass { ... }
클래스가 final로 선언되어 있으면 상속을 해줄 수 없다.
더 이상 확장해서는 안 되는 클래스, 누군가 이 클래스를 상속받아서 내용을 변경해서는 안 되는 클래스를 선언할 때는 final로 선언하면 된다.
final 메서드
public abstract class FinalMethodClass {
public final void printLog(String data) {
System.out.println("Data = " + data);
}
}
메서드를 final로 선언하면 더 이상 Overriding할 수 없다.
FinalMethodClass를 상속해도 printLog()
메서드는 override할 수 없다.
final 변수
변수에서 final을 쓰는 것은 좀 다르다.
변수에 final을 사용하면 그 변수는 “더 이상 바꿀 수 없다.”라는 말이다.
그래서 인스턴스 변수나 static으로 선언된 클래스 변수는 선언과 함께 값을 지정해야만 한다.
final int instanceVariable;
위 코드는 컴파일 에러가 발생한다.
final로 선언했기 때문에 변수 생성과 동시에 초기화를 해야만 컴파일시 에러가 발생하지 않는다.
생성자나 메서드에서 초기화하면 되지 않나? 라고 생각할 수도 있겠지만,
그러면 중복되어 변수 값이 선언될 수도 있기 때문에 final의 기본 의도를 벗어난다.
그렇다면 매개변수나 지역변수는 어떨까?
public void method(final int parameter) {
final int localVariable;
}
매개변수나 지역변수를 final로 선언하는 경우에는 반드시 선언할 때 초기화할 필요는 없다.
매개변수는 이미 초기화되어 넘어왔고,
지역변수는 메서드를 선언하는 중괄호 내에서만 참조되므로 다른 곳에서 변경할 일이 없다.
그러나 매개변수, 지역변수 초기화 후 다른 값을 할당하려고 하면 컴파일 에러가 발생한다.
참조 자료형 변수
public class FinalReferenceType {
final MemberDTO dto = new MemberDTO();
}
public static void main(Stirng args[]) {
FinalReferenceType referenceType = new FinalReferenceType();
referenceType.checkDTO();
}
public void checkDTO() {
System.out.println(dto);
dto = new MemberDTO();
}
객체를 final로 선언했을 때는 어떻게 처리될까?
dto가 final이기 때문에 값을 할당할 수 없다는 메시지와 함께 에러가 발생한다.
참조 자료형도 두 번 이상 값을 할당하거나 새로 생성자를 사용하여 초기화할 수 없다.
checkDTO()
메서드를 수정해보자.
public void checkDTO() {
System.out.println(dto);
dto.name = "Hyerin";
System.out.println(dto);
}
메서드를 이렇게 변경한 후 컴파일해 보면 전혀 문제가 없다.
name 값이 “Hyerin”으로 잘 할당되어 있다.
dto 객체, 즉 MemberDTO 클래스의 객체는 FinalReferenceType에서 두 번 이상 생성하 수 없다.
하지만 그 객체의 안에 있는 객체들은 final로 선언된 것이 아니기 때문에 그러한 제약이 없다.
enum
enum 클래스라는 상수의 집합도 있다.
final로 String과 같은 문자열이나 숫자들을 나타내는 기본 자료형의 값을 고정할 수 있다.
이를 “상수(constant)”라고 한다.
어떤 클래스가 상수만으로 만들어져 있을 경우에는 class 선언 부분에 enum이라고 선언하면
이 객체는 상수의 집합이다.
를 명시적으로 나타낸다.
// 선언
public enum OverTimeValues {
THREE_HOUR,
FIVE_HOUR;
}
// 사용
OverTimeValues.THREE_HOUR
enum 타입은 enum클래스이름.상수이름
을 지정함으로써 클래스의 객체 생성이 완료된다고 생각하면 된다.
enum 클래스는 생성자를 만들 수 있지만 생성자를 통해 객체를 생성할 수는 없다.
public enum OverTimeValues {
THREE_HOUR(18000);
private final int amout;
OverTimeValues(int amout) {
this.amount = amount;
}
}
enum 클래스의 생성자는 아무것도 명시하지 않는 package-private
과 private
만 접근 제어자로 사용할 수 있다.
public
이나 protected
를 생성자로 사용해서는 안된다.
각 상수를 enum 클래스 내에서 선언할 때에만 이 생성자를 사용할 수 있다.
그렇다면 enum 클래스는 생성자가 없는데 어떻게 이상없이 작동했을까?
enum 클래스도 일반 클래스와 마찬가지로 컴파일할 때 생성자를 자동으로 만들어 준다.
enum 클래스의 부모
enum 클래스의 부모는 무조건 java.lang.Enum
이어야 한다.
자바에서 다중 상속은 허용되지 않지만 일반 클래스들은 상속에 상속을 거쳐서 여러 부모를 가질 수 있다.
하지만 enum 클래스는 무조건 java.lang.Enum
이라는 클래스의 상속을 받는다.
extends java.lang.Enum
이라는 문장을 사용하지는 않지만
컴파일러가 알아서 이 문장을 추가해서 컴파일한다.
그렇기 때문에 enum에 extends
하면 안 된다.
Enum 클래스의 생성자는 다음과 같다.
protected Enum(String name, int ordinal)
컴파일러에서 자동으로 호출되도록 해놓은 생성자다.
개발자가 이 생성자를 호출할 수 없다.
name은 enum 상수의 이름이고, ordinal은 enum의 순서이다.
Enum 클래스의 부모 클래스는 Object 클래스이기 때문에 Object 클래스의 메서드들은 모두 사용할 수 있다.
그러나 개발자들이 Object 클래스의 4개 메서드를 오버라이딩하지 못하도록 막아놓았다.
예외
- checked exception
- error
- runtime exception & unchecked exception
Error
에러는 자바 프로그램 밖에서 발생한 예외를 말한다.
Error는 프로세스에 영향을 주고, Exception은 스레드에만 영향을 준다.
런타임 예외
런타임 예외는 예외가 발생할 것을 미리 감지하지 못했을 때 발생한다.
컴파일할 때 예외가 발생하지 않는다. 하지만 실행시에는 발생할 가능성이 있다.
컴파일시 체크하지 않기 때문에 unchecked exception 이라고도 부른다.
Exception을 바로 확장한 클래스들이 Checked 예외이며,
RuntimeException 밑에 확장되어 있는 클래스들이 런타임 예외들이다.
Throwable: java.lang.Throwable
Exception과 Error의 공통 부모 클래스는 당연히 Object 클래스다.
공통 부모 클래스가 하나 더 있는데 java.lang
패키지에 선언된 Throwable 클래스다.
상속 관계가 이렇게 되어 있는 이유는 Exception이나 Error의 성경은 다르지만
모두 동일한 이름의 메서드를 사용하여 처리할 수 있도록 하기 위함이다.
Throwable 생성자
Thowable()
Thowable(String message)
Throwable(String message, Throwable cause)
Throwable(Throwable cause)
message: 예외 메시지
cause: 별도의 예외 원인
Throwable 오버라이딩 메서드
Throwable 클래스에 선언되어 있고 Exception 클래스에서 오버라이딩한 메서드는 10개가 넘는다.
많이 사용되는 메서드를 살펴보자.
-
getMessage()
예외 메시지를 String 형태로 제공받는다.
메시지를 활용하여 별도의 예외 메시지를 사용자에게 보여주려고 할 때 좋다.
-
toString()
예외 메시지를 String 형태로 제공받는다.
getMessage()
보다 약간 더 자세하게, 예외 클래스 이름도 같이 제공한다.
-
printStackTrace()
가장 첫 줄에는 예외 메시지를 출력하고 두 번째 줄부터는
예외가 발생하게 된 메서드들의 호출관계(스택 트레이스)를 출력해준다.
public void throwable() {
int[] intArray = new int[5];
try {
intArray = null;
System.out.println(intArray[5]);
} catch(Throwable t) {
// *
}
}
위 *
위치에 위에서 알아본 메서드를 사용해보자.
System.out.println(t.getMessage());
// 출력 : null
System.out.println(t.toString());
// 출력 : java.lang.NullPointerException
System.out.println(t.printStackTrace());
/* 출력 :
java.lang.NullPointerException
at c.exception.ThrowableSample.throwable(...)
at c.exception.ThrowableSample.main(...)
*/
예외를 만들어보자.
Throwable을 직접 상속 받는 클래스는 Exception과 Error가 있다고 했다.
Exception을 처리하는 예외 클래스는 개발자가 임의로 추가해서 만들 수 있다.
Throwable이나 그 자식 클래스의 상속을 받아야 한다.
public class MyException extends Exception {
public MyException() {
super();
}
public MyException(String message) {
super(messaage);
}
}
이렇게 만든 예외 클래스는 어떻게 사용할까?
try {
if(number > 12) {
throw new MyException("Number is over than 12");
} catch(MyException e) {
e.printStackTrace();
}
}
MyException이 예외 클래스가 되려면 Throwable 클래스의 자식 클래스가 되어야 한다고 했다.
만약 MyException을 선언할 때 관련된 클래스를 확장하지 않았을 때는
이 부분에서 제대로 컴파일이 되지 않는다.
자바 예외 처리 전략
예외를 처리할 때 표준을 정해두고 진행해야 한다.
(1) 예외가 항상 발생하지 않고, 실행시에 발생할 확률이 매우 높은 경우 런타임 예외로 만드는 것이 나을 수도 있다.
즉, extends RuntimeException
으로 선언하는 것이다.
이렇게 되면, 해당 예외를 던지는 메서드를 사용하더라도 try-catch
로 묶지 않아도 컴파일시에 예외가 발생하지 않는다.
그러나 이 경우에는 예외가 발생할 경우 해당 클래스를 호출하는 다른 클래스에서 예외를 처리하도록 구조적인 안전장치가 되어 있어야만 한다.
try-catch
로 묶지 않은 메서드를 호출하는 메서드에서 예외를 처리하는 try-catch
가 되어 있는 것을 의미한다.
public void methodCaller() {
try {
methodCalled();
} catch(Exception e) {
// 예외처리
}
}
public void methodCallee() {
// RuntimeException 예외 발생 가능성이 있는 부분
}
이와 같이 unchecked exception인 RuntimeException이 발생하는 메서드가 있다면
그 메서드를 호출하는 메서드는 try-catch
로 묶어주지 않더라도 컴파일할 때 문제가 발생하지는 않는다.
(2) 임의의 예외 클래스를 만들 때에는 반드시 try-catch
로 묶어줄 필요가 있을 경우에만 Exception 클래스로 확장한다.
일반적으로 실행시 예외를 처리할 수 있는 경우에는 RuntimeException 클래스를 확장하는 것을 권장한다.
(3) catch문 내에서 아무런 작업 없이 공백으로 놔두면 예외 분석이 어려워지므로 꼭 로그 처리와 같은 예외 처리를 해줘야만 한다.
예외 관련 Q&A
-
예외를 처리하기 위한 세 가지 블록은?
try/catch/finally
-
(1)번 답 중 “예외가 발생하든 안하든 반드시 실행해야 하는 부분”은 어떤 블록인가요?
finally
-
(1)번 답 중에서 “여기서 예외가 발생할 것이니 조심하세요.”라고 선언하는 블록은 어떤 블록인가요?
try
- 예외의 종류 세 가지는 각각 무엇인가요?
Error
RuntimeException(unchecked exception)
Checked Exception
-
프로세스에 치명적인 영향을 주는 문제가 발생한 것을 무엇이라고 하나요?
Error
-
try나 catch 블록 내에서 예외를 발생시키는 키워드는 무엇인가요?
throw new Exception
-
메서드 선언 시 어떤 예외를 던질 수도 있다고 선언할 때 사용하는 키워드?
throws Exception
- 직접 예외를 만들 때 어떤 클래스의 상속을 받아서 만들어야 하나요?
Throwable
이나Throwable
의 자식 클래스
String
public final class String extends Object
public final 로 선언되었다.
public : 누구나 사용할 수 있다.
final : 이 클래스를 확장할 수 없다.
implements Serializable, Comparable<String>, CharSequence
-
Serializable 인터페이스
구현해야 하는 메서드가 하나도 없는 인터페이스다.
구현한다고 선언만 하면 해당 객체를 파일로 저장하거나 다른 서버에 전송 가능한 상태가 된다.
-
Comparable 인터페이스
이 인터페이스는compareTo()
라는 메서드만 선언되어 있다.
이 메서드는 매개변수로 넘어가는 객체와 현재 객체가 같은지를 비교하는데 사용한다.
-
CharSequence 인터페이스
이 인터페이스는 해당 클래스가 문자열을 다루기 위한 클래스라는 것을 명시적으로 나타내는 데 사용된다.
Null 체크
어떤 참조 자료형도 널이 될 수 있다.
객체가 널이라는 것은?
- 객체가 아무런 초기화가 되어 있지 않음
- 클래스에 선언되어 있는 어떤 메서드도 사용할 수 없음
비교 equals
vs. compareTo
메서드 이름은 다르지만 매개변수로 넘어온 값과 String 객체가 같은지 비교하기 위한 메서드다.
단지 IgnoreCase가 붙은 메서드들은 대소문자 구분을 할지 안할지 여부만 다르다.
immutable String
JDK5 이상은 String 더하기 연산을 할 경우, 컴파일할 때 자동으로 해당 연산을 StringBuilder로 변환해 준다.
따라서 일일이 더하는 작업을 변환해 줄 필요는 없으나 for 루프와 같은 반복 연산을 할 때에는 자동으로 변환해주지 않는다.
CharSequence
String, StringBuilder, StringBuffer 클래스의 공통점은
CharSequence 인터페이스를 구현했다는 점이다.
세 가지 중 하나의 클래스를 사용하여 매개변수로 받는 작업을 할 때
String이나 StringBuilder 타입으로 받는 것보다 CharSequence 타입으로 받는 것이 좋다.
클래스 안의 클래스
자바에서 클래스 안에 클래스가 들어갈 수 있다.
이러한 클래스를 Nested 클래스
라고 부른다.
Nested 클래스는 선언한 방법에 따라 static nested class
와 inner class
로 구분된다.
차이는 static으로 선언되었는지 여부다.
inner class
는 또 두가지로 나뉘는데 로컬(지역) 내부 클래스
, 익명 내부 클래스
라고 부른다.
왜 Nested 클래스를 만드는 걸까?
-
한 곳에서만 사용되는 클래스를 논리적으로 묶어서 처리할 필요가 있을 때 > Static Nested 사용 이유
- 캡슐화가 필요할 때 > inner class 사용 이유
A 클래스에 private 변수가 있다. 이 변수에 접근하고 싶은 B라는 클래스를 선언하고
B 클래스를 외부에 노출시키고 싶지 않을 경우 즉 내부 구현을 감추고 싶을 때를 의미한다. - 소스의 가독성과 유지보수성을 높이고 싶을 때
static nested
내부 클래스는 감싸고 있는 외부 클래스의 어떤 변수도 접근할 수 있다.
private로 선언된 변수까지도 접근 가능하다.
하지만 static nested 클래스를 그렇게 사용하는 것은 불가능하다.
이름에서 알 수 있듯이 static 하기 때문이다.
public class OuterOfStatic { // (1)
static class StaticNested{ // (2)
private int value = 0;
// getter, setter
}
}
이 클래스를 컴파일해 보자.
내부에 있는 Nested 클래스는 별도로 컴파일할 필요가 없다. 자동으로 컴파일된다.
그러면 이 static nested 클래스의 객체는 어떻게 생성할까?
OuterOfStatic.StaticNested staticNested = new OuterOfStatic.StaticNested();
감싸고 있는 클래스 이름 뒤에 .(점)
을 찍고 쓰면 된다.
객체 사용 방법은 일반 클래스와 동일하다.
그렇다면 왜 static nested 클래스를 만들까?
클래스를 묶기 위해서다.
School 클래스: 학교 관리 클래스
University 클래스: 대학교 관리 클래스
Student 클래스: School 학생이지 University 학생인지 불분명하다.
이렇게 겉으로 보기에 유사하지만 내부적으로 구현이 달라야 할 때 static nested 클래스를 사용한다.
static nested 클래스로 만드면 School에서 만든 경우, University 클래스에서는 사용할 수 없다.
내부 클래스
public class OuterOfInner {
class Inner {
private int value = 0;
// getter, setter
}
}
static 선언이 없다.
그러므로 이 Inner 클래스의 객체를 생성하는 방법도 다르다.
OuterOfInner outer = new OuterOfInner();
OuterOfInner.Inner inner = outer.newInner();
Inner 클래스의 객체를 생성하기 전에는 먼저 Inner 클래스를 감싸고 있는 Outer 클래스의 객체를 만들어야만 한다.
outer 객체를 통해서 Inner 클래스의 객체를 만들어 낼 수 있다.
이와 같이 복잡한 Inner 클래스 객체를 왜 사용하는걸까?
앞에서 이러한 내부 클래스를 만드는 이유를 캡슐화 때문이라고 했다.
하나의 클래스에서 어떤 공통적인 작업을 수행하는 클래스가 필요한데 다른 클래스에서는 전혀 필요가 없을 때
이러한 내부 클래스를 만들어 사용한다.
익명 클래스
내부 클래스를 만드는 것보다도 더 간단한 방법은 익명 클래스를 만드는 것이다.
말 그대로 이름이 없는 클래스다.
public class Machine {
Remocon tv = new Remocon() {
@Override
public void on() { . . . }
};
}
Machine machine = new Machine();
machine.tv.on();
클래스 이름도 없고 객체 이름도 없기 때문에 다른 클래스나 메서드에서 참조할 수 없다.
그래서 객체를 해당 클래스 내에서 재사용하려면, 객체 생성 후 사용하면 된다.
(그러나 재사용할 일이 없을 때 사용하도록 하자.)
왜 익명 클래스라는 것을 제공하는 것일까?
클래스를 만들고 그 클래스를 호출하면 그 정보는 메모리에 올라간다.
즉 클래스를 많이 만들면 만들수록 메모리를 많이 필요해지고 애플리케이션을 시작할 때 더 많은 시간이 소요된다.
따라서 이렇게 간단한 방법으로 객체를 생성할 수 있도록 해놓았다.
내부 클래스 특징
Nested 클래스를 사용하려면 알고 있어야 하는 사항은 참조 가능한 변수들이다.
- static nested 클래스에서 외부 클래스의 static 변수만 참조할 수 있다.
만약 참조하는 코드가 있다면 컴파일시에 에러가 발생하므로 어떻게라도 사용할 수 없다.
- 내부 클래스와 익명 클래스는 외부 클래스의 어떤 변수라도 참조할 수 있다.
그렇다면 반대로 외부 클래스에서 static nested 클래스의 인스턴스 변수나
내부 클래스의 인스턴스 변수로서의 접근하는 것은 가능할까?
반대로의 참조도 가능하다. private라고 해도 접근할 수 있다.
어노테이션이란?
어노테이션은 클래스나 메서드 등의 선언시에 @
를 사용하는 것을 의미한다.
- 컴파일 정보를 알려주거나
- 컴파일할 때와 설치(deployment)시 작업을 지정하거나
- 실행할 때 별도의 처리가 필요할 때
자바에서 사용하기 위해서 정해져 있는 어노테이션은 3개
-
@Override
해당 메서드가 부모 클래스에 있는 메서드를 오버라이드 했다는 것을 명시적으로 선언
-
@Deprecated
미리 만들어져 있는 클래스나 메서드가 더 이상 사용되지 않는 경우
그런 것을 deprecated라고 하는데 컴파일러에게 “사용하지 않으니 누가 쓴다면 경고해달라.”라고 알려주는 것이다.
-
@SupressWarnings
컴파일에서 경고를 알리는 경우가 있다.
일부러 이렇게 코딩했으니 경고해줄 필요가 없다고 알려주는 것이다.
메타 어노테이션이라는 것은 개발자가 어노테이션을 선언할 때 사용한다.
@Target
@Retention
@Documented
@Inherited
어노테이션 선언 방법
추가로 알아야 하는 어노테이션은 @interface
이다.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserAnnotation {
public int numer();
public String text() default "This is first annotation";
}
상속을 지원하지 않는다.
자바의 상속은 많은 이점을 제공한다.
enum 클래스와 마찬가지로 미리 만들어 놓은 어노테이션을 확장하는 것이 불가능하다.