프로그래밍/java 2014. 6. 22. 16:02

http://stophyun.tistory.com/37

//
프로그래밍/java 2014. 6. 22. 15:54

 

즉 특정 URL의 웹프로그램을 실행 시키는게 목적이거나

 

특정 URL의 내용을 읽어 오는게 목적이라면..

 

URLConnection객체를 사용

 

import java.net.URL;
import java.net.URLConnection;
import java.net.MalformedURLException;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;

 

public class URLConn{
    public static void main(String args[]){
        URL url;//URL 주소 객체
        URLConnection connection;//URL접속을 가지는 객체
        InputStream is;//URL접속에서 내용을 읽기위한 Stream
        InputStreamReader isr;
        BufferedReader br;

        try{
            //URL객체를 생성하고 해당 URL로 접속한다..
            url = new URL(args[0]);
            connection = url.openConnection();

            //내용을 읽어오기위한 InputStream객체를 생성한다..
            is = connection.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);

            //내용을 읽어서 화면에 출력한다..
            String buf = null;
            while(true){
                buf = br.readLine();
                if(buf == null) break;
                System.out.println(buf);
            }
        }catch(MalformedURLException mue){
            System.err.println("잘못되 URL입니다. 사용법 : java URLConn http://hostname/path]");
            System.exit(1);
        }catch(IOException ioe){
            System.err.println("IOException " + ioe);
            ioe.printStackTrace();
            System.exit(1);
        }
    }
};


//
프로그래밍/java 2014. 6. 15. 11:47

Object 클래스 정리


java.lang은 자바 프로그램에서 가장 많이 사용되는 패키지로서 자바 프로그램내에 'import' 문을 사용하지 않아도 자동으로 포함된다. 그만큼 자바 프로그램의 기본이 되는 클래스들과 인터페이스들이 포함되어 있다.


다음은 java.lang 패키지의 클래스 구조도이다.



물론 java.lang 패키지에는 더 많은 클래스들이 있다.  더 많은 클래스는 여기서 확인하자.  JAVA API DOC : Java.lang


Object 클래스는 java.lang 패키지 내의 최상위 클래스이며, String 클래스는 변하지 않는 문자열을 다룰 때, StringBuffer 클래스는 내용이 계속 변하는 문자열을 다룰 때 사용될 수 있다.



2. Object 클래스


java.lang.Object 클래스는 자바 API의 모든 클래스와 사용자가 정의한 모든 클래스의 최상위 클래스이다. 즉, 모든 자바 클래스들은 Object 클래스로부터 상속받는다.


사용자가 클래스를 정의할 때 클래스 선언부에 명시적으로 extends java.lang.Object를 지정하지 않아도 자동으로 상속받게 된다. 다시 말해 Object 클래스의 모든 메서드와 변수는 다른 모든 클래스에서도 사용 가능할 수 있다는 말이다.


* Object 클래스의 주요 메소드


 메소드

설 명 

boolean equals(Object obj) 

 두 개의 객체가 같은지 비교하여 같으면 true를, 같지 않으면 false를 반환한다.

String toString() 

현재 객체의 문자열을 반환한다. 

protected Object clone() 

객체를 복사한다. 

protected void finalize() 

가비지 컬렉션 직전에 객체의 리소스를 정리할 때 호출한다. 

Class getClass() 

객체의 클래스형을 반환한다. 

int hashCode() 

객체의 코드값을 반환한다. 

void notify() 

wait된 스레드 실행을 재개할 때 호출한다. 

void notifyAll() 

wait된 모든 스레드 실행을 재개할 때 호출한다. 

void wait() 

스레드를 일시적으로 중지할 때 호출한다. 

void wait(long timeout) 

주어진 시간만큼 스레드를 일시적으로 중지할 때 호출한다. 

void wait(long timeout, int nanos) 

주어진 시간만큼 스레드를 일시적으로 중지할 때 호출한다.


equals 메소드는 두 개의 객체를 참조하는 참조값이 같은 객체일 때 true를 반환하므로, 참조하는 객체의 주소값이 같은 경우를 말한다.


출처 : http://hyeonstorage.tistory.com/178



//
프로그래밍/java 2014. 6. 13. 16:39


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import java.util.*;
 
public class SpeedTest {
 
    public static long add( List list ) // 순차적인 삽입.
    {
        long start_time = System.currentTimeMillis();
         
        for (int i = 0 ; i<100000 ; i++)
        {
            list.add(i+"");
        }
         
        long end_time = System.currentTimeMillis();
         
        return end_time - start_time;
    }
     
    public static long add2( List list ) // 중간에 삽입
    {
        long start_time = System.currentTimeMillis();
         
        for (int i = 0 ; i<100000 ; i++)
        {
            list.add(1000 , "a"); // 100번째 에 삽입
        }
         
        long end_time = System.currentTimeMillis();
         
        return end_time - start_time;
    }
     
    public static long remove(List list)
    {
        long start_time = System.currentTimeMillis();
         
        for (int i = 0 ; i<100000 ; i++)
        {
            list.remove(500);
        }
         
        long end_time = System.currentTimeMillis();
         
        return end_time - start_time;
         
    }
    public static long remove2(List list)
    {
        long start_time = System.currentTimeMillis();
         
        for (int i = list.size()-1 ; i>0 ; i--)
        {
            list.remove(i);
        }
         
        long end_time = System.currentTimeMillis();
         
        return end_time - start_time;
         
    }
    public static long access(List list)
    {
        long start_time = System.currentTimeMillis();
         
        for (int i = 0; i<1000 ; i++)
        {
            list.get(i);
        }
         
        long end_time = System.currentTimeMillis();
         
        return end_time - start_time;
    }
    public static void main(String[] args)
    {
         
        ArrayList al = new ArrayList( 1000000 );   
        LinkedList ll = new LinkedList();
         
         
         
        System.out.println("순차적으로 삽입하는 시간");
        System.out.println("Arraylist " + add(al));
        System.out.println("LinkedList " + add(ll));
        //순차적일 경우는 arraylist 가 빠르다.
         
         
        System.out.println("중간에 추가하는 시간");
        System.out.println("Arraylist " + add2(al));
        System.out.println("LinkedList " + add2(ll));
        //중간에 추가할경우 LinkedList 가 빠르다 훨신!
         
        System.out.println("접근시간 테스트");
        System.out.println("Arraylist " + access(al));
        System.out.println("LinkedList " + access(ll));
         
         
        System.out.println("중간에서 삭제하기");
        System.out.println("Arraylist " + remove(al));
        System.out.println("LinkedList " + remove(ll));
        //중간에 삭제할경우 LinkedList 가 빠름
         
         
        System.out.println("순차적으로 삭제하기");
        System.out.println("Arraylist " + remove2(al));
        System.out.println("LinkedList " + remove2(ll));
        //순차적삭제는 Arraylist가 빠름
 
         
 
         
    }
}

1. 순차적으로 추가/삭제하는경우에는 ArrayList가 LinkedList보다 빠르다

2. 중간에 데이터를 추가.삭제 하는경우에는 LinkedList가 ArrayList 보다 훨씬! 빠르다. 

3. 특정요소에 접근할 경우에는 Arraylist가 빠르다.

배열의 경우 n번째 요소에 접근할 경우 아래와 같은 수식으로 접근하기 때문에 빠르다.

n번째 요소의 주소 = 배열의 주소 + n * 데이터 타입의 크기.


//
프로그래밍/java 2014. 6. 13. 16:35

HashMap, ArrayList, LinkedList 속도 비교


테스트 엘리먼트는 Integer 인스턴스 5,000,000(5백만개)를 이용하여 수행하였다.

탐색과 삭제에서는 균등하게 떨어진 4990개의 데이터를 이용하였다.


테스트 결과 :

5000000개의 인스턴스 생성 시간 0.548829807초


HashMap Test

입력 소요 시간  2.415268645초

탐색 소요 시간 0.002399381초

삭제 소요 시간 0.002615092초


ArrayList

입력 소요 시간  0.381054002초

탐색 소요 시간 1.99475E-4초

삭제 소요 시간 137.231368119초


LinkedList

입력 소요 시간  1.503839756초

탐색 소요 시간 52.905209243초

삭제 소요 시간 52.587791295초



HashMap의 경우는 입력되는 시간을 제외하면 우수한 성능을 보였고,

(탐색과 삭제 시에 인덱스가 아닌 키값으로 하였음)


ArrayList 의 경우에는 내부적으로 배열을 쓰는 컬렉션 답게 탐색에서는 매우 우수한 속도를 보였지만

삭제 시에 배열의 구조가 변경되므로 매우 느린 속도를 보였다.


LinkedList는.. 탐색, 삭제 모두 순차 탐색을 하므로(실제로 이중 연결 링크드리스트로 되어있고, Head, Rear 포인터를 이용해서, 탐색하려고 하는 인덱스와 리스트 크기의 반과 비교해서 인덱스가 작은 경우 앞에서부터 탐색하고, 큰 경우 뒤에서부터 탐색하도록 되어있어 평균적으로 n / 2의 시간 복잡도를 가진다.) 많이 느렸다. -_-



따라서 HashMap은 Key, Value 쌍을 가지는 데이터를 관리할 때 용이하고,

ArrayList는 데이터가 입력 되고 삭제가 빈번하지 않은 경우에 사용하면 되고,

Linkedlist는 Queue와 같이 Head와 Read와 가까이에서 탐색, 삭제가 이뤄지는 경우에 쓰면 좋을 듯 하다.



테스트에 사용한 코드는 아래와 같습니다. 참고 하실 분은 아래의 소스를 사용하시면 됩니다.

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

public class TestCode {
	static Integer[] testArray = new Integer[5000000];
	Integer[] values = new Integer[4990];

	public void hashMapTest() {
		long start = System.nanoTime();

		HashMap hashmap = new HashMap();
		for(Integer integer : testArray){
			hashmap.put(integer, integer);
		}
		
		long end = System.nanoTime();
		
		System.out.println("\nHashMap Test");
		System.out.println("\t입력 소요 시간  " + second(start, end) + "초");
		
		start = System.nanoTime();
		for(Integer value : values){
			hashmap.get(value);
		}		
		end = System.nanoTime();
		System.out.println("\t탐색 소요 시간 " + second(start, end) + "초");
		
		start = System.nanoTime();
		for(Integer value : values){
			hashmap.remove(value);
		}
		end = System.nanoTime();
		System.out.println("\t삭제 소요 시간 " + second(start, end) + "초");
	}
	
	public void arrayListTest(){
		long start = System.nanoTime();
		
		ArrayList arrayList = new ArrayList();
		for(Integer integer : testArray){
			arrayList.add(integer);
		}
		
		long end = System.nanoTime();
		
		System.out.println("\nArrayList");
		System.out.println("\t입력 소요 시간  " + second(start, end) + "초");
		
		start = System.nanoTime();
		for(Integer value : values){
			arrayList.get(value);
		}		
		end = System.nanoTime();
		System.out.println("\t탐색 소요 시간 " + second(start, end) + "초");
		
		start = System.nanoTime();
		for(Integer value : values){
			arrayList.remove(value);
		}
		end = System.nanoTime();
		System.out.println("\t삭제 소요 시간 " + second(start, end) + "초");
	}
	
	public void linkedListTest(){
		long start = System.nanoTime();
		
		List linkedList = new LinkedList();
		for(Integer integer : testArray){
			linkedList.add(integer);
		}
		
		long end = System.nanoTime();
		
		System.out.println("\nLinkedList");
		System.out.println("\t입력 소요 시간  " + second(start, end) + "초");
		start = System.nanoTime();
		for(int value : values){
			linkedList.get(value);
		}		
		end = System.nanoTime();
		System.out.println("\t탐색 소요 시간 " + second(start, end) + "초");
		
		start = System.nanoTime();
		for(int value : values){
			linkedList.remove(value);
		}
		end = System.nanoTime();
		System.out.println("\t삭제 소요 시간 " + second(start, end) + "초");
	}


	private void prepare() {
		long start = System.nanoTime();
		for (int i = 0; i < testArray.length; i++) {
			testArray[i] = i;
		}
		long end = System.nanoTime();
		
		ArrayList temp = new ArrayList(1000);
		for(int i = 0 ; i < 4990 ; i++){
			temp.add(i * 1000);
		}
		temp.toArray(values);
		
		
		System.out.println(testArray.length + "개의 인스턴스 생성 시간 " +
				second(start, end) + "초");
		
	}
	
	private double second(long start, long end){
		return (end - start) / Math.pow(10, 9);
	}

	public void start() {
		prepare();
		hashMapTest();
		arrayListTest();
		linkedListTest();
	}

	public static void main(String[] args) {
		TestCode test = new TestCode();
		test.start();
	}
}

출처 : http://nnoco.tistory.com/73


//
프로그래밍/java 2014. 6. 9. 23:00

LinkedList 와 ArrayList 는 둘다 List 인터페이스를 따르지만, 구현방법은 서로 다릅니다. LinkedList 는 이중 연결 리스트(doubly-linked list) 이고, ArrayList 는 동적으로 크기가 변경되는 배열로 되어 있습니다.

링크드 리스트 와 배열의 속성 때문에, 어떤 일을 하는지에 따라 실행 속도가 달라지게 됩니다.

LinkedList

  • 특정 원소에 접근하는 것은 O(n)
  • 원소를 추가하는 것은 O(1)
  • 특정 원소를 제거하는 것은 O(n)
  • Iterator.remove 는 O(1)

For ArrayList

  • (역주: 특정 인덱스에) 접근 하는 것은 O(1)
  • 원소를 추가하는 것은 평균적으로 O(1), 하지만 최악의 경우엔, 어레이 크기를 늘리고 복사해야 되기 때문에 O(n)
  • 특정 원소를 제거하는 것은 O(n)

LinkedList 를 쓰면 constant(역주 O(1)) 시간안에 추가 와 제거(역주: Iterator.remove) 를 할 수 있지만, 원소를 접근 할 때, 순차적(역주: 리스트의 처음부터 링크를 따라가서) 으로 접근 할 수 밖에 없다. 다시말하면, 리스트 어딘가에 있는 특정 원소를 접근하는데는 리스트 크기에 비례하는 시간이 걸린다.

ArrayLists 는 반대로, 임의 접근(random access) 가 가능하다. 즉 어떤 원소도 (역주: index로) 즉각 얻어올 수 있다. 하지만, 젤 끝이 아닌 곳에 원소를 더하거나 빼는 것은, 그 원소 이후의 모든 원소를 옮기는 일을 수반하게 된다. 그리고 현재 배열 사이즈를 초과해서 원소를 더하려고 하면, 새로운(보통 두배 크기의) 배열이 생성되고, 원래 배열에서 값들이 새로운 배열로 복사되어야 한다. 따라서 ArrayList 에 원소를 더하는 것은, 최악의 상황에선 O(n) 이고, 평균적으론 O(1) 이다.

따라서, 하고자 하는 바에 따라서, 어떤 리스트를 쓸지 잘 정해야 한다. 원소들을 순차적으로 접근하는 것은 양쪽이 크게 차이가 나지 않는다. (ArrayList 를 순회하는 것이 사실 조금 빠르지만, 정말 이 차이를 걱정해야 할만한 일은 많지 않다.)

또한, 큰 리스트를 만든다면, 메모리 사용량에도 관심을 가져야 할 것이다. LinkedList 는 앞과 뒤 원소를 가리키는 포인터를 저장해야 하기 때문에 원소 하나에 대한 메모리 사용량이 ArrayList 보다 더 많다. 하지만 ArrayList 는 실제 가지고 있는 원소 갯수가 아닌, capacity 만큼의 메모리를 잡아먹는다는 단점이 있다.

ArrayList 의 기본 capacity 는 매우 작다. (java 1.4-6 에서 10이다) 하지만, 구현방식이 배열이기 때문에, 원소를 더하면서 배열의 크기를 조정하게 된다. 많은 원소를 추가할 것이라고 예상할 수 있을 때에는, 처음부터 크기를 크게 잡아서, 배열을 늘릴때 드는 비용을 피해갈 수도 있다.

Vector 도 List 인터페이스를 따르고 ArrayList 와도 매우 비슷하다. 차이점은 Vector 는 동기화 되기때문에 thread-safe(여러 thread 에서 사용해도 안전하다) 라는 점이다. 동기화 때문에 ArrayList 보다 약간 느리기도 하다. 내가 알기론, 대부분 자바 개발자들은 Vector 보단 ArrayList 를 선호한다. thread 환경에서 동기화를 걱정해야되면 아마 더 명시적인 동기화 방법을 사용하는 경우가 많은 것 같다.

//
프로그래밍/java 2014. 6. 6. 11:25


java의 synchronized 분석


요즘 jsr 133 자바 메모리 모델을 보고 있는데, 처음에 lock거는 부분에 대해 나오더군요.

자바에서 lock을 거는 것은 보통 synchronized 구문을 이용하는데, 일반적인 자바 책에서는 "synchronized를 걸면 동시 접속이 안된다." 까지만 나와 있었는데, 보다가 보니 재미있는 부분들이 있어서 정리해볼랍니다.


synchronized (anObject){

// code!!

}

위와 같은 구문은 anObject를 기준으로 잡습니다. 그렇기 때문에 완전히 다른 객체끼리도 동기화가 가능합니다. 다음은 제가 주로 사용하는 디버깅 코드입니다.


synchronized (java.lang.Object.class) {

        System.out.println("===========디버깅 시작했다~================");

        System.out.println("time:"

                + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));

        System.out.print(new Throwable().getStackTrace()[0].getClassName() + "."

                + new Throwable().getStackTrace()[0].getMethodName() + "()");

        System.out.println("  line: " + new Throwable().getStackTrace()[0].getLineNumber());

        System.out.println(something);

        System.out.println("===========디버깅 끝났다~================");

    }


java.lang.Object는 하나이므로 java.lang.Object를 기준으로 동기화를 시키면 저 코드가 어디 들어가 있더라도 저 코드들끼리 꼬이는 일은 없습니다. 한번 =======디버깅 시작했다.========= 구문이 찍히면, ======== 디버깅 끝났다.===== 구문이 찍히기 전까지는 다른 쓰레드에서 저런 디버깅 메시지를 찍지 않고 기다리게됩니다.


메쏘드에 선언된 synchronized는 자기 객체(this)를 기준으로 동기화합니다.(static 메쏘드의 경우는 자기 클래스를 기준으로!) 그 객체에서 synchronized를 건 모든 메쏘드끼리도 동기화가 됩니다.


다음 코드를 보세요.


package synch.method1;


public class BlackOrWhite {

    private String str;

    private final String BLACK = "black";

    private final String WHITE = "white";

    

    public synchronized void black(){

        str = BLACK;

        try {

            long sleep = (long) (Math.random()*100L);

            Thread.sleep(sleep);

            if (!str.equals(BLACK)) {

                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");

            }

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

    public synchronized void white(){

        str = WHITE;

        try {

            long sleep = (long) (Math.random()*100L);

            Thread.sleep(sleep);

            if (!str.equals(WHITE)) {

                System.out.println("+++++++++++++++++broken!!++++++++++++++++++++");

            }

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

    }

}


package synch.method1;


import java.util.Iterator;


public class Test {


    /**

     * @param args

     */

    public static void main(String[] args) {

        final BlackOrWhite bow = new BlackOrWhite();


        Thread white = new Thread() {

            public void run() {

                while (true) {

                    bow.white();

                }

            }

        };

        Thread black = new Thread() {

            public void run() {

                while (true) {

                    bow.black();

                }

            }

        };

        white.start();

        black.start();

    }

}


두개의 쓰레드가 BlackOrWhite 클래스의 인스턴스인 bow라는 변수를 공유하고 있습니다. 하나의 쓰레드는 black()이란 메쏘드만 주구장창 호출하고, 다른 하나의 쓰레드는 white()만 주구장창 호출해댑니다. 그런데, black()이란 메쏘드와 white()라는 메쏘드가 서로 동기화가 됩니다. 위 코드를 실행시키면 broken!이 찍히는 일은 절대 없습니다. 다시 말해 서로 다른 쓰레드에서 black()과 white()를 호출하지만 두 메쏘드 모두 bow라는 인스턴스에 소속되기 때문에 두 메쏘드가 동시에 진행되는 일은 일어나지 않습니다.


정리를 하자면.

synchronized foo(){

    //메쏘드 내용.

}

foo(){

    synchronized(this){

        //내용

    }

}

은 같은 얘기가 됩니다.


경우에 따라서 method 간에 동기화가 필요 없는 경우도 있을 수 있습니다. 다음의 예를 봅시다.


package synch.two;


import java.util.HashMap;

import java.util.Map;


public class TwoMap {

    private Map<String, String> map1 = new HashMap<String, String>();

    private Map<String, String> map2 = new HashMap<String, String>();

    

    public synchronized void put1(String key, String value){

        map1.put(key, value);

    }

    public synchronized void put2(String key, String value){

        map2.put(key, value);

    }

    

    public synchronized String get1(String key){

        return map1.get(key);

    }

    public synchronized String get2(String key){

        return map2.get(key);

    }

}


위의 예제에서는 두개의 map을 가진 객체가 있습니다. 두 map에 각각 put과 get을 지원합니다. put1()과 get1()은 동기화가 되어야 하지만, put1()과 put2()가 동기화될 필요는 없습니다. 그러나 method에 synchronized가 걸려 있기 때문에 4개의 메쏘드 모두 상호 동기화가 됩니다. 그다지 바람직하지 않은 코드죠.


위의 코드는 아래와 같이 바뀌는 것이 좋습니다.

package synch.two;


import java.util.HashMap;

import java.util.Map;


public class TwoMap {

    private Map<String, String> map1 = new HashMap<String, String>();

    private Map<String, String> map2 = new HashMap<String, String>();

    private final Object syncObj1 = new Object();

    private final Object syncObj2 = new Object();

    

    public void put1(String key, String value){

        synchronized (syncObj1) {

            map1.put(key, value);

        }

    }

    public void put2(String key, String value){        

        synchronized (syncObj2) {

            map2.put(key, value);

        }

    }

  

    public String get1(String key){

        synchronized (syncObj1) {

            return map1.get(key);

        }

    }

    public String get2(String key){

        synchronized (syncObj2) {

            return map2.get(key);

        }

    }

}


이렇게 하면 put1()과 get1() 끼리만, put2()와 get2()끼리만 동기화가 됩니다. 위의 예제에서는 굳이 syncObj1, syncObj2를 만들지 않고, map1과 map2를 이용하여 동기화하여도 됩니다만, 동기화를 위해 저렇게 동기화 전용 멤버 변수를 만들 수도 있다는 것을 보여드릴라고 한 겁니다.


출처 : http://iilii.egloos.com/4071694

//
프로그래밍/java 2014. 6. 1. 16:52


//
프로그래밍/java 2014. 6. 1. 16:51
리플렉션(reflection)은 자바 프로그래밍 언어의 기능 중 하나다. 리플렉션을 사용하면 자바 프로그램을 실행해서 해당 프로그램을 조사하거나 스스로를 살펴볼(introspect) 수 있다. 또한 프로그램의 내부 프로퍼티를 조작할 수도 있다. 예를 들어 리플렉션을 사용하면 자바 클래스에서 해당 클래스의 멤버의 이름을 모두 획득해서 표시할 수 있다.

자바 클래스에서 스스로를 조사하고 조작하는 이러한 기능이 그리 대단해 보이지 않을 수도 있다. 하지만 이러한 기능을 지원하지 않는 프로그래밍 언어도 있다. 예를 들어 파스칼, C, C++ 프로그램에서는 해당 프로그램 안에 정의된 함수에 대한 정보를 얻을 수 있는 방법이 없다.

실제로 자바 빈(JavaBeans)에서 리플렉션을 적용했다. 자바 빈을 사용하면 소프트웨어 컴포넌트를 빌더 툴을 사용해서 시각적으로 조작할 수 있다. 이러한 빌더 툴은  자바 컴포넌트(클래스)가 동적으로  로드될 때, 리플렉션을 사용해서 해당 컴포넌트의 프로퍼티를 획득한다.


간단한 예제
리플렉션이 어떻게 동작하는지 아래의 간단한 예를 통해 살펴보자:
   import java.lang.reflect.*;
 
   public class DumpMethods {
      public static void main(String args[])
      {
         try {
            Class c = Class.forName(args[0]);
            Method m[] = c.getDeclaredMethods();
            for (int i = 0; i < m.length; i++)
            System.out.println(m[i].toString());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

아래와 같이 실행해 보면
java DumpMethods java.util.Stack


결과는 다음과 같다:

public java.lang.Object java.util.Stack.push(java.lang.Object)
public synchronized java.lang.Object java.util.Stack.pop()
public synchronized java.lang.Object java.util.Stack.peek()
public boolean java.util.Stack.empty()
public synchronized int java.util.Stack.search(java.lang.Object)


보다시피 java.util.Stack 클래스의 메서드 명이 출력된다. 메서드의 파라미터와 리턴 타입 또한 패키지 명을 포함해서 출력된다.

이 프로그램은 Class.forName을 사용해서 기술된 클래스를 로드한 후, getDeclaredMethods를 호출해서 해당 클래스에 정의된 메서드 목록을 얻는다. java.lang.reflect.Method는 클래스에서 단일 메서드를 나타내는 클래스다.


리플렉션 사용을 위한 설정
Method와 같이 리플렉션을 위한 클래스는 java.lang.reflect 패키지에서 찾을 수 있다. 이 패키지에 있는 클래스를 사용하려면 세 단계를 거쳐야만 한다. 첫 단계로 조작하려는 클래스에 해당하는 java.lang.Class 객체를 얻어야 한다. java.lang.Class는 실행 중인 자바 프로그램에서 클래스 또는 인터페이스를 나타내기 위해 사용한다.

Class 객체를 얻을 수 있는 한 가지 방법은 다음과 같다:

Class c = Class.forName("java.lang.String");


와 같이 사용하면 String에 대한 Class 객체를 얻을 수 있다. 또는 다음과 같이 사용할 수도 있다:

Class c = int.class;   // 또는
Class c = Integer.TYPE; 


와 같이 사용하면 기본형에 대한 Claass 정보를 얻을 수 있다. 두 번째 방법의 경우, 기본현에 대한 래퍼 클래스(Integer와 같이)에 미리 정의된 TYPE 필드에 접근한다.

두 번째 단계는 getDeclaredMethods와 같은 메서드를 호출하는 작업으로, getDeclaredMethods를 호출하면 해당 클래스에 선언된 메서드의 목록을 모두 얻을 수 있다.

이러한 정보를 모두 얻고 나면, 세 번째 단계로 리플렉션 API를 사용해서 해당 정보를 조작할 수 있다. 예를 들어 아래와 같은 일련의 문장을 실행하면,

Class c = Class.forName("java.lang.String");
Method m[] = c.getDeclaredMethods();
System.out.println(m[0].toString());


String에 선언된 첫 번째 메서드를 텍스트 형식으로 표시하게 된다.

이후에 다룰 예제에서는 지금 설명한 세 단계를 결합해서 사용한다. 이를 통해 특정한 목적에 맞게 리플렉션을 활용하는 방식을 효과적으로 처리하는 예제를 한꺼번에 보여주고자 한다.


instanceof 연산자 흉내내기
Class 정보를 일단 얻고 나면, 이 다음에는 주로 해당 Class 객체에 대하 기본적인 질문은 하는 작업을 한다. 예를 들어 Class.isInstance 메서드를 사용하면 instanceof 연산자를 흉내낼 수 있다:

   class A {}

   public class instance1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("A");
            boolean b1 = cls.isInstance(new Integer(37));
            System.out.println(b1);
            boolean b2 = cls.isInstance(new A());
            System.out.println(b2);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


이 예제에서는 클래스 
A에 대한 Class 객체를 생성한 후, 해댕 객체가 클래스 A의 인스턴스인지 검사한다. 해당 객체는 Integer(37)의 인스턴스는 아니지만, new A()의 인스턴스이기는 하다.


클래스의 메서드에 대해 조사하기
리플렉션을 사용할 때 가장 기본적이면서도 유용한 사용처는 특정 클래스에 정의된 메서드를 조사하는 일이다. 아래의 코드를 사용하면 클래스의 메서드를 조사할 수 있다:

   import java.lang.reflect.*;

   public class method1 {
      private int f1(Object p, int x) throws NullPointerException
      {
         if (p == null)
            throw new NullPointerException();
         return x;
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method1");
        
            Method methlist[] = cls.getDeclaredMethods();
            for (int i = 0; i < methlist.length; i++) {  
               Method m = methlist[i];
               System.out.println("name =" + m.getName());
               System.out.println("decl class = " + m.getDeclaringClass());

               Class pvec[] = m.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                  System.out.println("param #" + j + " " + pvec[j]);

               Class evec[] = m.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                  System.out.println("exc #" + j + " " + evec[j]);

               System.out.println("return type = " + m.getReturnType());
               System.out.println("-----");
            }
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


이 프로그램에서는 먼저 method1에 대한 Class 객체를 얻는다. 그런 후 해당 객체에 
getDeclaredMethods를 호출해서 Method 객체의 목록을 얻는다. method1 클래스에 정의된 메서드마다 하나의 Method 객체를 얻을 수 있다. 이때 public, protected, package, private 메서드 모두 포함된다. getDeclaredMethods가 아니라 getMethods를 사용하면 상속받은 메서드에 대한 정보까지도 얻을 수 있다.

이렇게 Method 객체의 목록을 얻고 나면, 이후의 작업은 간단한다. 메서드마다 단순히 파라미터 타입, 예외 타입, 리턴 타입을 출력한다. 이때 타입이 기본형이든지 아니면 클래스 형이든지 관계없이, 각 타입은 Class 기술자를 사용해서 차례대로 표시된다. 이 프로그램을 실행하면 출력은 다음과 같다:

name = f1
decl class = class method1
param #0 class java.lang.Object
param #1 int
exc #0 class java.lang.NullPointerException
return type = int
-----
name = main
decl class = class method1
param #0 class [Ljava.lang.String;
return type = void
-----



생성자에 대한 정보 얻기
클래스의 생성자에 대한 정보를 얻는 일도 마찬가지 방식으로 처리할 수 있다. 다음 예를 보자:

 import java.lang.reflect.*;
        
   public class constructor1 {
      public constructor1()
      {
      }
        
      protected constructor1(int i, double d)
      {
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor1");
        
           Constructor ctorlist[] = cls.getDeclaredConstructors();
         for (int i = 0; i < ctorlist.length; i++) {
               Constructor ct = ctorlist[i];
               System.out.println("name = " + ct.getName());
               System.out.println("decl class = " + ct.getDeclaringClass());

               Class pvec[] = ct.getParameterTypes();
               for (int j = 0; j < pvec.length; j++)
                  System.out.println("param #" + j + " " + pvec[j]);

               Class evec[] = ct.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                  System.out.println("exc #" + j + " " + evec[j]);

               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
      }
   }


생성자에서는 사실 리턴 타입이 없으므로, 이 예제에서는 리턴 타입에 대한 정보는 얻지 않는다.

이 프로그램을 실행하면 출력은 다음과 같다:

name = constructor1
decl class = class constructor1
-----
name = constructor1
decl class = class constructor1
param #0 int
param #1 double
-----



클래스 필드에 대한 정보 얻기
특정 클래스에 정의된 데이터 필드에 대한 정보를 얻을 수도 있다. 이를 위해 아래와 같은 코드를 사용할 수 있다:

   import java.lang.reflect.*;
       
   public class field1 {
      private double d;
      public static final int i = 37;
      String s = "testing";
       
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field1");
       
            Field fieldlist[] = cls.getDeclaredFields();
            for (int i = 0; i < fieldlist.length; i++) {
               Field fld = fieldlist[i];
               System.out.println("name = " + fld.getName());
               System.out.println("decl class = " + fld.getDeclaringClass());
               System.out.println("type = " + fld.getType());
               int mod = fld.getModifiers();
               System.out.println("modifiers = " + Modifier.toString(mod));
               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
       }
   }


이 예제는 앞에서 본 예제와 비슷하다. 차이점은 여기에서 Modifier를 사용했다는 점이다. Modifier는 리플렉션용 클래스로, "private int"에서 보듯이 필드 멤버의 제한자를 나타낸다. 제한자 자체는 정수로 표현되고, Modifier.toString를 사용해서 문자열 형식으로 된 값을 얻을 수 있다. 이때 "공식적인" 선언 순서("static이 "final"보다 먼저 나온다)에 따른다. 이 프로그램의 실행 결과는 다음과 같다:

name = d
decl class = class field1
type = double
modifiers = private
-----
name = i
decl class = class field1
type = int
modifiers = public static final
-----
name = s
decl class = class field1
type = class java.lang.String
modifiers =
-----


메서드와 마찬가지로, 클래스에 선언된 필드에 대한 정보만을 얻을 수 있을 뿐만 아니라(getDeclaredFields), 슈퍼 클래스에 정의된 필드에 대한 정보도 얻을 수 있다(getFields).


이름으로 메서드를 호출하기
지금까지의 예제에서는 클래스의 정보를 얻는 방법과 관련해서 설명했다. 이와는 다른 방법으로도 리플렉션을 사용할 수 있다. 예를 들어 주어진 이름으로 메서드를 호출할 수도 있다.

아래의 예제를 통해, 주어진 이름을 사용해 메서드를 호출하는 방식을 살펴보자:

   import java.lang.reflect.*;
        
   public class method2 {
      public int add(int a, int b)
      {
         return a + b;
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method2");

           Class partypes[] = new Class[2];
           partypes[0] = Integer.TYPE;
           partypes[1] = Integer.TYPE;

           Method meth = cls.getMethod("add", partypes);

           method2 methobj = new method2();

           Object arglist[] = new Object[2];
           arglist[0] = new Integer(37);
           arglist[1] = new Integer(47);

           Object retobj = meth.invoke(methobj, arglist);

           Integer retval = (Integer)retobj;
           System.out.println(retval.intValue());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


특정 프로그램에서 add 메서드를 호출하기를 원하지만, 실행 시간 이전에는 어느 메서드를 호출할지 모른다고 가정해보자. 즉 메서드의 이름이 실행 시간에 주어진다고 해보자 (예를 들어 자바빈 개발 환경에서는 실제로 이러한 방식으로 처리된다). 위의 프로그램에서는 이러한 방식을 처리하는 한 가지 방법을 보여준다.

getMethod를 사용하면 정수 타입의 파라미터를 두개를 받고 인자로 주어진 이름을 가지는 메서드를 클래스에서 찾을 수 있다. 이 메서들 찾아서 Method 객체에 저장한 후, 적절한 타입의 객체 인스턴스에 대해 해당 Method 객체를 호출한다. 메서드를 호출하려면 파라미터 목록이 반든시 생성되어야 한다. 이 예제의 경우 Integer 객체를 사용해 기본형인 37과 47 정수 값을 래핑한다. 리턴 값(84)도 Integer객체로 래핑해야 한다.

새로운 객체 생성하기

생성자를 호출하는 일은 메서드를 호출하는 일과는 다르다. 사실 생성자를 호출하는 일은 새로운 객체를 생성하는 일이다 (더 정확하게 말하면 새로운 객체를 생성하는 일은 메모리를 할당하고 동시에 객체를 생성하는 일이다). 따라서 앞의 예제와 거의 비슷하게 만들어보자면 다음과 같다:

   import java.lang.reflect.*;
        
   public class constructor2 {
      public constructor2()
      {
      }
        
      public constructor2(int a, int b)
      {
         System.out.println("a = " + a + " b = " + b);
      }
        
      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor2");
           Class partypes[] = new Class[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;

            Constructor ct  = cls.getConstructor(partypes);

            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);

            Object retobj = ct.newInstance(arglist);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


이 경우 기술된 파라미터 타입으로 처리할 수 있는 생성자를 찾은 후, 해당 생성자를 호출해서 객체의 새로운 인스턴스를 생성한다. 이 방식은 완전히 동적이라는데 그 가치가 있다. 즉 생성자를 찾고, 해당 생성자를 컴파일 시간이 아니라 실행 시간에 호출한다.


필드의 값 변경하기
리플렉션을 활용할 수 있는 또 다른 사용처는 객체의 데이터 필드를 변경하는 일이다. 이 방식도 마찬가지로 리플렉션은 본질적으로 동적이라는데 그 가치가 있다. 즉 실행 중인 프로그램에서 이름을 기반으로 필드를 찾고, 해당 필드를 변경할 수 있다. 아래 예제에서 이 방식을 어떻게 사용하는지 보여준다:

   import java.lang.reflect.*;
        
   public class field2 {
      public double d;
        
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field2");
            Field fld = cls.getField("d");
            field2 f2obj = new field2();
            System.out.println("d = " + f2obj.d);

            fld.setDouble(f2obj, 12.34);
            System.out.println("d = " + f2obj.d);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


예제에서 보다시피 필드 d의 값을 12.34로 변경한다.


배열 사용하기

마지막으로 리플렉션을 사용해서  배열을 생성하고 조작해보자. 자바 언어에서 배열이란 클래스의 특별한 타입으로, 배열에 대한 참조는 Object 참조에 할당할 수 있다.

아래 예제를 통해 배열이 어떻게 동작하는지 살펴보자:

   import java.lang.reflect.*;
        
   public class array1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("java.lang.String");
            Object arr = Array.newInstance(cls, 10);
            Array.set(arr, 5, "this is a test");
            String s = (String)Array.get(arr, 5);
            System.out.println(s);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }


이 예제에서는 사이즈가 10인 String 배열을 생성한 후, 배열의 5번째 위치에 문자열 값을 할당한다. 그리고 나서 해당 값을 꺼내 표시한다.

아래 코드와 같이 배열을 좀더 세밀하게 조작할 수도 있다:

   import java.lang.reflect.*;
        
   public class array2 {
      public static void main(String args[])
      {
         int dims[] = new int[]{5, 10, 15};
         Object arr = Array.newInstance(Integer.TYPE, dims);
        
         Object arrobj = Array.get(arr, 3);
         Class cls = arrobj.getClass().getComponentType();
         System.out.println(cls);
         arrobj = Array.get(arrobj, 5);
         Array.setInt(arrobj, 10, 37);
        
         int arrcast[][][] = (int[][][])arr;
         System.out.println(arrcast[3][5][10]);
      }
   }


이 예제에서는 5 x 10 x 15 차원의 정수 배열을 만든 후, 해당 배열에서 [3][5][10] 위치에 있는 값에 37을 할당한다. 이때 다차원 배열은 사실 배열의 배열이다. 따라서 예를 들어 맨 처음 Array.get을 호출하고 나면, 그 결과인 arrobj는 10 x 15 배열에 해당한다. 이 배열에서 다시 배열을 꺼내서 사이즈가 15인 배열을 얻는다. 그리고 나서 이 배열의 10번째 이치에 Array.setInt를 사용해서 값을 할당한다.

생성하는 배열의 타입이 동적이며, 컴파일 시간에 배열의 타입이 정해지지 않아도 됨을 유심히 봐야 한다.


출처 : http://socurites.com/60

//
프로그래밍/java 2014. 6. 1. 15:55

1) String, StringBuffer, StringBuilder의 차이점과 쓰임새가 궁금해요. 그리고 성능은 어떤게 좋을까요?

일단 String과 StringBuffer, StringBuilder 둘을 비교할 수 있는데요. 
String은 문자열을 대표하는 것으로 문자열을 조작하는 경우 유용하게 사용할 수 있고요. 
문자열, 숫자, char 등을 concat할 때는 StringBuffer, StringBuilder를 사용할 수 있어요. 단, 복잡한 경우 의미가 있고, 단순한 경우에는 굳이 StringBuffer, StringBuilder를 쓰지 않고 "abc" + 1 + 'd'와 같이 +를 활용해 직접 합쳐도 됩니다.

StringBuffer, StringBuilder는 동기화 지원 여부입니다. 두 클래스가 제공하는 메소드는 같아요. 단, 메소드를 보면 StringBuffer는 각 메소드 별로 synchronized keyword가 존재하죠. 즉, 멀티 쓰레드 상태에서 동기화를 지원한다는 것이 다릅니다. 코드를 보면 다음과 같아요.


  public final class StringBuffer
    public synchronized StringBuffer append(String str) {
        super.append(str);
        return this;
    }

    public synchronized StringBuffer append(boolean b) {
        super.append(b);
        return this;
    }  

    [...]
}


public final class StringBuilder {
    public StringBuilder append(String str) {
        super.append(str);
        return this;
    }

    public StringBuilder append(boolean b) {
        super.append(b);
        return this;
    }      

    [...]
}

문자열을 +를 활용해 합치는 경우 정확히 어느 버전까지인지 모르겠는데요. 매번 String 인스턴스를 생성하는 방식이였어요. 그래서 성능상의 이슈가 많았죠. 이런 성능 이슈를 개선하기 위해 JDK 1.5 버전 이후에는 컴파일 단계에서 StringBuilder로 컴파일 되도록 변경되었어요. 그래서 JDK 1.5 이후부터는 +를 활용해도 성능상에 큰 이슈는 없습니다.

이 내용을 좀 더 구체적으로 파고 들어 보면 다음과 같아요.


    public String concat1(String start, String end) {
        return start + end;
    }

위 소스 코드를 컴파일 된 바이트 코드 결과물을 확인해 보면 다음과 같이 변경됩니다.


Compiled from "StringConcatenations.java"
public class StringConcatenations {
  public StringConcatenations();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public java.lang.String concat1(java.lang.String, java.lang.String);
    Code:
       0: new           #16                 // class java/lang/StringBuilder
       3: dup
       4: aload_1
       5: invokestatic  #18                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
       8: invokespecial #24                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      11: aload_2
      12: invokevirtual #27                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: invokevirtual #31                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      18: areturn

결과적으로 StringBuilder로 변환되는 것을 알 수 있어요. 최근 JDK 버전에서는 굳이 StringBuilder를 쓰지 않아도 되는거죠.


'프로그래밍 > java' 카테고리의 다른 글

java annotaion에 대해서  (0) 2014.06.01
Java Reflection에 대해  (0) 2014.06.01
자바 가비지컬렉션 과정에 관해  (0) 2014.05.31
추상화란..  (0) 2014.04.15
이클립스에서 디버깅  (0) 2013.02.03
//