Search

Chapter06. 클래스

생성일
2021/03/16 10:33
태그
Part 1

객체 지향 프로그래밍

Object Oriented Programming ( OOP )
현실 : 부품을 조립하여 하나의 완성품을 만든다.
객체 지향 프로그래밍 : 객체를 만들고 조립하여 하나의 완성된 프로그램을 만드는 기법.

객체란?

물리적으로 존재하거나 추상적으로 생각할 수 있는 것 중에서 자신의 속성을 가지고 있고 다른 것과 식별 가능한 것
자동차, 자전거, 책, 사람
학과, 주문, 강의, 업무
객체는 속성과 동작으로 구성되어 있다.
사람 : 속성(나이, 이름), 동작(잔다, 먹는다)
자동차 : 속성(배기량, 색상), 동작(달리다, 멈추다)
자바 : 속성(필드), 동작(메소드)
객체 모델링 : 현실의 객체를 소프트웨어 객체로 설계하는 것
현실의 속성, 동작 → 소프트웨어 필드, 메소드

객체의 상호작용

현실 세계의 모든 객체는 상호작용을 한다.
나는 컴퓨터를 통해서 유튜브를 본다 → '나' - '컴퓨터' 간의 상호작용
소프트웨어의 독립적인 객체들이 서로 상호작용을 한다.
상호작용 수단 = 메소드
객체가 다른 객체의 기능을 이용 = 메소드 호출
메소드 호출
객체 . 메소드
리턴값 = 객체.메소드(매개값1, 매개값2, ...);
Java
복사
리턴값 : 메소드가 실행되고난 이후 돌려주는 값
매개값 : 메소드를 실행하기 위해 필요한 값

객체 간의 관계

객체는 개별적으로 사용할 수 있지만, 보통 다른 객체와 관계를 맺고 있다.
집합 관계, 사용 관계, 상속 관계
집합 관계 : 객체와 객체를 구성하는 객체의 관계 ( 완성품 - 부품 )
사용 관계 : 서로 상호작용을 하는 객체의 관계 ( 사람 - 자동차 )
상속 관계 : 객체를 기반으로 생성된 하위 객체의 관계 ( 부모 - 자식 )

객체 지향 프로그래밍의 특징

캡슐화(Encapsulation)

필드, 메소드를 하나로 묶고 실제 구현 내용을 감추는 것
외부의 객체는 내부를 알 수 없고 의도적으로 드러낸 필드, 메소드만 이용할 수 있다.
외부의 잘못된 사용으로 객체가 손상되지 않기위한 목적
접근 제한자 : 캡슐화된 객체의 멤버(필드, 메소드)를 드러낼 것인지, 숨길 것인지 결정

상속(Inheritance)

부모의 재산을 자식이 물려받는 것
상위 객체의 필드와 메소드를 하위 객체가 물려받는 것
상위 객체의 재사용을 통해서 하위 객체를 쉽고 빠르고 중복을 최소화하여 설계
상위 객체의 수정으로 하위 객체에 영향을 주어 유지 보수가 용이함

다형성(Polymorphism)

동일한 타입으로부터 실행 결과가 다양한 객체를 이용할 수 있는 성질
자바는 다형성을 위해 부모 클래스 또는 인터페이스의 타입 변환을 허용
부모 타입에 모든 자식 객체 대입 가능
인터페이스 타입에 모든 구현 객체 대입 가능
객체의 부품화

객체와 클래스

클래스 : 객체를 생성하는데 필요한 필드와 메소드를 정의
클래스로부터 만들어진 객체 == 클래스의 인스턴스
클래스로부터 객체를 만드는 과정 = 인스턴스화
OOP 개발 단계
1.
클래스를 설계한다.
2.
설계한 클래스로 객체를 생성한다.
3.
생성한 객체를 사용한다.

클래스 선언

클래스의 이름 작성 규칙
1.
하나 이상의 문자
2.
첫 번째 글자로 숫자 불가능
3.
$와 _ 이외 특수문자 불가능
4.
자바 예약어 불가능
첫번째 문자는 대문자 ( 관례 )
클래스명과 동일한 파일명 작성 : Person 클래스 ⇒ Person.java
// Person.java public class Person { // 필드 // 메소드 } // 하나의 파일에 두 개의 클래스가 선언되면? class Male { } // 컴파일 시, 각 클래스명.class로 생성 // 파일명과 동일한 클래스만 public 접근 제한자 붙임 // 가급적 하나의 소스 파일에 하나의 클래스만 선언한다.
Java
복사

객체 생성과 클래스 변수

객체 생성 : new 클래스() → 생성자
new 연산자를 사용하여 객체를 생성하면 메모리의 힙 영역에 할당된다 → 객체의 주소를 반환한다.
참조 타입인 클래스 변수에 new 연산자가 반환한 객체의 주소를 저장
클래스 변수; 변수 = new 클래스(); or 클래스 변수 = new 클래스();
Java
복사
같은 클래스로부터 생성된 되었을지라도 new 연산자를 사용하여 생성된 객체는 서로 독립적이다.
라이브러리 클래스 / 실행 클래스
public class Student{ public static void main(String[] args){ // main() 메소드를 제공하는 Student 클래스 Study java = new Study(); // 다른 클래스에서 사용될 목적으로 설계된 Study 클래스 } }
Java
복사

클래스의 구성 멤버

필드, 생성자, 메소드 : 생략되거나 여러개 일 수 있다.

필드

객체의 고유 데이터, 부품 객체, 상태 정보를 저장하는 곳
선언 형태는 변수와 비슷하나 변수라고 부르지 않는다.
변수 : 생성자와 메소드 내에서만 사용됨 → 실행 종료시 소멸
필드 : 생성자와 메소드 모두에서 사용됨 → 객체와 함께 유지

생성자

객체 생성 시 new 연산자로 호출되는 특별한 중괄호 블록
필드 초기화 , 메소드 호출로 객체를 사용할 준비 담당
메소드와 비슷한 형태
클래스 이름으로 되어있고 리턴 타입이 없다.

메소드

객체의 동작에 해당하는 중괄호 블록
메소드를 호출하면 중괄호 블록 내의 코드가 실행
필드를 읽고 수정 + 다른 객체를 생성하여 다양한 기능을 수행하기도 함
객체 간의 데이터 전달의 수단으로 사용
외부로 부터 매개값을 받거나, 실행 후 어떤 값을 반환할 수도 있다.

필드

객체가 가지는 고유 데이터, 상태 데이터, 부품 객체를 저장하는 곳

필드 선언

클래스의 중괄호 안에 존재하며 생성자, 메소드의 앞과 뒤 어디든 선언이 가능하다.
생성자와 메소드 안에서 선언된 것은 로컬 변수가 된다.
변수 선언과 유사하여 클래스 멤버 변수라고도 부른다.
기본 타입, 참조 타입
초기값은 선언 시 주어질 수도, 생략될 수도 있다.
초기화하지 않은 필드는 객체 생성시 자동으로 기본 초기값으로 초기화된다.
정수 : 0, 실수 : 0.0, boolean : false, 참조 : null

필드 사용

필드를 사용하는 것은 필드값을 읽고, 변경하는 작업
객체 내부의 생성자와 메소드는 필드의 이름으로 읽고 변경
클래스 외부에서 사용할 경우, 우선 객체를 생성한 뒤 필드를 사용해야 한다 → 객체가 없으면 필드도 없다.
도트 연산자를 사용하여 객체 필드 사용 → 객체 . 필드

생성자

new 연산자와 함께 사용되어 클래스로부터 객체를 생성할 때 호출 → 객체의 초기화 담당
객체 초기화 : 필드를 초기화하거나, 메소드를 호출하여 객체를 사용할 준비를 하는 것
생성자를 실행시키지 않고는 클래스로부터 객체를 생성할 수 없다.
new 연산자와 함께 생성자가 실행되면 힙 영역에 객체가 생성되고 객체의 주소가 반환된다. 반환된 주소는 클래스 타입 변수에 저장되어 ( 참조 타입 ) 객체에 접근할 때 사용된다.

기본 생성자

모든 클래스는 무조건 하나 이상의 생성자를 갖는다.
클래스 내부에 생성자를 생략했다면 컴파일 시 기본 생성자를 바이트 코드(.class)에 추가한다.
기본 생성자 : 중괄호 {} 블록이 비어있는 생성자
클래스에 명시적으로 선언한 생성자가 있는 경우, 컴파일러는 기본 생성자를 추가하지 않는다.
Person me = new Person(); // 기본 생성자
Java
복사

생성자 선언

생서자는 메소드와 비슷한 형태를 갖지만 리턴 타입이 없고 클래스 이름과 동일하다.
생성자 선언시 매개변수를 추가하여 외부의 값을 생성자 블록 내부로 전달할 수 있다.
클래스에 매개변수가 존재하는 생성자를 명시적으로 선언한 경우, 객체 생성시 반드시 동일한 매개변수 타입과 순서로 생성자를 호출해야 한다 → 기본 생성자 사용 불가능
public class Car { Car(String color, int cc){ // 매개변수가 있는 생성자 선언 ... } }
Java
복사
public class CarExample { Car myCar = new Car(); // 사용 불가능 Car myCar = new Car("black", 3000); // 사용 가능 }
Java
복사

필드 초기화

클래스로 부터 객체가 생성될 때, 필드는 기본 초기값으로 초기화된다.
다른 방법으로 필드를 초기화 하는 경우 → 선언과 동시에 초기화 or 생성자에서 초기화
필드 선언과 동시에 초기화 : 동일한 클래스로부터 생성된 객체들은 생성 시점에서 필드의 값이 동일
생성자에서 초기화 : 객체 생성 시점에서 외부로 부터 매개 변수로 값을 받아 필드 초기화
초기화 하고자 하는 필드 명과 생성자의 매개 변수 명을 동일하게 하는 것이 관례
생성자 내부에서 필드에 접근 불가능 → 동일한 이름의 매개 변수의 우선순위가 더 높다
객체 스스로를 가리키는 'this'를 사용하여 필드에 접근
public class Korean{ String name; String ssn; public Korean(String name, String ssn){ name = name; // 필드와 매개변수 이름 동일. 필드 접근 불가 this.name = name; // 클래스로 부터 생성된 객체 자신 -> this 참조 타입 변수로 필드를 사용 } }
Java
복사

생성자 오버로딩

외부에서 제공하는 다양한 데이터를 이용하여 객체를 초기화 하기위해 생성자도 다양화될 필요가 있다.
매개 변수의 타입과 순서가 다른 여러개의 생성자를 선언하는 것
public class Person{ String name; int age; public Person(){ } public Person(String name){ this.name = name; } public Person(String name, int age){ this.name = name; this.age = age; } }
Java
복사
매개 변수의 타입과 개수, 순서가 똑같을 경우 매개 변수 이름만 바꾸는 것은 생성자 오버로딩이 아니다.
생성자가 오버로딩 된 경우, new 연산자로 생성자를 호출할 때, 외부에서 입력된 매개값의 타입과 개수, 순서에 의해 호출될 생성자를 결정한다.

다른 생성자 호출

생성자 오버로딩이 많아 질 경우, 생성자 간에 중복된 코드가 많아질 수 있다.
매개 변수만 다르고 필드 초기화 내용이 비슷한 생성자에서 자주 보인다.
this() : 자신의 다른 생성자를 호출하는 코드로 반드시 생성자의 첫줄에서만 사용한다.
this() 의 매개값은 호출할 생성자의 매개 변수 타입, 개수, 순서에 맞게 제공해야 하고 this() 코드 뒤에 추가적인 코드 작성이 가능하다.
String model; String color; int maxSpeed; Car(String model){ this.model = model; this.color = "silver"; this.maxSpeed = 200; } Car(String model, String color){ this.model = model; this.color = color; this.maxSpeed = 200; } Car(String model, String color, int maxSpeed){ this.model = model; this.color = color; this.maxSpeed = maxSpeed; }
Java
복사
Car(String model){ this(model, "silver", 200); } Car(String model, String color){ this(model, color, 200); } Car(String model, String color, int maxSpeed){ this.model = model; this.color = color; this.maxSpeed = maxSpeed; }
Java
복사

메소드

객체의 동작에 해당하는 부분
객체의 필드를 읽고 변경하거나 다른 객체를 생성하여 다양한 기능을 수행하기도 한다.
객체 간의 데이터 전달의 수단으로 외부로부터 매개값을 전달 받을 수 있고, 실행 후 값을 반환할 수 있다.

메소드 선언

선언부 ( 리턴타입 + 메소드 이름 + 매개 변수 ) 와 실행 블록 {} 으로 구성.
메소드 선언부를 메소드 시그니처 라고도 한다.

리턴 타입

리턴값 : 메소드 실행 후 반환하는 값
메소드는 리턴값이 있을 수도, 없을 수도 있다.
리턴값이 없는 메소드는 리턴 타입에 void가 와야한다.
리턴 타입이 있는 메소드는 리턴값을 받는 변수가 있을 수도, 없을 수도 있다.

메소드 이름

숫자로 시작하면 안된다.
$와 _ 를 제외한 특수 문자를 사용하면 안된다.
관례적으로 소문자로 작성한다.
두개 이상의 단어로 작성 할 경우, 뒤의 단어의 첫 글자는 대문자로 작성한다.
메소드의 기능을 파악할 수 있도록 너무 짧지 않게 작성한다.

매개 변수 선언

메소드가 필요로 하는 외부값을 받기 위해 사용하는 변수
매개 변수가 필요할 수도, 필요 없을수도 있다.
메소드 호출 시 매개값은 메소드 선언 시 작성한 매개 변수의 타입과 순서와 동일해야 한다.

매개 변수의 수를 모를 경우

매개 변수를 배열 타입으로 선언한다.
메소드를 호출하기 전, 배열을 생성해야한다.
매개 변수를 ' ... ' 를 사용해서 선언한다.
메소드 호출 시 값의 리스트를 넘겨준다.

리턴(return)문

리턴값이 있는 메소드

메소드 선언 시 리턴 타입이 있는 경우, 반드시 return 문을 사용해서 리턴값을 반환한다.
return 문이 실행되면 메소드는 즉시 종료된다.
리턴값은 선언시 지정한 리턴 타입이거나 리턴 타입으로 변환되어야 한다.
리턴 타입이 int 인 경우, 리턴값이 byte, char, short이면 자동으로 int로 변환이 된다.
동일한 블록 내에서 return 문의 뒤에는 다른 실행문이 올 수 없다.

리턴값이 없는 메소드(void)

리턴 타입이 void로 선언된 메소드도 return 문이 올 수 있다.
return 문이 실행되면 메소드는 즉시 종료된다.

메소드 호출

클래스 내부의 다른 메소드를 호출하는 경우 메소드 명으로 호출한다.
클래스 외부에서 메소드를 호출하는 경우, 객채를 생성하고 참조 변수를 사용해 메소드를 호출한다.
리턴 타입에 맞는 변수를 선언하여 리턴 값을 대입한다.

객체 내부에서 호출

메소드의 이름을 사용하고 매개 변수의 타입과 수에 맞게 호출한다.

객체 외부에서 호출

호출할 객체의 클래스로부터 객체를 생성한다.
객체 . 메소드 ( 매개변수 ... )

메소드 오버로딩

클래스 내에 동일한 이름의 메소드를 여러 개 선언하는 것
메소드 명은 동일하고 매개 변수의 타입, 개수, 순서중 하나가 달라야 한다.
매개값을 다양하게 받아 처리하기 위해 필요하다.
오버로딩된 메소드를 호출하는 경우, JVM은 매개값의 타입을 보고 메소드를 선택한다.
매개 변수 타입이 일치하지 않는 경우, 자동 형변환이 가능한 지 검사한다.
매개 변수의 순서와 타입, 개수가 똑같을 때, 매개 변수 이름과 리턴 타입이 다른 것은 오버로딩이 아니다.
리턴 타입은 JVM이 메소드를 선택하는 기준이 아니기 때문
Part 2

인스턴스 멤버와 this

객체(인스턴스)를 생성한 후 사용할 수 있는 필드와 메소드
필드 : 인스턴스 필드
메소드 : 인스턴스 메소드
객체를 생성해야 사용할 수 있다.
인스턴스 필드는 힙 영역의 각 개체에 존재
인스턴스 메소드는 메소드 영역에 존재되어 공유
객체 자신을 가리키는 this
인스턴스 필드의 이름과 생성자/메소드의 매개변수 이름이 동일한 경우
this.이름 → 인스턴스 필드임을 명시

정적 멤버와 static

정적(Static)의 의미는 '고정된'
정적 멤버 : 클래스에 고정되어 객체를 생성하지 않고도 사용할 수 있는 멤버 ( 클래스 멤버 )
정적 필드, 정적 메소드

정적 멤버 선언

public class 클래스 { static 타입 필드 static 리턴타입 메소드 }
Java
복사
클래스 로더가 클래스(바이트 코드)를 로딩해서 메소드 메모리 영역에 적재할 때, 클래스 별로 관리
클래스 로딩이 끝나면 바로 사용가능
필드 선언 기준
객체마다 가지고 있어야 할 데이터 : 인스턴스 필드
공용적인 데이터 : 정적 필드
예 ) 계산기 클래스 : 원의 둘레를 구할 때 필요한 '파이'는 정적 필드로 선언
메소드 선언 기준
인스턴스 필드를 사용하는 메소드 : 인스턴스 메소드
인스턴스 필드를 사용하지 않는 메소드 : 정적 메소드
예 ) 계산기 클래스 : 덧셈, 뺄셈 등 외부 데이터를 받아 수행하는 메소드는 정적 메소드 선언

정적 멤버 사용

클래스가 로딩되면 바로 사용 가능
클래스명 . 정적멤버
클래스명으로 접근하지만 객체를 생성해서 접근도 가능
클래스명으로 접근 권장

정적 초기화 블록

객체의 필드는 생성자 호출 시 초기화 → 객체를 생성하지 않고 사용하는 정적 필드는 ?
정적 블록에서 초기화 수행
정적 블록은 클래스 로딩될 때 자동으로 수행
public class TV { static String company = "Samsung"; static String model = "4K"; static String info; static { info = company + model + "70inch" ; } }
Java
복사

정적 메소드와 블록 선언 시 주의할 점

객체를 생성하지 않고 사용할 수 있는 특성
정적 메소드와 블록 내부에 인스턴스 멤버를 사용할 수 없다.
객체 자신을 가리키는 this 키워드 사용할 수 없다.
사용해야 하는 경우, 블록 안에서 객체를 생성한 후 사용. ( main() 메소드 포함 )
public class MyClass { static int A; int B; static { A = 1; B = 2; // Compile error ! } static void Method() { MyClass myClass = new MyClass(); myClass.B = 2; } public static void main(String[] args) { MyClass myClass = new MyClass(); myClass.B = 3; } }
Java
복사

싱글톤(Singleton)

전체 프로그램에서 단 하나만 생성하는 객체
클래스 외부에서 new 연산자로 객체 생성 불가
클래스 내부에서 객체 타입의 정적 필드 선언 및 생성자 호출
내부에서 생성한 객체를 반환하는 getInstance() 함수 선언
// Singleton.java public class Singleton { private static Singleton obj = new Singleton(); private Singleton(){ } static Singleton getInstance(){ return obj; } }
Java
복사
// UseSingleton.java public class UseSingleton { public static void main(String[] args) { Singleton obj1 = new Singleton(); // Compile Error ! Singleton obj2 = Singleton.getInstance(); Singleton obj3 = Singleton.getInstance(); if (obj2 == obj3) { System.out.println("같은 객체를 참조"); } } }
Java
복사

final 필드와 상수

final 필드

final은 '최종적'이라는 의미
저장된 초기값이 최종값 → 한 번 초기화 되면 값을 바꿀 수 없다
final 타입 필드명
Java
복사
초기값을 주는 방법
선언과 동시에 초기화
생성자를 통해 초기화
초기화 되지 않은 final 필드가 남아있으면 컴파일 에러 발생

상수(static final)

불변의 값
final 필드와 같다 → final = 상수 ??
final : 객체마다 다른 값
상수 : 모든 객체에 공용 ( static 특성 )
static final 타입 상수명 static { 상수명 = 초기값; }
Java
복사
상수명은 모두 대문자로 작성이 관례
여러 개의 단어는 언더바 ( _ )로 구분

패키지

여러 개의 클래스를 관리하기 위해서 사용
물리적인 형태는 파일 시스템과 동일
클래스 명이 동일하더라도 패키지가 다르면 다른 클래스로 인식
상위패키지.하위패키지.클래스명

패키지 선언

package 상위패키지.하위패키지 public class 클래스 { }
Java
복사
컴파일 시 파일 시스템의 폴더를 자동 생성
패키지명 작성
$와 _를 제외한 특수문자와 숫자로 시작 불가
java 로 시작하는 패키지는 자바 표준 API이므로 사용 불가
모두 소문자로 작성
도메인의 역순으로 작성

패키지 선언이 포함된 클래스 컴파일

javac -d 경로 : 경로부터 선언된 패키지가 생성된다.
java 패키지경로.바이트코드 : 패키지 시작 경로에서 실행

import 문

다른 패키지에 속한 클래스를 사용하는 방법
패키지와 클래스 명을 모두 작성
import 패키지명
package com.mycompany; public class Car { com.youcompnay.Tire tire = new com.yourcompay.Tire(); }
Java
복사
package com.mycompany; import com.yourcompany.Tire; public class Car { Tire tire = new Tire(); }
Java
복사
package 와 class 선언문 사이에 선언
패키지명 마지막에 * 를 작성하여 모든 클래스 사용
하위 패키지에 포함된 클래스는 사용 불가
서로 다른 패키지에 동일한 이름의 클래스를 사용하는 경우에는 모든 패키지명을 작성해야한다.

접근 제한자

외부로부터 클래스의 멤버 접근을 제한하기 위해서 사용
public : 모든 외부에서 자유롭게 사용
protected : 같은 패키지 또는 자식 클래스에서 사용
private : 모든 외부에서 사용 불가
default : 같은 패키지에서 사용

클래스의 접근 제한

클래스 선언시 적용할 수 있는 접근 제한자는 default, public
default : public을 생략하면 적용됨. 같은 패키지에서만 사용.
public : 모든 외부에서 사용. 라이브러리 클래스는 public 선언.

생성자의 접근 제한

기본 생성자의 접근 제한은 클래스의 접근 제한과 동일
default : 생성자 선언 시 접근 제한자를 생략하면 적용

필드와 메소드의 접근 제한

public : 필드와 메소드가 public 제한자인 경우, 클래스도 public을 가져야 한다.

Getter와 Setter 메소드

객체의 데이터를 외부에서 접근하면 객체의 무결성이 깨질 수 있다.
객체 지향 프로그래밍은 외부에서 호출 가능한 메소드를 통해 데이터를 변경하는 방법을 선호.
외부에서 넘기는 매개값을 검증
Setter : 메소드를 통해서 객체의 데이터 변경
Getter : 메소드를 통해서 객체의 데이터를 반환
public class Car { private int speed = 10; public void setSpeed(int speed){ if(speed < 0) { this.speed = 0; } else { this.speed = speed; } } public double getSpeed() { double km = speed * 1.6; return km; } private boolean run = false; public boolean isRun(){ return run; } }
Java
복사

어노테이션

메타데이터 → 컴파일, 런타임 시 코드를 어떻게 처리할 것인지 정의
@Override : 메소드가 재정의 되었음을 컴파일러에게 알려 검사 수행

어노테이션 타입 정의와 적용

정의 : 접근제한자 @interface 어노테이션이름
사용 : @어노테이션이름
엘리먼트를 멤버로 갖는다.
타입 엘리먼트이름()
default 값
public @interface AnnoName { String eleName(); int eleNum() default 5; }
Java
복사
@AnnoName(eleName="엘리먼트"); @AnnoName(eleName="엘리먼트", eleNum=3); // default 값이 없는 엘리먼트는 사용시 꼭 값을 작성해야한다.
Java
복사
어노테이션은 기본 엘리먼트인 value 멤버를 가질 수 있다.
public @interface AnnoName { String value(); int eleNum() default 5; }
Java
복사
@AnnoName("값"); // value 엘리먼트를 갖는 어노테이션 사용 시, 어노테이션 이름을 생략할 수 있다. @AnnoName(value="값", eleNum=3);
Java
복사

어노테이션 적용 대상

java.lang.annotation.ElementType 열거 상수로 정의
TYPE —— 클래스, 인터페이스, 열거 타입
ANNOTATION_TYPE —— 어노테이션
FIELD —— 필드
CONSTRUCTOR —— 생성자
METHOD —— 메소드
LOCAL_VARIABLE —— 로컬 변수
PACKAGE —— 패키지
적용 대상을 지정할 때 @Target 어노테이션을 사용
기본 엘리먼트 vaule는 ElementType 배열을 값으로 갖는다. → 여러 타입의 대상 지정
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) public @interface AnnoName { ... }
Java
복사

어노테이션 유지 정책

사용 용도에 따라 어노테이션을 어느 범위까지 적용할 것인지 지정
소스 범위, 컴파일 범위, 런타임 범위
java.lang.annotation.RetentionPolicy 열거 상수로 정의
SOURCE —— 소스 범위. 바이트 코드에 기록되지 않음.
CLASS —— 컴파일 범위. 바이트 코드에 정보 유지.
RUNTIME —— 런타임 범위. 리플렉션을 이용해서 런타임 시 어노테이션 정보 사용.
리플렉션 : 런타임 시 클래스의 메타 정보(필드, 생성자, 메소드, 적용 어노테이션)를 알아내는 것.
유지 정책을 지정할 때 @Retention 어노테이션을 사용
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface AnnoName { ... }
Java
복사

런타임 시 어노테이션 정보 사용하기

어노테이션 자체는 아무 동작을 하지 않는 표식
리플렉션을 사용하여 어노테이션의 적용 여부와 엘리먼트 값을 읽고 처리
클래스에 적용된 어노테이션 → java.lang.Class 이용
필드, 생성자, 메소드에 적용된 어노테이션 → Class의 메소드를 통해 java.lang.reflect 패키지의 Field, Constructur, Method 타입 배열을 얻어야 한다.
Field[] —— getFields() : 필드 정보를 Field 배열로 반환
Constructor[] —— getConstructors() : 생성자 정보를 Constructor 배열로 반환
Method[] —— getDeclaredMethods() : 메소드 정보를 Method 배열로 반환
Class, Field, Constructor, Method가 가지고 있는 메소드를 통해서 적용된 어노테이션 반환
boolean —— inAnnotationPresent(Class<? extends Annotation> annotaionClass)
지정한 어노테이션 적용 여부.
Class에서 호출 시 상위 클래스에 적용된 경우도 true 반환.
Annotation —— getAnnotation(Class<T> annotationClass)
지정한 어노테이션 적용시 해당 어노테이션 반환. 그렇지 않으면 null 반환.
Class에서 호출 시 상위 클래스에 적용된 경우도 어노테이션 반환.
Annotation[] —— getAnnotations()
적용된 모든 어노테이션을 반환. 없으면 길이가 0인 배열 반환.
Class에서 호출 시 상위 클래스에 적용된 어노테이션도 포함.
Annotation[] —— getDeclaredAnnotations()
직접 적용된 모든 어노테이션 반환.
Class에서 호출 시 상위 클래스에 적용된 어노테이션은 미포함.
// PrintAnnotation.java @Target({ElementType.METHOD}) // 메소드에 적용 @Retention(RetentionPolicy.RUNTIME) // 런타임 범위까지 유지 public @interface PrintAnnotation { String value() default "-"; int number() default 15; }
Java
복사
// Service.java public class Service { @PrintAnnotation // 엘리먼트 기본값 사용 public void method1() { System.out.println("실행 내용1"); } @PrintAnnotation("*") // 엘리먼트 value 값 변경 public void method2() { System.out.println("실행 내용2"); } @PrintAnnotation(value="#", number=20) // 모든 엘리먼트 값 변경 public void method3() { System.out.println("실행 내용3"); } }
Java
복사
// PrintAnnotationExample.java public class PrintAnnotationExample { public static void main(String[] args) { // Service 클래스의 모든 메소드의 배열 반환. Method[] declaredMethods = Service.class.getDeclaredMethods(); for ( Method method : declaredMethods ) { // 어노테이션 적용 여부 판단. if(method.isAnnotationPresent(PrintAnnotation.class)) { // 적용된 PrintAnnotation 객체 반환 PrintAnnotation printAnnotation = method.getAnnotation(PrintAnnotation.class); System.out.println("[" + method.getName() + "]"); for(int i=0; i<printAnnotation.number; i++){ System.out.print(printAnnotation.value()); } try{ method.invoke(new Service()); // 메소드 호출 } catch (Exception e) {} } } // for } }
Java
복사