2013. 7. 25. 16:50

FileStream, 파일스트림

 : 파일 입출력에 사용되는 클래스

 : 1. 바이트 단위 - FileInputStream, FileOutputStream

 : 2. 문자열 단위 - FileReader, FileWriter

 

※ 스트림이란?

: 일련의 연속된 데이터의 흐름

: 자바프로그램과 외부장치 사이의 데이터 교환을 위한 처리 방식

: 추상화, 실제 장치와 상관없이 공통된 접근 방식을 제공한다.

 

여기서는 파일 입출력 스트림만 다루고 있으며 다른 입출력 스트림에 대한 사항은 다음 링크를 참조

Link - I/O Stream, 입출력 스트림

 

※ 인코딩, Encoding 

 : 응용프로그램의 데이터를 스트림(Stream)형식으로 변환시켜 

 : 보조기억장치나 네트워크상에서 사용가능한 형태로 변환하는 작업

1) ANSI(미국표준) : 한글 2byte, 영어(숫자) 1byte

2) EUC-KR : 한글 2byte, 영어(숫자) 1byte

3) UTF-8 : 한글 3byte, 영어(숫자) 1byte

4) UTF-16 : 한글 2byte, 영어(숫자) 2byte

Link - 아스키코드표

 

 

 

 

1. FileIntputstream/FileOutputStream

 

■ FileOutputStream

 : 파일 출력 스트림

1) 스트림 열기

 

1
2
3
4
FileOutputStream fos = new FileOutputStream("d:\\web\\data.txt"[, true|false]);
// true : Append, 이어쓰기
// false : Create, 기존 내용에 덮어쓰기
// 기본값 : false

 

2) 조작

 

1
2
3
fos.write(97); //byte코드 값에 해당하는 문자(character)를 파일에 출력
fos.write(98);
fos.write(99);

 

3) 스트림 닫기

 

1
2
fos.close();
//실행 후 data.txt 파일을 열어보면 abc가 적혀있다.

 

4) 응용

 

1
2
3
4
5
6
7
8
9
String txt = "hello! Fine and strong day! if you ask my name, im waldo!"; //입력할 문자열
 
for (int i = 0; i < txt.length(); i++) //문자열의 길이만큼
{
    char c = txt.charAt(i); //문자로 형변환 후
    fos.write((int) c); //출력한다.
}
 
fos.close();

 

 

바이트배열을 활용하면 다음과 같다.

 

1
2
3
4
5
6
7
8
FileOutputStream fos = new FileOutputStream("d:\\web\\data.txt", false);
 
String txt = "hello! Fine and strong day! if you ask my name, im waldo!";
 
byte[] bs = txt.getBytes(); //문자열을 바이트 배열에 할당
fos.write(bs); //출력한다.
 
fos.close();

 

 

 

■ FileInputStream

 : 파일 입력 스트림

위에서 입력한 파일을 읽어서 콘솔에 출력해보자.

1) 스트림 열기

 

1
2
3
4
File dir = new File("d:\\data.txt");
FileInputStream fis = new FileInputStream(dir);
//혹은
FileInputStream fis = new FileInputStream("d:\\web\\data.txt");

 

2) 조작

 

1
2
3
4
5
6
7
8
int b;
 
while((b = fis.read()) != -1) //read()는 더 이상 읽을것이 없을 때 -1을 리턴한다. -1까지 루프
{
    System.out.print((char)b); //스트림이 읽은 바이트코드를 문자로 변환하여 출력
}
 
→ hello! Fine and strong day! if you ask my name, im waldo!

 

3) 스트림 닫기

 

1
fis.close();

 

※ 주의 

 이렇게 바이트로 읽어온 문자열은 1바이트 문자코드 방식에만 해당된다.

 즉, 어떤 인코딩이던 2바이트가 넘어가는 한글은 읽을 수 없다.

 

 

 

2. FileReader/FileWriter

 : stream이 바이트단위로만 읽고 쓰기가 가능한것을 개량한 클래스

 : 한글 처리가 가능하다.

 

■ FileWriter

1) 스트림 열

 

1
2
3
4
FileWriter fw = new FileWriter("d:\\web\\data.txt"[, true|false]);
//true : Append, 이어쓰기
//false : Create, 기존 내용에 덮어쓰기
//기본값 : false

 

2) 조작

 

1
2
String txt = "You just activated \r\nMY TRAP CARD";
fw.write(txt);

 

3) 스트림 닫기

 

1
fw.close();

 

 

한 눈에 보이듯이 바이트 처리를 알아서 하도록 설계되어있는 클래스이기 때문에 코드가 굉장히 간결하다.

가장 중요한 점은 stream에선 불가능했던 한글 처리가 가능하다는 것이다.

 

1
2
3
FileWriter fw = new FileWriter("D:\\web\\data2.txt", false);
fw.write("붑밥붑밥붑밥바 붑바밥붑밥붑밥바");
fw.close();

 

 

 

 

■ FileReader

 FileReader는 InputStream과 사용방법이 동일하다.

 

1) 스트림 열기

 

1
2
3
4
String dir = "D:\\web\\data2.txt";
FileReader fr = new FileReader(dir);
//혹은
FileReader fr = new FileReader("D:\\web\\data2.txt");

 

2) 조작

 

1
2
3
4
5
int b;
while((b = fr.read()) != -1) //파일 내용의 길이만큼 루프
{
    System.out.print((char)b); //읽어온 바이트코드를 문자로 변환. 출력
}

 

3) 스트림 닫기

 

1
fr.close();

 

FileReader 역시 stream에선 불가능했던 한글 처리가 가능하다.

 

 

 

3. BufferedReader/BufferedWriter

 : Reader와 Writer를 개량한 클래스

 : 기존 입출력 클래스보다 속도가 빠르다.

 

■ BufferedWriter

 

1
2
3
4
5
6
7
8
9
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\web\\data2.txt"));
 
bw.write("버퍼드리더를 활용한 \r\n파일출력");
bw.write("줄바꿈");
bw.newLine(); //"\r\n"
bw.write("줄바꼈지?");
 
bw.flush();    
bw.close();

 

 

BufferedWriter 의 경우 출력을 바로 하는것이 아니라 버퍼를 비울 때 출력한다.

 

flush() 메서드는 버퍼를 비우는 함수. flush()를 하지 않으면 출력은 처리되지 않는다.

※ close는 자동으로 버퍼를 비운다. 즉 스트림을 닫기만 해도 flush()는 생략할 수 있다.

 

 

■ BufferedReader

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
String path = "D:\\web\\data2.txt";
BufferedReader reader = new BufferedReader(new FileReader(path));
 
readLine()
String txt;
 
while((txt = reader.readLine()) != null) //읽을게 없으면 null 리턴
{
    System.out.println(txt);
}
 
reader.close();
 
read()
int b;
 
while((b = reader.read()) != -1)
{
    System.out.print((char)b);
}
reader.close();

 

 

 

 

 

4. BufferedFileStream

 

■ BufferedFileStream을 이용한 파일의 바이너리코드를 읽어와 다른 파일에 그대로 쓰기(복사)

1. 스트림 열기

 

1
2
3
//FileStream
FileInputStream is = new FileInputStream("D:\\AAA\\test.zip"); //읽어올 파일
FileOutputStream os = new FileOutputStream("D:\\AAA\\test_2.zip"); //출력할 파일

 

 

1
2
3
//BufferedStream : FileStream보다 속도가 빠르다.
BufferedInputStream is = new BufferedInputStream(new FileInputStream("D:\\web\\.zip"));
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream("D:\\AAA\\test_2.zip"));

 

2. 조작과 스트림 닫기

 

1
2
3
4
5
6
7
8
while ((c = is.read()) != -1) {
    os.write(c);
    len++; //복사크기
}
System.out.printf("총 %,d바이트 복사됨\n", len);
         
bis.close();
bos.close();

 

 

* FileStream이나 BufferedStream 모두 한글 깨짐없이 복사가능. 

 

'java' 카테고리의 다른 글

String  (0) 2013.07.25
Nested, 중첩클래스  (0) 2013.07.25
콘솔입력  (0) 2013.07.25
Exception, 예외  (0) 2013.07.25
Enumeration, 열거형  (0) 2013.07.25
Posted by 1+1은?
2013. 7. 25. 16:50

■ System.in.read()

 : InputStream + java.lang.System

 : 시스템으로부터 1바이트를 입력받아 정수형으로 리턴

 : 한글은 1바이트로 표현할 수 없기 때문에 숫자와 영문, 특수문자만 입력할 수 있다.

 

1
2
3
4
int input = System.in.read();
System.out.println(input);
 
1을 입력하면 49 출력 //바이트클래스는 모든 문자를 아스키코드로 처리한다.

 

   Link - 아스키코드표

 

아스키코드로 변환된 문자를 원래 입력한 문자로 바꾸고 싶다면?

 

1
2
3
(char)input;  //1 입력, 1 출력
//혹은
char input = (char)System.in.read();

 

 

주의할 점은 버퍼인데 콘솔에서 1을 입력했다고 해서 실제로 1만 입력되는 것은 아니다.

1입력 → 1 + \b + \r

 

여기서 \b, \r은 버퍼에 남아있게 되는데 이 때문에 다음과 같은 현상이 발생한다.

 

1
2
3
4
System.in.read(); //입력대기
System.in.read(); //\b
System.in.read(); //\r
System.in.read(); //입력대기

 

 

따라서 콘솔에서 연속으로 입력을 받아야 하는 경우엔 InputStream이 제공하는 skip() 메서드를 활용한다.

 

1
2
3
System.in.read();
System.in.skip(2);
System.in.read();

 

 

 

 

■ BufferedReader 

 : 문자, 배열, 행을 버퍼에 담은 후 문자형 입력스트림으로 텍스트를 읽어 들인다.

 

1
2
3
4
5
6
7
8
read() : 1바이트를 읽어온다. 따라서 연속으로 사용하려면 버퍼초기화가 필요함.
 //이 함수는 System.in.read()와 동작방식, 특징이 같음
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
br.read(); //입력대기
br.read(); //\b
br.read(); //\r
br.read(); //입력대기

 

 

※ 주의

read() 메서드는 사용자로부터 입력받은 입력버퍼의 값을 1문자 반환한다. 

버퍼가 비어있으면 새로 요구하고, 버퍼가 남아있으면 요구없이 스스로 다음 문자를 반환한다.

즉, 3문자를 입력하면 read를 다시 호출했을 때 입력값을 요청하는게 아닌 이미 입력된 문자 즉, 버퍼에서 값을 가져온다.

 

1
2
3
4
5
6
7
8
9
10
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
 
System.out.println(br.read());
System.out.println(br.read());
System.out.println(br.read());
 
여기서 123을 입력하면
49
50
51

 

 

 

readLine() : 문자열(한 줄)을 입력받는다. 버퍼초기화는 고려하지 않아도 된다.

 

1
2
System.out.println(br.readLine()); //입력대기
System.out.println(br.readLine()); //입력대기

 

 

 

 

■ Scanner

next() : 공백을 기준으로 첫문자만 읽어오며 나머지 문자는 버퍼에 저장

 

1
2
3
4
5
6
7
8
9
10
Scanner scan = new Scanner(System.in);
 
String str1 = scan.next();
String str2 = scan.next();
String str3 = scan.next();
 
여기서 a b c 를 입력하면
첫번째 next()는 a를,
두번째 next()는 b를,
세번째 next()는 c를 읽어옴.

 

 

nextLine() : 문자열(한 줄)을 읽는다. BufferedReader의 readLine()과 동일하다.

nextInt(), nextDouble() 등... 

 : 문자열을 읽어 해당 데이터형으로 변환한다. 

   next() 처럼 공백을 기준으로 첫 문자열만 읽어오며 나머지는 버퍼로 처리한다.

 

'java' 카테고리의 다른 글

Nested, 중첩클래스  (0) 2013.07.25
FileStream, 파일스트림  (0) 2013.07.25
Exception, 예외  (0) 2013.07.25
Enumeration, 열거형  (0) 2013.07.25
Generic, 제네릭  (0) 2013.07.25
Posted by 1+1은?
2013. 7. 25. 16:50

Exception, 예외

 : 런타임 중 특정 상황에서 발생하는 에러를 말한다. 런타임 에러와 같은 의미로 봐도 된다.

 : 특정작업에서는 예외처리를 명시하지 않으면 컴파일 에러가 발생한다.

 

※ 반드시 예외처리를 해야 하는 경우

1. 네트워크 입출력

2. 데이터베이스 입출력

3. 메모리 입출력

4. 파일 입출력

5. 메서드에서 예외를 미룰 때

 

 

 

■ try catch

 

1
2
3
4
5
6
7
8
9
try { //비즈니스코드 블럭
   ... 
} catch(Exception e) { //예외처리코드 블럭, 예외 발생 시에만 실행
   ... 
} finally {
   ...
}
 
//finally : try, catch 뒤에 오며 예외 발생 여부와 관계없이 무조건 실행된다.

 

 

 

1
2
3
4
5
6
7
try {
    throw new Exception("내가 만든 예외"); //Exception 강제 발생
 
} catch (Exception e) //try절에서 던진(발생한) 예외를 처리하는 블럭
{
    System.out.println(e);
}

 

 

ArrayIndexOutOfBoundsException의 경우 배열의 범위를 초과할 때 발생하는 예외인데

아래처럼 코드작성 시 해당 예외가 발생할 것을 예측하여 작성할 수 있다.

 

1
2
3
4
5
6
7
8
9
int[] nums = new int[] {10, 20, 30, 40, 50};
 
System.out.print("보고싶은 인덱스 : ");
int index = scan.nextInt();
 
if(index >= 0 && index <=4)
    System.out.println(nums[index]);
else //범위 초과 시
    System.out.println("올바른 색인번호를 입력하세요.");

 

 

 

하지만 우리가 모든 경우의 수를 예측하여 그에 해당하는 코드를 만드는 것은 불가능하다.

따라서 다음처럼 작성하면 모든 예외를 처리할 수 있게 된다.

 

1
2
3
4
5
6
7
8
try {
    System.out.println(nums[index]);
 
} catch (ArrayIndexOutOfBoundsException e) { // Exception 으로 대체가능
                                                // Exception : 모든 예외
    System.out.println(e); // 발생한 예외의 메시지를 콘솔에 출력
    System.out.println("올바른 색인번호를 입력하세요.");
}

 

 

 

 

 

■ throw / throws

 : 예외 미루기

 : 예외가 발생했을 때 발생한 지역이 아닌 호출한 지역에서 예외를 처리한다.

 

다음은 main 메서드에서 m01() 메서드를 호출했을때 발생한 예외를 main에서 처리하는 예제다.

 

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
class Excep {
    public void m01() {
        int[] nums = new int[] { 1 };
        try {
            System.out.println(nums[2]);
            // ArrayIndexOutOfBoundsException 발생
            // new ArrayIndexOutOfBoundsException(); : 1. 예외 객체 생성
            // throws new ArrayIndexOutOfBoundsException : 2. 예외객체를 던진다.
        } catch (Exception e) {
            throw e; // 이 메서드를 호출한 지역이 예외를 받는다
        }
    }
}
 
public class Test {
    public static void main(String[] args) {
        Excep ex = new Excep();
 
        try {
            ex.m01();
        } catch (Exception e) {
            System.out.println("메서드를 호출한 지점");
        }
    }
}

 

 

하지만 어차피 예외를 미룰거라면 try catch를 생략하는 방법이 있다.

여기선 throws 키워드를 사용한다.

 

1
2
3
4
5
6
class Excep {
    public void m01() throws Exception {
        int[] nums = new int[] {1};
        System.out.println(nums[2]);
    }
}

 

 

 

 

■ 다중 catch

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try {
    int[] nums = { 10, 20, 30 };
    System.out.println(nums[5]); // ArrayIndexOutOfBoundsException
 
    Random rnd = null;
    System.out.println(rnd.nextInt()); // NullPointerException
 
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("인덱스범위에러");
 
} catch (NullPointerException e) {
    System.out.println("null에러");
}
// 또 다른 예외 발생?
catch (Exception e) {
    // 던진 객체를 받을 수 있는 클래스가 없다면 부모클래스인 Exception에 업캐스팅으로 받는다.
    System.out.println("범용적 에러");
}

 

 

 

 

■ Exception클래스를 상속하여 예외를 의도적으로 활용하는 경우 예제

 

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
package old3;
 
public class Ex94_try {
    public static void main(String[] args) {
 
        //예외 코드
        // A001 : 배송 중 제품 파손
        // A002 : 배송 중 날짜 지연
        // A003 : 배송 중 잘못 배송
        // B001 : 사용 중 제품 불량
        // B002 : 사용 중 제품 오작동
         
        System.out.println("고객이 제품을 주문..");
         
        System.out.println("1. 주문접수 완료");
        System.out.println("2. 제품 포장");
        System.out.println("3. 제품 발송");
         
        System.out.println("고객이 제품을 수령..");
         
         
        try {
            System.out.println("제품파손 발견"); //A001
             
            throw new MyShopException("A001");
         
        } catch (MyShopException e) {
 
            System.out.println("예외 코드 : " + e);
            System.out.println("내선 번호 : " + e.checkNumber());
        }
    }
}
 
 
//사용자 정의 예외 클래스(Exception 파생클래스)
class MyShopException extends Exception {
    //예외 코드 관리-> 담당 상담원 안내
    private String code; //예외코드
     
    MyShopException(String code) {
        this.code = code;
    }
 
    //내선번호
    public String checkNumber() {
        String number = "";
        if (this.code.equals("A001")) number = "100";
        else if (this.code.equals("A002")) number = "110";
        else if (this.code.equals("A003")) number = "120";
        else if (this.code.equals("B001")) number = "200";
        else if (this.code.equals("B002")) number = "210";
         
        return number; //생성자로 받아온 Exception code를 비교해 해당 문자열을 리턴하는 메서드
    }
}

 

'java' 카테고리의 다른 글

FileStream, 파일스트림  (0) 2013.07.25
콘솔입력  (0) 2013.07.25
Enumeration, 열거형  (0) 2013.07.25
Generic, 제네릭  (0) 2013.07.25
상속과 구현, 업캐스팅  (0) 2013.07.25
Posted by 1+1은?
2013. 7. 25. 16:49

Enumeration, 열거형

 : 특정값만을 가질 수 있는 자료형

 

 

클래스를 작성할 때 enum 키워드를 사용한다.

enum Level

{

  ..

}

 

이런 enum 클래스는 스태틱 파이널 변수, 즉 상수만 선언할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum City {
    서울, 인천, 부산
}
 
enum Color {
    RED, BLUE, GREEN, YELLOW
}
 
City c1; // 열거형 변수 선언
c1 = City.서울; // 열거형 상수 대입
 
System.out.println(c1); // → 서울
System.out.println(City.인천); // → 인천
 
 
City c2 = City.대구; //에러
Color c3 = City.BLACK; //선언하지 않은 상수는 사용할 수 없음

 

 

 

다음은 enum 클래스의 상수를 가져와 조건검색에 활용하는 예제다.

 

1
2
3
4
5
6
7
8
9
10
enum Level {
아주높음, 높음, 중간, 낮음, 아주낮음
}
 
Level user = Level.중간;
if (user == Level.아주높음) {
    ...
} else if (user == Level.아주낮음) {
    ...
}

 

 

 

enum 클래스의 상수는 내부적으로 정수타입을 갖는다. 즉 swith문에 사용 가능하다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
enum Color {
RED, BLUE, GREEN, YELLOW
//1,   2,    3,      4
}
 
Color c = Color.RED;
 
switch (c) {
case RED:
    System.out.println("빨간색 옷을 선택함"); break;
case YELLOW:
    System.out.println("노란색 옷을 선택함"); break;
}


'java' 카테고리의 다른 글

콘솔입력  (0) 2013.07.25
Exception, 예외  (0) 2013.07.25
Generic, 제네릭  (0) 2013.07.25
상속과 구현, 업캐스팅  (0) 2013.07.25
팩토리얼(재귀호출)  (0) 2013.07.25
Posted by 1+1은?
2013. 7. 25. 16:49

Generic, 제네릭

 : 내부구조와 알고리즘을 동일하게 구현하되 취급되는 데이터의 자료형만 다른 메서드, 그러한 클래스를 구현하는 기술

 : 타입변수 사용하며 자료형은 컴파일 시 결정된다.

 

 

기본적인 구성

 

1
2
3
4
class Generic<T> {
    public T num;
    public T num2;
}

 

 

여기서 T란 타입변수를 의미하며(예약어 아님) 참조형만 저장할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//유형-1
class Gen1<Type> {
    public Type a;
}
 
// 유형-2
class Gen2<Type> {
    public Type a;
    public Type b;
    public Type c;
}
 
//유형-3
class Gen3<Type1, Type2> {
    public Type1 a;
    public Type2 b;
}

 

 

인스턴스 생성시엔 반드시 자료형을 명시해야 한다.

 

1
2
Generic<Integer> intGen = new Generic<Integer>();
Generic<Object> obGen = new Generic<Object>();

 

 

 

이 후 intGen 인스턴스의 타입변수는 Integer 형 값을 처리하며

obGen은 마찬가지로 Object 형의 값만 처리할 수 있다.

 

타입변수는 오직 멤버 변수만 가능하며 스태틱이나 파이널 키워드는 사용할 수 없다.

(타입변수의 자료형이 객체생성 시 결정되기 때문)

 

1
2
3
4
5
6
public static T num; //Cannot make a static reference to the non-static type T
 
public final T num = 0; //Type mismatch: cannot convert from int to T
 
public final static T num = 0; //Cannot make a static reference to the non-static type T
//당연히 초기화도 불가능

 

 

 

다음은 생성자로 참조값을 전달하고 그 값을 다시 가져오는 클래스를 구현한 예제다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Generic<T> {
    public T parameter;
     
    public Generic() {
        //제네릭 클래스의 기본생성자
    }
     
    public Generic(T parameter) {
        this.parameter = parameter;
    }
 
    public T getParameter() {
        return parameter;
    }
}
 
Generic<String> strGen = new Generic<String>("ㅎㅇ");
System.out.println(strGen.getParameter());
→ ㅎㅇ
 
Generic<Integer> intGen = new Generic<Integer>(12);
System.out.println(intGen.getParameter());
12

 

 

 

하지만 이런 제네릭 클래스의 경우엔 타입변수의 자료형을 알 수 없기 때문에 연산자를 사용할 수 없다.

 

다음은 타입변수를 연산해서 컴파일 에러가 발생하는 경우다.

 

1
2
3
4
public T getData() {
    return parameter + parameter;
        //The operator + is undefined for the argument type(s) T, T
}

 

 

 

List, Map은 대표적인 제네릭 클래스로 List처럼 자료형을 하나만 결정하는 유형과

Map과 같이 자료형을 각각 따로 지정하는 유형도 설계가 가능하다.

 

 

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
class Generic<Key, Value> {
    private Key key;
    private Value value;
     
    public void put(Key key, Value value) {
        this.key = key;
        this.value = value;
    }
     
    public Value get(Key key) {
        if(key.equals(this.key))
        {
            return this.value;
        }  
        return null;
    }
}
 
Generic<String, Object> map = new Generic<String, Object>();
map.put("한국의수도", "서울");
 
System.out.println(map.get("한국의수도"));
"서울"
 
System.out.println(map.get("서울의수도"));
null


'java' 카테고리의 다른 글

Exception, 예외  (0) 2013.07.25
Enumeration, 열거형  (0) 2013.07.25
상속과 구현, 업캐스팅  (0) 2013.07.25
팩토리얼(재귀호출)  (0) 2013.07.25
Args, 비정형인자  (0) 2013.07.25
Posted by 1+1은?