자바 클래스 정리 — 필드·생성자·메소드부터 어노테이션까지
이 글은 2021년 8월 기준 기록입니다.
객체는 속성과 동작을 함께 가진 것이다
객체란 물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서, 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것을 말합니다. 이때 객체의 속성을 필드(field), 동작을 메소드(method)라고 부릅니다.
객체들 간 상호작용의 수단은 메소드입니다.
리턴값 = 객체.메소드(매개값1, 매개값2, ... );
int result = Calculator.add(10, 20);
객체와 객체 사이에는 집합 관계, 사용 관계, 상속 관계가 있습니다.
객체 지향 프로그래밍의 세 가지 특징
캡슐화(Encapsulation)는 객체의 필드와 메소드를 하나로 묶고 실제 구현 내용을 감추는 것입니다. 외부 객체는 객체 내부의 구조를 알지 못하며, 객체가 노출해서 제공하는 필드와 메소드만 이용할 수 있습니다. 목적은 외부의 잘못된 사용으로 인해 객체가 손상되지 않도록 하는 것이고, 자바에서는 접근 제한자(Access Modifier)를 사용해 캡슐화합니다.
상속(Inheritance)은 상위 객체가 자기가 가지고 있는 필드와 메소드를 하위 객체에게 물려주어 하위 객체가 사용할 수 있도록 하는 것입니다. 상위 객체를 재사용해서 하위 객체를 쉽고 빠르게 설계할 수 있게 해 주고, 반복된 코드의 중복을 줄여 줍니다. 나아가 상위 객체를 수정하면 모든 하위 객체가 함께 수정되는 효과가 있어 유지 보수 시간을 최소화합니다.
다형성(Polymorphism)은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질입니다. 자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용합니다.
클래스는 설계도이고, 객체는 그것으로 찍어낸 실물이다
메모리에서 사용하고 싶은 객체가 있다면, 설계도에 해당하는 클래스로 해당 객체를 만드는 작업이 필요합니다. 클래스에는 객체를 생성하기 위한 필드와 메소드가 정의되어 있습니다.
클래스로부터 만들어진 객체를 그 클래스의 인스턴스(Instance)라고 합니다. 여기서 관계가 일대일이 아니라는 점이 중요합니다. 하나의 클래스로 여러 개의 인스턴스를 만들 수 있습니다. 하나의 설계도로 여러 대의 자동차를 만드는 것과 같습니다.
그래서 작업 순서는 클래스 설계 → 객체 생성 → 객체 사용이 됩니다. main() 메소드가 없는 클래스는 이 객체 생성 과정을 거쳐 사용해야 합니다.
클래스 이름은 소스 파일 이름과 일치해야 한다
클래스 이름은 다른 클래스와 식별할 목적으로 사용되므로 자바의 식별자 작성 규칙을 따라야 합니다.
- 하나 이상의 문자로 이루어져야 합니다.
- 첫 번째 글자로 숫자가 올 수 없습니다.
$,_외의 특수 문자는 사용할 수 없습니다.- 자바 키워드는 사용할 수 없습니다.
관례로는 영어로 작성하되 첫 글자를 대문자로 씁니다. 그리고 클래스 이름과 소스 파일 이름은 일치해야 합니다.
public class 클래스이름 {
}
public class 부분은 반드시 소문자로 작성해야 하고, 중괄호가 클래스의 시작({)과 끝(})을 나타냅니다.
일반적으로 소스 파일당 하나의 클래스를 선언하지만 두 개 이상의 클래스 선언도 가능합니다. 다만 가급적 소스 파일 하나당 클래스 하나를 선언하는 것이 좋고, 파일 이름과 동일한 이름의 클래스 선언에만 public 접근 제한자를 붙일 수 있습니다.
객체는 new 연산자가 힙에 만든다
클래스로부터 객체를 생성할 때는 new 연산자를 사용합니다.
new 클래스();
new는 클래스로부터 객체를 생성시키는 연산자이고, 그 뒤에는 클래스() 형태의 생성자가 옵니다. new 연산자로 생성된 객체는 메모리 힙(heap) 영역에 생성되고, 힙 영역에 객체를 생성한 후 그 객체의 주소를 리턴합니다. 이 주소를 참조 타입 변수인 클래스 변수에 저장해 두면, 변수를 통해 객체를 사용할 수 있습니다.
클래스 변수;
변수 = new 클래스();
클래스 변수 = new 클래스();
클래스는 용도에 따라 두 종류로 나뉩니다. 라이브러리 클래스는 다른 클래스에서 이용할 목적으로 설계되고, 실행용 클래스는 main() 메소드를 제공하는 역할을 합니다.
클래스의 구성 멤버는 필드·생성자·메소드 셋이다
클래스에는 객체가 가져야 할 구성 멤버가 선언됩니다. 필드(Field), 생성자(Constructor), 메소드(Method)입니다.
필드는 객체의 고유 데이터, 부품 객체, 상태 정보를 저장합니다. 선언 형태가 변수(variable)와 비슷하지만 필드를 변수라고 부르지는 않습니다. 둘의 수명이 다르기 때문입니다. 변수는 생성자와 메소드 내에서만 사용되고 생성자와 메소드가 실행 종료되면 자동 소멸되지만, 필드는 생성자와 메소드 전체에서 사용되며 객체가 소멸되지 않는 한 객체와 함께 존재합니다.
생성자는 new 연산자로 호출되는 특별한 중괄호 {} 블록으로, 객체 생성 시 초기화를 담당합니다. 메소드와 비슷하게 생겼지만 이름이 클래스 이름으로 되어 있고 리턴 타입이 없습니다.
메소드는 객체의 동작에 해당하는 중괄호 {} 블록입니다. 메소드를 호출하면 중괄호 블록에 있는 모든 코드들이 일괄적으로 실행됩니다. 메소드는 객체 간의 데이터 전달 수단으로도 사용되어, 외부로부터 매개값을 받을 수도 있고 어떤 값을 리턴할 수도 있습니다.
필드는 클래스 블록 안에, 생성자와 메소드 바깥에 선언한다
필드 선언은 클래스 중괄호 {} 블록 어디서든 존재할 수 있습니다. 다만 생성자와 메소드의 중괄호 블록 내부에는 선언할 수 없습니다. 그 자리에 쓰면 필드가 아니라 로컬 변수가 되기 때문입니다.
타입 필드 = 초기값;
선언 형태가 변수와 비슷해서 클래스 멤버 변수라고 부르기도 합니다. 초기값이 지정되지 않은 필드들은 객체 생성 시 자동으로 기본 초기값으로 설정됩니다.
사용하는 위치에 따라 방법이 갈립니다. 클래스 내부의 생성자나 메소드에서 사용할 경우 단순히 필드 이름으로 읽고 변경하면 됩니다. 반면 클래스 외부에서 사용할 경우 우선 클래스로부터 객체를 생성한 뒤에 필드를 사용해야 합니다. 필드는 객체에 소속된 데이터이므로, 객체가 존재하지 않으면 필드도 존재하지 않기 때문입니다.
생성자를 하나라도 선언하면 기본 생성자는 사라진다
모든 클래스는 생성자가 반드시 존재하며, 하나 이상을 가질 수 있습니다. 생성자 선언을 생략했다면 컴파일러가 기본 생성자를 바이트 코드에 자동으로 추가합니다.
클래스() {};
그래서 생성자를 선언하지 않아도 new 연산자 뒤에 기본 생성자를 호출해서 객체를 생성할 수 있습니다.
Car myCar = new Car();
여기에 함정이 하나 있습니다. 클래스에 명시적으로 선언한 생성자가 한 개라도 있으면, 컴파일러는 기본 생성자를 추가하지 않습니다.
생성자 선언은 다음 형태입니다.
클래스( 매개변수선언, ... ) {
객체의 초기화 코드
}
메소드와 비슷한 모양이지만 리턴 타입이 없고 클래스 이름과 동일합니다. 그리고 매개변수 선언 개수에 따라 그에 해당하는 생성자가 필요합니다.
필드 초기화는 선언 시점 또는 생성자에서
클래스로부터 객체가 생성될 때 필드는 기본 초기값으로 자동 설정됩니다. 다른 값으로 초기화하고 싶다면 필드를 선언할 때 초기값을 주거나, 생성자에서 초기값을 주면 됩니다.
두 방법은 결과가 다릅니다. 필드를 선언할 때 초기값을 주면 동일한 클래스로부터 생성되는 객체들이 모두 같은 데이터를 갖게 됩니다. 객체 생성 시점에 외부에서 제공하는 다양한 값들로 초기화해야 한다면 생성자에서 초기화해야 합니다.
생성자 안에서 필드를 초기화할 때는 관례적으로 필드 이름과 매개변수 이름을 동일하게 합니다. 이 경우 매개변수의 우선순위가 높아서 생성자 내부에서는 객체의 필드에 접근할 수 없게 됩니다. 이때 this.를 이용해 객체 자신의 필드에 접근합니다.
public Korean(String name, String ssn) {
this.name = name;
this.ssn = ssn;
}
오버로딩과 this()
매개 변수를 달리하는 생성자를 여러 개 선언하는 것을 생성자 오버로딩(Overloading)이라고 합니다.
생성자 오버로딩이 많아지면 생성자 간에 중복된 코드가 발생합니다. 이때는 필드 초기화 내용을 한 생성자에만 집중적으로 작성하고, 나머지 생성자는 초기화 내용을 가지고 있는 그 생성자를 호출하도록 합니다.
클래스( 매개변수선언, ... ) {
this( 매개변수, ..., 값, ... );
실행문;
}
메소드는 선언부와 실행 블록으로 나뉜다
메소드는 선언부(리턴 타입, 메소드 이름, 매개변수 선언)와 실행 블록으로 구성됩니다. 선언부를 메소드 시그니처라고도 합니다.
리턴 타입은 메소드가 실행 후 리턴하는 값의 타입입니다. 메소드는 리턴 값이 있을 수도 있고 없을 수도 있는데, 리턴 값이 없는 메소드는 타입 자리에 void가 오고 리턴값이 있는 메소드는 해당 리턴 타입이 옵니다. 호출하는 쪽에서도 차이가 생겨서, 리턴 값 없는 메소드는 그냥 실행하고 있는 메소드는 변수에 저장합니다.
메소드 이름은 숫자로 시작하면 안 되고, $와 _를 제외한 특수 문자를 사용하지 않아야 합니다. 관례적으로 소문자로 작성하고, 서로 다른 단어가 혼합된 이름이라면 뒤이어 오는 단어의 첫머리 글자를 대문자로 작성합니다.
매개 변수 선언은 메소드가 실행될 때 필요한 데이터를 외부로부터 받기 위해 사용합니다. 그런데 매개 변수의 수를 모를 때가 있습니다. 여러 개의 수를 모두 합산하는 메소드처럼 매개 변수 수를 알 수 없는 경우에는 배열 타입으로 선언하는데, 이 경우 메소드 호출 전에 배열을 선언해야 합니다. 배열을 선언하지 않고 값의 리스트만 넘겨주고 싶다면 ... 매개변수를 사용합니다.
리턴문
메소드 선언에 리턴 타입이 있는 메소드는 반드시 리턴문을 사용해서 리턴값을 지정해야 합니다.
return 리턴값;
리턴값은 리턴 타입이거나 리턴 타입으로 변환될 수 있어야 하며, return문 이후의 실행문은 실행되지 않습니다.
void로 선언된, 리턴값이 없는 메소드에서도 return문을 사용해 메소드를 종료시킬 수 있습니다.
return;
메소드 호출
클래스 내부에서 다른 메소드를 호출할 때는 이름만으로 부릅니다.
메소드( 매개값, ... );
타입 변수 = 메소드( 매개값, ... );
외부 클래스에서 메소드를 호출하려면 우선 클래스로부터 객체를 생성해야 합니다. 메소드는 객체에 소속된 멤버이므로, 객체가 존재하지 않으면 메소드도 존재하지 않기 때문입니다.
클래스 참조변수 = new 클래스( 매개값, ... );
타입 변수 = 참조변수.메소드( 매개값, ... );
메소드 오버로딩
클래스 내에 같은 이름의 메소드를 여러 개 선언하는 것을 메소드 오버로딩이라고 합니다. 조건은 매개 변수의 타입, 개수, 순서 등이 달라야 한다는 것입니다. JVM이 매개값의 타입을 보고 호출할 메소드를 선택하기 때문입니다. 다양한 매개값을 받아 처리할 수 있도록 하기 위한 장치입니다.
인스턴스 멤버는 객체에, 정적 멤버는 클래스에 붙는다
인스턴스(Instance) 멤버란 객체를 생성한 후 사용할 수 있는 필드와 메소드를 말합니다. 객체 내부에서 인스턴스 멤버에 접근하기 위해 this를 사용합니다.
정적 멤버는 클래스에 고정된 멤버로서, 객체를 생성하지 않고 사용할 수 있는 필드와 메소드입니다. 객체에 소속된 멤버가 아니라 클래스에 소속된 멤버이기 때문에 클래스 멤버라고도 합니다.
정적 멤버는 필드와 메소드 선언 시 static 키워드를 추가해 만듭니다.
public class 클래스 {
static 타입 필드 = 초기값;
static 리턴타입 메소드( 매개변수선언, ... ){};
}
정적 필드와 정적 메소드는 클래스에 고정된 멤버이므로, 클래스 로더가 클래스를 로딩해서 메소드 메모리 영역에 적재할 때 클래스별로 관리되고 로딩이 끝나면 바로 사용할 수 있습니다.
무엇을 정적으로 선언할지에 대한 기준은 단순합니다. 객체마다 가지고 있어야 할 데이터라면 인스턴스 필드로, 공용 데이터라면 정적 필드로 선언합니다. 마찬가지로 인스턴스 필드를 이용해서 실행해야 한다면 인스턴스 메소드로, 그렇지 않다면 정적 메소드로 선언합니다.
사용할 때는 클래스 이름과 함께 도트(.) 연산자로 접근합니다.
클래스.필드;
클래스.메소드( 매개값, ... );
정적 초기화 블록
정적 필드는 선언과 동시에 초기값을 주는 것이 보통이지만, 자바는 정적 필드의 초기화 작업을 위해 정적 블록도 제공합니다.
static {
...
}
정적 블록은 클래스가 메모리로 로딩될 때 자동으로 실행됩니다. 클래스 내부에 여러 개가 선언되어도 상관없으며, 선언된 순서대로 실행됩니다.
정적 멤버 안에서는 this를 쓸 수 없다
정적 메소드와 정적 블록은 객체가 없어도 실행된다는 특징 때문에 제약이 따라옵니다. 그 내부에서는 인스턴스 필드나 인스턴스 메소드를 사용할 수 없고, 객체 자신의 참조인 this 키워드도 사용할 수 없습니다.
정적 메소드와 정적 블록에서 인스턴스 멤버를 사용하고 싶다면 객체를 먼저 생성하고 참조 변수로 접근해야 합니다.
static void Method3() {
ClassName obj = new ClassName();
obj.field1 = 10;
obj.method1();
}
싱글톤은 생성자를 막는 데서 출발한다
전체 프로그램에서 단 하나의 객체만 만들도록 보장해야 하는 경우가 있습니다. 이 객체를 싱글톤(Singleton)이라고 합니다.
싱글톤을 만들려면 클래스 외부에서 new 연산자로 생성자를 호출할 수 없도록 막아야 합니다. 방법은 생성자 앞에 private 접근 제한자를 붙이는 것입니다. 그리고 자신의 타입인 정적 필드를 하나 선언해 자신의 객체를 생성해 초기화합니다. 클래스 내부에서는 new 연산자로 생성자를 호출할 수 있으므로 가능합니다. 이 정적 필드에도 private을 붙여 외부에서 필드값을 변경하지 못하도록 막고, 외부에서 호출할 수 있는 정적 메소드 getInstance()를 선언해 정적 필드가 참조하고 있는 자신의 객체를 리턴해 줍니다.
public class 클래스 {
private static 클래스 singleton = new 클래스();
private 클래스() {}
static 클래스 getInstance() {
return singleton;
}
}
getInstance() 메소드는 단 하나의 객체만 리턴하기 때문에, 아래 코드에서 변수1과 변수2는 동일한 객체를 참조합니다.
클래스 변수1 = 클래스.getInstance();
클래스 변수2 = 클래스.getInstance();
final 필드는 값을 고정하고, static final은 상수가 된다
final 필드는 초기값이 저장되면 프로그램 실행 도중에 수정할 수 없습니다.
final 타입 필드 = 초기값;
초기값을 줄 수 있는 방법은 딱 두 가지뿐입니다. 필드 선언 시에 주는 방법과 생성자에서 주는 방법입니다. 초기화하지 않은 final 필드를 그대로 남겨 두면 컴파일 에러가 발생합니다.
그런데 final 필드만으로는 상수가 되지 않습니다. final 필드는 객체마다 저장되고 생성자의 매개값을 통해 여러 가지 값을 가질 수 있기 때문입니다. 상수 선언은 static final이어야 합니다.
static final 타입 상수 = 초기값;
static final은 객체마다 저장되지 않고 클래스에만 포함되며, 한 번 초기값이 저장되면 변경할 수 없습니다. 정적 블록에서도 초기화할 수 있습니다. 상수 이름은 모두 대문자로 작성하는 것이 관례이고, 서로 다른 단어가 혼합된 경우 밑줄(_)로 단어들을 연결합니다.
패키지는 클래스를 유일하게 만드는 식별자다
프로젝트를 개발하다 보면 적게는 수십 개, 많게는 수백 개의 클래스를 작성하게 됩니다. 이 클래스들을 체계적으로 관리하기 위해 패키지(package)를 사용하며, 물리적인 형태는 파일 시스템의 폴더입니다.
패키지의 역할은 정리에 그치지 않습니다. 패키지는 클래스를 유일하게 만들어 주는 식별자 역할을 합니다. 클래스 이름이 동일하더라도 패키지가 다르면 다른 클래스로 인식합니다. 패키지가 상·하위로 구분되어 있다면 도트(.)를 사용해 상위패키지.하위패키지.클래스로 표현합니다.
패키지는 클래스를 컴파일하는 과정에서 자동으로 생성되는 폴더입니다.
package 상위패키지.하위패키지;
public class ClassName {}
패키지 이름에는 규칙이 있습니다.
- 숫자로 시작해서는 안 되고,
$와_를 제외한 특수 문자를 사용해서는 안 됩니다. java로 시작하는 패키지는 자바 표준 API에서만 사용하므로 사용해서는 안 됩니다.- 모두 소문자로 작성하는 것이 관례입니다.
패키지 중복을 피하기 위해 일반적으로 회사 도메인을 역순으로 하여 패키지 이름을 짓습니다.
패키지 선언이 포함된 클래스를 명령 프롬프트에서 컴파일할 경우에는 -d 옵션을 추가하고 경로를 지정해야 합니다. 이클립스에서는 프로젝트의 src 폴더를 선택하고 [File] - [New] - [Package]에서 상위 패키지와 하위 패키지를 도트로 구분해 입력한 뒤 Finish를 누르고, 해당 패키지를 선택한 상태에서 [File] - [New] - [Class]로 클래스를 만듭니다.
import문
같은 패키지에 속하는 클래스들은 아무런 조건 없이 서로를 사용할 수 있습니다. 다른 패키지에 속하는 클래스를 사용하려면 패키지와 클래스를 모두 기술하거나 import문을 이용합니다.
사용하고자 하는 패키지를 import문으로 선언해 두면 클래스를 사용할 때 패키지를 생략할 수 있습니다. import문이 작성되는 위치는 패키지 선언과 클래스 선언 사이입니다.
단, 서로 다른 패키지에 동일한 클래스 이름이 존재하고 둘 모두 import된 경우에는 패키지 이름 전체를 기술해야 합니다.
접근 제한자는 누가 쓸 수 있는지를 정한다
라이브러리 클래스를 설계할 때에는 외부 클래스에서 접근할 수 있는 멤버와 접근할 수 없는 멤버로 구분해서 필드, 생성자, 메소드를 설계합니다. 이러한 기능을 구현하기 위해 자바는 접근 제한자(Access Modifier)를 제공합니다.
| 접근 제한자 | 적용 대상 | 접근할 수 없는 클래스 |
|---|---|---|
public |
클래스, 필드, 생성자, 메소드 | 없음 |
protected |
필드, 생성자, 메소드 | 자식 클래스가 아닌 다른 패키지에 소속된 클래스 |
default |
클래스, 필드, 생성자, 메소드 | 다른 패키지에 소속된 클래스 |
private |
필드, 생성자, 메소드 | 모든 외부 클래스 |
클래스의 접근 제한
클래스를 선언할 때 고려해야 할 사항은 같은 패키지 내에서만 사용할 것인지, 다른 패키지에서도 사용할 수 있도록 할 것인지입니다. 클래스에 사용할 수 있는 접근 제한자는 default와 public 두 가지입니다.
class 클래스 {} // default
public class 클래스 {} // public
default 접근 제한은 같은 패키지에서는 아무 제한 없이 사용할 수 있지만 다른 패키지에서는 사용할 수 없습니다. public 접근 제한은 같은 패키지뿐만 아니라 다른 패키지에서도 아무런 제한 없이 사용할 수 있습니다.
생성자의 접근 제한
객체를 생성하려면 new 연산자로 생성자를 호출해야 합니다. 생성자가 호출될 때 적용되는 접근 제한은 public, protected, default, private 네 가지 모두입니다.
public class ClassName {
public ClassName() {} // public
protected ClassName() {} // protected
ClassName() {} // default
private ClassName() {} // private
}
클래스에 생성자를 선언하지 않았을 때 자동으로 생성되는 기본 생성자는 클래스의 접근 제한과 동일한 접근 제한을 가집니다.
| 접근 제한자 | 대상 | 설명 |
|---|---|---|
public |
클래스() | 모든 패키지에서 호출 가능. 단 클래스가 default 접근 제한을 가진다면 클래스 접근이 같은 패키지로 한정되므로 같은 패키지에서만 생성자를 호출할 수 있음 |
protected |
클래스() | 같은 패키지에 속하는 클래스에서 생성자 호출 가능. 다른 패키지에 속한 클래스가 해당 클래스의 자식 클래스라면 호출 가능 |
default |
클래스() | 같은 패키지에서만 생성자 호출 가능 |
private |
클래스() | 동일 패키지에서도 생성자를 호출하지 못함. 클래스 내부에서만 new 연산자로 객체를 만들 수 있음 |
필드와 메소드의 접근 제한
필드와 메소드를 선언할 때의 고려 사항은 클래스 내부에서만 사용할 것인지, 패키지 내에서만 사용할 것인지, 다른 패키지에서도 사용할 것인지입니다.
| 접근 제한자 | 대상 | 설명 |
|---|---|---|
public |
필드, 메소드() | 모든 패키지에서 필드와 메소드 사용 가능. 단 클래스가 default 접근 제한을 가진다면 같은 패키지에서만 사용 가능 |
protected |
필드, 메소드() | 같은 패키지에 속하는 클래스에서 사용 가능. 다른 패키지에 속한 클래스가 해당 클래스의 자식 클래스라면 사용 가능 |
default |
필드, 메소드() | 같은 패키지에서만 사용 가능 |
private |
필드, 메소드() | 클래스 내부에서만 사용 가능 |
Getter와 Setter는 데이터의 무결성을 지킨다
일반적으로 객체 지향 프로그래밍에서는 객체의 데이터를 객체 외부에서 직접 접근하는 것을 막습니다. 객체의 데이터를 외부에서 마음대로 읽고 변경할 경우 무결성이 깨질 수 있기 때문입니다. 그래서 메소드를 통해서 데이터를 변경하는 방법을 선호합니다.
데이터는 외부에서 접근할 수 없도록 막고 메소드는 공개하면, 매개값을 검증해서 유효한 값만 데이터로 저장할 수 있습니다. 이 역할을 하는 메소드가 Setter입니다.
읽을 때도 마찬가지입니다. 객체 외부에서 객체의 필드값을 그대로 사용하기 부적절한 경우, 메소드로 필드값을 가공해서 외부로 전달합니다. 이 역할을 하는 메소드가 Getter입니다.
따라서 클래스를 선언할 때 가능하다면 필드를 private으로 선언해서 외부로부터 보호하고, 필드에 대한 Setter와 Getter 메소드를 작성해서 사용합니다.
private 타입 fieldName;
public 리턴타입 getFieldName() { // Getter
return fieldName;
}
public 리턴타입 setFieldName(타입 fieldName) { // Setter
this.fieldName = fieldName;
}
필드 타입이 boolean일 경우 Getter는 get으로 시작하지 않고 is로 시작하는 것이 관례입니다 — isStop().
이클립스에서는 필드를 선언한 후 [Source] - [Generate Getters and Setters]로 자동 생성할 수 있습니다.
어노테이션은 코드에 붙이는 메타데이터다
어노테이션(Annotation)은 메타데이터라고 볼 수 있으며, @AnnotationName 형태로 씁니다. 용도는 세 가지입니다.
- 컴파일러에게 코드 문법 에러를 체크하도록 정보를 제공합니다.
- 소프트웨어 개발 툴이 빌드나 배포 시 코드를 자동으로 생성할 수 있도록 정보를 제공합니다.
- 실행 시 특정 기능을 실행하도록 정보를 제공합니다.
어노테이션 타입 정의는 인터페이스를 정의하는 것과 유사합니다.
public @interface AnnotationName {
}
어노테이션은 엘리먼트(element)를 멤버로 가질 수 있습니다. 각 엘리먼트는 타입과 이름으로 구성되며 디폴트 값을 가질 수 있습니다.
public @interface AnnotationName {
타입 elementName() default 값;
}
public @interface AnnotationName {
String elementName1();
int elementName2() default 5;
}
적용할 때는 다음과 같이 씁니다.
@AnnotationName(elementName1="값", elementName2=3)
@AnnotationName(elementName1="값")
elementName1은 default 값이 없기 때문에 반드시 값을 기술해야 합니다.
적용 대상은 @Target으로 좁힌다
어노테이션을 적용할 수 있는 대상은 java.lang.annotation.ElementType 열거 상수로 정의됩니다.
| ElementType 열거 상수 | 적용 대상 |
|---|---|
TYPE |
클래스, 인터페이스, 열거 타입 |
ANNOTATION_TYPE |
어노테이션 |
FIELD |
필드 |
CONSTRUCTOR |
생성자 |
METHOD |
메소드 |
LOCAL_VARIABLE |
로컬 변수 |
PACKAGE |
패키지 |
대상을 지정할 때에는 @Target 어노테이션을 사용합니다.
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
public @interface AnnotationName {}
이렇게 지정하면 생성자에는 적용할 수 없습니다.
@AnnotationName
public class ClassName {
@AnnotationName
private String fieldName;
@AnnotationName
public ClassName() {}
@AnnotationName
public void methodName() {}
}
유지 정책이 어디까지 살아남을지를 정한다
어노테이션은 사용 용도에 따라 어느 범위까지 유지할 것인지를 지정합니다. 유지 정책은 java.lang.annotation.RetentionPolicy 열거 상수로 정의됩니다.
| RetentionPolicy 열거 상수 | 설명 |
|---|---|
SOURCE |
소스상에서만 어노테이션 정보를 유지. 소스 코드를 분석할 때만 의미가 있으며, 바이트 코드 파일에는 정보가 남지 않음 |
CLASS |
바이트 코드 파일까지 어노테이션 정보를 유지. 리플렉션을 이용해서 어노테이션 정보를 얻을 수는 없음 |
RUNTIME |
바이트 코드 파일까지 어노테이션 정보를 유지하면서, 리플렉션을 이용해서 런타임 시에 어노테이션 정보를 얻을 수 있음 |
여기서 리플렉션이란 런타임 시에 클래스의 메타 정보를 얻는 기능입니다.
런타임 정책을 적용한 어노테이션은 다음과 같이 선언합니다.
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationName {}
런타임에 어노테이션 정보 읽기
어노테이션 자체는 아무런 동작을 가지지 않는 단지 표식일 뿐입니다. 그것에 의미를 부여하는 것은 읽는 쪽입니다. 리플렉션을 이용해서 어노테이션의 적용 여부와 엘리먼트 값을 읽고 처리할 수 있습니다.
클래스에 적용된 어노테이션 정보를 얻으려면 java.lang.Class를 이용합니다. 필드, 생성자, 메소드에 적용된 어노테이션 정보를 얻으려면 Class의 다음 메소드를 통해 java.lang.reflect 패키지의 Field, Constructor, Method 타입의 배열을 먼저 얻어야 합니다.
| 리턴 타입 | 메소드명(매개 변수) | 설명 |
|---|---|---|
Field[] |
getFields() |
필드 정보를 Field 배열로 리턴 |
Constructor[] |
getConstructors() |
생성자 정보를 Constructor 배열로 리턴 |
Method[] |
getDeclaredMethods() |
메소드 정보를 Method 배열로 리턴 |
그런 다음 Class, Field, Constructor, Method가 가지고 있는 다음 메소드를 호출해서 적용된 어노테이션 정보를 얻을 수 있습니다.
| 리턴 타입 | 메소드명(매개 변수) | 설명 |
|---|---|---|
boolean |
isAnnotationPresent(Class<? extends Annotation> annotationClass) |
지정한 어노테이션이 적용되었는지 여부. Class에서 호출했을 때 상위 클래스에 적용된 경우에도 true를 리턴 |
Annotation |
getAnnotation(Class<T> annotationClass) |
지정한 어노테이션이 적용되어 있으면 어노테이션을, 그렇지 않다면 null을 리턴. Class에서 호출했을 때 상위 클래스에 적용된 경우에도 어노테이션을 리턴 |
Annotation[] |
getAnnotations() |
적용된 모든 어노테이션을 리턴. Class에서 호출했을 때 상위 클래스에 적용된 어노테이션도 모두 포함. 적용된 어노테이션이 없을 경우 길이가 0인 배열을 리턴 |
Annotation[] |
getDeclaredAnnotations() |
직접 적용된 모든 어노테이션을 리턴. Class에서 호출했을 때 상위 클래스에 적용된 어노테이션은 포함되지 않음 |
클래스를 다룬다는 것은 결국 세 가지를 정하는 일입니다. 무엇을 데이터로 가질 것인가(필드), 어떻게 태어날 것인가(생성자), 무엇을 할 수 있는가(메소드). 접근 제한자와 static은 그 셋을 누구에게 어디까지 열어 줄지를 결정하는 장치입니다.
'IT·테크 > 개발' 카테고리의 다른 글
| 자바 조건문과 반복문 — if·switch·for·while 흐름 제어 (0) | 2026.07.23 |
|---|---|
| Git 기본기 — 스테이징부터 되돌리기와 브랜치까지 (0) | 2026.07.22 |
| 자바 연산자 정리 — 우선순위, 오버플로우, NaN과 Infinity (0) | 2026.07.22 |
| 자바 참조 타입 — 스택과 힙, 배열, 열거 타입 정리 (0) | 2026.07.21 |
| 자바 변수와 타입 — 기본 타입 8개와 타입 변환 규칙 (0) | 2026.07.20 |