정규표현식 (Regular Expression)

정규표현식

  • 특정 조건에 맞는 문자열을 검색/치환하는 패턴 기반의 식
  • 이메일, 전화번호 등 특정한 형식에 맞는지 validation하는 데에 사용

정규표현식 표현 방법

  • 정규 표현식의 기본 작성 방식

    1
    "/패턴/[플래그(Opt.)]"
  • 기본 메타 문자

    메타 문자 의미
    ^x x로 시작
    x$ x로 끝
    .x x앞에 하나의 문자가 있음
    x+ x가 1번 이상 반복
    x? x가 있거나 없음
    x* x가 0번 이상 반복
    `x y`
  • 괄호의 활용

    regexp 의미
    (xy) 괄호 안의 내용을 그룹화
    x{n} n번 반복됨
    x{n,} n번 이상 반복됨
    x{n, m} n번 이상, m번 이하 반복됨
    [xy] x 또는 y
    [a-z] 알파벳 소문자 (a~z)
    [0-9] 숫자
    [가-힣] 한글
    [^x] x가 아닌 것
    [^0-9] 숫자가 아닌 것
  • \ 축약 문자

    regexp 의미
    \^, \., … \ 뒤에 나오는 문자를 문자로 처리
    \b 단어의 경계를 찾는다.
    \B 단어의 경계가 아닌 것을 찾는다.
    \d 숫자를 찾는다.
    \D 숫자가 아닌 것을 찾는다.
    \s 공백 문자를 찾는다.
    \S 공백 문자가 아닌 것을 찾는다.
    \w [a-zA-Z0-9_]
    \W [^a-zA-Z0-9_]

    JAVA에서는 \\\ 처럼 입력해야 \\처럼 동작한다

  • 유용한 정규식 예

    • 한글 이름 : ^[가-힣]{2,5}$
    • 핸드폰 번호: ^01(0|1|2|6|9)[-\s]?\d{3,4}[-\s]?\d{4}$
    • 이메일 주소: ^[\w\.-]{1,64}@[\w\.-]{1,252}\.\w{2,4}$

Flag

Flag 기능
g 문자열 내 모든 패턴을 찾음
i 대소문자를 구분하지 않음
m 문자열의 모든 줄에서 찾음

정규표현식을 사용하는 클래스

Pattern 클래스

  • 정규식의 컴파일된 표현 입니다(정규식을 적용 가능하도록 컴파일해서 가지고 있습니다).

  • Pattern 클래스는 공개된 생성자를 제공하지 않습니다.

  • 패턴을 생성하려면 Pattern객체를 반환하는 정적 compile 메소드를 호출해야 합니다.

  • 이 메소드는 첫 번째 인자로 정규식 문자열을 받아 들입니다.

    메소드 설명
    public static Pattern compile(String regex) Pattern 객체를 생성
    public Matcher matcher(CharSequence input) 입력을 분석하는 Matcher 객체 생성
    public static boolean matches(String regex, CharSequence input) 입력이 regexp에 해당하는지 판단

Matcher 클래스

  • 패턴을 해석하고 입력 문자열에 대해 일치 작업을 수행하는 엔진입니다.

  • Pattern 클래스와 마찬가지로 Matcher는 어떤 공개된 생성자도 정의하고 있지 않습니다.

  • Matcher객체는 Pattern 객체의 matcher 메소드를 호출해서 얻습니다.

    메소드 설명
    find() 정규표현식에 부합되는 내용이 문자열에 포함되어 있는지 반환. 이전 검색 위치부터 이어서 검색.
    start() 패턴에 부합되는 요소의 시작 인덱스 반환
    end() 패턴에 부합되는 요소가 끝나는 위치 + 1을 반환
    matches() 문자열 전체가 정규표현식에 일치하는지 반환
    lookingAt() 비교하려는 문자열이 정규표현식으로 시작하는지 반환. 0번 인덱스부터 검색.
    replaceFirst() 일치하는 첫 패턴을 문자열로 대체
    replaceAll() 일치하는 모든 패턴을 문자열로 대체
    reset() Matcher의 정보를 리셋하여 0번 인덱스부터 다시 검색

정규식 캡쳐 그룹

  • 정규표현식에서 캡쳐링 그룹은 괄호로 둘러싼 영역이다.

  • 사용 예

    • 순서로 그룹 가져오기

      1
      2
      3
      4
      5
      6
      7
      8
      9
      Pattern pattern2 = Pattern.compile("^\\w+):(\\w+)\\.(\\w+)$");
      Matcher matcher2 = pattern2.matcher("filename:temp.png");

      matcher2.find();
      System.out.println(matcher2.group()); // 매칭된 전체가 출력
      System.out.println(matcher2.group(0)); // 매칭된 전체가 출력
      System.out.println(matcher2.group(1)); // 첫번째 그룹
      System.out.println(matcher2.group(2));
      System.out.println(matcher2.group(3));
    • Name으로 그룹 가져오기

      1
      2
      3
      4
      5
      6
      7
      8
       Pattern pattern2 = Pattern.compile("^(?<field>\\w+):(?<name>\\w+)\\.(?<ext>\\w+)$");
      Matcher matcher2 = pattern2.matcher("filename:temp.png");

      matcher2.find();

      System.out.println(matcher2.group("field")); // name group
      System.out.println(matcher2.group("name"));
      System.out.println(matcher2.group("ext"));

Comment and share

Multi-Thread Programming

Process and Thread

  • Process: OS로부터 메모리를 할당받아 실행중인 프로그램
  • Thread: 프로세스 동작의 최소 단위로, 하나의 프로세스는 여러 스레드로 이루어질 수 있다.

멀티스레드 프로그래밍의 장단점

  • 장점
    • 여러 동작을 병렬적으로 처리하여 CPU의 사용률 향상
    • 시간이 걸리는 동작을 분리하여 프로그램의 응답성 향상
  • 단점
    • Context Switching 오버헤드 발생
    • 스레드 제어의 어려움

스레드 구현

  • 스레드 생성 방법

    1. 익명함수

      1
      2
      3
      4
      5
      Thread threadOne = new Thread(new Runnable() {
      public void run() {
      System.out.println("Hello Thread!");
      }
      });
    2. 람다식

      1
      2
      3
      Thread threadTwo = new Thread(() -> {
      System.out.println("Hello Again, Thread!");
      });
    3. Thread Class 상속

      1
      2
      3
      4
      5
      6
      class MyThread extends Thread {
      public void run() {
      System.out.println("Hello Again Again, Thread!");
      }
      }
      Thread threadThree = new MyThread();
  • 스레드 실행

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Thread threadOne = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
    System.out.print("1");
    }
    });

    Thread threadTwo = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
    System.out.print("2");
    }
    });

    threadOne.start();
    threadTwo.start();
    System.out.println("Done!");

    Thread 객체는 일회성이기 때문에 여러 번 start()메소드를 호출 할 수 없다


스레드의 상태 및 제어

  • 스레드의 상태

    • getState() 메소드로 스레드의 상태를 확인할 수 있다.

      열거형 상수 설명
      NEW start() 메소드가 아직 호출되지 않음
      RUNNABLE JVM에 의해 실행 가능한 상태
      BLOCKED 객체가 블락된 상태
      WAITING sleep(), wait(), join() 등에 의해 무한히 대기 중인 상태
      TIMED_WAITING sleep(), wait(), join() 등에 의해 정해진 시간 동안 대기 중인 상태
      TERMINATE run() 메소드가 종료된 상태
  • 스레드의 우선순위 제어

    1
    2
    3
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    메소드 설명
    void setPriority(int newPriority) 새로운 우선순위로 설정한다.
    int getPriority() 우선순위를 반환한다.
  • sleep()을 이용한 제어

    1
    2
    Thread.sleep(1000); // ms
    Thread.sleep(100, 200); // ms + ns
  • join()을 이용한 스레드 조인

    • 스레드 동작을 동기화하기 위해 사용

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      Thread t1 = new Thread(() -> System.out.println("A"));
      Thread t2 = new Thread(() -> System.out.println("B"));

      t1.start(); // t1 thread start

      t1.join(); // t1 thread 종료시 까지 다른 thread 대기

      t2.start(); // t2 thread start

      t2.join(100); // 100ms 기다린 후에도 t2가 종료되지 않으면 다른 쓰레드 시작

      System.out.println("C");
  • interrupt()를 이용한 대기 중지

    • 기존 동작을 방해하고 반응을 강제하는 메소드
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      Thread tSleep = new Thread(() -> {
      try {
      Thread.sleep(1000);
      } catch (InterruptedException e) {
      System.out.println("Interrupted");
      }
      });

      tSleep.start();

      try {
      Thread.sleep(500);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }

      tSleep.interrupt();
  • yield()를 이용한 상태 제어

    • sleep()과 달리 곧바로 RUNNABLE 상태로 변경
    • 다른 쓰레드에게 양보하고 바로 실행 대기
    • 테스트용으로 쓰임 -> 쓰레드의 동작이 명확하지 않음(바로 양보하지 않는다)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      new Thread(() -> {
      for (int i = 0; i < 20; i++) {
      if (i % 2 == 0) {
      System.out.print("1");
      } else {
      Thread.yield();
      }
      }
      }).start();

      new Thread(() -> {
      for (int i = 0; i < 20; i++) {
      if (i % 2 == 0) {
      System.out.print("2");
      } else {
      Thread.yield();
      }
      }
      }).start();
  • 스레드의 종료

    • run() 메소드의 종료
    • stop() 메소드 호출 (deprecated)

데몬 스레드

  • 정의 : 다른 스레드가 종료될 경우 함께 종료되는 보조 스레드

  • 활용법

    • 보통 대기하며 동작하는 무한 루프로 구현
    • 일정 시간 마다 동작
    • interrupt에 의해서 동작
  • setDaemon() 메소드로 데몬 스레드로 설정

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class DaemonThread extends Thread {
    public DaemonThread() {
    this.setDaemon(true);
    }

    @Override
    public void run() {
    while (true) {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.println("Daemon Thread Run");
    }
    }
    }

데이터 공유와 동기화

  • 스레드간 데이터 공유 시 신뢰성에 문제가 발생할 수 있음

    결과는 1000000이 나오지 않는다 -> count값이 동기화되지 않았기 때문

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Counter {
    int count = 0;
    }
    public class Main {
    public static void main(String args[]){
    Counter counter = new Counter();

    for (int i = 0; i < 1000; i++) {
    new Thread(() -> {
    for (int j = 0; j < 1000; j++) {
    counter.count = counter.count + 1;
    }
    }).start();
    }

    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    System.out.println(counter.count);
    }
    }
  • synchronized 키워드 사용

    1
    2
    3
    synchronized void method() {
    // 공유 데이터 사용
    }
    1
    2
    3
    4
    5
    void method() {
    synchronized(sharedObj) {
    // 공유 데이터 사용
    }
    }
    • Example

      • this 객체의 Intrinsic Lock을 이용한 구현

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        class Counter {
        private int count = 0;
        public int increaseCount() {
        synchronized (this) {
        return ++count; // 읽고, 수정하고, 쓰는 작업
        }
        }

        public int getCount() {
        return count;
        }
        }
      • 메소드에 synchronized 키워드 사용

        • synchronized 키워드가 사용된 메소드를 호출하기 위해서는 해당 객체를 소유해야만 호출이 가능. 소유하지 못하면 Blocking
          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          class Counter {
          private int count = 0;
          public synchronized int increaseCount() {
          return ++count; // 읽고, 수정하고, 쓰는 작업
          }

          public int getCount() {
          return count;
          }
          }
  • wait(), notify(), notifyAll()

    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
    class WorkObject {
    public synchronized void methodA() {
    System.out.println("ThreadA의 methodA() 작업 실행");
    notify(); // 일시정지 상태에 있는 ThreadB를 실행 대기상태로 만듬
    try {
    wait(); // ThreadA를 일시 정지 상태로 만듬
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    public synchronized void methodB() {
    System.out.println("ThreadB의 methodB() 작업 실행");
    notify(); // 일시정지 상태에 있는 ThreadA를 실행 대기상태로 만듬
    try {
    wait(); // ThreadB를 일시 정지 상태로 만듬
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }


    class ThreadA extends Thread {
    private WorkObject workObject;

    ThreadA(WorkObject workObject) {
    this.workObject = workObject;
    }

    public void run() {
    for(int i=0; i<10; i++) {
    workObject.methodA(); // 공유객체의 methodA를 반복적으로 호출
    }
    }
    }

    class ThreadB extends Thread{
    private WorkObject workObject;

    ThreadB(WorkObject workObject) {
    this.workObject = workObject;
    }

    public void run() {
    for(int i=0; i<10; i++) {
    workObject.methodB(); // 공유객체의 methodA를 반복적으로 호출
    }
    }
    }

    class Main {
    public static void main(String[] args) {
    WorkObject sharedObject = new WorkObject();

    ThreadA threadA = new ThreadA(sharedObject);
    ThreadB threadB = new ThreadB(sharedObject);

    threadA.start();
    threadB.start();
    }
    }

세마포어

  • 사전적 의미 : 횟대(깃발)

  • n개의 깃발을 놓고, 여러 스레드가 경쟁하도록 하는 sync 기법

  • n = 1이면, BinarySemaphore라고 하며, Lock과 유사하게 동작

  • 메소드

    • release(int permits) : permits만큼의 자원 개수 반환
    • acquire(int permits) : permits만큼의 자원 사용 (Blocking O)
    • tryAcquire(long timeout, TimeUnit unit) : unit단위의 timeout이후 자원을 얻지 못하면 false, 얻으면 true (Blocking X)
    • acquireUninterruptibly() : 자원을 얻지못해도 interrupt 발생 X
  • Example

    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
    public class Main {
    public static void main(String[] args) {
    Semaphore sem = new Semaphore(1);

    sem.release(10);
    sem.tryAcquire();
    System.out.println(sem.availablePermits());

    try { // Blocking으로 동작
    sem.acquire(12);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    sem.acquireUninterruptibly(); // interrupt에 반응하지 않음
    try {
    System.out.println(sem.tryAcquire(100, TimeUnit.MILLISECONDS)); // Blocking 하지 않고, 실패하면 false
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    sem.release();

    System.out.println(sem.availablePermits());
    }
    }
  • Dining Philosopher (식사하는 철학자)

    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
    class Philosopher extends Thread {
    private final int id;
    private final Fork left;
    private final Fork right;

    public Philosopher(int id, Fork left, Fork right) {
    this.id = id;
    this.left = left;
    this.right = right;
    }

    @Override
    public void run() {
    while (true) {
    try {
    left.acquire();
    System.out.println(id + ": left taken");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    try {
    if(!right.tryAcquire(1000, TimeUnit.MILLISECONDS)){
    left.release();
    Thread.yield();
    continue;
    }
    System.out.println(id + ": right taken");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    try {
    Thread.sleep(2000);
    System.out.println(id + "is eating");
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

    left.release();
    right.release();
    Thread.yield();
    }
    }
    }

    class Fork extends Semaphore {

    public Fork() {
    super(1);
    }
    }
    public class Main {
    public static void main(String[] args) {
    Philosopher[] phils = new Philosopher[5];
    Fork[] forks = new Fork[5];

    for (int i = 0; i < 5; i++) {
    forks[i] = new Fork();
    }

    for (int i = 0; i < 5 - 1; i++) {
    phils[i] = new Philosopher(i, forks[i], forks[(i+1) % 5]);
    }

    phils[4] = new Philosopher(4, forks[0], forks[4]);
    for (int i = 0; i < 5; i++){
    phils[i].start();
    }
    }
    }

JCF와 Thread

  • Vector VS List

    • List
      • 메소드들이 Synchronized 되지 않음
      • 속도는 빠르다.
    • Vector
      • 메소드들이 Synchronized 되어 있음
      • 속도는 느리다.
  • List를 Synchronized 되게 하는 방법

    • Collections의 synchronizedList() 사용
      1
      2
      List<Integer> list1 = new ArrayList<>();
      List<Integer> list2 = Collections.synchronizedList(list1);

      Thread Pool

  • Thread Pool이란?

    • 미리 생성해 둔 Thread의 집합을 스레드 풀이라고 한다.
    • 미리 Thread를 생성해 두고, 작업만 Thread에 할당하여 동작
    • 사용 이유
      • Thread를 직접 만들어 사용할 경우, Multi-Thread 작업을 계속 할 때, Thread 생성/삭제 오버해드가 크다.
      • 배치작업 (모아두고 한 번에 처리하는 작업)에 유용하여 많이 사용된다.
  • Thread Pool 생성

    • Single Thread Pool

      1
      ExecutorService pool = Executors.newSingleThreadExecutor();
      • Thread가 1개인 ThreadPool 생성(SingleThread)
      • 실패 시, 새로운 Thread를 생성하지 않음
    • Cached Thread Pool

      1
      ExecutorService pool = Executors.newCachedThreadPool();
      • 초기 Thread 0개
      • 코어 Thread 0개 (일하지 않아도 종료시키지 않는 Thread)
      • 요청 작업보다 Thead가 부족하면 새 스레드 생성
      • 60초동안 일하지 않는 Thread 제거
    • Fixed Thread Pool

      1
      ExecutorService pool = Executors.newFixedThreadPool(int nThreads);
      • 최대 Thread nThreads개
      • 코어 Thread nThreads개
      • 작업하지 않는 Thread도 제거하지 않고 동작
    • Scheduler Thread Pool

      1
      ScheduledExecutorService pool = Executors.newScheduledThreadPool(int corePoolSize);
      • 지정된 delay후에 실행하도록함(주기적으로 실행하는 명령을 예약)
  • 작업 생성과 처리 요청

    • 작업 생성

      • Runnable 구현 클래스

        1
        2
        3
        4
        5
        6
        Runnable task = new Runnable(){
        @Override
        public void run(){
        // 작업 내용
        }
        }
      • Callable 구현 클래스

        1
        2
        3
        4
        5
        6
        7
        Callable<T> task = new Callable<T>(){
        @Override
        public T call() throws Exception{
        // 작업 내용
        return T;
        }

        Runnable run()은 Return 값이 없고, Callable call()은 Return 값이 있음

    • 처리 요청

      • 매소드

        리턴 타입 메소드 설명
        void execute(Runnable command) Runnable을 작업 큐에 저장, 작업 처리 결과를 받지 못함
        Future<?> submit(Runnable task) Runnable 작업 큐에 저장, 리턴된 Future를 통해 작업 처리 결과를 얻을 수 있음
        Future submit(Runnable task, V result) Runnable 작업 큐에 저장, 리턴된 Future를 통해 작업 처리 결과를 얻을 수 있음
        Future submit(Callable task) Callable 작업 큐에 저장, 리턴된 Future를 통해 작업 처리 결과를 얻을 수 있음
        • execute()

          • 작업 처리 결과를 반환하지 않는다.

          • 작업 처리 도중 예외가 발생하면 스레드가 종료되고 해당 스레드는 스레드 풀에서 제거된다.

          • 다른 작업을 처리하기 위해 새로운 스레드를 생성한다.

        • submit()

          • 작업 처리 결과를 반환한다.

          • 작업 처리 도중 예외가 발생하더라도 스레드는 종료되지 않고 다음 작업을 위해 재사용

          • 스레드의 생성 오버헤드를 방지하기 위해서라도 submit() 을 가급적 사용한다.

  • Thread Pool 종료

    Thread Pool에 속한 Thread는 데몬 스레드가 아니다

    따라서 주 스레드 종료시 강제종료 되지 않기 때문에 main 스레드가 종료되어도 실행상태가 유지된다.

    어플리케이션을 종료하기 위해서는 스레드 풀을 강제종료시켜야 한다.

    • excutorService.shutdown()

      작업큐에 남아있는 작업까지 모두 마무리 후 종료 (오버헤드를 줄이기 위해 일반적으로 많이 사용.)

    • excutorService.shoutdownNow()

      작업큐 작업 잔량 상관없이 강제 종료

    • excutorService.awaitTermination(long timeout, TimeUnit unit)

      모든 작업 처리를 timeout 시간안에 처리하면 true 리턴 ,처리하지 못하면 작업스레드들을 interrupt()시키고 false리턴

Comment and share

Design Patterns

디자인 패턴이란

  • 자주 발생하는 문제를 해결하기 위해 설계된 재사용 가능한 해결책
    • Don’t reinvent the wheel
  • 소프트웨어 설계 문제를 쉽게 해결할 수 있도록 패턴화된 설계 방식
  • 팀원들이 공유할 수 있는 공통의 언어가 되어 소통을 원활하게 함

디자인 패턴의 구조

  • 문맥 (Context)
    • 패턴이 적용될 수 있는 문제 상황을 기술
  • 문제 (Problem)
    • 패턴이 적용되어 해결되어야 하는 여러 설계 이슈를 기술
  • 해결 (Solution)
    • 문제를 해결하는 설계 구성 요소와 구성 요소 사이의 관계를 기술

디자인 패턴의 종류

  • Gang of four 패턴

    • 생성 패턴 (Creational patterns)
      • 객체의 생성 방식에 관련된 패턴
      • Abstract Factory, Factory Method, Singleton …
    • 구조 패턴 (Structural patterns)
      • 클래스/객체를 조합한 구조를 가지는 패턴
      • Composite, Decorator …
    • 동작 패턴 (Behavioral patterns)
      • 클래스/객체 사이의 동작 분배에 관련된 패턴
      • Observer, State, Strategy, Template Method, Command …
  • 동시성 패턴 (Concurrency patterns)

    • Scheduling, Monitor, Lock …
  • 아키텍처 패턴 (Architecture patterns)

    • Model-View-Controller, Model-View-Presenter, Model-View-ViewModel …
  • 기타 패턴

    • Dependency injection, Lazy loading, Mock object …

대표적인 디자인 패턴

  • 싱글톤 패턴

    • 단 하나의 객체만 존재할 수 있는 클래스를 구현하는 패턴
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Singleton {
    private static Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
    if(instance == null) {
    instance = new Singleton();
    }
    return instance;
    }
    }
  • 팩토리 패턴

    • 구상 클래스 객체를 전담하여 생성하는 클래스를 구현하는 패턴
    • 팩토리 메소드 패턴 (ref)

    img1

    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
    interface Shape {
    void draw();
    }

    class Rectangle implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside Rectangle::draw() method.");
    }
    }

    class Square implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside Square::draw() method.");
    }
    }

    class Circle implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside Circle::draw() method.");
    }
    }

    class ShapeFactory {


    Shape getShape(String shapeType){
    if(shapeType == null){
    return null;
    }
    if(shapeType.equalsIgnoreCase("CIRCLE")){
    return new Circle();

    } else if(shapeType.equalsIgnoreCase("RECTANGLE")){
    return new Rectangle();

    } else if(shapeType.equalsIgnoreCase("SQUARE")){
    return new Square();
    }

    return null;
    }
    }
    class FactoryPatternDemo {
    public static void main(String[] args) {
    ShapeFactory shapeFactory = new ShapeFactory();

    Shape shape1 = shapeFactory.getShape("CIRCLE");
    shape1.draw();

    Shape shape2 = shapeFactory.getShape("RECTANGLE");

    shape2.draw();

    Shape shape3 = shapeFactory.getShape("SQUARE");

    shape3.draw();
    }
    }
    • 추상 팩토리 패턴 (ref)

    img2

    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
    public interface Shape {
    void draw();
    }

    class RoundedRectangle implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside RoundedRectangle::draw() method.");
    }
    }

    class RoundedSquare implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside RoundedSquare::draw() method.");
    }
    }

    class Rectangle implements Shape {
    @Override
    public void draw() {
    System.out.println("Inside Rectangle::draw() method.");
    }
    }

    abstract class AbstractFactory {
    abstract Shape getShape(String shapeType) ;
    }

    class ShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType){
    if(shapeType.equalsIgnoreCase("RECTANGLE")){
    return new Rectangle();
    }else if(shapeType.equalsIgnoreCase("SQUARE")){
    return new Square();
    }
    return null;
    }
    }

    class RoundedShapeFactory extends AbstractFactory {
    @Override
    public Shape getShape(String shapeType){
    if(shapeType.equalsIgnoreCase("RECTANGLE")){
    return new RoundedRectangle();
    }else if(shapeType.equalsIgnoreCase("SQUARE")){
    return new RoundedSquare();
    }
    return null;
    }
    }

    class FactoryProducer {
    public static AbstractFactory getFactory(boolean rounded){
    if(rounded){
    return new RoundedShapeFactory();
    }else{
    return new ShapeFactory();
    }
    }
    }

    class AbstractFactoryPatternDemo {
    public static void main(String[] args) {
    AbstractFactory shapeFactory = FactoryProducer.getFactory(false);

    Shape shape1 = shapeFactory.getShape("RECTANGLE");
    shape1.draw();
    Shape shape2 = shapeFactory.getShape("SQUARE");
    shape2.draw();

    AbstractFactory shapeFactory1 = FactoryProducer.getFactory(true);

    Shape shape3 = shapeFactory1.getShape("RECTANGLE");
    shape3.draw();
    Shape shape4 = shapeFactory1.getShape("SQUARE");
    shape4.draw();
    }
    }
  • 데코레이터 패턴

    • 생성자를 이용해 객체에 일정한 기능을 추가하는 패턴 (ref)

    img3

    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
    interface Shape {
    void draw();
    }

    class Rectangle implements Shape {

    @Override
    public void draw() {
    System.out.println("Shape: Rectangle");
    }
    }

    class Circle implements Shape {

    @Override
    public void draw() {
    System.out.println("Shape: Circle");
    }
    }

    abstract class ShapeDecorator implements Shape {
    protected Shape decoratedShape;

    public ShapeDecorator(Shape decoratedShape){
    this.decoratedShape = decoratedShape;
    }

    public void draw(){
    decoratedShape.draw();
    }
    }

    class RedShapeDecorator extends ShapeDecorator {

    public RedShapeDecorator(Shape decoratedShape) {
    super(decoratedShape);
    }

    @Override
    public void draw() {
    decoratedShape.draw();
    setRedBorder(decoratedShape);
    }

    private void setRedBorder(Shape decoratedShape){
    System.out.println("Border Color: Red");
    }
    }

    class DecoratorPatternDemo {
    public static void main(String[] args) {

    Shape circle = new Circle();

    Shape redCircle = new RedShapeDecorator(new Circle());

    Shape redRectangle = new RedShapeDecorator(new Rectangle());
    System.out.println("Circle with normal border");
    circle.draw();

    System.out.println("\nCircle of red border");
    redCircle.draw();

    System.out.println("\nRectangle of red border");
    redRectangle.draw();
    }
    }
  • 옵저버 패턴

    • Observable 객체의 변화를 Observer에서 알 수 있도록 하는 패턴 ref

    img4

    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
    class Subject {
    private List<Observer> observers = new ArrayList<Observer>();
    private int state;

    public int getState() {
    return state;
    }

    public void setState(int state) {
    this.state = state;
    notifyAllObservers();
    }

    public void attach(Observer observer){
    observers.add(observer);
    }

    public void notifyAllObservers(){
    for (Observer observer : observers) {
    observer.update();
    }
    }
    }

    abstract class Observer {
    protected Subject subject;
    public abstract void update();
    }

    class BinaryObserver extends Observer{

    public BinaryObserver(Subject subject){
    this.subject = subject;
    this.subject.attach(this);
    }

    @Override
    public void update() {
    System.out.println( "Binary String: " + Integer.toBinaryString( subject.getState() ) );
    }
    }

    class OctalObserver extends Observer{

    public OctalObserver(Subject subject){
    this.subject = subject;
    this.subject.attach(this);
    }

    @Override
    public void update() {
    System.out.println( "Octal String: " + Integer.toOctalString( subject.getState() ) );
    }
    }

    class HexaObserver extends Observer{

    public HexaObserver(Subject subject){
    this.subject = subject;
    this.subject.attach(this);
    }

    @Override
    public void update() {
    System.out.println( "Hex String: " + Integer.toHexString( subject.getState() ).toUpperCase() );
    }
    }

    class ObserverPatternDemo {
    public static void main(String[] args) {
    Subject subject = new Subject();

    new HexaObserver(subject);
    new OctalObserver(subject);
    new BinaryObserver(subject);

    System.out.println("First state change: 15");
    subject.setState(15);
    System.out.println("Second state change: 10");
    subject.setState(10);
    }
    }

Comment and share

애노테이션 (Annotations)

애노테이션이란

  • 애노테이션의 사전적인 의미는 주석
  • JVM, 컴파일러, 프레임워크 등에게 전달하는 메타데이터로 사용됨

기본 애노테이션

애노테이션 설명 비고
@Override 상속하여 오버라이드된 메소드
@Deprecated 앞으로 사라질 예정임을 표기
@SuppressWarnings 컴파일러에게 특정 경고 메세지를 무시하도록 한다. eg. @SuppressWarnings(“unused”)
@FunctionalInterface 함수형 인터페이스임을 표기 (Lambda)

애노테이션의 구성 요소

  • 애노테이션의 작성
    • 추상 메소드와 유사한 형태로 구현
1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) // 메타 애노테이션
@Retention(RetentionPolicy.SOURCE) // 메타 애노테이션
public @interface SuppressWarnings { // 애노테이션 선언
String [] value(); // 애노테이션 속성
}
  • 애노테이션의 사용
    • key=value 형태로 애노테이션에 속성의 값 전달
1
2
3
4
5
@SuppressWarnings(value = {"unused", "rawtypes"}) // 키 = 값 배열
@SuppressWarnings(value = "unused") // 값이 하나인 경우 배열 생략 가능
@SuppressWarnings({"unused", "rawtypes"}) // 속성이 value 하나인 경우 키 생략 가능

@CustomAnnotation(key1 = "value1", key2 = {"value2", "value3"}) // 속성이 여러개인 경우 키를 여러개 사용
  • Target 설정

    • 애노테이션을 사용할 수 있는 대상을 설정

    • ElementType 열거형 상수 사용

      ElementType 범위
      TYPE 클래스, 인터페이스, 애노테이션, 열거형
      FIELD 필드(멤버 변수), 열거형 상수
      METHOD 메소드
      PARAMETER 메소드의 파라미터
      CONSTRUCTOR 생성자
      LOCAL_VARIABLE 로컬 변수
      ANNOTATION_TYPE 애노테이션
      PACKAGE 패키지
  • Retention 설정

    • 애노테이션 정보를 언제까지 유지할 것인지 설정

    • RetentionPolicy 열거형 상수 사용

    • reflection: java.lang.reflect API

      RetentionPolicy 범위
      SOURCE 컴파일러가 사용. 빌드된 .class 파일에는 포함되지 않음
      CLASS .class 파일에 포함되나, VM이 사용하지 않으며 reflection에 정보를 포함하지 않음
      RUNTIME .class에 포함, VM에서 인식, reflection에 정보를 포함

사용자 정의 애노테이션

  • 사용자 정의 애노테이션 구현

    1
    2
    3
    4
    5
    6
    @Retention(RUNTIME)
    @Target(FIELD)
    public @interface CustomAnnotation {
    String value();
    String valueTwo() default "기본값";
    }
  • 사용자 정의 애노테이션 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class CustomAnnotationUsage {
    @CustomAnnotation("game")
    String gameName = "여러분의 틱택토";
    @CustomAnnotation(value = "server", valueTwo = "localhost")
    String serverIP;
    @CustomAnnotation("game")
    String gameMode = "AI vs. AI";
    @CustomAnnotation(value = "server", valueTwo = "8888")
    String serverPort;
    }
  • Reflection을 이용하여 애노테이션에 할당된 값 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class CustomAnnotationTest {
    public static void main(String[] args) throws Exception {
    CustomAnnotationUsage obj = new CustomAnnotationUsage();
    Map<String, Object> gameProp = new HashMap<>();
    Map<String, Object> serverProp = new HashMap<>();

    Field[] fields = CustomAnnotationUsage.class.getDeclaredFields();
    for (Field field: fields) {
    CustomAnnotation annotation = field.getDeclaredAnnotation(CustomAnnotation.class);
    if (field.get(obj) == null) {
    field.set(obj, annotation.valueTwo());
    }
    if (annotation.value().equals("game")) {
    gameProp.put(field.getName(), field.get(obj));
    } else {
    serverProp.put(field.getName(), field.get(obj));
    }
    }

    System.out.println(gameProp);
    System.out.println(serverProp);

    }
    }

Comment and share

자바 컬렉션 프레임워크 (Java Collections Framework; JCF)

컬렉션 프레임워크란

  • java.utils에 속한 일련의 클래스로, 자료구조를 담당
  • 잘 짜여진 인터페이스를 기반으로 다양한 자료구조를 구현
  • 제네릭 클래스로 되어 있어, 다양한 객체를 요소로 담을 수 있다.

JCF 인터페이스

  • List 인터페이스
    • Collection 인터페이스를 상속
    • 순서가 있는 데이터의 집합으로, 데이터의 중복을 허용
    • 구현체: ArrayList, LinkedList(, Vector, Stack)
  • Set 인터페이스
    • Collection 인터페이스를 상속
    • 순서가 없는 데이터의 집합으로, 데이터의 중복을 허용하지 않음
    • 구현체: HashSet, TreeSet
  • Map 인터페이스
    • Key-Value 쌍으로 이루어진 데이터의 집합
    • 구현체: HashMap, TreeMap, Hashtable, Properties

Collection 인터페이스

  • Collection 인터페이스의 주요 메소드

    메소드 설명
    boolean add(E e) 요소 e를 컬렉션에 추가한다.
    boolean addAll(Collection<? exteionds E> c) 다른 컬렉션 c의 모든 요소를 컬렉션에 추가한다.
    void clear() 컬렉션의 모든 요소를 제거한다.
    boolean contains(Object o) 컬렉션에 요소 o가 존재하는지 확인한다.
    boolean containsAll(Collection<?> c) 컬렉션 c의 모든 요소가 컬렉션에 포함되는지 확인한다.
    boolean equals(Object o) 컬렉션 o와 같은 내용을 포함하는지 비교한다.
    boolean isEmpty() 컬렉션이 비어있는지 확인한다.
    Iterator<E> iterator() 컬렉션의 Iterator를 반환한다.
    boolean remove(Object o) 컬렉션에서 요소 o를 제거한다.
    boolean removeAll(Collection<?> c) 컬렉션 c에 속한 모든 요소를 컬렉션에서 제거한다.
    boolean retainAll(Collection<?> c) 컬렉션 c에 포함된 객체만 남기고 나머지 요소를 제거한다.
    int size() 컬렉션에 속한 요소의 개수를 반환한다.
    T[] toArray(T[] a) 컬렉션의 요소들을 T[] 배열로 반환한다.
  • Iterator

    • iterator()로 반환되는 객체로, Collection에 저장된 요소에 접근하기 위해 사용

      메소드 설명
      boolean hasNext() 다음 요소가 있는지 확인
      E next() 다음 요소가 있을 경우 반환
      void remove() 현재 위치의 요소를 삭제
    • Iterator의 활용

      1
      2
      3
      4
      5
      Iterator<String> iter = collection.iterator();
      while (iter.hasNext()) {
      String string = iter.next();
      System.out.println(string);
      }
      1
      2
      3
      for (String string: collection) {
      System.out.println(string);
      }

List 인터페이스

  • 순서가 있는 데이터의 집합을 다루는 인터페이스

  • List는 인덱스를 이용해 요소를 구분할 수 있어, 데이터의 중복을 허용

  • Collection 인터페이스를 상속받았으며, 추가 메소드가 구현되어 있다.

    메소드 설명
    void add(int index, E element) index 위치에 요소 element를 삽입한다.
    boolean addAll(int index, Collection<? extends E> c) index위치부터 컬렉션 c의 모든 요소를 추가한다.
    E get(int index) index 위치에 저장된 요소를 반환한다.
    int indexOf(Object o) 객체 o가 저장된 첫번째 인덱스를 반환한다. 없을 경우 -1을 반환한다.
    int lastIndexOf(Object o) 객체 o가 저장된 마지막 인덱스를 반환한다. 없을 경우 -1을 반환한다.
    ListIterator<E> listIterator() ListIterator 객체를 반환한다.
    E remove(int index) index에 위치한 객체를 제거하고 반환한다.
    E set(int index, E element) index에 위치한 요소를 element로 대체한다.
    List<E> subList(int fromIndex, int toIndex) fromIndex에 위치한 요소부터 toIndex의 직전에 위치한 요소까지 포함한 List를 반환한다.
  • List 인터페이스의 구현체

    1
    2
    List<String> list = new ArrayList<>();
    list = new LinkedList<>(); // 동일 인터페이스로 다른 구현체를 사용 가능
    • ArrayList<E>

      • 제네릭 클래스로 구현된 자료구조

      • 배열을 기반으로 구현된 클래스로, 가장 자주 활용되며 활용도가 높다.

      • ArrayList의 생성자는 세 종류가 주어진다.

        1
        2
        3
        public ArrayList()
        public Arraylist(int initialCapacity)
        public ArrayList(Collection<? extends E>)
    • LinkedList<E>

      • 제네릭 클래스로 구현된 자료구조

      • 연결리스트를 기반으로 구현된 클래스로, 배열의 단점을 극복하기 위한 구현체

      • ArrayList vs. LinkedList

        구현체 순차 추가/수정/삭제 비순차 추가/수정/삭제 조회
        ArrayList 빠름 느림 빠름
        LinkedList 느림 빠름 느림
    • ArrayList, LinkedList

      • Object를 요소로 가지는 List 구현체 (Java5 이전)
  • List의 정렬

    • Collections 클래스의 sort() 메소드 사용
      • public static <T extends Comparable<? super T> void sort(List<T> list)
        • 객체에 선언된 public int compareTo(T t) 메소드로 비교하여 정렬
      • public static <T> void sort(List<T> list, Comparator<? super T> c)
        • Comparator 객체 c에 정의된 public int compare(T o1, T o2) 메소드로 비교하여 정렬
  • ListIterator

    • listIterator()로 반환되는 객체로, 양방향 사용이 가능
    • Iterator를 상속받은 클래스
    메소드 설명
    boolean hasPrevious() 이전 요소가 있는지 확인
    E previous() 이전 요소가 있을 경우 반환

Set 인터페이스

  • 순서가 없는 데이터의 집합을 다루는 인터페이스

  • 중복되는 데이터를 효율적으로 제거하는 데에 쓸 수 있음

  • Collection 인터페이스를 상속받아 메소드를 구현하고 있음

  • Set의 구현체

    • HashSet<E>

      • Set의 대표적인 구현체로, 기본적인 기능이 구현되어 있다.
    • TreeSet<E>

      • NavigableSet<E> 인터페이스를 구현하며, 이진 탐색 트리 자료구조이다.

      • 객체를 삽입하는 동시에 정렬되는 특성상 비교가 가능해야 한다.

        1
        2
        public TreeSet() // Comparable 구현체의 경우
        public TreeSet(Comparator<? super E> comparator) // 별도로 비교 객체 삽입
      • TreeSet의 메소드

      메소드 설명
      public E first() 정렬된 첫 요소를 반환
      public E last() 정렬된 마지막 요소를 반환
      public E lower(E e) e 객체의 바로 이전 객체를 반환
      public E higher(E e) e 객체의 바로 다음 객체를 반환
      public E floor(E e) e 또는 e 보다 이전 객체를 반환
      public E ceiling(E e) e 또는 e 보다 이후 객체를 반환
      public E pollFirst() 정렬된 첫 요소를 제거하고 반환
      public E pollLast() 정렬된 마지막 요소를 제거하고 반환
      public NavigableSet<E> descendingSet() 내림차순으로 정렬하는 객체를 반환
      public NavigableSet<E> headSet(E toElement, boolean inclusive) toElement 이전 요소로 구성된 객체 반환
      public NavigableSet<E> tailSet(E fromElement, boolean inclusive) fromElement 이후 요소로 구성된 객체 반환
      public NavigableSet<E> subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive) fromElement 이후, toElement 이전 요소로 구성된 객체 반환

Map 인터페이스

  • Key와 Value의 쌍으로 구성된 자료의 집합을 다루는 인터페이스\

    • Key, Value 쌍은 Map.Entry<K, V> 객체로 관리
  • Key는 중복될 수 없으며, Value는 중복이 가능

  • Map<K, V>의 구현체

    • HashMap<K, V> : 가장 많이 사용되는 Map의 구현체
    • TreeMap<K, V> : 정렬 기준이 더해진 NavigableMap을 구현한 구현체
    • Hashtable<K, V> : HashMap과 유사하지만 성능이 떨어지는 대신 멀티스레드에 안전한 구현체
    • Properties : 속성 파일을 손쉽게 관리하는, Hashtable<K, V>의 하위 클래스 구현체
  • Map<K, V>의 주요 메소드

    메소드 설명
    void clear() Map의 모든 요소를 삭제
    boolean containsKey(Object key) key가 존재하는지 여부를 반환
    boolean containsValue(Object value) value가 존재하는지 여부를 반환
    Set<Map.Entry<K,V>> entrySet() Map의 Entry 요소를 Set에 담아 반환
    V get(Object key) key에 해당하는 value 객체 반환
    Set<K> keySet() key 요소를 Set 형태로 반환
    V put(K key, V value) key와 value 쌍으로 구성된 Entry 추가. 기존에 등록된 key라면 해당 value를, 없으면 null을 반환
    void putAll(Map<? extends K, ? extends V> m) m에 속한 모든 Entry를 추가
    V remove(Object key) key에 해당하는 Entry를 제거하고 반환. 등록되지 않은 key라면 null 반환
    Collection<V> values value 객체들을 Collection에 담아 반환
    int size() Map에 포함된 Entry 객체의 수 반환
    boolean isEmpty() Map에 데이터가 없는지 여부 반환
  • TreeMap<K, V>의 주요 메소드

    메소드 설명
    public Map.Entry<K, V> firstEntry() 정렬된 첫 Entry 반환
    public Map.Entry<K, V> lastEntry() 정렬된 마지막 Entry 반환
    public Map.Entry<K, V> lowerEntry(K key) key 바로 이전 Entry 반환
    public Map.Entry<K, V> higherEntry(K key) key 바로 다음 Entry 반환
    public Map.Entry<K, V> floorEntry(K key) key 또는 key 보다 이전 Entry 반환
    public Map.Entry<K, V> ceilingEntry(K key) key 또는 key 보다 이후 Entry 반환
    public Map.Entry<K, V> pollFirstEntry() 정렬된 첫 Entry를 제거하고 반환
    public Map.Entry<K, V> pollLastEntry() 정렬된 마지막 Entry를 제거하고 반환
    public NavigableSet<E> descendingKeySet() 내림차순으로 정렬하는 Set 반환
    public NavigableSet<E> descendingKeyMap() 내림차순으로 정렬하는 Map 반환
    public NavigableMap<K, V> headMap(K toKey, boolean inclusive) toKey 이전 Entry로 구성된 객체 반환
    public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) fromKey 이후 Entry로 구성된 객체 반환
    public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) fromKey 이후, toKey 이전 Entry로 구성된 객체 반환
  • Properties의 주요 메소드

    메소드 설명
    getProperty() key에 해당하는 속성 값을 반환
    stringPropertyNames() key의 목록을 Set<String>으로 반환
    setProperty() key로 value를 등록하고 기존 value 값을 반환
    load() reader를 이용해 속성을 읽어온다. (.properties)
    loadFromXML() XML 형식으로 저장된 속성을 읽어온다.
    store() writer를 이용해 속성을 저장한다.
    storeToXML() XML 형식으로 속성 파일을 저장한다.

Comment and share

I/O

I/O와 스트림

  • I/O란 입출력(Input/Output)을 함께 부르는 것
  • 자바의 I/O는 스트림을 이용해 데이터를 주고 받는 형식으로 이루어짐
    • 데이터의 소스와 목적지를 노드(node)라 부름
    • 노드는 키보드, 모니터, 파일, 메모리, 데이터베이스, 다른 프로그램 등이 될 수 있음

노드 스트림

InputStream과 Reader

  • InputStream의 주요 메소드 (byte 단위)

    메소드 설명
    int read() byte 하나를 읽어서 int로 반환. 읽을 값이 없으면 -1
    int read(byte b[]) 데이터를 읽어 b를 채우고, 읽은 바이트 수를 반환
    int read(byte b[], int offset, int len) 최대 len개의 바이트를 읽어 b의 offset 위치부터 채운다.
    void close() 스트림을 종료하고 자원을 반납
    int available() 읽을 수 있는 데이터의 크기를 반납
    long skip(long n) 스트림에서 n만큼 건너 뛴다.
    void mark(int readLimit) reset()으로 돌아갈 위치를 표시한다. readLimit은 돌릴 수 있는 최대 바이트 수
    void reset() mark()가 호출된 지점으로 돌아간다.
    boolean markSupported() mark, reset 메소드의 지원 여부를 반환
    1
    2
    3
    4
    5
    6
    7
    8
    try (InputStream input = System.in) {
    int read = -1;
    while ((read = input.read()) != -1) {
    System.out.printf("Integer: %d\nCharacter: %c\n", read, read);
    } catch(IOException e) {
    e.printStackTrace();
    }
    }
  • Reader의 주요 메소드 (char 단위)

    메소드 설명
    int read() char 하나를 읽어서 int로 반환. 읽을 값이 없으면 -1
    int read(char cbuf[]) 데이터를 읽어 cbuf를 채우고, 읽은 char 수를 반환
    int read(char cbuf[], int off, int len) 최대 len개의 char를 읽어 cbuf의 offset 위치부터 채운다.
    int read(java.nio.CharBuffer target) NIO target에 데이터를 저장한다.
    void close() 스트림을 종료하고 자원을 반납
    int available() 읽을 수 있는 데이터의 크기를 반납
    long skip(long n) 스트림에서 n만큼 건너 뛴다.
    void mark(int readAheadLimit) reset()으로 돌아갈 위치를 표시한다. readLimit은 돌릴 수 있는 최대 바이트 수
    void reset() mark()가 호출된 지점으로 돌아간다.
    boolean markSupported() mark, reset 메소드의 지원 여부를 반환

OutputStream과 Writer

  • OutputStream의 주요 메소드 (byte 단위)

    메소드 설명
    void write(int b) b 내용을 byte로 출력
    void write(byte b[]) b를 문자열로 변환하여 출력
    void write(byte b[], int off, int len) b의 off부터 (off + len - 1)만큼을 문자열로 변환하여 출력
    void close() 스트림을 종료하고 자원을 반납. close() 내부적으로 flush() 호출
    void flush() 버퍼 스트림에서 버퍼의 내용을 출력하고 비운다.
  • Writer의 주요 메소드 (char 단위)

    메소드 설명
    void write(int c) c 내용을 char로 출력
    void write(char cbuf[]) cbuf를 문자열로 변환하여 출력
    void write(char cbuf[], int off, int len) cbuf의 off부터 (off + len - 1)만큼을 문자열로 변환하여 출력
    void write(String str) str을 출력한다.
    void write(String str, int off, int len) str의 off부터 (off + len - 1)만큼을 출력
    Writer append(CharSequence csq) csq를 출력하고 Writer 반환
    Writer append(CharSequence csq, int start, int end) csq의 start부터 end까지를 출력하고 Writer 반환
    Writer append(char c) c를 출력하고 Writer 반환
    void close() 스트림을 종료하고 자원을 반납. close() 내부적으로 flush() 호출
    void flush() 버퍼 스트림에서 버퍼의 내용을 출력하고 비운다.

다양한 입출력 처리

  • 메모리 기반의 입/출력

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    char[] memory = "메모리 입출력 테스트".toCharArray();
    char[] buffer = new char[5];
    int read = 0;
    try (CharArrayReader reader = new CharArrayReader(memory);
    CharArrayWriter writer = newCharArrayWriter();) {
    while ((read = reader.read(buffer) > 0)) {
    wrtier.write(buffer, 0, read);
    }
    System.out.println(Arrays.toString(writer.toCharArray()));
    } catch (IOException e) {
    e.printStackTrace();
    }
  • 파일 기반의 입출력

    • 생성자 및 생성/삭제 메소드

      생성자 및 메소드 설명
      File(String pathname) pathname에 해당하는 파일 생성. 기본 경로는 애플리케이션의 시작 경로
      File(String parent, String child) parent 경로 아래 child 파일 생성
      File(File parent, String child) parent 경로 아래 child 파일 생성
      File(URI uri) file로 시작하는 URI 객체를 이용해 파일 생성
      boolean createNewFile() 새로운 파일을 생성
      boolean mkdir() 새로운 디렉토리를 생성
      boolean mkdirs() 경로상의 모든 디렉토리를 생성
      boolean delete() 파일/디렉토리 삭제
      void deleteOnExit() 애플리케이션 종료시 자동으로 삭제
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      String filePath = "D:" + File.separator + "Temp" + File.separator + "MyTemp";

      File fileOne = new File(filePath);
      fileOne.mkdir();

      File fileTwo = new File(filePath, "file2.txt");
      fileTwo.createNewFile();

      File fileThree = new File(fileOne, "file3.txt");
      fileThree.createNewFile();

      File fileFour = new File(new URI("file:///d:/Temp/MyTemp/file4.txt"));
      fileFour.createNewFile();
      fileFour.deleteOnExit();
    • File 클래스의 주요 메소드

      • getName(), getParent(), getParentFile(), getPath()
      • isAbsolute(), getAbsolutePath(), getCanonicalPath(), toURI()
      • canRead(), canWrite()
      • isDirectory(), isFile()
      • length(), list(), listFiles(), lastModified(), renameTo()
    • FileInputStream, FileOutputStream

      • 파일명이나 File 객체를 이용하여 입출력 스트림 생성 가능
      • FileOutputStream에서 boolean append를 true로 하면 기존 파일에 이어서 쓴다.
    • FileReader, FileWriter

      • 파일명이나 File 객체를 이용하여 입출력 Reader 생성 가능
      • FileWriter에서 boolean append를 true로 하면 기존 파일에 이어서 쓴다.

보조 스트림

보조 스트림의 특징

  • 스트림에 부가적인 기능 제공
  • 노드(데이터 소스/목적지)와 직접 연결되지 않고, 다른 스트림과 연결
  • Stream Chaining: 스트림을 여러개 연결하여 사용

보조 스트림의 종류

보조 스트림 기능
InputStreamReader byte 스트림을 char 스트림으로 변환
OutputStreamWriter byte 스트림을 char 스트림으로 변환
BufferedReader 버퍼링을 통해 스트림 속도 향상
BufferedWriter 버퍼링을 통해 스트림 속도 향상
BufferedInputStream 버퍼링을 통해 스트림 속도 향상
BufferedOutputStream 버퍼링을 통해 스트림 속도 향상
DataInputStream 기본 데이터형 전송
DataOutputStream 기본 데이터형 전송
ObjectInputStream 객체 전송
ObjectOuputStream 객체 전송
PrintWriter 문자열 표현으로 출력
PrintStream 문자열 표현으로 출력

스트림 자료형 변경

  • 캐릭터셋: utf-8, ms949, euc-kr

    1
    2
    3
    4
    5
    InputStreamReader readerOne = new InputStreamReader(System.in);
    InputStreamReader readerTwo = new InputStreamReader(System.in, "utf-8");

    OutputStreamWriter writerOne = new OutputStreamWriter(System.out);
    OutputStreamWriter writerTwo = new OutputStreamWriter(System.out, "ms949");

버퍼를 이용한 스트림

  • 기본 버퍼 크기: 8192 bytes

    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
    static void copyStream(InputStream input, OutputStream output) throws IOException {
    byte [] buff = new byte[1024];
    int read = -1;
    while ((read = input.read(buff) > 0)) {
    output.write(buff, 0, read);
    }
    }

    public static void main(String[] args) {
    File src = new File("C:/Windows/explorer.exe");
    File dst = new File("C:/temp/explorer.exe");
    try (FileInputStream in = new FileInputStream(src);
    FileOutputStream out = new FileOutputStream(target);
    BufferedInputStream buffIn = new BufferedInputStream(in);
    BufferedOutputStream buffOut = new BufferedOutputStream(out);) {
    long start = System.nanoTime();
    copyStream(in, out);
    System.out.println(System.nanoTime() - start);

    long start = System.nanoTime();
    copyStream(bin, bout);
    System.out.println(System.nanoTime() - start);
    } catch (IOException e) {
    e.printStackTrace();
    }

    }
  • BufferedReader는 readLine() 메소드를 사용할 수 있다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    File src = new File("./src.txt");
    try (BufferedReader buffReader = new BufferedReader(new FileReader(src));) {
    String line = null;
    while ((line = br.readLine()) != null) {
    System.out.println(line);
    }
    } catch (IOException e) {
    e.printStackTrace();
    }

기본 데이터형 전송 스트림

  • byte, char 외에 다른 기본 데이터형도 전송 가능한 스트림

    • 읽은 후에는 자료형을 알 수 없으므로 쓴 순서대로 읽어야 함
    클래스 메소드
    DataInputStream boolean readBoolean()
    byte readByte()
    short readShort()
    int readInt()
    long readLong()
    float readFloat()
    double readDouble()
    char readChar()
    String readUTF()
    DataOutputStream void writeBoolean(bool v)
    void writeByte(byte v)
    void writeShort(short v)
    void writeInt(int v)
    void writeLong(long v)
    void writeFloat(float v)
    void writeDouble(double v)
    void writeChar(char v)
    void writeUTF(String v)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    File src = new File("c:/Temp/data.dat");
    try (DataOutputStream out = new DataOutputStream(new FileOutputStream(src))) {
    out.writeUTF("김패캠");
    out.writeInt(15);
    out.writeFloat(14.23);
    }

    try (DataInputStream in = new DataInputStream(new FileInputStream(src))) {
    String string = in.readUTF();
    int integer = in.readInt();
    float floatNum = in.readFloat();
    System.out.println(string + " " + integer + " " + floatNum);
    }

객체 저장을 위한 스트림

  • 일반적인 참조형 객체를 저장하기 위한 스트림

  • 직렬화(Serialization)와 역직렬화(Deserialization) 사용

    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
    class Foo implements Serializable { // has-a 관계의 모든 클래스가 Serializable이어야 함
    static final long serialVersionUID = 1L; // 객체의 버전 관리

    String userName;
    int id;

    transient String passWord;

    @Override
    public String toString() {
    return userName + " " + id + " " + passWord;
    }
    }

    class FooTest {
    public static void main (String [] args) {
    File dst = new File("C:/Temp/obj.data");
    Foo foo = new Foo();
    foo.userName = "김사탕";
    foo.id = 142;
    foo.passWord = "qwer1234";

    try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(dst));
    ObjectInputStream in = new ObjectInputStream(new FileInputStream(dst));) {
    out.writeObject(foo);
    Object read = in.readobject();
    if (read != null && read instanceof Foo) {
    Foo readFoo = (Foo)read;
    System.out.println(readFoo);
    }
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }

  • 부모 클래스가 Serializable이 아닌 경우 자식 클래스에서 직접 처리

    • writeObject(), readObject()를 자식 클래스에서 직접 구현
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class ParentFoo {
    int memVarOne;
    double memVarTwo;
    }

    class ChildFoo extends ParentFoo implements Serializable {
    @Override
    private void writeObject(ObjectOutputStream out) throws IOException {
    out.writeInt(memVarOne);
    out.writeDouble(memVarTwo);
    out.defaultWriteObject();
    }
    @Override
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    memVarOne = in.readInt();
    memVarTwo = in.readDouble();
    in.defaultReadObject();
    }
    }

Comment and share

java.lang 패키지

java.lang

  • Java에서 가장 기본적이며 자주 사용되는 클래스를 모은 패키지
  • 별도로 import하지 않아도 사용이 가능한, Java의 기본 중의 기본

Object Class

  • 모든 클래스의 조상 클래스로, 클래스가 갖춰야할 기본 기능을 제공

  • 필요한 경우 Object 클래스에 정의된 메소드를 Override하여 사용

    메소드 설명
    public final native Class<?> getClass() 현재 객체의 클래스를 반환한다.
    public native int hashCode() 현재 객체의 해시코드 값을 반환한다.
    public boolean equals() 현재 객체와 대상이 같은 객체를 참조하는지 여부를 반환한다.
    public String toString() 객체를 문자열로 변환하여 반환한다.
    proteted native clone() throws CloneNotSupportedException 객체를 복사하여 새로운 객체로 반환한다.

equals()

  • Object 클래스의 equals() 메소드 구현

    • 객체의 참조만을 비교
    1
    2
    3
    public boolean equals(Object obj) {
    return (this == obj);
    }
  • String 클래스의 equals() 메소드 구현 (Overriding)

    • 효율적으로 객체의 내용이 동일한지 비교
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public boolean equals(Object anObject) {
    if (this == anObject) {
    return true;
    }
    if (anObject instanceof String) {
    String aString = (String)anObject;
    if (!COMPACT_STRINGS || this.coder == aString.coder) {
    return StringLatin1.equals(value, aString.value);
    }
    }
    return false;
    }

hashCode()

  • 객체를 구분하기 위해 사용하는 정수값(해시코드)을 반환
    • 프로그램 내에서 객체마다 유일하므로 주소값처럼 사용 가능
  • native 메소드이므로 구현 내용을 볼 수 없음
  • hashCode()의 제한 사항
    • 한 객체의 hashCode()를 여러번 호출할 경우, equals()에 사용하는 값이 변하지 않으면 동일한 값을 반환해야 한다.
    • equals() 메소드가 같다고 판단한 두 객체의 hashCode() 반환값은 같아야 한다.
    • equals() 메소드가 다르다고 판단한 두 객체의 hashCode()가 반드시 다를 필요는 없으나, 다른 값이 나오면 HashTable 성능이 향상된다.

clone()

  • 자신을 복제하여 새로운 객체를 생성하는 메소드

  • 배열을 복제할 경우 새로운 객체를 생성하므로 원본을 훼손하지 않을 수 있음

  • clone() override 시 Cloneable 인터페이스를 구현해야 함

    1
    2
    int [] ints = {1, 2, 3, 4, 5};
    int [] ints2 = ints.clone();

getClass()

  • Class 에 대한 정보를 담고 있는 Class 객체를 반환

  • 객체의 getClass() 메소드 또는 해당 클래스의 정적 변수 class를 이용 가능

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class Foo {
    public void methodA() {
    System.out.println("method A called.");
    }
    }

    class FooTest {
    public static void main(String[] args) throws Exception {
    Foo foo = new Foo();
    Class fooClass = foo.getClass();

    System.out.println(fooClass.getName());
    System.out.println(fooClass.getDeclaredMethods().length);
    System.out.println(Foo.class.getDeclaredMethod("methodA").invoke(foo));
    }
    }

System

  • 실행 중인 OS와 interact하기 위한 클래스

  • System 클래스의 주요 정적 변수

    속성 설명
    public static final PrintStream err 오류를 출력하기 위한 표준 출력 스트림
    public static final InputStream in 표준 입력을 처리하기 위한 입력 스트림
    public static final PrintStream out 표준 출력을 처리하기 위한 출력 스트림
  • System 클래스의 주요 정적 메소드

    메소드 설명
    arraycopy() src 배열의 내용을 dst 배열로 복사한다.
    currentTimeMillis() 현재 시스템 시간을 ms로 반환한다.
    exit() 프로그램을 종료한다
    gc() GC를 요청한다.
    getenv() 환경 변수의 값을 반환한다.
    getProperties() 시스템 속성을 Property로 반환한다.
    getProperty() 시스템 속성 값을 문자열로 반환한다. 없을 경우 null 또는 def를 반환
    identityHashCode() 객체의 해시코드 값을 반환한다.
    lineSeparator() 시스템의 줄넘김 문자열을 반환한다. UNIX: \n, WINDOWS: \r\n
    nanoTime() 시스템 시간을 ns로 반환한다.
    setProperties() 시스템 속성을 한번에 설정한다.
    setProperty() 시스템 속성을 하나씩 설정한다.
    • 주요 Property

      속성 설명
      user.country OS 로케일 정보
      java.io.tmpdir 임시 경로
      line.separator 줄넘김 문자열
      user.home 유저 홈 경로
      file.separator 파일 경로 구분

Math

  • 수학 계산에 필요한 메소드를 가진 final 클래스

    메소드 설명
    abs() 절대값을 반환한다.
    ceil() 올림 값을 double로 반환한다.
    floor() 내림 값을 double로 반환한다.
    max() 두 값 중 더 큰 값을 반환한다.
    min() 두 값 중 더 작은 값을 반환한다.
    random() 0 이상 1.0 미만의 임의의 값을 반환한다.
    round() 소수점 첫째자리에서 반올림한 정수 값을 반환한다.
    addExact() 덧셈 연산으로, Overflow 발생 시 ArithmaticException 발생.
    subtractExact() 뺄셈 연산으로, Overflow 발생 시 ArithmaticException 발생.
    multiplyExact() 곱셈 연산으로, Overflow 발생 시 ArithmaticException 발생.

Comment and share

스트림 API (Stream API)

스트림 API란

  • Java 8에서 추가된 java.util.stream 패키지
  • 컬렉션의 요소를 람다식으로 처리할 수 있도록 하는 함수형 프로그래밍 도구

스트림 API의 특징

  • 간결한 코드 작성
1
2
3
4
5
6
7
8
9
10
11
// JAVA 7
List<String> list = Arrays.asList("fast", "campus", "rocks");
List<String> newList = new ArrayList<>();

for (String s: list) {
newList.add(s.toUpperCase());
}

for (String s: newList) {
System.out.println(s);
}
1
2
3
4
// JAVA 8
List<String> list = Arrays.asList("fast", "campus", "rocks");
Stream<String> stream = list.stream();
stream.map(String::toUpperCase).forEach(System.out::println);
  • 데이터 소스에 대한 공통의 접근 방식 제공

    • List, Set, Array 등 다양한 경우에 대해 표준화된 방식으로 데이터 접근 가능
    • 특히 Stream::sorted() 메소드는 공통된 정렬 방식을 제공
  • 중간 처리와 최종 처리를 지원

    • Mapping, Filtering, Sorting 등 중간 처리 지원 (여러개 사용 가능)
    • Iteration, Count, Average, Summation, Reduce 등 최종 처리 지원 (마지막에 하나 사용 가능)
  • 데이터 소스와 처리 결과를 분리

    • 원본 데이터를 유지하여 처리에 의한 부작용 방지
    • 처리 결과를 새로운 컬렉션으로 저장 가능

스트림 API를 이용한 자료 처리

스트림 API의 종류

종류 처리 대상
Stream<T> 일반적인 객체를 처리
IntStream 기본 자료형 int를 처리
LongStream 기본 자료형 long을 처리
DoubleStream 기본 자료형 double을 처리

스트림 객체 생성 메소드

  • 데이터 소스로부터 스트림 생성

    데이터 소스 메소드
    Collection default Stream<E> stream()
    Collection default Stream<E> parallelStream()
    Arrays public static <T> Stream<T> stream(T[] array)
    Arrays public static <T> Stream<T> of(T ... values)
    Arrays public static IntStream stream(int[] array)
    Arrays public static IntStream of(int ... values)
    Arrays public static LongStream stream(long[] array)
    Arrays public static LongStream of(long ... values)
    Arrays public static DoubleStream stream(double[] array)
    Arrays public static DoubleStream of(double ... values)
  • 정수 범위와 java.util.Random으로부터 생성

    구분 메소드
    int형 범위 public static IntStream range(int startInclusive, int endExclusive)
    int형 범위 public static IntStream rangeClosed(int startInclusive, int endInclusive)
    long형 범위 public static LongStream range(long startInclusive, long endExclusive)
    long형 범위 public static LongStream rangeClosed(long startInclusive, long endInclusive)
    Random p형 값 public PStream ps()
    Random p형 값 public PStream ps(long streamSize)
    Random p형 값 public PStream ps(long streamSize, p origin, p bound)
    1
    2
    3
    4
    5
    6
    IntStream integers = IntStream.range(0, 10);
    integers.forEach(System.out::println);

    Random random = new Random();
    LongStream longs = random.longs(100, 1, 10);
    System.out.println(longs.average().getAsDouble());

중간 처리 메소드

동작 메소드
필터링 dinstict(), filter()
자르기 skip(), limit()
정렬 sorted()
매핑 flatMap(), flatMapToP(), map(), mapToP(), asDoubleStream(), asLongStream()
조회 peek()

필터링

필터링은 스트림의 일부 요소를 제거하는 역할을 한다.

  • Stream<T> distict() : 스트림 요소의 중복을 제거한 스트림을 반환
  • Stream<T> filter(Predicate<? super T> predicate) : Predicate에서 true에 해당하는 요소만으로 구성된 스트림을 반환

자르기

자르기는 스트림의 일부 요소를 한번에 생략한다.

  • Stream<T> limit(long maxSize) : 최대 maxSize까지 구성된 스트림을 반환
  • Stream<T> skip(long n) : 스트림의 상위 n개 요소를 생략한 스트림을 반환

정렬

스트림 요소의 compareTo() 또는 입력받은 Comparator를 이용해 정렬

  • Stream<T> sorted() : Comparable 객체를 정렬한 스트림 반환
  • Stream<T> sorted(Comparator<? super T> comparator) : Comparator를 이용하여 정렬된 스트림 반환
    • Comparator의 정적 메소드와 기본 메소드
      • 정적 메소드: naturalOrder(), reverseOrder(), comparing(), comparingP(), nullsFirst(), nullsLast()
      • 기본 메소드: reversed(), thenComparing(), thenComparingP()

매핑

Function 인터페이스를 이용해 요소의 값을 변환한다.

  • map 계열

    • <R> Stream<R> map(Function<? super T, ? extends R> mapper) : 기존 스트림의 T 타입 요소를 R 타입으로 변환하여 새로운 스트림 반환
    • PStream mapToP(ToPFunction<? super T> mapper) : R이 기본형 타입으로 제한된 map()
  • flatMap 계열

    • <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) : 스트림의 T 타입 요소가 n개의 R 타입 요소로 매핑된 새로운 스트림을 반환
    • PStream flatMapToP(Function<? super T, ? extends PStream> mapper) : R이 기본형 타입으로 제한된 flatMap()
    1
    2
    3
    4
    List<String> list = Arrays.asList("java", "backend", "best", "course");
    System.out.println(list.stream().flatMap(data -> {
    return Arrays.stream(data.split(""));
    }).distinct().count());

조회

스트림 처리 과정에 영향을 미치지 않으며, 중간 결과를 확인할 수 있으며 입력받은 스트림과 동일한 스트림을 반환한다.

  • Stream<T> peek(Consumer<? super T> action)

    1
    2
    3
    4
    5
    6
    List<String> list = Arrays.asList("java", "backend", "best", "course");
    System.out.println(list.stream().flatMap(data -> {
    return Arrays.stream(data.split(""));
    }).peek(s -> System.out.println("flatMap:" + s))
    .distinct().peek(s -> System.out.println("distinct:" + s))
    .count());

최종 처리 메소드

동작 메소드
매칭 allMatch(), anyMatch(), noneMatch()
수집 collect()
루핑 forEach()
집계 count(), max(), min(), average(), sum(), reduce()
조사 findFirst(), findAny()

매칭

Predicate 계열을 이용해 스트림 요소들이 특정 조건에 만족하는지 조사하는 메소드

  • boolean allMatch(Predicate<? super T> predicate) : 스트림의 모든 요소가 Predicate를 만족하면 true를 반환
  • boolean anyMatch(Predicate<? super T> predicate) : 스트림의 요소 중 하나라도 Predicate를 만족하면 true를 반환
  • boolean noneMatch(Predicate<? super T> predicate) : 스트림의 요소 중 하나라도 Predicate를 만족하지 않으면 true를 반환

집계

  • 기본 집계 메소드

    • 기본형 스트림의 통계 : count(), sum(), average(), min(), max()
    • T 타입 스트림의 통계 : count(), min(), max() (min, max의 경우 Comparator 필요)
  • reduce() 메소드

    • Optional<T> reduce(BinaryOperator<T> accumulator) : accumulator를 수행하고 Optional<T> 타입 반환
    • T reduce(T identity, BinaryOperator<T> accumulator) : identity를 초기값으로 하여, accumulator를 이용해 집계 연산
    • <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner) : combiner를 이용해 병렬 스트림 결합
  • java.util.Optional<T>

    • T 타입 객체의 null 여부에 따라 다르게 동작하는 Wrapper 클래스
    • Optional 클래스의 정적 메소드를 이용해 Optional 객체 생성
      • public static <T> Optional<T> of(T value) : value가 null인 경우 NullPointerException을 발생시키는 Wrapping 메소드
      • public static <T> Optional<T> ofNullable(T value) : value가 null인 경우 empty()의 결과를 리턴하는 Wrapping 메소드
      • public static <T> Optional<T> empty() : 값이 비어있는 Optional 객체를 리턴
    • Optional 객체를 처리하는 메소드
      • public T get() : Optional의 값을 리턴하며, null일 경우 NullPointerException 발생
      • public void ifPresent(Consumer<? super T> consumer) : Optional 값이 null이 아닐 경우 consumer를 이용해 소비한다.
      • public T orElse(T other) : Optional의 값이 null일 경우 other를 반환한다.
      • public T orElseGet(Supplier<? extends T> other) : Optional의 값이 null일 경우 Supplier를 통해 공급받은 값을 반환한다.
      • public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X : Optional의 값이 null일 경우 exceptionSupplier에서 공급받은 예외를 throw

반복

forEach() 메소드로 스트림 요소를 순차적으로 Consumer<T>를 이용해 소비

  • void forEach(Comsumer<? super T> action) : 스트림의 각 요소를 action으로 소비

조사

첫번째 요소 또는 아무 요소를 조사하는 최종 처리 메소드. 필터링 등으로 인해 스트림에 요소가 남아있는지 확인할 수 있다.

  • Optional<T> findFirst() : 스트림의 첫 요소 또는 empty Optional 객체를 반환
  • Optional<T> findAny() : 스트림의 아무 요소나 가지는 Optional 객체를 반환

수집

필요한 요소를 수집하여 새로운 Collection으로 구성하여 반환하는 메소드

  • collect() 메소드

    • <R, A> R collect(Collector<? super T, A, R> collector) : collector를 이용해 새로운 Collection R에 담아 반환

      • Collectors의 정적 메소드: toList(), toSet(), toCollection(), toMap(), toConcurrentMap()
    • <R, A> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) : supplier를 통해 공급된 컨테이너 R에 accumulator를 이용해 T값을 저장. 병렬처리 스트림에 사용될 경우 combiner를 이용해 스레드별 컨테이너 R을 통합

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      String[] array = {"Java", "Is", "Fun", "Isn't", "It", "?"};
      List<String> list = Arrays.stream(array)
      .filter(s -> s.length() >= 3)
      .collect(Collectors.toList()); // ArrayList
      // .collect(Collectors.toCollection(LinkedList::new))
      System.out.println(list.getClass().getName() + ":" + list);


      Set<String> set = Arrays.stream(array)
      .filter(s -> s.length() >= 3)
      .collect(Collectors.toSet()); // HashSet
      // .collect(Collectors.toCollection(HashSet::new))
      System.out.println(set.getClass().getName() + ":" + set);

      Map<String, Integer> map = Arrays.stream(array)
      .filter(s -> s.length() >= 3)
      .collect(Collectors.toMap(s -> s, String::length)); // HashMap
      // .collect(Collectors.toCollection(s -> s, String::length, (oldVal, newVal) -> newVal, TreeMap::new))
      System.out.println(map.getClass().getName() + map);
  • Collectors의 정적 메소드를 이용한 그룹화와 분리

    • public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) : classifier를 key값으로, 해당하는 값의 목록을 List인 value로 가지는 Map으로 스트림을 수집하는 Collector를 반환
      • public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) : List 대신 downstream collector로 수집
    • public static <T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) : predicate 결과를 key로, 해당하는 값의 목록을 List value로 가지는 Map으로 스트림을 수집하는 Collector를 반환
      • public static <T, A, D> Collector<T, ?, Map<Boolean, D>> partitioningBy(Predicate<? super T> predicate, Collector<? super T, A, D> downstream)) : List 대신 downstream collector로 수집
    1
    2
    3
    4
    5
    6
    7
    8
    9
    String[] array = {"Java", "Is", "Fun", "Isn't", "It", "?"};

    Map<Character, List<String>> map1 = Arrays.stream(array)
    .collect(Collectors.groupingBy(s -> s.charAt(0)));
    System.out.println(map1);

    Map<Boolean, List<String>> map2 = Arrays.stream(array)
    .collect(Collectors.partitioningBy(s -> s.length() >= 3));
    System.out.println(map2);
  • 집계를 위한 Collector

    • Downstream collector로 집계를 위한 Collector를 사용할 경우 유용하다.
    • counting(), summingP(), averagingP(), maxBy(), minBy(), reducing()
    1
    2
    3
    4
    5
    6
    String[] array = {"Java", "Is", "Fun", "Isn't", "It", "?"};

    Map<Character, Long> map = Arrays.stream(array)
    .collect(Collectors.groupingBy(s -> s.charAt(0),
    Collectors.counting()));
    System.out.println(map);

병렬 스트림

  • 병렬 스트림의 생성

    • stream() 대신 parallelStream()으로 변경
    • stream 생성 후 parallel()으로 병렬화
  • combiner를 이용해 병렬 스트림으로 생성된 컬렉션을 결합

    • BiConsumer<T, K> combiner : T 객체에 K 객체를 결합

Comment and share

람다식 (Lambda Expression)

람다식이란

  • Java 1.8에서 추가된 함수형 프로그래밍 기법
  • 객체지향 프로그래밍과 다르게, 비즈니스 로직만을 빠르게 구현하는 특징
  • 객체지향 언어인 Java에서 메소드를 함수처럼 사용하는 형식

람다식의 예

배열의 정렬

  • 기본 정렬 방식을 이용한 정렬

    1
    2
    3
    4
    String [] strings = {"fast", "campus", "java", "backend", "school"};
    System.out.println(Arrays.toString(strings));
    Arrays.sort(strings);
    System.out.println(Arrays.toString(strings));
    1
    2
    3
    public int compare(String o1, String o2) {
    return o1.compareTo(o2);
    }
  • 익명 내부 클래스를 이용한 정렬 방식 변경

    1
    2
    3
    4
    5
    6
    Arrays.sort(strings, new Comparator<String>() { // 객체를 생성하여 전달
    @Override
    public int compare(String o1, String o2) {
    return o1.compareTo(o2) * -1; // 내림차순
    }
    });
  • 람다식을 이용한 정렬 방식의 변경

    1
    Arrays.sort(strings, (o1, o2) -> {return o1.compareTo(o2) * -1;});
    1
    2
    3
    4
    Comparator<String> comp = (o1, o2) -> {
    return o1.compareTo(o2) * -1;
    };
    Arrays.sort(strings, comp);
  • 함수형 인터페이스 (Functional Interface)

    • 추상 메소드가 단 하나 존재하는지 검사

      1
      2
      3
      4
      5
      @FunctionalInterface
      public interface Comparator<T> {
      int compare(T o1, T o2);
      ...
      }

람다식의 형식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(String x) -> { System.out.println(x); }

(x) -> { System.out.println(x); }

x -> System.out.println(x)

() -> System.out.println("x"); // () 생략 불가능

(x) -> {
System.out.println(x);
return x;
}

(x) -> "x: " + x

// x -> "x :" + x // () 생략 불가능

람다식의 변수 참조

  • 익명 내부 클래스와 동일한 규칙으로 변수 참조

  • 문법적으로 this의 사용법만 다름

    • 익명의 내부 클래스와 달리 외부 클래스를 참조
    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
    @FunctionalInterface
    interface FunctionalInterface {
    String method();
    }

    class Main {
    void functionalMethod(FunctionalInterface obj) {
    System.out.println(obj.method());
    }

    public static void main(String[] args) {
    functionalMethod(()-> {
    System.out.println("this: " + this);
    System.out.println("OuterClass.this: " + Main.this);
    return "Lambda expression used."
    });

    functionalMethod(new FunctionalInterface() {
    @Override
    String method() {
    System.out.println("this: " + this);
    System.out.println("OuterClass.this: " + Main.this);
    return "Anonymous inner class used."
    });
    }

    }

표준 함수형 인터페이스

  • 자주 사용되는 함수형 인터페이스를 정의해 둔 API
  • Consumer, Supplier, Function, Operation, Predicate 계열이 있다.
계열 입력 출력 메소드 설명
Consumer O X void accept(T) 입력을 소비
Supplier X O T get() 출력을 공급
Function O O T apply(R) 입력 -> 출력 함수 매핑
Operation O O T apply(T) 입력을 연산하여 동일 타입의 출력으로 리턴
Predicate O boolean boolean test(T) 입력을 판단

표준 함수형 인터페이스의 종류

Consumer 계열

인터페이스 메소드
Consumer<T> void accept(T t)
BiConsumer<T, U> void accept(T t, U u)
IntConsumer void accept(int value)
LongConsumer void accept(long value)
DoubleConsumer void accept(double value)
ObjIntConsumer<T> void accept(T t, int value)
ObjLongConsumer<T> void accept(T t, long value)
ObjDoubleConsumer<T> void accept(T t, double value)

Supplier 계열

인터페이스 메소드
Supplier<T> T get()
BooleanSupplier boolean getAsBoolean()
IntSupplier int getAsInt()
LongSupplier long getAsLong()
DoubleSupplier double getAsDouble()

Function 계열

인터페이스 메소드
Function<T, R> R apply(T t)
BiFunction<T, U, R> R apply(T t, U u)
PFunction<R> R apply(p value)
PtoQFunction q applyAsQ(p value)
ToPFunction<T> p applyAsP(T t)
ToPBiFunction<T, U> p applyAsP(T t, U u)
1
2
P, Q : Int, Long, Double
p, q : int, long, double

Operator 계열

인터페이스 메소드
UnaryOperator<T> T apply(T t)
BinaryOperator<T> T apply(T t1, T t2)
IntUnaryOperator int applyAsInt(int value)
LongUnaryOperator long applyAsLong(long value)
DoubleUnaryOperator double applyAsDouble(double value)
IntBinaryOperator int applyAsInt(int value1, int value2)
LongBinaryOperator long applyAsLong(long value, long value2)
DoubleBinaryOperator double applyAsDouble(double value, double value2)

Predicate 계열

인터페이스 메소드
Predicate<T> boolean test(T t)
BiPredicate<T, U> boolean test(T t, U u)
IntPredicate boolean test(int value)
LongPredicate boolean test(long value)
DoublePredicate boolean test(double value)

표준 함수형 인터페이스의 메소드

andThen(), compose()

  • 두 개 이상의 함수형 인터페이스를 연결하기 위해 사용

    • A.andThen(B): A를 먼저 실행하고 B를 실행. Consumer, Function, Operator 계열 지원
    1
    2
    3
    Function<String, String> funcOne = x -> "str: " + x;
    Function<String, String> funcTwo = x -> "{" + x + "}";
    Function<String, String> andThen = funcOne.andThen(funcTwo); // {str: x}
    • A.compose(B): B를 먼저 실행하고 A를 실행. Function, Operator 계열 지원
    1
    2
    3
    IntUnaryOperator multTwo = x -> x * 2;
    IntUnaryOperator addTen = x -> x + 10;
    IntUnaryOperator multTwo.compose(addTen); // (x + 10) * 2

and(), or(), negate(), isEqual()

  • Predicate 계열의 기본 메소드

    • and(), or(), negate()
    • 각각 &&, ||, !의 동작을 한다.
    1
    2
    3
    4
    DoublePredicate predOne = x -> x > 0.5;
    DoublePredicate predTwo = x -> x < 0.0;
    DoublePredicate predThree = predOne.or(predTwo);
    DoublePredicate predFour = predThree.negate();
  • Predicate 계열의 클래스 메소드

    • isEqual()
    1
    Predicate<String> isEqualToString = Predicate.isEqual("String");

minBy(), maxBy()

  • BinaryOperator 클래스의 클래스 메소드

    • Comparator<T>를 파라미터로 받아 최소값/최대값을 구하는 BinaryOperator<T>를 리턴
    1
    2
    3
    4
    5
    6
    7
    BinaryOperator<String> minBy = BinaryOperator.minBy((o1, o2) -> {
    return len(o1) > len(o2) ? 1 : -1;
    });

    BinaryOperator<Integer> maxBy = BinaryOperator.maxBy((val1, val2) -> {
    return val1.compareTo(val2);
    });

람다식에 메소드/생성자 사용

  • 이미 구현된 메소드가 있는 경우, 다양한 방식으로 람다식을 대체 가능

ClassName::instanceMethod

  • 첫번째 파라미터를 객체로, 두번째 파라미터를 메소드 입력으로 사용

    1
    2
    String[] strings = { "A", "B", "D", "C" };
    Arrays.sort(strings, String::compareTo);

ClassName::classMethod

  • 클래스 메소드의 입력으로 모든 파라미터가 사용됨

    1
    Function<String, Integer> parser = Integer::parseInt;

instance::instanceMethod

  • 주어진 객체의 메소드를 호출

    1
    2
    3
    String string = "StringA";
    Predicate<String> pred = string::equals;
    System.out.println(pred.apply("StringA"));

ClassName::new

  • 생성자를 이용하여 객체를 생성하는 람다식

    1
    2
    IntFunction<String> func = String::new;
    String string = func.apply(10);

ClassName[]::new

  • 배열을 생성하는 람다식

    1
    2
    IntFunction<String[]> func = String[]::new;
    String [] strings = func.apply(100);

Comment and share

내부 클래스 (Inner Classes)

내부 클래스란

  • 클래스 내부에 선언하는 클래스로 중첩 클래스(Nested Class)라고도 부름
  • HAS-A 관계에 있는 클래스가 해당 클래스에서만 사용될 경우 주로 사용

내부 클래스의 종류

  • 클래스 영역에 선언
    • 인스턴스 내부 클래스: 외부 클래스의 멤버 변수처럼 사용 가능
    • 클래스 내부 클래스: static이 붙은 정적인 내부 클래스
  • 로컬 영역에 선언
    • 로컬 내부 클래스: 선언된 영역의 Scope 내에서 사용 가능
    • 익명 내부 클래스: 객체를 일회성으로 생성하기 위한 이름이 없는 클래스

다양한 내부 클래스

인스턴스 내부 클래스 (Instance Inner Class)

  • 클래스 영역에 static 키워드 없이 선언된 클래스

  • 외부 클래스의 private을 포함한 모든 멤버에 접근 가능

  • 외부 클래스 객체를 통해서만 생성 가능

  • static 멤버 변수는 사용할 수 없으나, static final은 사용 가능

    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 Outer {
    class InstanceInner {
    int innerMemberVar = 1;
    //static int staticMemberVar = 1;
    static final int CONSTANT_VAR = 10;

    void innerMethod() {
    System.out.println(innerMemberVar);
    System.out.println(outerMemberVar);
    }
    }

    private int outerMemberVar = 2;

    void outerMethod() {
    IstanceInner obj = new InstanceInner();
    obj.innerMethod();
    }

    public static void main(String[] args) {
    Outer outer = new Outer();
    InstanceInner inner = outer.new InstanceInner();
    inner.innerMethod();
    }

    }
  • 외부 클래스의 멤버 변수와 이름이 겹치는 경우 this 활용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Outer {
    class InstanceInner {
    int var = 1;

    void innerMethod() {
    int var = 0;
    System.out.println(var);// 0
    System.out.println(this.var);// 1
    System.out.println(Outer.this.var);// 2
    System.out.println(Outer.y); // 3 static variable
    }
    }

    private int var = 2;
    private static int y = 3;

    public static void main(String[] args) {
    Outer outer = new Outer();
    InstanceInner inner = outer.new InstanceInner();
    inner.innerMethod();
    }

    }

클래스 내부 클래스 (Class Inner Class)

  • 클래스 영역에 static 키워드와 함께 선언된 내부 클래스

  • 외부 클래스 객체가 없어도 생성 가능

  • 외부 클래스의 private을 포함한 모든 멤버에 접근 가능

  • static 멤버 변수를 가질 수 있음

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    class Outer {
    static class ClassInner {
    int innerVar = 1;
    static int staticInnerVar = 100;

    void innerMethod() {
    Outer outer = new Outer();
    System.out.println(outer.outerVar);
    System.out.println(innerVar);
    }
    }

    private int outerVar = 2;

    void outerMethod() {
    ClassInner inner = new ClassInner();
    inner.innerMethod();
    }

    public static void main(String[] args) {
    ClassInner inner = new ClassInner();
    inner.innerMethod();
    }
    }

로컬 내부 클래스 (Local Inner Class)

  • 클래스 영역이 아닌 로컬 영역에서 선언된 클래스

    • 메소드, 생성자, 초기화 블록의 내부
  • 정적 변수는 가질 수 없으나, static final 변수는 가질 수 있음

  • 로컬 영역의 변수 다음과 같은 사항을 따름

    • Java1.7: final로 선언된 변수에만 접근 가능
    • Java1.8: 모든 변수에 접근 가능하나, final로 간주되어 새로운 값 할당 불가
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    void outerMethod() {
    int var = 1; // 메소드 호출 스택

    class LocalInner {
    void innerMethod() {
    System.out.println(var);
    }
    }

    LocalInner inner = new LocalInner(); // 힙 영역
    inner.innerMethod();
    }

익명의 내부 클래스 (Anonymous Inner Class)

  • 로컬 내부 클래스와 동일하나, 이름이 없어 선언 즉시 한 번 사용 가능

  • 추상 클래스나 인터페이스의 구현에 많이 사용

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    interface IFoo {
    void do();
    }

    class AnonymousInnerClassTest {
    static void useFoo(IFoo foo) {
    foo.do();
    }

    public static void main(String[] args) {
    useFoo(new IFoo() {
    @Override
    void do() {
    System.out.println("Do!");
    }
    });
    }
    }

Comment and share

Yechan Kim

author.bio


author.job