(1) 리플렉션(Reflection)

언어로그/Java 2011. 4. 7. 16:47




자바에서 리플렉션은 유연성을 제공하기 위해 필수적인 기법이다. 물론 리플렉션이 없더라도 훌륭한 코드를
작성할 수 있다. 하지만 리플렉션을 사용하면 좀더 유연한 프로그램을 작성할 수 있다.  자바에서 리플렉션을
이해하기 위해서,  자바 클래스 파일은 바이트 코드로 컴파일 되며 실행시간에 이 바이트 코드가 해석되어 
실행된다는 것을 아는 것이 첫 출발점이 된다.  이 바이트 코드에는 클래스에 대한 모든 정보를 포함하고 있다.  
클래스 파일이 있는 위치와 이 클래스 파일의 이름을 알수 있다면 언제든지 바이트 코드를 뒤져서 클래스에 대한
정보를 얻어낼 수 있다.  이제부터 리플렉션을 통해 어떻게, 어떤 정보를 얻을 수 있는지 알아보자. 



1. 리플렉션(Reflection) 
리플렉션은  구체적인 클래스 타입을 알지 못해도,  컴파일된 바이트 코드를 통해 역으로 클래스에 정보를 알아내어
클래스를 사용할 수 있는 기법을 의미한다.  마치 거울에 비친 모습과 유사하여 리플렉션이란 이름을 붙힌 것 같다. 



2. 리플렉션을 사용하는 이유
 리플렉션은  조합(Composition)과 함께 사용되어 다형성을 구현하는 강력한 도구이다. 조합을 사용하여 교체할 수 있는 
위임 클래스를 리플렉션을 통해 동적/정적으로 생성하고 교체하는 방식으로 사용된다.  프레임워크에서 유연성이 있는 동작을
위해 자주 사용되는 방식이기도 하다.  


3. 리플렉션을 통해 얻을 수 있는 정보 
리플렉션을 통해 얻을 수 있는 정보에 대해서 알아보자. 
 
ClassName
Class Modifiers (public, private, synchronized 등)
Package Info
Superclass
Implemented Interfaces
Constructors
Methods
Fields
Annotations

이외에도 더 많은 클래스 정보를 얻을 수 있다. java.lang.Class 클래스에 대한 JavaDoc 문서를 참고하자. 



3.1 Class Object
 클래스 정보를 얻기 위해 가장 먼저 해야할 일은 정보를 담고 있는 java.lang.Class 객체를  획득하는 것이다.
프리미티 타입과 배열 타입을 포함하여 자바의 모든 타입들은 연관된 Class 객체를 가지고 있으며,  컴파일 타임에  
클래스의 이름을 알수 있다면,  다음과 같이  Class 객체를 얻을 수 있다.
 Class myObjectClass = MyObject.class
 컴파일 타임에 이름을 알수 없다면, 런타임에 문자열로 된 이름으로 부터 클래스 객체를 아래와 같이 얻을 수 있다. 
String className =  ... // 클래스 풀네임 
Class myObjectClass = Class.forName(className); 
이때 문자열로 된 클래스 이름은 패키지 경로까지 포함한 풀네임이여야 하며, 해당 패키지에 클래스가 존재하지 않으면
Class.forName 메소드는 ClassNotFoundException 예외를 던지게 된다.


3.1 Class Name
Class 객체로부터 2가지 버전의 클래스 이름을 얻을 수 있다.  getName() 메소드를 사용하면 패키지까지 포함한 풀네임을 얻을 수
있고, getSimpleName() 을 사용하여 패키지가 포함되지 않은 클래스 이름을 얻을 수 있다. 
// 클래스 풀네임 
Class aClass = ... // 이전에 얻은 클래스 객체 
String className = aClass.getName();

// 클래스 심플 네임 
Class  aClass = ... // 이전에 얻은 클래스 객체
String simpleClassName = aClass.getSimpleName();


3.2 Modifier
Class 객체로부터 변경자에 접근할 수 있다.  클래스 변경자는 public, private, static 과 같은 키워드를 의미한다. 
클래스에 대한 플래그 비트가 설정된  int 값을 얻을 수 있으며  java.lang.reflect.Modifier 클래스에 있는 메소드를
통해 해당 플래그가 켜져있는지 확인할 수 있다. 
// 변경자 얻기 
Class  aClass = ... // 이전에 얻은 클래스 객체
int modifiers = aClass.getModifiers();
 
// 변경자 플래그 확인 메소드들
 Modifier.isAbstract(int modifiers)
 Modifier.isFinal(int modifiers)
 Modifier.isInterface(int modifiers)
 Modifier.isNative(int modifiers)
 Modifier.isPrivate(int modifiers)
 Modifier.isProtected(int modifiers)
 Modifier.isPublic(int modifiers)
 Modifier.isStatic(int modifiers)
 Modifier.isStrict(int modifiers)
 Modifier.isSynchronized(int modifiers)
 Modifier.isTransient(int modifiers)
 Modifier.isVolatile(int modifiers)
 
 
3.3 Package Info
다음과 같이 Class 객체로부터 패키지에 대한 정보를 얻는다. 
// 패키지 정보 얻기
Class  aClass = ... // 이전에 얻은 클래스 객체
Package package = aClass.getPackage();

Package 객체로부터 패지지 이름과 같은 정보에 접근할 수 있다. 또한 패키지가 위치한  classpath에 있는jar 파일의
Manifest 파일에서도 이 패키지에 대한 특정한 정보를 얻을 수 있다.  
(예를 들면 Manifest 파일에 지정된  패지키 버전 번호 같은...)


3.4 Superclass
아래와 같이 수퍼클래스의 class 객체를 얻을 수 있다. 
// 수퍼 클래스의 class 객체 얻기 
Class superclass = aClass.getSuperclass();


3.5 Implemented Interfaces
클래스 객체에 의해 구현된 인터페이스의 목록을 얻어보자. 
// 구현한 인터페이스 목록 얻기 
Class  aClass = ... // 이전에 얻은 클래스 객체
Class[] interfaces = aClass.getInterfaces();

수퍼클래스가 구현한 인터페이스지만, 자식클래스가 특별히 해당 인터페이스를 구현한다고 명시하지 않으면,
해당 인터페이스는 목록에 포함되지 않는 것에 주의하자. 구현하는 완전한 인터페이스의 목록을 얻기 위해서는
자신의 수퍼클래스의 구현 인터페이스 목록을 재귀적으로 확인해야 한다.


3.6 Constructors
다음과 같이 클래스의 생성자 목록에 접근한다.
// 클래스 생성자 목록 얻기 
Constructor[] constructors = aClass.getConstructors();



3.7 Methods 
다음과 같이 클래스의 메소드들에 접근한다. 
// 메소드 목록 얻기 
Method[] methods = aClass.getMethods();



3.8 Fields
다음과 같이 클래스의 멤버 변수들에 접근한다. 
// 필드 목록 얻기 
 Field[] fields = aClass.getFields();



3.9 Annotations
다음과 같이 클래스의 어노테이션에 접근한다. 
// 어노테이션 목록 얻기
Annotation[] annotations = aClass.getAnnotations();
 
※ 리플렉션을 사용하여 Annotation을 처리하는 것은 아래 포스트를 참고!!
Java-어노테이션(Annotation)
Java-어노테이션 사용하기




4. 리플렉션 사용 예
게시물 정보를 갖는 Board 클래스에 대한 정보를 리플렉션을 사용하여 출력하는 예를 보자.  
Board 클래스에 대한 정의는 아래와 같다.
package com.tistory.hiddenviewer.reflection;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

public class Board implements ActionListener{

	public final static String boardName = "MyBoard"; 
	public ArrayList boardList;
	
	public int seq;
	protected String title;
	private String contents;
	
	public Board() {
		this(10);
	}
	
	public Board(int count) {
		this.boardList = new ArrayList(count);
	}
	
	
	public int getSeq() {
		return seq;
	}

	public void setSeq(int seq) {
		this.seq = seq;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getContents() {
		return contents;
	}

	public void setContents(String contents) {
		this.contents = contents;
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
	}

}



Board 클래스에 대한 정보를 리플렉션을 사용하여 출력하였다.  

package com.tistory.hiddenviewer.reflection.executor;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class BoardReflectionExecutor {


	public static void main(String[] args) throws IOException, ClassNotFoundException {
		
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		
		System.out.print("생성할 클래스 이름을 입력하세요(패키지 포함): ");
		String className = br.readLine();
		
		Class cls = Class.forName(className);
		
		// 클래스 이름얻기 
		String classFullName = cls.getName();
		String classSimpleName = cls.getSimpleName();
		
		System.out.println("class full name: " + classFullName);
		System.out.println("class simple name: " + classSimpleName);
		
		// 변경자 얻기 
		int modifiers = cls.getModifiers();
		
		if (Modifier.isPublic(modifiers)) {
			System.out.println("class is public class");
		} 
		if (Modifier.isFinal(modifiers)) {
			System.out.println("class is final class");
		}

		// 패키지 얻기 
		Package pkg = cls.getPackage();
		System.out.println("package name: " + pkg.getName());
		
		
		// 수퍼클래스 얻기 
		Class superCls = cls.getSuperclass();
		System.out.println("super class name :" + superCls.getName());
		
		
		// 구현 인터페이스 목록 얻기 
		Class[] interfaces = cls.getInterfaces();
		for (Class cs : interfaces) {
			System.out.println("this class implements " + cs.getName() + " interface");
		}
		
		// 생성자 목록 얻기 
		Constructor[] conturctors = cls.getConstructors();
		for (Constructor constructor : conturctors) {
			System.out.println("Constructor: " + constructor.getName());
		}
		
		// 메서드 목록 얻기 
		Method[] methods = cls.getMethods();
		for (Method method : methods) {
			System.out.println(method.getReturnType() + " " + method.getName() + "(...)");
		}
		
		
		// 프로퍼티 목록 얻기 
		Field[] fields = cls.getFields();
		for (Field field : fields) {
			System.out.println(field.getType() + " " + field.getName());
		}
		
	
		// 어노테이션 얻기 
		Annotation[] annotations = cls.getAnnotations();
		for (Annotation annotation : annotations) {
			System.out.println(annotation.toString());
		}
		
	}

}



출력결과
 


출력 결과를 보면 알겠지만 private,  protected  변경자를 갖는 필드들은 출력되지 않고 public 필드들만 출력이 되었다. 
다음 포스팅에서는  리플렉션을 사용하여 실세로  해당 타입의 객체를 생성 /  private 필드에 접근 / 메소드를 호출 하는 방법에
대해 알아보자.  

라인 입출력 함수

언어로그/C/C++ 2011. 3. 27. 21:07



라인단위로 입출력을 다루는 함수들에 대해서 알아보자.  널문자를 삽입해주는지 여부와 버퍼에서 개행문자를
제거하는지 여부에 주목하여 보자. 


scanf(const char *TEMPLATE, ...)
scanf 함수를 사용하여, 한 라인의 문자열을 읽을 수 있지만, 개행문자를 표준입력버퍼에 남겨놓는다.  
연이은 읽기 동작에 오동작을 초래할 수 있기때문에, 입력버퍼에서 개행문자를 제거하는 것을 잊지 말자!



char* fgets(char *S, int COUNT, FILE *STREAM)
지정한 입력버퍼에서 최대 COUNT-1 개의 문자를 읽되, 엔터를 칠때까지  배열  S에 저장하는 함수이다. 
배열의 공간이 충분하다면, 개행문자와 함께 널문자를 삽입한다. (엔터에 의해 종료) 
하지만 공간이 충분하지 않다면,  COUNT-1개의 문자와 널문자를 삽입하고 종료한다. (사이즈 제약의 의해 종료)



int fputs(const char *S, FILE *STREAM)
버퍼 S의 내용을 지정한 출력버퍼에 출력하는 함수로, 널문자를 개행문자로 변환하지 않는다.  




fgets와 fputs 보다는 덜 사용되지만 라인단위로 입출력을 할 수 있는 다음과 같은 함수가 있다. 
char* gets(char *S)
표준 입력버퍼에서 개행문자를 만날 때까지 읽는 함수로,  개행문자는 S에 저장하지 않지만, 입력퍼버에서는 제거한다




int puts(const char *S)
S 문자열을 표준출력버퍼에 출력하는 함수로 널문자를 만나면 개행문자로 변환하여 출력한다. 

'언어로그 > C/C++' 카테고리의 다른 글

음수의 표현  (0) 2011.06.13
문자열 조작함수 직접 구현하기  (2) 2011.04.29
라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27

배열의 이해

언어로그/C/C++ 2011. 3. 27. 20:53



1. 배열이란?
프로그램이 필요한 이유 중의 하나는 많은 양의 데이터를 처리하기 위해서이다.  
그중에서도 동일한 형태를 갖는 다수의 데이터를 다루기 위해 배열을 사용할 수 있다.  배열은 연속적인 메모리 공간에
할당된 동일한 데이터 타입을 갖는 변수들의 묶음이다.  

int array[3];

위와 같은 선언은 4바이트의 메모리가 연속적으로 3개가 할당되어 총 12바이트의 메모리가 생성된다. 
이 때 array 라는 배열의 식별자에는 할당된 메모리의 시작주소가 할당된다.  즉 포인터 상수이다. 
이렇게 할당된 메모리 공간에 배열의 식별자를 사용하여 접근할 수 있다. 


배열의 첨자를 사용하여 접근하는 방법
scanf("%d %d %d \n", &array[0], &array[1], &array[2] );


배열 식별자(포인터 상수)를 사용하여 접근하는 방법
scanf("%d %d %d \n", array,  array + 1, array + 2 );



2. 배열의 초기화
배열 또한 일반적인 변수와 동일한 초기화 규칙을 따른다. 지역변수로 선언된 배열은 기본으로 쓰레기값으로 초기화되며, 
전역변수로 선언된 경우는 0으로 초기화 된다.  배열의 선언과 초기화를 다음과 같이 할 수 있다. 

int array[3] = { 10 , 20, 30 };   // 명시적으로 요소의 개수와 각 초기값을 설정   
int array[] = { 10, 20 , 30 };     // 초기값의 수에 따라 요소의 수가 결정됨      
int array[3] = { 10 };           // 첫요소만 10으로, 나머지는 0 으로 초기화됨       
int array[3] = { 0 };           // 모든 요소를 0으로 초기화    

특히 마지막 문장은 다수의 요소를 갖는 배열을 초기화 할 때 유용한 구문이다. 
int array[100] = { 0 };

'언어로그 > C/C++' 카테고리의 다른 글

문자열 조작함수 직접 구현하기  (2) 2011.04.29
라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27

기억부류(Storage Class) / 변수

언어로그/C/C++ 2011. 3. 27. 20:40





데이터, 스택, 힙 영역
추상적인 분류이지만 프로그램을 위한 메모리 공간은 크게 데이터영역,  스택(Stack)영역,  힙(Heap)영역으로 나뉜다.
데이터 영역에는 전역(Global)변수와 정적(Static) 변수에게 할당되는 메모리 공간으로 프로그램 시작부터 끝나는 시점까지
존재하는 메모리 영역이다. 스택영역은 블럭 또는 함수 Scope 에 할당되는 메모리 공간이며,  제어흐름이 활성화 되었을 때
생성된다. 힙 영역은 프로그램이 유동적으로 사용할 수 있는 메모리 공간으로, 프로그래머가 해당 메모리를 필요한 만큼
사용하고 난 뒤 메모리를 해제해야 한다. 



레지스터 변수,  정적변수
변수의 타입 앞에 register 라는 키워드를 사용하여, 레지스터 변수를 선언할 수 있다.  레지스터 변수는 CPU의 메모리가 아닌
레지스터에 데이터를 저장하여,  데이터 접근 속도를 향상시킨다.  하지만  register 키워드를 사용했다고 무조건 레지스터에
데이터를 저장하는 것은 아닌다. 일반적으로 CPU에 존재하는 30여 가지의 레지스터 중 1~2 개의 레지스터가 남는데, 이 
레지스터들이 사용되고 있지 않을 경우에 사용되며, 그렇지 않을  경우 메모리에 할당된다. 

register int  i,  j;   // 레지스터에  i, j 생성을 요청.     

 
정적(Static) 변수는 전역변수와 같이 메모리의 데이터 영역에  생성이 된다.  지역 정적변수는 함수가 종료된 후에도 
그 값을 사용하고 싶을 경우에 쓰인다.  함수 내부에 선언된 지역정적 변수는 함수 호출시점에 최초로 생성이 되며, 
이후 메모리 데이터 영역에 프로그램 종료시점까지 유지가 된다.

void func()
{
    static int i = 0;    // 연이은 호출에도 처음 생성된 i가 사용됨.
    i++;
}
   

'언어로그 > C/C++' 카테고리의 다른 글

라인 입출력 함수  (0) 2011.03.27
배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11

전역변수와 지역변수

언어로그/C/C++ 2011. 3. 27. 20:15




1. 지역변수(local variable)
지역변수는 변수가 선언된 블럭(Scope) 내부에서만 유효한 변수이다. 지역변수는 제어흐름이 해당 블럭에
 진입되는 시점에 생성되고 , 블럭을 빠져나가는 순간 소멸되는데 흔히 자동변수라 하여 auto라는 키워드가 붙지만, 
디폴트로 생략할 수 있다. 또한 동일한 범위(Scope)  내 있는 코드에서만 접근할 수 있으며 생성 시, 의미없는 값이
들어가 있기 때문에 사용전에 반드시 초기화를 해야한다. 




2. 전역변수(global variable)
전역변수는 프로그램 실행타입 내내 유효한 변수이다. main함수가 호출되기 전에 생성되어, 프로그램 종료시 메모리가
회수된다.  프로그램 어떤 영역에서도 접근할 수 있는 특징이 있으며,  생성시 자동으로 0으로 초기화가 이루어진다.




3. 지역변수와 전역변수는 어떻게 사용해야하나?
전역변수는 어떤영역에서도 데이터에 접근할 수 있기 때문에 데이터 공유에 유용하다.  하지만 프로그램 실행타임
동안 계속 사용되기 때문에 과하게 사용하면 메모리에 낭비를 가져온다. 또한 전역변수에 대한 변경은 그 변수를
사용하는 모든 함수에 파급효과를 미쳐서 강한 결합(tight coupling)의 겨로가를 가져온다. 그렇기 때문에  프로그램을
용이하게 하는 범위에서 최소한으로 사용하고, 그 외의 경우는 지역변수를 사용하는 것이 좋다. 

 

'언어로그 > C/C++' 카테고리의 다른 글

배열의 이해  (0) 2011.03.27
기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11
매크로, 연산자  (3) 2011.03.10

함수의 이해

언어로그/C/C++ 2011. 3. 27. 20:10




함수가 필요한 이유?
중복된 작업을 함수로 분리하면,  한번 작성으로 여러번 사용할 수 있어서 효율적인 프로그램이 된다.
또한 큰 문제를 작은 문제들로 나누어 이해하기 쉬워지며, 수정사항이 발생하면 함수 부분만 수정하면
되기 때문에 유지보수도 용이해진다. 


 
함수의 원형(프로토타입)과 정의
C언어에서 함수는 원형(프로토타입)과 정의로  분리된다.  함수의 원형(프로토타입)은 보통 헤더파일이나
메인함수 이전에 선언이 되며,  컴파일러에게 이러 이러한 함수를 아래에 정의했으니까 메인함수 또는 특정함수에서 
이 함수를 사용해도 됨을 알려주는 것이다. 
함수의 정의는 실제적으로 함수가 하는 일을 기술하는 부분이다.  C언어에서는 함수의 시그니처로 함수의 이름만을
사용하기 때문에, 파라미터의 타입과 반환값의 타입이 달라도 동일한 이름의 함수를 사용 할 수  없다.


#include <stdio.h>
 
void swap(int , int);// 함수원형(프로토타입)   

int main() {
...
return 0;
}

void swap(int a, int b) {   // 함수의 정의         
...
}





Call By Value vs Call By Reference
함수 호출시 파라미터를 넘기는 방식에 따라  Call By Value 또는 Call By Reference 라고 구분한다.
함수 호출시 변수들의 데이터(값)가 복사되는데, 함수 내에서 이 복사된 변수에  대한 조작은 원본 변수에
에  아무런 영향을 미치지 못하는데 이러한 호출방식을 Call By Value라고 부른다.   
파라미터로 변수의 주소를 넘기면, 피호출자 쪽에서는 이 주소를 조작하여 원본 변수의 데이터를 변경 할
수 있다. 이러한 호출방식을 Call By Pointer 또는 Call By Reference 라고 부른다.

 
Call By Value
#include <stdio.h>

main() {
    int a = 1, b = 5;
    swap(a, b);// 변수의 값을 전달함 
}

void swap(int a, int b)  {// main함수의 a, b는 변경되지 않음    
    int temp = a;
    a = b;
    b = temp      
}


Call By Reference 
#include <stdio.h>

main()  {
    int a = 1, b = 5;
    swap(&a, &b);// 변수의 주소를 전달함    
}
 
void swap(int *a, int *b)  { // main함수의 a, b가 변경됨    
    int temp = *a;
    *a = *b;
    *b = temp;
}

'언어로그 > C/C++' 카테고리의 다른 글

기억부류(Storage Class) / 변수  (0) 2011.03.27
전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11
매크로, 연산자  (3) 2011.03.10
표준입출력함수 getchar() / putchar()  (0) 2011.02.22

예외처리 (Exception Handling)

언어로그/Java 2011. 3. 24. 00:37



예외처리는 객체지향 프로그래밍을 지원하는 언어에서 에러를 처리하기 위한 메커니즘이다. 전통적인 절차지향
언어에서는 함수의 반환값을 통해 에러 유무를 판단하였다. 매번 반환값을 검사하는 작업은 상당히 번거롭고, 
그 반환값의 의미 또한 프로그래머가 인지하기 어려운 형태여서 에러처리가 불편하였다. 반면 예외처리 방식은
예외가 발생하면 그에 대한 정보를  얻을 수 있고, 제어 흐름 또한 쉽게 이동시킬 수 있는 방식이다.  
하지만 아무리 언어차원에서 좋은 메커니즘을 제공하더라도 언제, 어떻게 사용해야하는지 모른다면 무용지물이다.
C++와 Java 언어를 처음 접했을 때에도 언제, 어디서 예외를 던지고 잡아야 하는지가 난제였다.  예외처리에 대해서
좀더 자세히 알아보자. 


자바의 철학은 "잘못 만든 코드는 실행되지 않아야 한다" 는 것이다. 그래서 에러를 잡는 가장 이상적인 시기는 컴파일
시기이다. 하지만 모든 에러를 컴파일 시에 검출할 수 는 없으며,  실제론 실행시점에 어떠한 절차를 거치면서 심각한 문제들이
더 발생하게 된다.  발생한 에러는,  프로그램이 수행하는 절차와 연관되어 그 에러를 올바르게 처리하는 방법을 아는 수신자가
처리해야한다.  그렇다. 처리하는 방법을 아는 수신자가 처리하게 해야하는 것이 정답이다. 이 개념을 설명하기에 앞서 
자바에서 지원하는 예외(Exception) 클래스에 대해서 알아보자. 
 
 
1. 예외란 무엇인가?
예외란 프로그램 절차상에서 정상 흐름을 벗어난 이벤트이다. 
 


2.  자바의 예외처리 장점
비지니스 로직과 예외처리 코드를 분리해주는 장점이 있다.  try {} 블럭 안에서 비지니스 로직을 처리하며, 
그에 대한 예외처리는 catch {} 라는  별도의 블럭에서 수행도히기 때문에 로직과 예외처리 영역이 명확히
분리가 된다.  또한 예외처리 영역에서는 예외클래스의 이름을 바탕으로 예외의 종류, 발생위치와 관련된 정보를
확인할 수 있다.  (예외이름이 발생한 문제를 나타내도록 알아보기 쉽게 지어야 하는 이유이다.)                                    
 

3. 예외관련 클래스 상속도 
다음은 예외 관련 클래스 상속도이다.  모든 예외클랫들은 Throwable 인터페이스를 구현한다.  발생시 로직에 따라 적절히
처리 될 수 있는 문제들을 Exception 이라 하며,  심각한 문제들로 시스템을 중단해야할 문제들을 Error 라고 한다. 
일반적으로 Checked  Exception을 상속하는 커스텀 예외 클래스를 생성하여 사용한다. 


 
 

 
4. 예외처리를 위한 전략
발생한 예외는 상위계층으로 throw 하거나 또는  try~catch 구문을 사용하여 직접 처리할 수 있다.  throw 할지, 직접처리 할지에 
대한 결정은 처리 절차에 의존한다.  
 
 throw 할 예외와 직접 try~catch 여부를 어떻게 결정할 것인가?
  
- 예외로 무엇을 할지 알 때까지는 그것을 catch 하지 말아라
   - 예외 발생시 어떻게 해야하는지 알고있는 절차를 다루는 클래스에게 까지 예외를 throw 하자.

다음 일반적인 프로그래밍 패턴 중에 언제  throw 와 try~catch 할지에 대한 사항이다. 
 데이터 추출     -  throw 한다. 
 유효성 검사     -  throw 한다. 
 처 리                 -  try ~ catch 
 결과출력         -  try ~ catch
 
 1, 2 번 같은 경우 데이터를 제공하는 곳에서 예외를 처리 할 책임이 있다. 상위로 예외를 던진다(throw)
3. 4  번의 경우, 상위에서 제공하는 데이터에서 이상이 없고, 내가 처리하는 동안 발생한 예외므로 내가 처리할 책임이 있다. 예외를 try ~ catch 한다
 
 
예외를 위한 계획적인 전략 사용하기!
코딩하다 즉흥적으로 삽인하는 예외처리 코드가 아닌,  처음 절차에서부터 계획된 예외처리 전략을 사용해야 한다
예외처리 전략을 위한 과정은 다음과 같다
예외클래스를 정의하고,  언제, 어디서  throw 되고, try~catch 되야하는지 결정한다
비지니스 로직에 전념하고, 예외처리가 필요한 곳에는 주석으로 예외처리 표시를 한다.
예외처리가 필요한(주석을 단 부분)에 실제 처리코드를 삽입한다.
       
 
 
5. checked Exception 과 unchecked Exception

5.1 checked Exception 
컴파일 타임 예외로, 비지니스 데이터에 위해한 예외로 시스템 서비스에 부자연스러움을 가져오는 예외이다. 
소스에 예외처리를 하지  않으면, 컴파일러 에러가 발생한다. 
ex) IOException,  NumberFormatException
 
5.2 unchecked Exception
런타임 예외로, 비지니스 데이터에 위해하지 않지만, 시스템에 영향을 미칠 수 있는 예외이다. 
unchecked Exception 은 try ~ catch 블럭이 없어도, 컴파일 에러가 발생하지 않는다.  주로  비지니스 로직상이 아닌 
프로로램 자체상의 에러로,  프로그램 작성을 잘 못했을 때 발생한다. 
 ex) ClassCastException, IndexOutOfBoundsException, IllegalStateException
 

다음은  checked 예외인 Exception 을 상속하여, 다양한 생성자를 갖도록 오버로딩하여 사용하는 예이다. 
 
 
 
  다음은 unchecked 예외인 RuntimeExcepting 을 상속하여,  다양한 생성자 오버로딩를 하여 사용하는 예이다. 




'언어로그 > Java' 카테고리의 다른 글

(2) 리플렉션(Reflection) 사용하기  (0) 2011.04.07
(1) 리플렉션(Reflection)  (3) 2011.04.07
예외처리 (Exception Handling)  (0) 2011.03.24
[Java] 어노테이션 사용하기  (1) 2011.03.19
[Java] 어노테이션(Annotation)  (2) 2011.03.15
[자바] (1) 자바소개  (0) 2011.03.10

[Java] 어노테이션 사용하기

언어로그/Java 2011. 3. 19. 23:38



Annotation을 실제로 사용하는 예제를 알아보자.  

첫번째 예제는 UseCase라는 어노테이션을 정의한다. 이 어노테이션은 id와  description이라는 값을
멤버로 갖으며 각각 기능번호와 기능에 대한설명을 나타낸다.  Password 검사와 관련된 클래스에스는 각 메소드에 
UseCase 어노테이션을 사용하여 메서드들이 어떤 유스케이스를 구현하고 있는지를 표시한다. 
나중에 모든 유스케이스를 구현하는 모든 메소드들이 잘 구현되었는지 확인하기 위해  UseCaseTracker 를 사용하여 
어노테이션 정보를 출력한다. (코드는 Thinking in Java 4E 에 있는 예제코드를 사용하였다. )


1. UseCase 어노테이션 정의
메서드에 사용할 어노테이션이므로 @Target을 ElementType.METHOD를 설정하였고, 
 런타임 시에 사용되기 때문에 @Rention을 RetentionPolicy.RUNTIME 로 설정하여 class 파일에 어노테이션 정보가 남도록
지정하였다. 
package net.atgame.annotation.usecase;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public int id();
	public String description() default "no description";
}



2. PasswordUtil 클래스에 정의한  어노테이션을 사용한다. 
UseCase 어노테이션은 코드 작성간에는 주석의 역할을 하고,  코드 실행시에는 테스트로서의 역할도 한다.
import java.util.List;

public class PasswordUtils {
	
	@UseCase(id = 47, description = "passwords must contain at least one numeric")
	public boolean validatePassword(String password) {
		return (password.matches("\\w*\\d\\w*"));
	}
	
	@UseCase(id = 48)
	public String encryptPassword(String password) {
		return new StringBuilder(password).reverse().toString();
	}
	
	@UseCase(id = 49, description = "New passwords can't equal perviously used ones")
	public boolean checkForNewPassword(List prevPasswords, String password) {
		return !prevPasswords.contains(password);
	}
}




3.  UseCaseTracker를 사용하여 모든 유스케이스를 구현하는 메소드들이 작성되었는지 확인한다. 

아래 코드에서는 리플렉션을 사용하여 유스케이스 아이디 47, 48, 49, 50 을 사용하는 메소드들을 검색하여 
그 메서드의 어노테이션 멤버를  출력하고 있다.  이 프로그램 실행을 통해 어떤 유스케이스가 아직 구현되지 
않았는지 알 수 있다. 
package net.atgame.annotation.usecase;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class UseCaseTracker {
	
	public static void trackUseCases(List useCases, Class cl) {
		for (Method m : cl.getDeclaredMethods()) {
			UseCase uc = m.getAnnotation(UseCase.class);
			if (uc != null) {
				System.out.println("Found Use Case:" + uc.id() + " " + uc.description());
				useCases.remove(new Integer(uc.id()));
			}
		}
		
		for (int i : useCases) {
			System.out.println("Warning: Missing use case~" + i);
		}
	}
	
	public static void main(String[] args) {
		List useCases = new ArrayList();
		Collections.addAll(useCases, 47, 48, 49, 50);
		trackUseCases(useCases, PasswordUtils.class);
	}
}

어노테이션이 어떻게 정의되고 사용되는지 보여주기 위해 위 예제를 사용하였다. 어노테이션(UserCase)을 정의를 했다면, 
그 어노테이션을 처리하기 위한 클래스(UseCaseTracker)도 반드시 작성해야함을 눈여겨보자.












 이제 좀더 실용적인 두번째 예제를 소개하겠다.  데이터베이스 접속 처리를 하기위해 데이터베이스 종류에 맞는 
SQL을 작성하여 DBMS에 전송해야한다.  데이터베이스에 맞는 SQL 작성! 이 상당히 코드를 지저분하게 만들수 있기 때문에
모듈화가 잘 고려되어야 한다. 데이터베이스별 SQL 작성을 안할 수는 없지만  어노테이션을 사용하면, DB 접속에 관한 부분을
클라이언트 프로그래머에게 투명하게 처리할 수 있다. 그 의미에 대해서 알아보자 


1. 테이블명을 나타내기 위한 DBTable 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DBTable {
	public String name() default "";
}


2. 테이블 컬럼의 타입을 나타내기 위한 SQLInteger 와 SQLString 어노테이션을 정의한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLInteger {
	String name() default "";
	Constraints constraints() default @Constraints;
}



3. 제약조건을 나타내기 위한 Constraint 어노테이션을 정의한다.
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface SQLString {
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}



4. Memeber 모델 클래스에 정의한 어노테이션들을 사용한다. 
package net.atgame.annotation.database;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraints {
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}



5. TableCreator 클래스에서 어노테이션 정보를 사용하여, 데이베이스 생성 SQL를 만들어낸다. 
package net.atgame.annotation.database;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

public class TableCreator {
	
	public static void main(String[] args) throws Exception {
		if (args.length < 1) {
			System.out.println("arguments: annotated classes");
			System.exit(0);
		}
		// 모든 엔티티 클래스를 읽음 
		for (String className : args) {
			Class cl = Class.forName(className);
			// 클래스 어노테이션 가져오기 
			DBTable dbTable = cl.getAnnotation(DBTable.class);
			if (dbTable == null) {
				System.out.println("No DBTable annotations in class " + className);
				continue;
			}
			String tableName = dbTable.name();
			
			if (tableName.length() < 1) {
				tableName = cl.getName().toUpperCase();
			}
			List columnDefs = new ArrayList();
			
			/// 필드목록 조회 
			for (Field field : cl.getDeclaredFields()) {
				String columnName = null;
				Annotation[] anns = field.getDeclaredAnnotations();
				if (anns.length < 1) {
					continue;
				}
				if (anns[0] instanceof SQLInteger)  {
					SQLInteger sInt = (SQLInteger)anns[0];
					if (sInt.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sInt.name();
					}
					columnDefs.add(columnName + " INT" + getConstraints(sInt.constraints()));
				}
				if (anns[0] instanceof SQLString) {
					SQLString sString = (SQLString)anns[0];
					if (sString.name().length() < 1) {
						columnName = field.getName().toUpperCase();
					} else {
						columnName = sString.name();
					}
					columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));
				}
				
			}
			StringBuilder createCommand = new StringBuilder("CREATE TABLE " + tableName + " (");
			for (String columnDef : columnDefs) {
				createCommand.append("\n " + columnDef + ",");
			}
			// 마지막 쉼표제거 
			String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";
			System.out.println("Table Creation SQL for " + className + " is :\n" + tableCreate);
		}
		
	}
	
	private static String getConstraints(Constraints con) {
		String constraints = "";
		if (!con.allowNull()) {
			constraints += " NOT NULL";
		}
		if (con.primaryKey()) {
			constraints += " PRIMARY KEY";
		}
		if (con.unique()) {
			constraints += " UNIQUE";
		}
		return constraints;
	}
}



어노테이션을 읽어 데이터베이스 SQL을 생성하는 부분에 주목하자. 
Model 클래스 어노테이션 정보를 읽어서 DB에 해당하는 SQL를 생성하고, 테이블을 생성하게 할 수 있다.  
더 나아가 getter 와 setter 메소드를 오버라이드하여 인스턴스에 대한 조작만으로 데이터베이스 CRUD(등록,조회,수정,삭제)
연산을 처리하게 할 수도 있을 것이다. 이렇게 하면 데이터베이스 종류에 따른 번거로운 처리를 클라이언트 프로그래머에게
좀더 투명하게 사용할 수 있다. 


이상 어노테이션을 사용하는 예제를 간략히 알아보았다. 어노테이션을 잘 활용하면 간결하고, 투명한 코드를 작성할 수 있다.
 하지만 언제나 그렇듯 "잘 활용하면" 이라는 조건이 붙는다.  어떻게 잘 활용할지에 대한 생각은 이제 개인의 몫이다.
 

'언어로그 > Java' 카테고리의 다른 글

(2) 리플렉션(Reflection) 사용하기  (0) 2011.04.07
(1) 리플렉션(Reflection)  (3) 2011.04.07
예외처리 (Exception Handling)  (0) 2011.03.24
[Java] 어노테이션 사용하기  (1) 2011.03.19
[Java] 어노테이션(Annotation)  (2) 2011.03.15
[자바] (1) 자바소개  (0) 2011.03.10
  • 장곰부대 2018.06.12 09:20 신고 ADDR 수정/삭제 답글

    안녕하세요 블로그 글 잘보고 있습니다.

    궁금한 점이 있어서 질문 드려봅니다.

    마지막 코드 약 22번째 줄에서
    String tableName = dbTable.name();
    if(tableName.length() < 1) {
    tableName = c1.getName().toUpperCase();
    }
    이걸 왜하는 건지 궁금합니다.
    아예 처음부터
    tableName = c1.getName() 이런식으로 처리하면 안됩니까?

[Java] 어노테이션(Annotation)

언어로그/Java 2011. 3. 15. 01:40



프로그래밍을 하면서 @Override 와 @Test와 같은 어노테이션들을 많이 사용했지만,  그 중요성이나 의미를 
깊이 있게 생각해보지는 못했었다.  단순히 컴파일러에게 알려주기 위한 표식정도로 생각했었다.  그런데  Spring Roo 와 
Spring3.0 과 같은 최신 프레임웍들의 변화 경향을 보면,  어노테이션을 적극활용하는 쪽으로 변화되고 있다.  어노테이션을
사용하여 코드의 작성량도 한결 줄어들었다고 한다.  어노테이션들의 어떤 특성을 활용한 것일까?  어노테이션이란 뭘까?
최신 프레임웍들에 변화경향을 보기에 앞서, 어노테이션에 대해서 먼저 알아보았다.



1. Annotation 이란?
Annotation은 JEE5 부터 새롭게 추가된 문법요소이다.  사전적으로는 "주석"이라는  의미를 가지고 있으며, 의미대로
자바 코드에 주석처럼 달아 특수한 의미를 부여해준다.   이 특별한 의미는 컴파일 타임 또는 런타임에 해석될 수 있다.
 

 

2.  기존 웹애플리케이션들의 문제점
기존의 자바 웹애플리케이션들은 선언적인 프로그래밍 방식을 사용한다.  선언적이란  프로그램의 전체 및 각 레이어별
구성과 설정값들을 외부의 XML 설정파일에 명시하는 방식을 의미한다.  변경이 될 수 있는 데이터들을 최대한 코드가 아닌
외부설정파일에 분리하기 때문에 변경 요구사항이 들어왔을 때, 재컴파일 없이도 쉽게 변경사항을 적용할 수 가 있다. 
유연성이란 장점을 얻었지만,  단점 또한 존재한다. 프로그램 작성을 위해 매번 많은 설정파일을 작성해야 한다는 것이다. 
그 규모가 커질수록 설정의 양도 많아지게 되며 이를 잘 구조화 할 수 있는 방법도 필요하게 된다. 또 하나의 단점은 이것보다
좀 더 크다.   도메인 데이터 처리정보가 Model 클래스, 서비스 클래스,  XML 설정파일에 분산되어 있어서,   이를 확인하기
위해서는 Model , Service 클래스와 XML 설정파일을 모두 뒤져야 한다는 것이다. 




3. Annotation의 사용했을 때의 장점은? 
어노테이션을 사용하면 위와 같은 문제를 해결 할 수 있다.  데이터에 대한 유효성 검사조건을 어노테이션을 사용하여
Model 클래스에 직접 명시함으로써 해당 데이터들에 대한 유효조건을 쉽게 파악할수 있게되며, 코드의 양도 줄어든다.
(엄밀히 말하면,  코드의 양은 줄어들지 않는다. 하지만 코드가 깔끔해지고, 어노테이션의 재사용도 가능해진다. )



4. 그렇다면 XML 설정은 사용하지 않아야 하는가?
앞서 말했다시피, XML은 유연성을 확보해준다.   어노테이션을 사용하여 한번 빌드된 코드는 수정을 위해서는 
재컴파일 해야하는 단점이 있다.  애플리케이션 전체적인 설정이나 디플로이 환경에 따라 변경되는 사항들은 XML 설정을
사용하자.  각각의 장단점을 파악하고, 언제 무엇을 사용해야할지 아는 것이 중요하다. 


그럼 이제부터 어노테이션을 사용해보자. 사용을 위해 먼저 선행지식들에 대해 잠깐 알아보자. 


5. 일반적인  어노테이션의 용도 
어노테이션은 크게 ① 문서화 ② 컴파일러 체크 ③ 코드 분석을 위한 용도로 사용된다.  문법적으로는 @기호가 
붙은 심볼을 사용하며 패키지, 클래스, 메소드,  프로퍼티, 변수에 명시할 수 있다. 이 어노테이션이 붙은 소스를 
컴파일 시에 수집하여 API 문서화 할수도 있지만 기존에 JavaDoc 라는 좋은 문서화 도구가 있기 때문에 "문서화" 는 
가장 비중이 낮은 어노테이션의 사용법이다. 
또한 컴파일 타임에 에러를 발생시켜 주어 개발자에서 위험요소를 경고해주거나 확인하는 목적으로도 사용된다. 
가장 큰 비중을 갖는 것은 코드 분석 또는 "메타-데이터" 로서의 용도이다. 메타-데이터 란 데이터를 위한 데이터, 즉 
데이터에 대해 설명하는 데이터를 의미한다.  메타데이터로서 어노테이션의 효용을 가장 잘 느낄 수 있는 부분이
JEE 설정과 유효성 검사 부분이다. 



5.  어노테이션의 분류
어노테이션은 메타데이터 저장을 위해 클래스처럼 멤버를 갖을 수 있다. 이 멤버의 개수에 따라 Marker 어노테이션,
Single-value,  Full 어노테이션으로 분류할 수 있다. 


①  Marker 어노테이션
     - 멤버 변수가 없으며, 단순히 표식으로서 사용되는 어노테이션이다. 컴파일러에게 어떤 의미를 전달한다. 

②  Single-value  어노테이션
    - 멤버로 단일변수만을 갖는 어노테이션이다. 단일변수 밖에 없기 때문에 (값)만을 명시하여 데이터를 전달할 수 있다.  

 Full 어노테이션
    -멤버로 둘 이상의 변수를 갖는 어노테이션으로, 데이터를 (값=쌍)의 형태로 전달한다. 




6.  빌트인(Built-in) 어노테이션 
자바 SDK에서 지원하는 어노테이션으로 @Override, @Deprecated, @SupressWarning 등이 있다. 



@Override 
 어노테이션은 현재 메소드가 수퍼클래스의 메소드를 오버라이드한 메소드임을 컴파일러에게 명시한다. 만일
수퍼 클래스에 해당하는 메소드가 없으면 컴파일러가 인지하고 에러를 발생시켜 준다. 

 

@Deprecated 

 마커 어노테이션으로  차후 버전에 지원되지 않을 수 있기 때문에 더 이상 사용되지  말아야할 메소드를 나타낸다.  

 특이하게 더이상 사용되지 말아야할 메소드와 같은 라인상에 놓여져야 한다. (이유모름)

(두 플래그  -deprecated 또는 Xlint:deprecated 중 하나와 javac 명령어를 사용하여 컴파일러 경고를  켜야만, 

 컴파일러 에러를 발생시켜준다)

@SupressWarning
의미데로 경고를 제거하는 어노테이션이다. Object형을 엘리먼트로 하는 컬렉션을 사용하면,  컴파일러 경고가 발생하는데
이 어노테이션을 사용하여 프로그래머의 의도적인 Object 형 사용임을 알려 경고를 제거할 수 있다. 




7. 커스텀(Custom) 어노테이션 

클래스와 같이 어노테이션을 임의로 정의하여 사용할 수 있다. 어노테이션은 interface 키워드 앞에 @를 붙여 표시한다. 


1) 커스텀 어노테이션 정의하 



// 메소드와 클래스가 여전히 작업중임을 나타내기 위해 정의한 마커 어노테이션
public @interface InProgress { }

어노테이션 정의파일을 컴파일하고, 이 파일을 클래스패스에서 참조할 수 있으면 다른 소스코드상에서 어노테이션을

사용할 수 있다. 



2) 멤버 추가하기 

어노테이션 유형은 멤버변수를 가질 수 있으며, 이 변수들  컴파일시 또는 런타임에 메타-데이터로서 사용될 수 있다.  
이렇게 정의를 하면 자동으로 
accessor와 mutator를 제공해준다.


// 해당 태스크가 완료되야함 나타내는 어노테이션

public @interface TODO {

  String value();           

}


정의한 어노테이션을 다음과 같이 사용한다. 


@TODO("Figure out the amount of interest per month")

public void calculateInterest(float amount, float rate) {

  // ...

}


단일 멤버를 갖을 경우에만 위와 같이 사용할 수 있다. 



3) 디폴트 값 설정하기 
 

public @interface GroupTODO {


  public enum Severity { CRITICAL, IMPORTANT, TRIVIAL, DOCUMENTATION };


  Severity severity() default Severity.IMPORTANT;

  String item();

  String assignedTo();

  String dateAssigned();

}


디폴트값을 사용한 예는 다음과 같다. 

@GroupTODO(

    item="Figure out the amount of interest per month",

    assignedTo="Brett McLaughlin",

    dateAssigned="08/04/2004"

  )

  public  void calculateInterest(float amount, float rate) {

    // ...

  }





4
) 메타-어노테이션

어노테이션에 사용되는 어노테이션으로 해당 어노테이션의 동작대상 및 보여타임을 결정한다. 


① @Target 메타-어노테이션

   어노테이션이 적용되는 동작 프로그래밍 

엘리멘트를 지정하며, 다음과 같은 값들이 있다. 

package java.lang.annotation;


public enum ElementType {

  TYPE,       // Class, Interface, enum 

  FIELD,                       // 프로퍼티 (enum 나열값들은 제외)

  METHOD,       // 메서드

  PARAMETER,       // 메서드 파라미터

  CONSTRUCTOR,                     // 생성자

  LOCAL_VARIABLE,       // 로컬변수 또는 Catch문

  ANNOTATION_TYPE,       //  어노테이션(메타 어노테이션)_

  PACKAGE       //  자바 패키지

}




② @Retention 메타-어노테이션 

     자바컴파일러가 어노테이션을 다루는 방법과 관련이 있다.  소스파일, 클래스파일, 런타임 중 어느시점까지

    어노테이션을 보유하고 있을 것인지를 결정한다. 


package java.lang.annotation;


public enum RetentionPolicy {

  SOURCE, // 어노테이션이 컴파일러에 의해 버려짐

  CLASS, // 어노테이션이 클래스파일에 저장되지만, JVM에게 무시됨

  RUNTIME // 어노테이션이 클래스파일에 저장되며, JVM에 의해 읽혀짐 

}

 

위 세값 중 하나를 인자로 취해 다음과 같이 사용된다. 


@Retention(RetentionPolicy.SOURCE)

public @interface SuppressWarnings {

  ...

}






'언어로그 > Java' 카테고리의 다른 글

(2) 리플렉션(Reflection) 사용하기  (0) 2011.04.07
(1) 리플렉션(Reflection)  (3) 2011.04.07
예외처리 (Exception Handling)  (0) 2011.03.24
[Java] 어노테이션 사용하기  (1) 2011.03.19
[Java] 어노테이션(Annotation)  (2) 2011.03.15
[자바] (1) 자바소개  (0) 2011.03.10
  • alecsander 2012.05.30 13:05 ADDR 수정/삭제 답글

    좋은 자료 감사합니다^^ 담아가요~

  • 반가 2016.06.09 12:59 ADDR 수정/삭제 답글

    좋은 정보 잘보고 갑니다

포인터의 이해

언어로그/C/C++ 2011. 3. 11. 00:58





1. 포인터(pointer)
단어의 의미와 같이 특정 메모리를 가리킬 수 있는(참조) 변수를 의미한다. 
포인터는 변수의 주소를 저장할 수 있으며, 32비트 시스템에서 통상 4바이트를 갖는다.




2. 포인터를 사용하는 이유는?
포인터를 사용하면,  빠르고 간결한 프로그램을 작성할 수 있기 때문이다. 포인터를 제대로 사용하지
못하면 난해한 프로그램이 될 수 있지만, 잘 알고 사용하면, 유연하면서도 , 빠르고 간결한 프로그램을 작성할 수 있다.



3. 포인터의 특성
포인터를 사용하여 일반변수와 함수를 다룰 수 있다.  포인터로 변수에 접근하기 위해서는 포인터가 다룰 메모리의 크기를
알아야 의미있게 메모리를 해석할 수 있다. 그렇게 때문에 포인터 변수 선언시 어떻게 메모리를 해석할 것인지 알려주어야
한다.  (포인터 타입은 포인터가 가리키는 대상체 메모리의 단위크기이다.)

char    *cp;
int    *ip;
float    *fp;
double    *dp;

위와 같은 선언은  포인터가 가리키고 있는 메모리를 몇 바이트씩 읽되, 어떻게 데이터를 읽어내겠다는 것을 의미한다.

int a;
int *ip1, *ip2;
ip1 = &a;
ip2 = ip1;

포인터도 변수이기 때문에 포인터 변수에 다른 변수에 주소값을 갖도록 변경할 수 있으며, 또한 변수에 대해 동일한 주소를 
가리키는 여러 포인터를 유지 할 수도 있다.





4. 포인터 연산
포인터 간의 곱셉과 나눗셈은 허용되지 않는다. (컴파일 에러 발생)
포인터 간에 덧셈은 허용하지 않지만, 포인터와 상수의 덧셈은 허용된다..
포인터 간에 뺄셈과 포인터와 상수간에 뺄셈도 허용된다. 

ip1 * ip2// 컴파일 에러     
ip1 / ip2// 컴파일 에러   
ip1 + ip2// 컴파일 에러   

위와 같은 경우는 컴파일 에러가 발생한다.  포인터가 갖을 수 있는 일정 주소범위를 벗어날 수 있기때문에 에러로 보호된다.

ip1 + 1//  4바이트(int) 뒤로 이동 
ip1 - ip2          //  ip1이 1004, ip2가 1008이면, 1이 반환됨 ( (1008-1004)/4 )       
ip2 - 1    //  4바이트(int) 앞으로 이동

포인터 연산을 위해 사용된다. 포인터에 1을 더하는 것은 포인터가 메모리 블럭을 한칸 건너뛰는 작용을,  
1을 빼는 것은 포인터가 이전 메모리 블럭으로 건너뛰는 작용을 한다.  포인터에서 포인터를 빼는 것은 
메모리의 차를 구하는 연산으로,  주소값에 차를 포인터가 가리키는 타입의 바이트수로 나눗값이 계산된다. 
즉 메모리블럭의 차가 반환된다.

 ※ %I64d 변환코드 : double을 첫번째 비트를 부호비트, 나머지 63비트를 데이터 비트로 해서 정수로 해석한다.
                                      큰 단위의 정수를 다룰 때 사용한다.  




5. 증감연산자와 역참조 연산자의 결합 
*pa++;// *pa;pa = pa + 1;              
(*pa)++; // *pa;  *pa = *pa + 1;        
++*p;// *pa = *pa + 1;*pa;     
*++p;// pa = pa + 1; *pa;      

증감연산자와 역참조 연산자를 위와 같이 조합하여 다양하게 사용할 수 있지만, 실행되는 각 의미는 매우 다르다.
역참조 연산자 *와 증감연산자++는 그 우선순위가 같지만, 좌측결합성을  갖기때문에 오른쪽에서 왼쪽으로 평가된다.
(오른쪽에 있는 연산자를 먼저 적용한다.)





6. void 형 포인터
어떤 타입의 형도 가리킬 수 있는 포인터. 즉 대상체 크기가 정해져 있지 않은 포인터이다.
형변환 없이도 void형 포인터변수에 다른 타입의 포인터를 대입할 수 있다. 하지만 void형 포인터에서
값을 읽을 때는  몇바이트를 읽고, 어떻게 데이터를 해석해야 하는지를 알아야 하기 때문에 캐스트 연산자를
사용해야 한다.  void형 포인터를 사용하면, 각 타입을 위한 포인터 변수를 만들어야 하는 번거로움을 줄여 사용변수에 
개수를 줄일 수 있고, 함수의 인자로 사용하여 유연한 프로그램을 작성할 수 있다.  하지만 잘못 사용하면 가독성이 
떨어지고, 난해한 프로그램이 될 수 있다. 


'언어로그 > C/C++' 카테고리의 다른 글

전역변수와 지역변수  (0) 2011.03.27
함수의 이해  (0) 2011.03.27
포인터의 이해  (0) 2011.03.11
매크로, 연산자  (3) 2011.03.10
표준입출력함수 getchar() / putchar()  (0) 2011.02.22
비표준 입출력함수 getch(), getche()  (0) 2011.02.22