레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Java인 게시물을 표시합니다. 모든 게시물 표시

2016년 3월 28일 월요일

Java Generic의 모든 것

의미와 기본 용법

  • Java Generic Type은 클래스의 타입을 parameter로 만든 것이다. 
  • Java 5 (SDK 1.5)부터 추가되었으며 주로 Java Collection에서 많이 사용된다.
  • 다루는 클래스의 타입은 일반적으로 선언하면서도 실제 사용할 때에는 구체적으로 어떤 클래스 타입인지 명시하게 함으로써 내부적으로, 또는 사용시 형변환을 하지 않아도 되게 한다. (컴파일러가 자동으로 캐스팅 해준다)
  • 만일 일반적인 클래스를 Object로 선언하면 사용할 때 구체 클래스 타입으로 형변환을 해야하는데, 이때 실수를 하게되면 run time에 가서야 detect를 할 수 있다. 

public class Box {
    private Object object;

    public void set(Object object) {
        this.object = object;
    }

    public Object get() {
        return object;
    }
}



  • 예를 들어, 위의 클래스를 사용하는 코드를 보자. 

...
    Box box = new Box();
    box.set(new String("Hello World"));
    // force downcasting regardless of type in box
    String inbox = (String)box.get();
...



  • Box안에 어떠한 클래스가 들어 있든지 위의 코드는 꺼내서 String타입으로 강제 형변환을 시킨다. 이 부분이 잘못되어도 compile time에는 알 수 없다.
  • 이것을 generic type으로 선언해보자. 

public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }
}



  • 그러면 아래와 같이 사용할 수 있다. (아래의 type은 String Box라고 읽는다)

...
    Box box<String> = new Box<String>();
    box.set(new String("Hello World"));
    // downcasting is not needed
    String inbox = box.get();
...



  • Box안에 String외의 다른 클래스를 넣으려는 시도는 compile time에 detect된다. 그리고 꺼내 쓸 때에는 따로 downcasting이 필요 없다. 
  • 두 개 이상의 복수개 type을 사용할 수도 있다. 

public class Box<T, S> {
    private T first;
    private S second;

    public (T t, S s) {
        this.t = t;
        this.s = s;
    }

    public T getFirst() { return t; }
    public S getSecond() { return s; }
}



Type parameter의 naming convention

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S, U, V etc. - 2nd, 3rd, 4th types


Generic Method와 type inference

  • 꼭 클래스 단위의 사용이 아니라도 좋다. 메소드에서 사용할 수 있는데, 그것을 Generic method라 부른다. 메소드 시그니처를 유념하라. (U는 클래스 선언부에 사용되지 않은 type parameter이다)

public class Box<T> {
...
    public <U> void printBox<U info> {
        System.out.println(info);
    }
...



  • 사용하는 코드를 보자.

...
    Box box<Integer> = new Box<Integer>();
    box.set(new Interger(1));
    // downcasting is not needed
    box.printBox("Hello World");
...



  • 함수 call에 대해서 명시적으로 어떤 타입인지 선언하지 않았는데도 잘 돈다. 이는 컴파일러가 printBox의 인자가 String타입임을 알고 있기 때문에 U가 String인 것도 알기 때문이다. 이러한 처리를 타입추론 (type inference)라고 한다. 
  • 뿐만 아니라 타입추론를 통해 생성자에서 타입 생략도 가능하다. 예를 들어 아래와 같은 Generic type 클래스의 생성자가 있을 때

public class Box<T, S> {
    private T first;
    private S second;

    public (T t, S s) {
        this.t = t;
        this.s = s;
    }
...



  • 아래와 같은 코드도 동작한다. 

    Box box<Integer, String> = new Box<Integer, String>(new Integer(1), "Hello");
    Box box = new Box(new Integer(1), "Hello");



  • 위의 두 줄은 동일하게 동작한다.


Type Bound

  • 상속 관계를 선언함으로써 사용되는 type에 제한을 가할 수도 있다 
  • 한정적 형인자 (bounded type parameter)

public class Box<T extends Numbers> {
...


    • 여기서 설령 interface를 상속 받는다고 하더라도 'implements' 키워드를 사용하지 않고 'extends' 키워드를 공통적으로 사용한다. 
  • 재귀적 형 한정 (recursive type bound)

public class Box<T extends Comparable<T>> {
...


    • 상속받는 타입이 하필 generic type일 경우 이런 일이 벌어진다.



Wildcard

  • Generic type class을 parameter로 사용할 때 아직 구체 타입을 정하지 않은 경우라면 wildcard를 쓰는 것이 좋다.
  • 비한정적 와일드카드 자료형(unbounded wildcard type)

static int numElementsInCommon(Set<?> s1, Set<?> s2) {
    int result = 0;
    for (Object o1: s1)
        if (s2.contains(o1))
            result++;
    return result;
}



    • 즉, 어떤 class type에 대한 Set인지 상관없이 Set의 메소드를 이용하고 싶을 때 위와 같은 코드가 된다. 참고로 위의 예제에서 Set의 두 타입은 서로 달라도 무방하다.
    • 그렇다면 위의 코드를 그냥 원천 타입 (raw type)의 Set으로 사용하면 어떨까?

static int numElementsInCommon(Set s1, Set s2) {
    int result = 0;
    for (Object o1: s1)
        if (s2.contains(o1))
            result++;
    return result;
}



    • 메소드 시그니처 빼고는 다를게 없다. 그러나 이렇게 사용하지 마라. 왜냐하면 Set의 원천타입을 사용한다는 것은 Set이 타입에 대해 안전하지 않다는 뜻이다.
  • 한정적 와일드카드 자료형(bounded wildcard type)
    • 파라메터 클래스 타입을 제한하고 싶다면 와일드카드와 extends 키워드를 사용하면 된다.

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
...
    public void pushAll(Iterable<E> src) {
        for (E e : src)
            push(e);
    }

    public void popAll(Collection<E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }
}



    • 위의 코드에서 pushAll()과 popAll()은 잘 돌까?
    • 아래와 같이 pushAll()을 사용할 경우 컴파일 에러가 발생한다.

    Stack<Number> numberStack = new Stack<Number>();
    Iterable<Integer> integers = ...;
    numberStack.pushAll(integers);



    • 왜냐하면 Integer는 Number이지만 Stack<Integer>와 Stack<Number>는 아무런 상관이 없기 때문이다.
    • popAll()의 경우도 마찬가지이다.

    Stack<Number> numberStack = new Stack<Number>();
    Collection<Object> objects = ...;
    numberStack.popAll(objects);



    • 따라서 두 메소드의 선언부는 아래와 같이 수정되어야 한다.

public class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
...
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src)
            push(e);
    }

    public void popAll(Collection<? super E> dst) {
        while (!isEmpty())
            dst.add(pop());
    }
}



    • super 한정자는 어떤 클래스의 조상 클래스여야 한다는 제한이다.
    • 보통 in-param에서는 extends를 사용하고 out-param에서는 super을 사용하고, in-out param에서는 wildcard를 사용하지 않는 것이 좋다. (특히 out-param에서 extends를 사용하는 실수를 하지 마라)


Generic type의 생략

  • Java 7부터는 generic type의 객체를 생성할 때 선언부에 타입 변수를 적었다면 instance 생성부에서는 타입 변수를 생략할 수도 있다. (꺽쇄만 남는다)

    Box box<Integer> = new Box<>();


Java Generic type vs. C++ Template

  • Java Generic typ과 C++ Template는 생긴 것은 비슷하지만 두 언어가 이를 처리하는 과정은 아주 많이 다르다.
  • Java의 Generic은 타입 제거 (type erasure)라는 개념에 근거한다. 이 기법은 소스 코드를 Java 가상 머신 (JVM)이 인식하는 바이트 코드로 변환할 때 인자로 주어진 타입을 제거하는 기술이다.
  • 가령 아래와 같은 코드를 작성했다면

ArrayList<String> list = new ArrayList<String>();
list.add(new String("hello"));
String str = list.get(0);


  • 컴파일러는 이 코드를 다음과 같이 변환한다. 즉, Generic code들은 컴파일 타임에 모두 제거되고 컴파일러가 자동으로 downcasting을 해주는 것이다.

ArrayList list = new ArrayList();
list.add(new String("hello"));
String str = (String) list.get(0);


  • 따라서 Java Generic이 있다고 해서 크게 달라지는 것은 없다. 뭔가를 좀 더 예쁘게 작성할 수 있게 되었을 뿐이다. 그래서 Java Generic을 때로 '문법적 양념 (syntactic sugar)'라고 부르는 것이다.
  • C++의 경우에는 상황이 좀 다르다. C++에서 Template은 좀 더 우아한 형태의 매크로다. 컴파일러는 인자로 주어진 각각의 타입에 대해 별도의 템플릿 코드를 생성한다. MyClass<Foo>가 MyClass<Bar>와 static 변수를 공유하지 않는 것을 보면 알 수 있다. (MyClass<Foo>로 만든 두 객체는 static 변수를 공유한다)
  • 반면 Java static 변수는 MyClass로 만든 모든 객체가 공유한다. 제네릭 인자로 어떤 타입을 주었는지 관계 없다.
  • 이러한 구조적 차이 때문에 Java Generic과 C++ Template에는 다른 점이 많다. 그 중 일부는 다음과 같다.
    • C++ Template에는 int와 같은 기본 타입을 인자로 넘길 수 있다. Java Generic에서는 불가능하다. 모든 타입은 Object를 상속해야 하며 따라서 int 대신 Integer를 사용해야 한다.
    • Java의 경우, 제네릭 타입 인자를 특정한 타입이 되도록 제한할 수 있다. 가령 CardDeck을 제네릭 클래스로 정의할 때, 그 인자로는 CardGame의 하위 클래스만 사용되도록 제한하는 것이 가능하다. (한정적 형인자)
    • C++ Template은 인자로 주어진 타입으로부터 객체를 만들어 낼 수 있다. Java에서는 불가능하다.
    • Java에서 Generic type 인자는 static 메서드나 변수를 선언하는데 사용될 수 없다. MyClass<Foo>와 MyClass<Bar>가 공히 이 메서드와 변수를 공유할 것이기 때문이다. C++에서는 이 두 클래스는 다른 클래스이므로 템플릿 타입 인자를 static 메서드나 변수를 선언하는데 사용할 수 있다.
    • Java에서 MyClass로 만든 모든 객체는 제네릭 타입 인자가 무엇이냐에 관계없이 전부 동등한 타입이다. 실행시간에 타입 인자 정보는 삭제된다. C++에서는 다른 템플릿 타입 인자를 사용해 만든 객체는 서로 다른 타입의 객체이다.




Android에서 Parcelable한 generic class 만들기

  • 핵심은 class loader인데 generic type (T 변수)의 getClass().getClassLoader()를 이용한다.

public class MyParcelableClass<T> implements Parcelable {
    private T mValue;

    @Override
    public void writeToParcel(Parcel parcelOut, int flags) {
        parcelOut.writeValue(mValue);
    }

    @SuppressWarnings("rawtypes")
    public static final Parcelable.Creator<GenericParcelable> CREATOR = new Parcelable.Creator<GenericParcelable>() {
        ...
    }

    public MyParcelableClass(T value) {
        this.mValue = value;
    }

    @SuppressWarnings("unchecked")
    private GenericParcelable(Parcel parcelIn) {
       try {
            mValue = (T)parcelIn.readValue(mValue.getClass().getClassLoader());
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public T getValue() {
         return (T) mValue;
    }
}



  • 그런데 generic class를 AIDL에서 선언할 수 없는데 그렇다고 <>를 빼고 선언하면 코드에서 warning이 발생한다. 역시 현재로선 @SuppressWarnings("unchecked")를 사용하여 warning을 무시하는 수 밖에 없다.

Java Collection - PriorityQueue 용법

특징
  • Heap 자료구조를 구현함


주요 메소드
  • http://developer.android.com/reference/java/util/PriorityQueue.html  
  • PriorityQueue(), PriorityQueue(int initialCapacity, Comparator<? super E> comparator) - 생성자
  • Boolean add(E o) - 요소를 집어넣는 메소드, 내부적으로 offer(E o)를 호출하는데 offer(E o)와 동일하다고 보면 된다.
  • E poll() - 큐에서 top priority 요소를 꺼내는 메소드
  • E peek() - 큐에서 top priority 요소를 꺼내지는 않고 보는 메소드
  • 그 밖에 size(), remove(Object o), clear() 등도 있다.


PriorityQueue를 이용하여 min heap과 max heap을 만드는 방법
  • 기본 생성자 - PriorityQueue() - 를 사용하면 min heap이 되고 역순으로 비교하는 comparator를 사용하면 max heap이 된다. 
  • 특별히 Collections.reverseOrder()가 제공된다. 대신 custom comparator를 사용할 수 있는 생성자에선 initial capacity도 함께 지정할 수 밖에 없다. 


Example
  • min heap과 max heap을 유지하면서 median값을 O(log n)에 제공하는 메소드

private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>();
private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(10, Collections.reverseOrder());

// 하위 수는 maxHeap에, 상위 수는 minHeap에 넣는다
// maxHeap의 개수가 minHeap과 같거나 하나 많도록 유지한다
public void addNumber(int number) {
    if (maxHeap.size() == minHeap.size()) {
        if ((minHeap.peek() != null) && number > minHeap.peek()) {
            maxHeap.offer(minHeap.poll()); // move the smallest of minHeap to maxHeap
            minHeap.offer(number);
        } else {
            maxHeap.offer(number);
        }
    } else {
        if (number < maxHeap.peek()) {
            minHeap.offer(maxHeap.poll());
            maxHeap.offer(number);
        } else {
            minHeap.offer(number);
        }
    }
}

public double getMedian() {
    if (maxHeap.isEmpty()) return 0d; // error

    if (maxHeap.size() == minHeap.size()) {
        return (minHeap.peek() + maxHeap.peek()) / 2;
    } else if (maxHeap.size() > minHeap.size()) {
        return maxHeap.peek();
    }
}

Java Collection - Map 용법

Map iteration 하기

  • 1. Entry 이용하기

for (Map.Entry<String, String> entry : mMap.entrySet())
{
    System.out.println(entry.getKey() + "/" + entry.getValue());
}


  • 2. Iterator 이용하기

Iterator<Entry<String, String> iter = myMap.entrySet().iterator();
while (iter.hasNext()) {
    Entry<String, String> entry = iter.next();
    System.out.println(entry.getKey() + "/" + entry.getValue());
}


Map으로부터 value의 List 뽑아내기

  • Map의 values() 메소드를 사용한다.


List<Value> list = new ArrayList<Value>(map.values());


Map의 특정 element update하기

  • 그냥 같은 key로 put하면 기존 값이 update됨. Don't worry~


Java Collection - Set 용법

주요 메소드

일반 array로부터 Set 초기화 하기
  • Arrays.asList() 메소드를 사용한다.

String[] array = {"apple", "orange"};
HashSet<String> set = new HashSet<String>(Arrays.asList(array));


Set으로부터 일반 array 뽑아내기
  • Set의 toArray() 메소드를 사용한다. 

HashSet<String> set;
set.toArray(new String[set.size()]);


EnumSet 순회하기

public static enum MyFruitType {
    ORANGE(0),
    APPLE(1);

    private final int mValue;

    private MyFruitType(int value) {
        this.mValue = value;
    }

    public int getValue() {
        return mValue;
    }
}

for (MyFruitType type : MyFruitType.values()) {
     System.out.println(type.getValue());
}


EnumSet의 enum에 대한 string 얻어오기
  • Enum 값의 toString()을 이용한다.

System.out.println(MyFruitType.ORANGE.toString() + "="
                 + MyFruitType.ORANGE.getValue());


  • 사실 그냥 toString() 없이 enum값 자체를 써도 똑같음 

System.out.println(MyFruitType.ORANGE + "="
                 + MyFruitType.ORANGE.getValue());



TreeSet
  • TreeSet은 SortedSet interface에 대한 구현체이다. SortedSet 구현체로는 ConcurrentSkipListSet, NavigableSet, TreeSet이 있다. 
  • SortedSet의 메소드로는 first(), last(), subSet(E start, E end), tailSet(E start)가 있다. 정렬이 되어 있으니까 순서상 제일 앞의 것, 뒤의 것, 중간 토막, 뒤 토막을 얻어내는 메소드를 제공하는 것이다. 
  • TreeSet은 SortedSet의 메소드를 구현하고 있고 Element는 Comparable<E>을 구현하고 있어야 한다.
  • 많이 사용하는 메소드로서 아래와 같은 것이 있다. 
    • boolean add(E object)
    • E ceiling(E e) // 주어진 e보다 크거나 같은 것들 중 가장 앞의 것 (작은 것), 없으면 null
    • boolean contains(Object object)
    • Iterator<E> iterator(), Iterator<E> descendingIterator()
    • E floor(E e) // 주어진 e보다 작거나 같은 것들 중에서 가장 뒤의 것 (큰 것), 없으면 null
    • E higher(E e) // 주어진 e보다 큰 것들 중에서 가장 앞의 것 (작은 것), 없으면 null
    • E lower(E e) // 주어진 e보다 작은 것들 중에서 가장 뒤의 것 (큰 것), 없으면 null
    • E pollFirst(), E pollLast() // first(), last()와 달리 원소를 제거하면서 가져옴

BitSet
  • bit 단위로 data를 저장하고 조작할 수 있도록 제공되는 class
  • 기본적으로 특정 bit를 조작하는 아래와 같은 메소드들이 있다. 
    • void set(int index) // 특정 bit를 1로 함
    • void set(int index, boolean v) // 특정 bit를 주어진 0 / 1로 함
    • void clear() // 전체를 0으로 함
    • void clear(int index) // 특정 bit를 0으로 함
    • void flip(int index) // 특정 bit를 reverse 함
    • boolean get() // 특정 bit의 값을 읽음
    • void and(BitSet bitSet) // 주어진 BitSet과 and 연산
    • void or(BitSet bitSet) // 주어진 BitSet과 or 연산
    • void xor(BitSet bitSet) // 주어진 BitSet과 xor 연산 
  • 샘플 코드 

BitSet bs1 = new BitSet(16);
BitSet bs2 = new BitSet(16);

// set some bits
for (int i = 0; i < 16; i++) {
    if ((i % 2) == 0) bs1.set(i); // 0101010101010101
    if ((i % 5) != 0) bs2.set(i); // 0111101111011110
}

System.out.println("BitSet 1: " + bs1);
System.out.println("BitSet 2: " + bs2);

bs2.and(bs1);
System.out.println("BitSet 1 AND BitSet 2: " + bs2);

bs2.or(bs1);
System.out.println("BitSet 1 OR BitSet 2: " + bs2);

bs2.xor(bs1);
System.out.println("BitSet 1 XOR BitSet 2: " + bs2);


  • 결과 
BitSet 1: {0, 2, 4, 6, 8, 10, 12, 14}
BitSet 2: {1, 2, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14}
BitSet 1 AND BitSet 2: {2, 4, 6, 8, 12, 14}
BitSet 1 OR BitSet 2: {0, 2, 4, 6, 8, 10, 12, 14}
BitSet 1 XOR BitSet 2: {}


Java Collection - List 용법

주요 메소드

List로부터 array 만들기
  • List의 toArray() 메소드를 사용한다.

List<String> list = new ArrayList<String>();
list.add("India");
list.add("Switzerland");
list.add("Italy");
list.add("France");
String[] countries = list.toArray(new String[list.size()]);


Array(배열)로부터 List 만들기
  • Arrays.asList() 메소드를 사용한다.

String[] array = {"India", "Switzerland", "Italy", "France"};
List<String> list = Arrays.asList(array);


List sorting 하기
  • Collections의 static method를 사용한다. (interface인 Collection이 아니다)

List<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(3);
list.add(8);
list.add(1);
Collections.sort(list);


  • 만일 custom class에 대해서 Collections.sort()를 사용하고 싶으면 그 객체가 Comparable<T>를 implements (compareTo() 메소드를 overwrite해야 한다) 하거나, 

public class MyData implements Comparable {
...
    @Override
    public int compareTo(MyData other) {
        // 0을 반환하는 케이스는 필요하면 넣으면 된다.
        return (this.data <= other.data) ? -1 : 1; 
    }
...


  • 아니면 Comparator<T>를 (주로 anonymous로) 정의하여 sort() 메소드 호출시 함께 넣어준다. (compare()를 overwrite한다)

Collections.sort(list, new Comparator<MyData>() {
    @Override
    public int compare(MyData lhs, MyData rhs) {
        if (lhs < rhs) return -1;
        else if (lhs == rhs) return 0; 
        else return 1;
    });




2016년 3월 27일 일요일

Java StringTokenizer와 String.split() 용법

StringTokenizer
  • String을 기본적으로 공백문자 기준으로 token화 시키는 것이다.
  • 용법은 문자열을 생성자에 넣어주고 hasMoreTokens()와 nextToken()을 순환하면 된다. 

    예제

StringTokenizer st = new StringTokenizer("this is a test");

while (st.hasMoreTokens()) {
    System.out.println(st.nextToken());
}

  • 공백문자가 아닌 다른 문자를 delimiter로 넣어주고 싶으면 생성자에 넣어주면된다. 예를 들어, 공백문자, 탭, 콤마를 delimiter로 지정하고 싶다면 아래와 같이 사용한다. (신기한 건 s와 t 앞에 backslash를 두 번 한다는 것이다. Regular expression에 근거)

StringTokenizer st = new StringTokenizer("this is a test", ",\\s\\t");



String.split()
  • StringTokenizer는 호환성을 위해 남겨진 코드이고 새로운 코드에서는 String의 split()을 사용하자.

String[] tokens = "this is a test".split("\\s");

for (token : tokens) {
    System.out.println(token);
}


  • 기능적으로는 똑같지만 약간의 차이는 있다. Tokenizer의 경우 delimiter가 연속되면 그 모든 것들이 무시되는 반면 split()의 경우에는 delimiter 사이를 공백문자열로 하여 반환한다.


Java 파일 입출력


  • 자바에서 파일 입출력은 크게 두 종류로 나뉘는 듯 하다. 하나는 FileReader/FileWriter이고 다른 하나는 FileInputStream/FileOutputStream이다.  

FileReader/FileWriter 
  • FileReader/FileWriter는 character 단위로 읽기 쓰기를 하는데 효율을 향상시키기 위해서 BufferedReader/BufferedWrite와 결합하여 사용하기도 한다. 

    FileReader 코드 예제
  • 예제에서는 한 글자씩 읽고 있지만 read() 메소드의 인자로 char 배열을 넣어주면 배열을 크기만큼 읽어올 수도 있다. 

public static void readFile(String filePath) throws IOException {
    FileReader reader = new FileReader(filePath);
    int ch;

    while ((ch = reader.read()) != -1) { // 한 글자씩 받아온다
        System.out.print((char)ch);
    }
    reader.close();  // 다 읽었으면 닫아주어야 한다.
}


    FileWriter 코드 예제

public static void writeFile(String filePath, String content) throws IOException {
    FileWriter writer = new FileWriter(filePath);

    writer.write(content); // 한꺼번에 쓴다.
    writer.append("---End of File---"); // 파일 끝에 추가한다.
    writer.close();  // 다 썼으면 닫아주어야 한다.
}


BufferedReader + FileReader, BufferedWriter + FileWriter 조합
  • 효율을 향상시키기 위해서 FileReader/FileWriter를 BufferedReader/BufferedWrite와 결합하여 사용하기도 한다. 

    BufferedReader 코드 예제
  • 버퍼를 사용하여 효율적으로 읽는데, 현실적으로 한 라인씩 읽는 메서드가 있어서 편하다.

public static void readFile(String filePath) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader(filePath));
    String line;

    while ((line = reader.readLine()) != null) { // 한 라인씩 받아온다
        System.out.print(line);
    }
    reader.close();  // 다 읽었으면 닫아주어야 한다.
}


    BufferedWriter 코드 예제
  • FileWriter만 사용하면 문자열을 전달할 때마다 파일에 쓰기 작업을 하는데 BufferedWrtier를 사용하면 버퍼가 꽉 찰 때까지 기다렸다가 파일에 쓴다. (버퍼가 차기 전에 파일에 쓰고 싶다면 flush()를 호출)

public static void writeFile(String filePath, String content) throws IOException {
    BufferedWriter writer = new BufferedWriter(new FileWriter(filePath));

    writer.write(content);
    writer.newLine(); // 공백 라인 추가
    writer.close();
}


FileInputStream/FileOutputStream
  • 앞선 FileReader/FileWriter, BufferedReader/BufferedWrite가 문자열을 다룬다면 FileInputStream/FileOutputStream은 바이트를 다룬다. 

    FileInputStream 코드 예제
  • 예제에서는 한 바이트씩 읽고 있지만 read() 메소드의 인자로 byte 배열을 넣어주면 배열의 크기만큼 읽어온다.

public static void readFile(String filePath) throws IOException {
    FileInputStream fis = new FileInputStream(filePath);
    int ch;

    while ((ch = fis.read()) != -1) { // 한 바이트씩 받아온다
        System.out.print((char)ch);
    }
    fis.close();  // 다 읽었으면 닫아주어야 한다.
}


    FileOutputStream 코드 예제

public static void writeFile(String filePath, String content) throws IOException {
    // true면 이어쓰기, false면 새로쓰기
    FileOutputStream fos = new FileOutputStream(filePath, false);

    for (char ch : content.toCharArray()) {
        fos.write(ch);
    }
    fos.close();
}

  • FileInputStream/FileOutputStream도 사실 비효율적이다. 따라서 버퍼를 활용하려면 BufferedInputStream/BufferedOutputStream에 wrapping해서 사용해야 한다. 

Java 배열의 생성과 초기화

배열의 생성과 초기화
  • 자바에서 배열은 객체다. 
  • 배열을 선언하는 것을 보자 

// 보통은 아래와 같이 선언과 생성을 한다.
// Java에서는 기본적으로 boolean array의 경우 false,
// int array의 경우 0, Object array의 경우 null로 자동 초기화된다.
int [] intArray = new int[4]; 

// 특정 값을 대입하면서 배열을 선언하였다. 
int [] intArray = {1, 2, 3, 4}; 

// 물론 선언과 생성&대입을 분리할 수도 있다.
int [] intArray; // 배열도 객체이므로 이 경우 null이다. 
intArray = new int[] {1, 2, 3, 4}; 

// 이번엔 선언과 생성, 대입을 모두 분리해보자.
boolean [] boolArray; // 객체이므로 이 경우 null이다. 
boolArray = new boolean[4]; // 이 시점에 객체의 주소가 들어가게 된다. 
Arrays.fill(boolArray, true); // 일괄적으로 값을 대입할 때 Arrays.fill()을 사용한다.

  • 배열은 객체이므로 변수에는 null이 들어가거나 객체의 주소가 들어간다. 
  • 생성만하면 초기값이 자동으로 할당되는 것에 주의하자.

2차원 배열
  • for-each를 이용하여 iteration 하기

String properties[][] = {
                {"application_id", "text"}, {"sample_time", "integer"},
                };

for (String[] propertyInfo : properties) {
    System.out.println(propertyInfo[0] + ": " + propertyInfo[1]);
}
// or
for (String[] propertyInfo : properties) {
    for (String detail : propertyInfo) {
        System.out.println(detail);
    }
}