여기서 말하는 복사의 대상은 바로 '객체'다.

객체를 복사하는데 있어 객체의 '무엇'을 복사하느냐가 깊은 복사와 얕은 복사를 나누는 기준이 되는데 그 대상은 각각 다음과 같다.

Deep Copy Shallow Copy
객체 자체를 복제 (clone) 하여 새 객체 생성 객체의 주소값만 복사

그러면 코드 상에서는 각각 어떻게 다른 결과물을 만들어 내는지 한번 보기로 한다.

우선, Car라고 하는 클래스를 하나 정의하고, 다음과 같이 구현해 보자.


public class Car {
    private String name;
    private int price;
    private String color;

    public Car() {

    }

    public Car(String name) {
        this.name = name;
    }

    public Car(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public Car(String name, String color) {
        this.name = name;
        this.color = color;
    }

    public Car(String name, int price, String color) {
        this.name = name;
        this.price = price;
        this.color = color;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}

그리고 메인 함수에서 이 클래스의 인스턴스를 하나 생성한다.

Car c1 = new Car("Lamborghini", 1000000000, "Yellow");

Shallow copy (얕은 복사)

얕은 복사를 하려면 새 인스턴스를 생성하고, 그 인스턴스의 값으로 위에 생성한 c1 인스턴스를 할당해 주면 된다.

Car c2 = c1;

여기서 c2 인스턴스의 이름을 Lamborghini에서 Maserati로 바꿔 보고, 그 이후 결과값을 한번 조회해 본다.

c2.setName("Maserati");
System.out.println(c1.getName());
System.out.println(c2.getName());

// 두 인스턴스의 해시코드 값도 동일하게 출력되어 나온다. 즉 가리키는 주소값이 같은 객체라는 뜻.
System.out.println(c1.hashCode());
System.out.println(c2.hashCode());

실행해 보니 c2 인스턴스의 이름이 바뀌긴 했는데, c1 인스턴스의 이름까지 바뀌는 현상이 발생했다. 왜일까?

그것은 바로, 맨 위에서 언급한 것처럼 이 경우에는 c1 객체의 주소값을 복사하여 c2 인스턴스를 만들었기 때문이다. 즉 두 인스턴스는 힙메모리 상에서 같은 메모리 주소를 참조하고 있으며, 따라서 그 메모리 주소가 갖는 값이 변경되면 그 영향이 c1, c2 둘에게 다 간다.

그렇다면 복사한 인스턴스의 값이 변경되어도 기존 인스턴스에는 영향이 없게 하려면 어떻게 해야 할까? 여기서 등장하는 개념이 '깊은 복사'다.

Deep copy (깊은 복사)

깊은 복사는 객체를 복사하되 이를 별개의 메모리 공간에 할당하는 것이다. 자바에서는 이를 clone (복제)한다고 표현하는데, 이를 위해서는 깊은 복사의 대상이 clone이 가능하도록 Cloneable 인터페이스를 구현하고 있어야 한다. 따라서 위에서 선언한 Car 클래스를 다음과 같이 수정해 본다.

public class Car implements Cloneable {
    ...

    @Override
    protected Car clone() throws CloneNotSupportedException {
        return (Car) super.clone();
    }
}

새로 추가된 메소드는, Cloneable 인터페이스가 갖고 있는 clone() 메소드를 오버라이딩 한 것으로, Car 타입의 인스턴스를 복제하여 리턴한다는 내용이다. 이 메소드를 호출하려면 다음과 같이 하면 된다.

// deep copy
try {
    Car c3 = c1.clone();
    c3.setName("BMW");

    // c1과 c3의 이름이 이번에는 각각 다르게 출력된다. 즉 한쪽의 변화가 다른 한쪽에 영향을 주지 않는다.
    System.out.println(c1.getName());
    System.out.println(c3.getName());

    // 두 해시코드의 값이 다르게 나온다. 즉 두 인스턴스는 각기 다른 주소값을 갖는다.
    System.out.println(c1.hashCode());
    System.out.println(c3.hashCode());
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}

clone() 메소드가 CloneNotSupportedException을 던지기 때문에, 이 메소드 실행은 try-catch 구문 내에서 한다.

실행해 보면 한쪽 인스턴스의 값 변화가 다른 한쪽 인스턴스의 값에 영향을 주지 않고 있으며, 각 인스턴스가 가리키고 있는 주소값이 다른 것을 알 수 있다.

반응형

마커 인터페이스 (Marker interface)란, 인터페이스의 일종입니다.


대부분 인터페이스가 추상메소드를 가지고 있고, 따라서 클래스에서 인터페이스 구현 시 추상메소드를 오버라이드 해야 하는데요, 마커 인터페이스는 그런 추상메소드가 없는 인터페이스 입니다. 


마커 인터페이스로는 대표적으로 직렬화를 할 때 쓰이는 Serializable, 객체 복제가 가능하도록 하는 Cloneable 인터페이스가 있습니다.

반응형

객체지향 프로그래밍에서는 어떤 클래스, 그 클래스의 변수 그리고 메소드에 대해 외부 클래스에서의 접근 여부를 허용할 수 있습니다. 이 경우 어느 선까지 접근을 허용할 것인지 범위를 설정하는 데 있어서 등장하는 개념이 접근자 (access modifier)라고 합니다.


 

 클래스

변수 

메소드 

 public

 O

 O

 O

 protected

X

 O

 O

 private

X

 O

 O

 default (default 키워드가 붙는게 아니라 어떤 접근자도 붙지 않는 경우)

 O

 O


클래스의 경우, 외부 클래스에서 상속을 통해서만 접근되거나 아니면 접근 자체가 허용되지 않도록 하는 것은 금지되어 있습니다. 따라서 public 혹은 아무런 수식어가 붙지 않은 default만이 허용됩니다. 각자 키워드가 갖는 의미를 간단히 살펴보도록 하겠습니다.


1. public

이 키워드는 외부에서 아무런 제한 없이 접근이 가능합니다.


2. protected

이 키워드는 같은 패키지 내에서 상속받는 클래스에서만 접근이 가능하도록 지정할 필요가 있을 때 사용하는 키워드 입니다.


3. private

private 변수나 메소드는 외부 클래스에서 접근하지 못하게 합니다.


4. default (접근자 없음)

이 키워드는 같은 패키지 내에서만 외부 클래스의 접근을 허용합니다.

반응형

Cloneable 인터페이스는 인스턴스가 복제 가능하도록 하기 위해서 구현해야 하는 인터페이스이다. 가장 간단한 사용법은 다음과 같다.

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
public class Car implements Cloneable {
    
    private String brand;
    private int price;
    
    public String getBrand() {
        return brand;
    }
    
    public void setBrand(String brand) {
        this.brand = brand;
    }
    
    public int getPrice() {
        return price;
    }
    
    public void setPrice(int price) {
        this.price = price;
    }
 
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
cs


일단 저렇게 복제가 가능하도록 하긴 했습니다만, 여기서 한가지 짚고 넘어가야 하는 개념이 생긴듯 합니다. 복제라는 개념이 정확히 무슨 의미일까요? 


복제는 deep copy와 shallow copy로 다시 나뉘게 됩니다. 결론부터 말하자면 deep copy는 한 인스턴스의 내용을 복제하여 새로운 메모리 공간에다가 생성한다는 의미라고 보면 되겠고, shallow copy는 복제의 대상을 참조하는 또다른 인스턴스를 만드는 행위라고 할 수 있겠습니다. 위의 코드를 참조하여 아래와 같이 인스턴스 4개를 생성하고, 이중 2개 인스턴스는 각각 다른 인스턴스를 deep copy한것과 shallow copy해보기로 하였습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Car car1 = new Car();
car1.setBrand("Hyundai Motors");
 
Car car2 = new Car();
car2.setBrand("Mercedes Benz");
 
// shallow copy
Car car3 = car1;
Car car4 = new Car4();
try {
    // example of deep copy
    car4 = car2.clone();
catch(CloneNotSupportedException e) {
    System.out.println(e.getMessage());
}
cs


만들어진 인스턴스의 해시코드를 확인해 보고자 다음과 같이 각 인스턴스의 해시코드를 출력해 보기로 하였습니다.

1
2
3
4
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
System.out.println(car3.hashCode());
System.out.println(car4.hashCode());
cs


그리고 그 결과는 다음과 같습니다.

1
2
3
4
1829164700
2018699554
1829164700
1311053135
cs


즉, shallow copy를 한 car3 인스턴스는 car1과 같은 해시코드를 공유하는 반면 deep copy를 한 car4 인스턴스는 새로운 해시코드를 갖는다는 것을 확인할 수 있습니다.

반응형

'Java > Core Java' 카테고리의 다른 글

[자바] 마커 인터페이스란?  (0) 2017.04.24

쓰레드를 여러개 놓고 이들을 동시에 실행하려면 우선 다음과 같은 절차를 밟으면 되는줄 알고 다음과 같은 방법으로 테스트를 해보기로 하였습니다.


1. 쓰레드 몇개 생성

테스트를 위해 더하기, 빼기, 곱하기, 나누기 등을 반복적으로 하는 쓰레드를 다음과 같이 생성해 보았습니다.


(1) Add.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
public class Add extends Thread {
    
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            // Val 클래스의 val이라는 정적 변수에 1 더합니다.
            Val.val++;
            
            // 그리고 변한 값을 콘솔에 보여줍니다.
            System.out.println("Value after addition: " + Val.val);
            
            try {
                // sleep메소드는 일정 시간 동안 메소드의 실행을 일시적으로 중지합니다.
                // 여기서 인자는 1000분의 1초값이므로 0.1초는 100이 됩니다.
                Thread.sleep(100);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs

(2) Subtract.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
public class Subtract implements Runnable {
 
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            Val.val--;
            System.out.println("Value after subtract: " + Val.val);
            try {
                Thread.sleep(110);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
 
cs

(3) Multiply.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
public class Multiply implements Runnable {
 
    @Override
    public void run() {
        for(int i=0; i<50; i++) {
            Val.val = Val.val * 1.1;
            
            System.out.println("Value after multiplication: " + Val.val);
            try {
                Thread.sleep(200);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs

(4) Division.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
public class Division implements Runnable {
 
    @Override
    public void run() {
        for(int i=0; i<50; i++) {
            
            Val.val = Val.val / 1.2;
            System.out.println("Value after division: " + Val.val);
            
            try {
                Thread.sleep(220);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
 
cs


2. 공유할 값을 갖는 클래스 생성

1
2
3
4
5
 
public class Val {
    public static double val = 0.0;
}
 
cs


3. 마지막으로 위의 소스코드를 테스트 하기 위해 메인메소드를 갖는 클래스를 만들었습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        Multiply multiply = new Multiply();
        Division division = new Division();
        
        add.start();
        subtract.run();
        multiply.run();
        division.run();
    }
}
 
cs


이렇게 해서 실행을 해봤는데, 기대하기로는 각 쓰레드가 반복하면서 공유하는 값을 각자 가진 연산기능을 수행하여 변경할줄 알았습니다. 그런데 막상 코드를 실행해 보니, 더하기와 빼기 쓰레드 만이 동시에 실행이 되었고, 이들 쓰레드가 종료된 다음에야 곱하기 쓰레드가 실행되고 그다음에 마지막으로 나누기 쓰레드가 실행이 되었습니다. 


기존에 원했던 동시 실행을 하기 위해서 이래저래 조사를 해본 결과, 원하던 바를 구현하기 위해서는 ExecutorService와 Executor라는 클래스를 활용하여야 한다는 것을 알았습니다. 이 두 클래스는 java.util.concurrent 패키지 소속으로, 동시 프로그래밍 (concurrent programming) 구현을 위해 존재하는 라이브러리인 것입니다. 


ExecutorService 클래스는 동시에 실행하고자 하는 쓰레드를 하나의 쓰레드풀로 묶어서 실행할 수 있게 해주며, 따라서 위의 메인메소드를 다음과 같이 수정해 보도록 하겠습니다. 위의 네가지 쓰레드를 하나의 풀로 묶기 위해 ExecutorService의 인스턴스를 생성하고, 거기에 포함시키기 위해 submit() 메소드를 호출하여 각 쓰레드를 담습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        Multiply multiply = new Multiply();
        Division division = new Division();
        
        ExecutorService exs = Executors.newFixedThreadPool(4);
        exs.submit(add);
        exs.submit(subtract);
        exs.submit(multiply);
        exs.submit(division);
    }
}
 
cs


submit() 호출 후 별도의 start()나 run() 메소드를 호출하지 않습니다. 이대로 실행해보면 초반에 의도했던대로 쓰레드가 동시에 실행되는 것을 확인할 수 있습니다.

반응형

'Java > 스레드' 카테고리의 다른 글

[자바] Thread (1) - Thread는 무엇인가?  (0) 2017.04.21

자바 NIO는 New IO의 줄임말이라고 합니다. 


그런데 기존에 IO 라이브러리가 제공이 되었는데 왜 그렇다면 이 NIO가 새로이 등장하였을까? 답은 입출력을 하는 방식의 차이에 있습니다. 이번 포스트에서는 IO와 NIO가 가지고 있는 차이점에 대해 간략하게 알아보는 시간을 갖도록 하겠습니다.


1. 입출력 방식

자바 IO에서는 입출력이 바이트 단위로 혹은 문자 단위로 이루어 집니다. 바이트 단위로 이루어지는 경우에는 InputStream / OutputStream을, 문자 단위로 이루어지는 경우에는 Reader / Writer를 사용합니다. 입력과 출력에 따라 각각 별도로 InputStream / Reader 혹은 OutputStream / Writer를 생성해 주어야 합니다.


한편 NIO에서는 입출력이 채널 (Channel)을 통해 이루어 집니다. IO에서 입력과 출력이 구분되어 있는 것에 반해 채널은 양방향 소통이 가능합니다. 파일을 읽고 쓰는 경우를 예로 들자면 FileChannel 하나만 생성해 주면 됩니다.


2. 버퍼

버퍼라는 개념에 대해서는 이전에 작성한 포스트에서 확인해 보실 수 있습니다. (여기를 클릭해 주세요)


IO에서는 버퍼의 사용이 필수가 아닙니다. 물론 성능 문제상 버퍼를 사용하는 것이 권장되긴 하지만, BufferedReader나 BufferedWriter 등 클래스가 별도로 존재하여 이들을 함께 활용하지 않는 이상 버퍼 없는 입출력을 만들 수 있습니다.


NIO에서는 기본적으로 버퍼를 사용해서 입출력을 하도록 설계되어 있다는 점이 IO와 다릅니다. 채널은 버퍼에 저장된 데이터를 출력하고 버퍼에 데이터를 입력하는 방식이 NIO의 기본입니다.


3. 블로킹? 넌블로킹?

좀 더 쉽게 설명하기 위해 한가지 사례를 들겠습니다. 사원 A랑 B에게 각각 보고서를 작성하라는 일을 시켰습니다. 그리고 일을 하는 도중에 양쪽 모두에게 커피를 타오라는 일과 문서 복사를 해오라는 일을 추가했다고 가정합시다. A는 멀티태스킹이 되지 않는 사람이라 보고서를 먼저 작성하고 그 다음에 커피를 타오고 마지막으로 복사를 한 반면, B는 보고서를 작성하는 도중에 시킨 일들을 도중에 다 했습니다. A 사원의 업무 스타일에 해당하는게 바로 IO이고 B사원에게 해당하는게 NIO라고 할 수 있겠습니다.


IO에서 read() 메소드를 호출하여 내용을 읽어들인다고 했을 때, 데이터가 입력되기 전까지 작업 쓰레드는 대기 상태로 전환되며, 이를 블로킹이라고 합니다. 블로킹 상태에 있는 동안 이 쓰레드는 다른 작업을 수행할 수 없고, 인터럽트를 통해 블로킹을 빠져나올 수도 없습니다. 


NIO는 넌블로킹이랑 블로킹이 둘다 가능하도록 되어 있습니다. 그런데 블로킹에서도 IO와 한가지 중요한 차이점이 있는데 그것은 바로 NIO에서는 블록된 상태에서도 인터럽트가 가능하도록 되어 있다는 것입니다. 넌블로킹의 핵심은 바로 셀렉터 (Selector)인데요, 여러개의 채널 중에서 바로 읽고 쓸 준비가 완료된 채널을 제공하는 역할을 합니다. 이렇게 제공된 채널만을 가지고 작업 쓰레드가 처리하기 때문에 넌블로킹 방식이라고 말할 수 있습니다.


지금까지 NIO에 대한 개관이었구요, 다음 포스트에서는 NIO의 각 클래스를 소개하면서 이를 활용한 예시 코드를 함께 제공하며 설명하도록 하겠습니다.

반응형

컬렉션 프레임워크는 자료구조과 관련된 클래스들의 집합입니다. 자료구조는 프로그래밍 중에서 데이터 처리를 용이하게 하기 위해 각각의 목적에 따라 그에 맞는 구조로 구성된 개념을 말합니다. 자료구조와 관련해서는 추후 내용을 보강하겠습니다.


어쨌든 이 컬렉션 프레임워크는 크게 Collection 인터페이스를 구현한 클래스 및 인터페이스들과 Map 인터페이스를 구현한 클래스 및 인터페이스들로 나뉘게 됩니다. 이 중에서 Collection 인터페이스는 List 및 Set 인터페이스가 상속합니다. List와 Set의 특징은 각각 다음과 같습니다.


(1) List - 선형 자료구조. 선형구조라고 함은 자료가 순서를 가지고 저장된다는 것을 의미합니다. 단, 데이터의 중복을 허용합니다.

(2) Set - 집합의 개념을 구현한 자료구조 입니다. 순서를 유지하지 않고 데이터를 저장하는 경우에 활용되는 개념이며, 데이터의 중복은 허용되지 않습니다.


그럼 여기서 질문 하나, 왜 Map 인터페이스는 List나 Set와 같이 Collection 인터페이스를 상속하지 않는 걸까요? 그것은 바로 자료구조의 특징 때문에 그렇습니다. 앞선 List와 Set과 달리, Map 자료구조는 Key-Value 형태를 띱니다. 예를 들면 apple이라는 키를 이용해서 '사과'라는 결과를 사전에서 찾아오듯이 말이죠

반응형

우리가 프로그램을 만들어서 그것을 실행할 때, 이것이 실행되는 것 자체를 프로세스라고 합니다. 그리고 프로세스는 최소한 하나의 쓰레드 (Thread)를 갖고 있습니다.


프로세스 자체는 직렬적인 성격을 갖고 있습니다. 즉 프로세스는 순서대로 프로그램을 처리하는데요, 경우에 따라 어떤 두가지 일을 동시에 진행하여야 하는 경우가 있을 수 있습니다. 이 사례는 아래의 코드를 통해 설명하도록 하겠습니다. 아무튼 이렇게 일을 분리하여 동시에 처리하기 위해 도입된 개념을 바로 쓰레드라고 합니다.


자바에서 쓰레드를 구현하는 방법은 크게 두가지가 있습니다.


1. Thread 클래스를 상속하여 구현

2. Runnable 인터페이스를 구현


두가지 쓰레드가 0으로 초기화 된 한 변수를 공유하면서 첫번째 쓰레드는 0.1초마다 그 변수에 1씩 더하는 역할을 총 1000번 하고 두번째 쓰레드는 0.11초마다 1을 빼는 역할을 총 1000회 하는 코드를 만들어 보겠습니다. 따라서 다음과 같이 세가지 클래스를 만듭니다.


(1) 정적 변수 값을 하나 보유한 Val 클래스

1
2
3
4
public class Val {
    public static int val = 0;
}
 
cs

(2) 0.1초마다 Val 클래스의 정적 변수에 1씩 더하는 역할을 총 100번 하는 클래스. 이 클래스는 Thread 클래스를 상속하여 만들어 보겠습니다. 코드에서 보시다시피 실행이 되고자 하는 내용은 run() 메소드를 오버라이드 하여 그 안에 작성을 해야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
public class Add extends Thread {
    
    @Override
    public void run() {
 
        // for문 내부의 명령을 100번 실행한다는 의미입니다.
        for(int i=0; i<100; i++) {
            // Val 클래스의 val이라는 정적 변수에 1 더합니다.
            Val.val++;
            
            // 그리고 변한 값을 콘솔에 보여줍니다.
            System.out.println("Value of val: " + Val.val);
            
            try {
                // sleep메소드는 일정 시간 동안 메소드의 실행을 일시적으로 중지합니다.
                // 여기서 인자는 1000분의 1초값이므로 0.1초는 100이 됩니다.
                Thread.sleep(100);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
cs

(3) 0.11초마다 Val 클래스의 정적 변수에서 1씩 빼는 역할을 총 100번 하는 클래스. 쓰레드 구현의 또다른 방법으로 이번에는 Runnable 인터페이스를 구현하여 만들어 보았습니다. 이 경우에도 run() 메소드를 오버라이드 하면서 그 안에 실행되고자 하는 내용을 입력합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 
public class Subtract implements Runnable {
 
    @Override
    public void run() {
        
        for(int i=0; i<100; i++) {
            Val.val--;
            System.out.println("Value of val: " + Val.val);
            try {
                Thread.sleep(110);
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
 
}
cs

(4) 위의 두가지 쓰레드를 실행하는 메인 메소드를 담은 테스트 클래스.

1
2
3
4
5
6
7
8
9
10
11
 
public class ThreadTest {
    
    public static void main(String args[]) {
        Add add = new Add();
        Subtract subtract = new Subtract();
        
        add.start();
        subtract.run();
    }
}
cs


코드에서 보면, 한가지 눈에 들어오는 차이점이 있습니다. Thread 클래스를 상속하여 만든 add 인스턴스를 실행하기 위해서는 start() 메소드를 실행하고, Runnable 인터페이스를 구현하여 만든 subtract 인스턴스를 실행하는 곳에서는 run() 메소드를 실행한다는 것입니다. 왜 그럴까요?


기본적으로 Thread 클래스는 start() 메소드를 호출함으로써 run() 메소드가 실행되도록 되어 있습니다. 비록 위의 add 인스턴스에서 run() 메소드를 직접 호출할 수도 있지만, 그 경우에 결과는 기대했던 것과는 전혀 다르게 나오게 됩니다. 프로그램이 순차적으로 실행되어 add 인스턴스가 갖고 있는 프로그램이 먼저 실행이 되고 이것이 다 끝난 뒤에야 나중에 나오는 subtract 인스턴스가 실행되는 것입니다. 


따라서 앞으로 쓰레드를 활용하여 프로그램을 만들 때, Thread 클래스를 상속한 쓰레드는 run() 메소드를 오버라이드 하여 내용을 채우고 이를 실행할 때는 start() 메소드를 호출한다는 점과 Runnable 인터페이스를 구현한 쓰레드는 run() 메소드 자체를 오버라이드 하여 호출한다는 점을 기억해야 할 것입니다.

반응형

'Java > 스레드' 카테고리의 다른 글

[자바] ExecutorService 클래스 소개  (0) 2017.04.24

텍스트 파일을 읽어들이기 위해서는 FileReader와 BufferedReader가 필요합니다. FileReader 클래스는 파일에 저장된 바이트를 유니코드 문자로 변환해서 읽어들이는 역할을 담당하고, BufferedReader는 FileReader로 읽어들인 내용을 버퍼링 하여 문자, 문자 배열, 문자열 라인 등을 보다 효율적으로 처리하도록 돕는 역할을 합니다.


BufferedReader는 readLine() 메소드를 통해 \n이나 \r을 만나기 전까지의 문자열을 한 줄로 인식하여 읽어들이는 메소드입니다. 이를 바탕으로 해서 더이상 읽을 내용이 없을 때까지 내용을 가져와서 콘솔에 출력을 하는 프로그램을 만들어 보기로 하겠습니다.

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
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
public class ReadFromFile {
    public static void main(String args[]) {
        BufferedReader bReader = null;
        
        try {
            
            String s;
            File file = new File("test1.txt");
            bReader = new BufferedReader(new FileReader(file));
            
            // 더이상 읽어들일게 없을 때까지 읽어들이게 합니다.
            while((s = bReader.readLine()) != null) {
                System.out.println(s);
            }
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(bReader != null) bReader.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs


반응형

텍스트 파일을 생성하고 파일에 텍스트 쓰기 세번째 글입니다.


앞서서 설명한 내용과 이번에 설명할 내용의 가장 큰 차이점은, 앞에서 설명한 방식은 Writer를 활용한 쓰기를 말한 것이며 이번에는 OutputStream을 활용한다는 것입니다. FileOutputStream은 OutputStream을 상속한 클래스로서, 파일로 바이트 단위의 출력을 내보내는 클래스 입니다.


 파일에 내용을 쓸 때 FileWriter 및 BufferedWriter를 쓰는 경우와 비교를 했을 때 차이점은, 출력하고자 하는 대상을 먼저 바이트 배열로 바꿔줘야 한다는 것입니다. 왜냐면 앞서 언급된 FileOutputStream의 상위 클래스인 OutputStream은 바이트 단위의 출력을 다루기 때문입니다. 이 과정을 통해 출력 대상 데이터가 일련의 이진 데이터 (binary data)로 변환됩니다.


그리고 flush() 메소드까지 호출한 후에 close() 메소드를 통해 FileOutputStream의 인스턴스를 닫아주어야 한다는 점도 다른점입니다.


예제 코드는 다음과 같습니다.


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
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
 
public class WriteToFile {
    public static void main(String args[]) {
        
        String message = "This is a sample message.\n";
        
        File file = new File("test1.txt");
        FileOutputStream fos = null;
        
        try {
            fos = new FileOutputStream(file);
            
            // FileOutputStream 클래스가 파일에 바이트를 내보내는 역할을 하는 클래스이므로
            // 내보낼 내용을 바이트로 변환을 하는 작업이 필요합니다.
            byte[] content = message.getBytes();
            
            fos.write(content);
            fos.flush();
            fos.close();
            
            System.out.println("DONE");
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fos != null) fos.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
cs


반응형

+ Recent posts