1. SQL 쿼리를 자바로 표현하기


SQL 질의 언어에서 조건절을 작성하여 

칼로리가 400 이하인 음식의 이름을 아래 쿼리처럼 뽑을 수 있다.


1
2
3
4
SELECT a.name
  FROM DISHES a
 WHERE a.calorie < 400
;
cs


위 쿼리와 마찬가지로 자바 컬렉션으로도 비슷한 기능을 만들 수 있다.

아래 코드는 스트림을 이용하여 칼로리가 400 이하인 음식을 정렬하여 이름을 리스트로 뽑고 있다. 

스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리 할 수 있다.


1
2
3
4
5
6
7
8
9
10
public static List<String> getLowCaloricDishesNames(List<Dish> dishes) {
 
    List<String> list = dishes.stream()
            .filter(a -> a.getCalories() < 400)
            .sorted(Comparator.comparing(Dish::getCalories))
            .map(Dish::getName)
            .collect(Collectors.toList());
    return list;
 
}
cs



2. 스트림 API 특징은 다음과 같다.

 

- 선언형 : 더 간결하고 가독성이 좋아진다.

- 조립할수 있음 : 유연성이 높아진다.

- 병렬화 : 성능이 좋아진다.



3. 스트림이란 뭘까?


스트림이란 "데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소(Sequence of elements)"로 정의할 수 있다.


데이터의 연속된 요소를 관리하는 것은 컬렉션에서 처리하는데?

 

컬렉션은 시간과 공간의 복잡과 관련된 요소 저장 및 접근 연산이 주를 이루고

스트림은 filter, sorted, map 처럼 계산식이 주를 이룬다.

스트림은 filter, map, reduce, find, math, sort 등으로 데이터를 조작할수 있다.


스트림은 파이프라이닝의 특징을 갖고 있다. 

대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다.

게으름, 쇼트서킷 같은 최적화도 얻을 수 있다.



4. 컬렉션과 스트림의 차이가 뭘까?


DVD에 영화가 저장되어 있다고 하면 DVD는 컬렉션이다.

인터넷 스트리밍으로 같은 영화를 시청한다고 하자.


데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이이다.

컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 구조이다.


반면 스트림은 요청할떄만 요소를 계산하는 고정된 자료구조이다.

(스트림에 요소를 추가하거나 스트림에서 요소를 제거 할 수 없다.)


반복자와 마찬가지로 스트림도 한번만 탐색 할 수 있다. 

탐색된 스트림의 요소는 소비된다.

아래는 stream을 foreach로 반복하며 단어를 뽑아내는데 

아래쪽 같은 foreach는 IllegalStateException error가 발생한다.



1
2
3
4
5
6
List<String> names = Arrays.asList("Java8""Lambdas""In""Action");
 
Stream<String> s = names.stream();
 
s.forEach(System.out::println);
s.forEach(System.out::println); // IllegalStateException error 
cs



스트림은 아래와 같이 중간 연산, 최종 연산으로 이루어진다.


1
2
3
4
5
6
7
8
9
10
public static List<String> getLowCaloricDishesNames(List<Dish> dishes) {
 
    List<String> list = dishes.stream()                // 스트림 얻기
            .filter(a -> a.getCalories() > 300)        // 중간 연산
            .map(Dish::getName)                        // 중간 연산
            .limit(3)                                 // 중간 연산
            .collect(Collectors.toList());             // 최종 연산
 
    return list;
}
cs



filter, map, limit는 서로 연결되어 파이프라인을 형성하고 collect로 파이프라인을 실행한 다음에 담게 된다.

중간 연산의 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것,

즉 게으르다는 것(lazy) 이다.


중간 연산을 합친 다음에 합쳐진 중간연산을 최종연산으로 한번에 처리하기 때문이다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static List<String> getLowCaloricDishesNames(List<Dish> dishes) {
    
    List<String> list = dishes.stream()            
            .filter(a -> {
                System.out.println("filtering: " + a.getName());
                return a.getCalories() > 300;
            })    // 필터한 요리명 출력
            .map(a -> {
                System.out.println("mapping: "+a.getName());
                return a.getName();
            }) // 추출한 요리명 출력
            .limit(3)                                        
            .collect(Collectors.toList());                
    
    System.out.println(list);
    return list;
}
cs


위 코드는 각 중간연산의 실행결과가 어떻게 진행되는지 확인해볼수 있는 코드이다.

실행하면 아래와 같은 결과가 나타난다.


1
2
3
4
5
6
7
filtering: pork
mapping: pork
filtering: beef
mapping: beef
filtering: chicken
mapping: chicken
[pork, beef, chicken]
cs

 

결과를 보면 300 칼로리가 넘는 음식은 여러개지만 처음에 3개만 선택되었고 filter와 map은 서로다른 연산이지만

한과정으로 병합되었다.



5. 스트림에서 가장 많이 사용되는 map() 이란 무엇일까?


스트림은 함수를 인수로 받는 map 메서드를 지원한다. 

인수로 제공된 함수는 각 요소에 적용되며 함수를 적용한 결과가 새로운 요소로 매핑된다.

새로운 버전을 만든다는 개념에 가깝다.

변환에 가까운 매핑이라는 단어를 사용한다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
List<Dish> menu = Dish.menu;
List<String> dishNames = menu.stream()
        .map(Dish::getName)
        .collect(toList());
 
System.out.println(dishNames);
// [pork, beef, chicken, french fries, rice, season fruit, pizza, prawns, salmon]
 
 
List<String> words = Arrays.asList("Hello""World");
List<Integer> wordLengths = words.stream()
        .map(String::length)
        .collect(toList());
 
System.out.println(wordLengths);
// [5,5]
 
List<String> uniqueChar = words.stream()
        .map(w -> w.split(""))      // 배열 변환
        .flatMap(Arrays::stream)    // 생성된 스트림을 하나의 스트림으로 평면화
        .distinct()
        .collect(Collectors.toList());
 
System.out.println(uniqueChar);
// [H, e, l, o, W, r, d]
cs


새롭게 변환해서 가져오겠다 라는 뜻이다.

map으로 연산한 결과를 하나의 스트림으로 평면화할때 flatMap을 사용한다.



6. 스트림에서 검색과 매칭을 확인할때는?


특정 속성이 데이터 집합에 있는지 여부를 검색하는 데이터 처리도 자주 사용되는데

스트림 API는 allMatch, anyMatch, noneMatch, findFirst, findAny ... 등 메서드를 제공한다.



프레디케이트가 주어진 스트림에서 적어도 한 요소와 일치하는지 확인할때 

anyMatch 메서드를 사용한다.



1
2
3
4
private static boolean isVegetarian() {
    return menu.stream()
            .anyMatch(Dish::isVegetarian);
}
cs


allMatch는 모든 요소가 주어진 프레디케이트와 일치하는지 검사한다.


1
2
3
4
private static boolean isLowCalory() {
    return menu.stream()
            .allMatch(d -> d.getCalories() < 1000);
}
cs



noneMatch는 주어진 프레디케이트와 일치하는 요소가 없는지 확인한다.


1
2
3
4
private static boolean isHighCalory() {
    return menu.stream()
            .noneMatch(d -> d.getCalories() >= 1000);
}
cs



findAny는 임의의 요소를 반환한다.


1
2
3
4
5
private static Optional<Dish> findAnyVegeDish() {
    return menu.stream()
            .filter(Dish::isVegetarian)
            .findAny();
}
cs



findFirst는 첫번째 요소를 반환한다.


1
2
3
4
5
private static Optional<Dish> findFirstDish() {
    return menu.stream()
            .filter(Dish::isVegetarian)
            .findFirst();
}
cs



 



자바8은 간결한 코드, 멀티코어 프로세서의 쉬울 활용 이라는 두가지 요구사항을 기반으로 한다.


1. 스트림 처리 


스트림이란 한번에 한개씩 만들어지는 연속적인 데이터 항목들의 모임이다. 

java.util.stream 패키지에 스트림 api 추가되었다.

반복되는 패턴으로 주어진 조건에 따라 


- 데이터를 필터링(무게가 100 이상인 사과만)

- 데이터를 추출(사과 무게 필드 추출)

- 데이터를 그룹화(초록색 사과와 빨간색 사과로 그룹화)


컬렉션을 필터링하는 가장 빠른방법은

컬렉션을 스트림으로 바꾸고 병렬로 처리 후 리스트로 다시 복원한다.


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
import java.util.*;
import java.util.stream.*;
 
public class Main {
 
    public static class Apple {
        private int weight = 0;
        private String color = "";
 
        public Apple(int weight, String color) {
            this.weight = weight;
            this.color = color;
        }
 
        public int getWeight() {
            return weight;
        }
 
        public void setWeight(int weight) {
            this.weight = weight;
        }
 
        public String getColor() {
            return color;
        }
 
        public void setColor(String color) {
            this.color = color;
        }
 
        @SuppressWarnings("boxing")
        @Override
        public String toString() {
            return String.format("Apple{color='%s', weight=%d}", color, weight);
        }
    }
    
    private static final List<Apple> inventory = Arrays.asList(
            new Apple(80"GREEN")
          , new Apple(155"GREEN")
          , new Apple(120"RED"));
    
    public static void main(String[] args) {
 
        List<Apple> heavyApples = inventory.stream()
                .filter((Apple a) -> a.getWeight() > 100)
                .collect(Collectors.toList());
        
        System.out.println(heavyApples);
        // [Apple{color='green', weight=155}, Apple{color='red', weight=120}]
    }
}
cs



2. 디폴드 메서드 지원


예를 들어 Collections.sort(list)는 정렬 하는 메소드이다. 

자바8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 하는데

자바8 이후에는 디폴트 sort메서드가 나와 구현하지 않아도 된다.



3. 동작 파라미터화 코드 전달하기


계속 변경 되는 요구사항에 대응하기 위해 엔지니어링 비용이 가장 최소화 될수 있게 유지보수가 쉬워야한다.


동작 파라미터화를 이용하면 자주 바뀌는 요구사항에 효과적으로 대응할 수 있다.

동작 파라미터는 아직은 어떻게 실행할 것인지 결정하지 않은 코드 블록이다.


변화하는 요구사항에 대응하기 위해 다음과 같은 문제가 주어진다고 가정하자.


3.1 녹색사과 필터링하기



1
2
3
4
5
6
7
8
9
10
// 초록색 사과만 추출 
public static List<Apple> filterGreenApples(List<Apple> inventory) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (Color.GREEN.name().equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}
cs


위 메소드는 녹색사과만 필터링 할수 있다.

만약 빨간 사과도 필터링 한다고 하면 

색깔만 다른 동일한 메소드 내용의 다른 메소드명( fliterRedApples )으로 생성할수도 있지만

argument enum Color를 추가하여 값을 받아와 처리한다.



3.2 Color 인자 추가하기


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// enum Color
enum Color{
     RED
    ,GREEN
}
 
// Color를 파라미터화
public static List<Apple> filterApplesByColor(List<Apple> inventory, Color color) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (color.name().equals(apple.getColor())) {
            result.add(apple);
        }
    }
    return result;
}
cs


Color라는 인자를 추가하여 메소드의 인자가 1개 늘었다.

하지만 색깔뿐만 아니라 필터로 과일의 무게, 적합한지 flag값도 인자로 받아본다고 가정해보자.



3.3 모든 속성을 메서드 파라미터로 추가하기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
// 모든 속성을 메서드 파라미터로 추가
public static List<Apple> filterApplesAllAttr(List<Apple> inventory, Color color
        , int weight, boolean flag) {
    List<Apple> result = new ArrayList<>();
    for (Apple apple : inventory) {
        if (color.name().equals(apple.getColor()) && flag == true) {
            if(apple.getWeight() > weight) {
                result.add(apple);
            }
        }
    }
    return result;
}
cs


인자가 총 4개가 되었다. 모든속성을 받아와 

조건에 따라 적합한 데이터를 추출하여 뽑아오는것이 가능해졌지만

가독성도 떨어지고 앞으로 추가될 여러 인자(5개~10개)에 유연하게 대처할 수 없다.


참 또는 거짓을 반환하는 함수 predicate(프레디케이트를) 사용하여 

선택조건을 결정하는 인터페이스를 정의해보자.



3.4 추상적 조건으로 필터링하기


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
// 사과 predicate 인터페이스
public interface ApplePredicate{
    boolean test(Apple apple);
}
 
// 초록색 사과 prediate
public static class AppleGreenColorPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return (Main.Color.GREEN.name()).equals(apple.getColor());
    }
}
 
// 무거운 사과 prediate
public static class AppleHeavyWeightPredicate implements ApplePredicate {
    public boolean test(Apple apple) {
        return apple.getWeight() > 150;
    }
}
 
// predicate를 사용하여 추상적 조건으로 필터링하기
public static List<Apple> filterApples(List<Apple> inventory, ApplePredicate p){
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory) {
        if(p.test(apple)) {
            result.add(apple);
        }
    }
    return result;
}
cs


인터페이스 ApplePredicate를 구현하는 클래스는 

모두 들어갈 수 있게 되어 좀 더 유연하게 파라미터를 넣을 수 있다. 

메인에서 다음과 같이 선언 후 넣으면 각 조건에 맞게 추상적으로 데이터를 추출할 수 있다.



1
2
filterApples(inventory, new AppleHeavyWeightPredicate())
filterApples(inventory, new AppleGreenColorPredicate())
cs




3.5 익명 클래스를 사용하여 클래스 선언과 인스턴스화를 동시해 해서 가져오기 


1
2
3
4
5
6
// 클래스 선언과 인스턴스화 동시 수행하는 익명 클래스 사용하기 
static List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
    public boolean test(Apple apple) {
        return Color.RED.name().equals(apple.getColor());
    }
});
cs


predicate와 익명클래스를 같이 사용하여 필요한 데이터를 추출해 올 수 도 있다.


위 코드는 자바8의 람다 표현식을 이용하여 간단하게 표현할 수도 있다.


1
2
// 람다 표현식 사용
static List<Apple> result = filterApples(inventory, (Apple apple) -> Color.RED.name().equals(apple.getColor()));
cs


정리하면


동작 파라미터화를 이용해 변화하는 요구사항에 잘 대응할 수 있는 코드를 구현할 수 있으며 엔지니어링 비용 또한 줄일 수 있다.

predicate 인터페이스를 생성하여 여러 클래스에서 predicate를 구현하여 같은 메소드명의 다른 내용인 메소드(오버라이드)를 구현 할 수 있다.  


마지막으로 전체 소스코드를 첨부한다.


전체소스코드.zip


  1. 중랑구보안관 2020.08.04 15:12 신고

    글들이 자세하게 설명이 잘 되어있어서 도움이 많이 되었습니다.
    감사합니다~

  2. 나그네 2020.08.19 13:35

    저작권 문제는 어떻게 해결하신건지요?

    • effortDev 2020.08.27 22:38 신고

      안녕하세요. 구글링과 서적을 통해 공부한 지식을 정리하여 기술공유하는 블로그이니 너그러이 이해 부탁드립니다. 방문해주셔서 감사합니다.


JSONObject로 생성, JSONObject 리스트 JSONArray에 넣기, JSONArray를 ArrayList에 넣기



1. string 형태의 배열문자열을 split 하여 string array에 넣는다. 


1
2
String[] myCodeArr = [01020304]
String[] motherCodeArr = [999897]
cs



2. string array에 들어간 데이터 값과 name, updateTime값을 JSONObject에 다시 넣는다.


1
2
3
4
5
6
JSONObject jsonObj1 = new JSONObject();

jsonObj1.put("myCode", myCodeArr);
jsonObj1.put("name", name);
jsonObj1.put("motherCode", motherCodeArr);
jsonObj1.put("updatedTime", updatedTime);
cs


1
2
3
4
5
6
  "myCode":["01","02","03","04"],
  "name":"jihyun",
  "motherCode":["99","98","97"],
  "updatedTime":"2019-11-05 12:48:12"
}
cs



3. JSONObject를 여러개 갖고 싶어 JSONArray 안에 다시 넣는다.


1
2
JSONArray jsonArr1 = new JSONArray();
jsonArr1.put(jsonObj1);
cs


1
2
3
4
5
6
7
8
9
10
11
12
13
14
   { 
      "myCode":["01","02","03","04"],
      "name":"jihyun",
      "motherCode":["99","98","97"],
      "updatedTime":"2019-11-05 12:48:12"
   },
   { 
      "myCode":["11","22","33"],
      "name":"yumi",
      "motherCode":["20","30"],
      "updatedTime":"2019-11-05 12:48:12"
   }
]
cs



4. JSONArray에 담긴 것을 JSONObject 형식을 유지한 ArrayList로 만들고 싶다.

JSONArray에 담긴 것을 하나씩 JSONObject로 뽑아내어 ArrayList에 add 시킨다.

JSONObject[] jsons안에 array사이즈를 할당하고 toArray() 를 사용해 arrayJson이 갖고 있는 값의 형태로 arraylist를 생성한다.


1
2
3
4
5
6
7
8
9
ArrayList<JSONObject> arrayJson = new ArrayList<JSONObject>();
 
for (int k = 0; k < jsonArr1.length(); k++) {
    JSONObject tempJson = jsonArr1.getJSONObject(k);
    arrayJson.add(tempJson);
}
 
JSONObject[] jsons = new JSONObject[arrayJson.size()];
arrayJson.toArray(jsons);
cs



5. 전체 소스


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
private void arrIntoJson() throws JSONException {
 
    String name = "";
    String myCode = "";
    String motherCode = "";
    JSONArray jsonArr1 = new JSONArray();
    
    for(int i=0; i<2; i++) {
    
        if(i==0) {
            name = "jihyun";
            myCode = "[01,02,03,04]";
            motherCode = "[99,98,97]";
        }else {
            name = "yumi";
            myCode = "[11,22,33]";
            motherCode = "[20,30]";
        }
        
        String updatedTime = String.valueOf(DateUtil.getLocalDateTime("yyyy-MM-dd HH:mm:ss"));
        String[] myCodeArr = myCode.substring(1, myCode.length()-1).split(",");
        String[] motherCodeArr = motherCode.substring(1, motherCode.length()-1).split(",");
        
        JSONObject jsonObj1 = new JSONObject();
        jsonObj1.put("name", name);
        jsonObj1.put("myCode", myCodeArr);
        jsonObj1.put("motherCode", motherCodeArr);
        jsonObj1.put("updatedTime", updatedTime);
        jsonArr1.put(jsonObj1);
    }
    
    ArrayList<JSONObject> arrayJson = new ArrayList<JSONObject>();
    
    for (int k = 0; k < jsonArr1.length(); k++) {
        JSONObject tempJson = jsonArr1.getJSONObject(k);
        arrayJson.add(tempJson);
    }
    
    JSONObject[] jsons = new JSONObject[arrayJson.size()];
    arrayJson.toArray(jsons);
    
    System.out.println(jsons);
}
cs


arrayJson의 값


1
2
3
4
5
6
7
8
9
10
11
12
13
14
   { 
      "myCode":["01","02","03","04"],
      "name":"jihyun",
      "motherCode":["99","98","97"],
      "updatedTime":"2019-11-05 12:48:12"
   },
   { 
      "myCode":["11","22","33"],
      "name":"yumi",
      "motherCode":["20","30"],
      "updatedTime":"2019-11-05 12:48:12"
   }
]
cs




module-info.java를 통해 

서로 다른 프로젝트 a, b에 대해 a에서 선언한 클래스를 b에서 불러와 사용할수 있다.


common.widget프로젝트에는 

com.logicbig 패키지에 RendererSupport.java라는 파일이 있고

org.jwidgets 패키지에 SimpleRenderer.java라는 파일이 있다.


common.widget 프로젝트(a)의 module-info.java에 외부에 노출하고 싶은 패키지를 exports해주고



1
2
3
module common.widget {
    exports com.logicbig;    // 외부에 노출하고싶은 패키지
}
cs




패키지를 require시 접근하여 사용할수 있다.




1
2
3
4
module data.widget {
    requires common.widget;    // 사용할 패키지
    requires java.sql;
}
cs




requires로 선언후 Java Build Path로 Module path를 잡아주면된다.






이후 프로젝트(b)에서 다른 프로젝트(a)에 있는 클래스를 호출하여 객체를 생성하거나 메소드를 활용할 수 있다.



프로젝트 a의 RendererSupport.java


1
2
3
4
5
6
7
8
9
package com.logicbig;
 
import org.jwidgets.SimpleRenderer;
 
public class RendererSupport {
  public void render(Object object) {
      new SimpleRenderer().renderAsString(object);
  }
}
cs




프로젝트 a의 SimpleRenderer.java


1
2
3
4
5
6
7
package org.jwidgets;
 
public class SimpleRenderer {
  public void renderAsString(Object object) {
      System.out.println(object);
  }
}
cs




프로젝트 b의 Component.java


1
2
3
4
5
6
7
8
9
10
11
12
13
package com.example;
 
import com.logicbig.RendererSupport;
import java.sql.*;
 
// java.base 그 외 다른 모듈 패키지를 쓰려면 module-info에 require로 넣어줘야함.
 
public class Component {
  public static void main(String[] args) {
      RendererSupport support = new RendererSupport();
      support.render("Test Object");
  }
}
cs




프로젝트 b의 Component.java파일에서 프로젝트 a의 RenderSupport 클래스를 호출하여 객체를 생성하는 것을 확인할 수 있다.



module-info.java에 대해 공부하여 서로 다른 프로젝트 간의 exports, require에 대해 알 수 있었다.



소스코드 첨부 :


common.widget.zip

data.widget.zip


  1. 궁금 2020.06.16 21:43

    잘 배우고가요!!!



자바8의 Time 및 Date 관련 새로운 API를 공부하게 되어 정리하게 되었다. 



1. LocalDate, LocalTime, LocalDateTime, DateTimeFormatter 사용하기


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
package dateTimeTest;
 
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Locale;
 
public class LocalDateTimeTest {
 
    public static void main(String[] args) {
 
        // 1. LocalDate 사용
        LocalDate today = LocalDate.now();
        System.out.println(today);
        
        System.out.println("년월일"+today.getYear() + "/" + today.getMonth() + " "+today.getMonthValue() 
        +"/"+today.getDayOfMonth() + " 지난일자 " + today.getDayOfYear());
        System.out.println("요일 " + today.getDayOfWeek()+" "+today.getDayOfWeek().getValue());
        
        // 특정 날짜를 지정해서 LocalDate 생성
        LocalDate endDay = LocalDate.of(20191231);
        System.out.println("현재 기준 몇일 남아 있는지 "+ today.until(endDay, ChronoUnit.DAYS));
        
        System.out.println("현재 기준 1개월 후 "+today.plusMonths(1));
        System.out.println(DayOfWeek.TUESDAY.plus(3));
        
        // LocalTime 사용
        LocalTime now = LocalTime.now();
        System.out.println(now);
        System.out.println("시분초나노초" + now.getHour() + " " + now.getMinute()
                                        + " " + now.getSecond() + " " +now.getNano());
        
        // 특정 시간을 지정해서 LocalTime 생성
        LocalTime bedTime = LocalTime.of(2340);
        LocalTime wakeTime = bedTime.plusHours(8);
        System.out.println(wakeTime);
        
        // LocalDateTime 사용
        LocalDateTime dt = LocalDateTime.now();
        System.out.println(dt);
 
        LocalDate date = dt.toLocalDate();
        System.out.println(date);
        
        LocalTime time = dt.toLocalTime();
        System.out.println(time);
        
        // 특정 날짜와 시간을 지정해서 LocalDateTime 생성
        LocalDateTime dt2 = LocalDateTime.of(2019,2,19,13,20,50);
        System.out.println(dt2);
        System.out.println(dt2.getMonth());
        
        dt2.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        
        // 포맷을 직접 설정한 Formatter 생성
        DateTimeFormatter myFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss E a", Locale.KOREAN);
        System.out.println(dt2.format(myFormat));    
    }
}
cs





2. LocalDateTime 응용해 현재로부터 월급날까지 남은 일수 계산하기



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
package dateTimeTest;
 
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
 
public class SalaryDate {
    
    // 내 월급날(매월 25일)은 몇일 남았을까?
    public static void main(String[] args) {
         LocalDate today = LocalDate.now();
//        LocalDate today = LocalDate.of(2019, 2, 26);
        
        // 1. 기준이 되는 날짜를 구하기
        LocalDate theDay = LocalDate.from(today);
        System.out.println("오늘날짜 : "+theDay);
        
        // 2. 월급날 구하기 매월 25일 
        int salaryDay = 25;
        int remainDay = 0;
 
        if(theDay.getDayOfMonth() > salaryDay) {
            LocalDate lastDayOfMonth = theDay
                    .with(TemporalAdjusters.lastDayOfMonth());
            remainDay = lastDayOfMonth.getDayOfMonth()-theDay.getDayOfMonth()+salaryDay;
        }else if(theDay.getDayOfMonth() < salaryDay) {
            remainDay = salaryDay-theDay.getDayOfMonth();
        }else {
            remainDay = 0;
        }
        
        System.out.println("월급까지 "+remainDay+"일 남았습니다.");
    }
}
 
cs


1
2
오늘날짜 : 2019-02-20
월급까지 5일 남았습니다.
cs



소스코드 첨부: 


myLamdaTest.zip





1. 스트림이란?


스트림은 자바8부터 추가된 컬렉션의 저장요소를 하나씩 참조해서 람다식으로 처리할수 있도록 해주는 반복자이다.


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
package streamTest;
 
import java.util.Arrays;
import java.util.List;
 
public class myStreamTest {
 
    public static void main(String[] args) {
        int count1 = 0;
         List<String> names = Arrays.asList("1""2""3""4""5");
         
         // 기존의 for문
        for (String w : names) {
            if(w.length() > 0) {
                count1++;
            }
        }
        
        // stream 방식
        long count2 = 
                names.stream()
                         .filter(a->a.length()>0)
                         .count();
        
        // 병렬 stream 방식
        long count3 = 
                names.parallelStream()
                         .filter(a->a.length()>0)
                         .count();
        
        System.out.println(count1);
        System.out.println(count2);
        System.out.println(count3);
    }
}
cs


count1, count2, count3의 결과는 같다. 

기존for문,스트림, 병렬스트림 을 사용해서 표현한 것을 확인할 수 있다.



2. 스트림의 특징


- 스트림을 활용하면 복잡한 코드를 간결하게 표현할 수 있다.

- 스트림은 컬렉션, 배열, 파일 등의 대량의 데이터를 가공 축소하여 데이터의 합계, 

평균값, 카운팅, 최대값, 최소값, 집계함수를 모두 표현할 수 있다.


- 오리지날 스트림에서 중간처리(필터, 매핑)를 하고 최종처리(집계 처리 결과물)를 하여 결과를 볼수 있다.

- 스트림은 iterator와 비슷한 역할을 하는 반복자이다.


- 스트림이 제공하는 요소처리 메서드는 함수적 인터페이스 타입이므로 람다식, 

메서드 참조를 이용하여 요소처리 내용을 인자로 전달할 수 있다.



3. 컬렉션과 스트림의 공통점과 차이점



공통점


- 연속된 요소형식의 값을 저장하는 자료구조 인터페이스를 제공한다.

- 둘다 순차적으로 요소에 접근한다.


차이점


컬렉션은


- 각 계산식을 만날 때마다 데이터가 계산된다. 

- 데이터의 접근, 읽기, 변경, 저장이 주요 관심사이다.

- 데이터에 접근하는 방법을 직접 작성해야한다.

- Iterator로 모든요소를 순환해야한다.

- 메모리에 모든 요소가 올라가 있는 상태에서 요소들을 누적시키며 결과를 계산한다.

- 메모리 사용량이 늘어난다.


스트림은


- 최종 연산이 실행 될 때에 데이터가 계산된다. 

- 계산식(람다)을 표현하는 것이 주요 관심사이다.

- 데이터에 접근하는 방법이 추상화되어있다.

- 계산식을 미리 적어두고 계산시에 람다식으로 JVM에 넘긴다.

- 내부에서 요소들을 어떻게 메모리에 올리는 지는 관심사가 아니다.

- 메모리 사용량이 줄어든다.



스트림을 활용해 코드를 개선해보자.





4. 스트림 생성방법 예제



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
package streamTest;
 
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.function.IntSupplier;
import java.util.stream.IntStream;
import java.util.stream.Stream;
 
public class MakingStreams {
 
    public static void main(String...args) throws Exception{
        
        // Stream.of
        Stream<String> stream = Stream.of("Java8""Lambdas""SangHyun");
        stream.map(String::toUpperCase).forEach(System.out::println);
        // result : UpperCase
        
        // Arrays.stream
        int[] numbers = {12345};
        System.out.println(Arrays.stream(numbers).sum());
        // result : 15
        
        // Stream.iterate
        Stream.iterate(0, n -> n + 2)
              .limit(5)
              .forEach(System.out::println);
        // result : 0 2 4 6 8
        
        // random stream of doubles with Stream.generate
        Stream.generate(Math::random)
              .limit(10)
              .forEach(System.out::println);
        // result : random data
        
        // stream of 1s with Stream.generate
        IntStream.generate(() -> 1)
                 .limit(5)
                 .forEach(System.out::println);
        // result : 1 1 1 1 1 
        
        IntStream.generate(new IntSupplier(){
            public int getAsInt(){
                return 2;
            }
        }).limit(5)
          .forEach(System.out::println);
        // result : 2 2 2 2 2
 
        // 피보나치 수열
        IntSupplier fib = new IntSupplier(){
                  private int previous = 0;
                  private int current = 1;
                  public int getAsInt(){
                      int nextValue = this.previous + this.current;
                      this.previous = this.current;
                      this.current = nextValue;
                      return this.previous;
                  }
              };
         IntStream.generate(fib).limit(10).forEach(System.out::println);
         // result : 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
         
         // 유니크 단어 카운트
         long uniqueWords = Files.lines(Paths.get("data2.txt"), Charset.defaultCharset())
                                 .flatMap(line -> Arrays.stream(line.split(" ")))
                                 .distinct()
                                 .count();
 
         System.out.println("There are " + uniqueWords + " unique words in data2.txt");
    }
}
cs




5. 스트림 활용하여 문제 풀기



5.1 Developer 모델



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
package streamTest;
 
public  class Developer{
    
    private String name;
    private String title;
 
    public Developer(String n, String c){
        this.name = n;
        this.title = c;
    }
 
    public String getName(){
        return this.name;
    }
 
    public String getTitle(){
        return this.title;
    }
 
    public void setTitle(String newTitle){
        this.title = newTitle;
    }
 
    public String toString(){
        return "Developer:"+this.name + " , " + this.title;
    }
}
cs




5.2 BiddingCompany 모델



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
package streamTest;
 
public class BiddingCompany{
 
    private Developer developer;
    private String companyName;
    private int year;
    private int salary;
 
    public BiddingCompany(Developer developer, String companyName, int year, int salary)
    {
        this.developer = developer;
        this.companyName = companyName;
        this.year = year;
        this.salary = salary;
    }
 
    public Developer getDeveloper(){ 
        return this.developer;
    }
    
    public String getCompanyName() {
        return companyName;
    }
 
    public int getYear(){
        return this.year;
    }
 
    public int getSalary(){
        return this.salary;
    }
    
    public String toString(){
        return "{" + this.developer + ", " +
               "companyName: "+this.companyName+", " +
               "year: "+this.year+", " +
               "salary:" + this.salary +"}";
    }
}
cs



5.3 StreamTestCode Main 코드



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package streamTest;
 
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
 
public class StreamTestCode {
    public static void main(String ...args){    
        // 개발자의 성명과 직급
        Developer sanghyun = new Developer("SangHyun""대리");
        Developer hyunYoung = new Developer("HyunYoung","사원");
        Developer jiSook = new Developer("JiSook","주임");
        Developer choa = new Developer("Choa","대리");
        
        // 입찰회사에서 개발자 채용을 위해 연봉제시 
        // 입찰한 개발자 이름, 참여한 회사이름, 설립연도 , 제시한연봉 
        List<BiddingCompany> company = Arrays.asList(
            new BiddingCompany(sanghyun, "쿠팡",20104000), 
            new BiddingCompany(sanghyun, "위메프",20115000), 
            new BiddingCompany(sanghyun, "sk플래닛",20127000), 
            new BiddingCompany(hyunYoung, "위메프",20102000),  
            new BiddingCompany(hyunYoung, "sk플래닛",20123000), 
            new BiddingCompany(jiSook, "쿠팡",20103500),
            new BiddingCompany(choa, "위메프",20113800
        );    
        
        // 쿼리 1 : 설립일이 2012년인 입찰회사를 모두 찾아서 연봉이 작은값부터 큰값으로 출력해라
        // 2012년이라는 특정한 조건이 있으므로 filter사용, 작은값부터 큰값 정렬 sorted 사용
        
        List<BiddingCompany> t1 = 
                company.stream()
                             .filter(a->a.getYear()==2012)
                             .sorted(Comparator.comparing(b->b.getSalary()))
                             .collect(Collectors.toList());
        
        t1.forEach(System.out::println);
        /* result : {Developer:HyunYoung , 사원, companyName: sk플래닛, year: 2012, salary:3000}
                        {Developer:SangHyun , 대리, companyName: sk플래닛, year: 2012, salary:7000} */
        
        // 쿼리 2 : 입찰회사 개발자 목록에서 개발자의 중복되지 않는 직급만 출력해라.
        // 특정한 조건이 딱 주어지지 않았으므로 map사용, 중복제거 distinct사용
        
        List<String>  t2_1 =     
                company.stream()
                             .map(BiddingCompany::getDeveloper)
                             .map(Developer::getTitle)
                             .distinct()
                             .collect(Collectors.toList());
 
        System.out.println("map 2번 사용");
        t2_1.forEach(System.out::println);
        /* result : 대리 사원 주임 */
        
        List<String>  t2_2 =         
                company.stream()
                             .map(tx -> tx.getDeveloper().getTitle()) 
                             .distinct()
                             .collect(Collectors.toList());
        
        System.out.println("map 1번 사용");
        t2_2.forEach(System.out::println);
        /* result : 대리 사원 주임 */
        
        // 쿼리 3: 개발자 중에서 대리직급을 찾아 이름순으로 정렬해라.
        
        company.stream()
                     .map(a->a.getDeveloper())
                     .filter(b->b.getTitle().equals("대리"))
                     .distinct()
                     .sorted(Comparator.comparing(c->c.getName()))
                     .forEach(System.out::println);
        /* result : Developer:Choa , 대리 
                        Developer:SangHyun , 대리 */
        
        // 쿼리 4: 알파벳 순서대로 정렬하여 개발자 이름만 출력해라
        
        String names = 
                company.stream()
                             .map(a->a.getDeveloper().getName())
                             .distinct()
                             .sorted()
                             .reduce("", (a1,a2) -> a1+a2+" ");
        
        System.out.println(names);
        /* result : Choa HyunYoung JiSook SangHyun */
        
        // 쿼리 5 : 사원 직급의 개발자가 있는가?
        
        boolean 사원 = 
                company.stream()
                             .anyMatch(tx -> tx.getDeveloper().getTitle().equals("사원"));
                                    
        System.out.println(사원);
        /* result : true */
        
        // 쿼리 6 : 대리직급의 개발자를 과장직급으로 업데이트 해라
        
        System.out.println("업데이트 전: "+company);
        company.stream()
                     .map(a->a.getDeveloper())
                     .filter(a->a.getTitle().equals("대리"))
                     .forEach(a->a.setTitle("과장"));
        System.out.println("업데이트 후: "+company);
        
        // 쿼리 7: 입찰한 모든회사 중에 가장 높은 연봉을 제시한 값은 얼마인가?
        
        int highSalary = 
                company.stream()
                             .mapToInt(a->a.getSalary())
                             .max()
                             .getAsInt();
        
        System.out.println(highSalary);
        /* result : 7000 */
    }
}
cs


소스 코드 첨부 :


myLamdaTest.zip




1. 람다식


람다는 하나의 abstract 메서드 만 가진 인터페이스를 함수형 인터페이스라고 한다.

람다식을 지원하는 java.util.functon 패키지가 추가되었다.



1.1 람다 표현과 특성


1
Runnable r = () -> System.out.println("Hello World");
cs


1
2
3
4
5
public interface Comparator<T> {
    public int compare(T o1, T o2);
}
 
Comparator<Integer> comparator = (o1, o2) -> o2 - o1;
cs


 - 이름이 없는 익명이다.

 - 메서드처럼 특정클래스에 종속되지 않는다. 

 - 람다식을 메서드의 인자로 전달하거나 변수값으로 저장할 수 있다.

 - 간결하다.

 



2. 스트림

 

자바8은 스트림과 람다를 활용하여 이전보다 간결하고 향상된 방법으로 collection을 처리한다.

 

stream은 순차(sequential), 병렬(parallel) 두 종류가 존재한다.

순차 stream은 싱글 쓰레드가 순차적으로 처리되며 병렬 stream은 fork/join 프레임워크로 구현된 

메서드에 의해 병렬로 처리된다.

 


2.1 stream의 장단점


장점은 Laziness, Collection의 interation처리를 자바에게 맡겨 둠으로써 jvm이 최적화할수 있는 기회를 제공한다.

단점은 재사용 불가하다. 한번 사용된 stream은 재사용이 불가능하여 필요에 따라 새롭게 만들어야 한다.

 


3. 함수형 인터페이스 활용하기


3.1 java.util.function 패키지의 함수형 인터페이스

 

함수형 인터페이스

 Descriptor

 Method명

 Predicate<T>

 T -> boolean

 test()

 Consumer<T>

 T -> void

 accept()

 Supplier<T>

 () -> T

 accept()

 Function<T,R>

 T -> R

 apply()

 UnaryOperator<T>

 T -> T

 identity()



위에 함수형 인터페이스를 활용해서 예제를 작성해보겠다.



3.2 predicate와 consumer 예제



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
package lamdaTest;
 
public class Grape {
    private int weight = 0;
    private String color = "";
 
    public Grape(int weight, String color){
        this.weight = weight;
        this.color = color;
    }
 
    public Integer getWeight() {
        return weight;
    }
 
    public void setWeight(Integer weight) {
        this.weight = weight;
    }
 
    public String getColor() {
        return color;
    }
 
    public void setColor(String color) {
        this.color = color;
    }
 
    public String toString() {
        return "Grape{" +
               "color='" + color + '\'' +
               ", weight=" + weight +
               '}';
    }
}
cs


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
package lamdaTest;
 
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
 
public class PredicateAndConsumer {
 
    public static void main(String... args) {
 
        // 청포도, 보라포도, 적포도
        List<Grape> inventory = 
                Arrays.asList(new Grape(70"green"), 
                              new Grape(90"purple"), 
                              new Grape(120"red"));
 
        // 1. Predicate 인터페이스 사용
        System.out.println("----- 1. Predicate 인터페이스 사용 -----");
        filter(inventory, grape -> grape.getWeight() >=100)
        .forEach(System.out::println);
        // result : Grape{color='red', weight=120}
        
        // 2. Consumer 인터페이스 사용
        System.out.println("----- 2. Consumer 인터페이스 사용 -----");
        printGrapeInfo(inventory, grape -> System.out.println(grape));
        // result : All List print
        
    }
 
    public static void printGrapeInfo(List<Grape> inventory, Consumer<Grape> consumer) {
        for (Grape grape : inventory) {
            consumer.accept(grape);
        }
    }
    
    public static List<Grape> filter(List<Grape> inventory, Predicate<Grape> p) {
        List<Grape> result = new ArrayList<>();
        for (Grape grape : inventory) {
            if (p.test(grape)) {
                result.add(grape);
            }
        }
        return result;
    }
}
cs



3.3 supplier 예제


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
package lamdaTest;
 
import java.util.function.Supplier;
 
class Animal {
    public void eat() {
        System.out.println("Eating something");
    }
}
 
class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("Eating fish");
    }
}
 
public class SupplierTest {
    static void eatSomething(Supplier<extends Animal> supplier) {
        Animal animal = supplier.get();
        animal.eat();
    }
 
    public static void main(String[] args) {
        // Using Lambda expression
        eatSomething(() -> new Animal());
        eatSomething(() -> new Cat());
        
        // Using Method Reference
        eatSomething(Animal::new);
        eatSomething(Cat::new);
    }
}
cs



3.4 function 예제


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
package lamdaTest;
 
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
 
public class FunctionTest {
 
    // Function 인터페이스
    public static List<String> getColorList(List<Grape> inventory, Function<Grape, String> function) {
        List<String> colorList = new ArrayList<String>();
        for (Grape grape : inventory) {
            colorList.add(function.apply(grape));
        }
        return colorList;
    }
 
    public static void main(String[] args) {
        List<Grape> grapeList = new ArrayList<>();
        grapeList.add(new Grape(150"green"));
        grapeList.add(new Grape(200"purple"));
        grapeList.add(new Grape(200"red"));
        grapeList.add(new Grape(150"red"));
 
        // 1. 익명 클래스 사용
        System.out.println("----- 1. 익명 클래스 사용 -----");
        getColorList(grapeList, new Function<Grape, String>() {
            @Override
            public String apply(Grape grape) {
                // TODO Auto-generated method stub
                return grape.getColor();
            }
        }).forEach(System.out::println);
        
 
        // 2. 람다식 사용
        System.out.println("----- 2. 람다식 사용 -----");
        getColorList(grapeList, grape -> grape.getColor()).forEach(System.out::println);
 
        // 3. 메소드 레퍼런스 사용
        System.out.println("----- 3. 메소드 레퍼런스 사용 -----");
        getColorList(grapeList, Grape::getColor).forEach(System.out::println);
 
    }
 
    public static class Grape {
        private Integer weight = 0;
        private String color = "";
 
        public Grape(Integer weight, String color) {
            this.weight = weight;
            this.color = color;
        }
 
        public Integer getWeight() {
            return weight;
        }
 
        public void setWeight(Integer weight) {
            this.weight = weight;
        }
 
        public String getColor() {
            return color;
        }
 
        public void setColor(String color) {
            this.color = color;
        }
 
        public String toString() {
            return "Grape{" + "color='" + color + '\'' + ", weight=" + weight + '}';
        }
    }
}
 
cs



3.5 operator 예제


 

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
package lamdaTest;
 
import java.util.function.IntBinaryOperator;
 
public class OperatorTest {
    private static int[] valueList = { 102030 };
 
    // IntBinaryOperator 사용
    public static int maxOrMin(IntBinaryOperator operator) {
        int result = valueList[0];
        for (int value : valueList) {
            result = operator.applyAsInt(result, value);
        }
        return result; }
 
    public static void main(String[] args) {
        // 최대값 얻기
        int maxValue = maxOrMin((n1,n2) ->{
            if(n1>=n2) return n1;
            else return n2;
        });
        System.out.println(maxValue);
        
        // 최소값 얻기
        int minValue = maxOrMin((n1,n2) ->{
            if(n1<=n2) return n1;
            else return n2;
        });
        System.out.println(minValue);
    }
}
cs



정리


1. 람다식은 익명클래스를 간결하게 표현할수 있다.

2. 함수형 인터페이스는 추상 메서드 하나만 정의된 인터페이스이다.

3. 메서드 레퍼런스를 이용하면 기존의 메서드 구현을 재사용 가능하다.

4. Comparator, Predicate, Function 같은 함수형 인터페이스는 람다식을 조합할수 이는 다양한 디폴트 메소드를 제공한다.



소스코드 첨부 :


myLamdaTest.zip





  1. Archanfel 2021.11.29 15:29

    supplier의 메소드가 accept()로 되어 있네요
    get이 맞습니다


1. 객체지향 디자인 패턴


객체지향 프로그램이 복잡해지면서 이를 간결하게 정리할 필요성이 생긴 관계로 '디자인 패턴'이라는 것이 생겼다. 

디자인 패턴 프로그래밍 형식을 정하는 일종의 약속이다.


객체 지향 프로그래밍 설계를 할 때 자주 발생하는 문제들을 피하기 위해 사용되는 패턴.


여러 사람이 협업해서 개발할 때 다른 사람이 작성한 코드, 기존에 존재하는 코드를 이해하는 것은 어렵다. 

이런 코드를 수정하거나 새로운 기능을 추가해야 하는데 의도치 않은 결과나 버그를 발생시키기 쉽고 

성능을 최적화시키기도 어렵다. 이로 인해 시간과 예산이 소모된다. 


디자인 패턴은 의사소통 수단의 일종으로서 이런 문제를 해결해준다. 

예를 들어 문제 해결의 제안에 있어서도 “기능마다 별도의 클래스를 만들고, 

그 기능들로 해야할 일을 한번에 처리해주는 클래스를 만들자.”라고 제안하는 것보다 

"Facade 패턴을 써보자."라고 제안하는 쪽이 이해하기 쉽다.


일반 프로그래머가 만나는 문제가 지구상에서 유일한 문제일 확률은 거의 없다. 

이미 수많은 사람들이 부딪힌 문제다. 따라서 전문가들이 기존에 해결책을 다 마련해 놓았다. 



2. 생성 패턴(추상 객체 인스턴스화)


2.1 팩토리(Factory Method)


상위 클래스와 하위 클래스가 있을 때, 팩토리 클래스를 사용하여 하위 클래스의 인스턴스를 생성하는 패턴.


Animal.java


1
2
3
4
5
6
public class Animal {
    Animal(){
        // 생성자
    }
    // 이하 Animal의 메소드들
}
cs


Dog.java


1
2
3
4
5
6
7
8
9
10
11
12
public class Dog extends Animal{
    Dog(){
        // 생성자
    }
    public Dog(String data){
        return;
    }
    // 이하 Dog의 메소드들
    public String toString(){
        return "Dog";
    }
}
cs


Cat.java


1
2
3
4
5
6
7
8
9
10
11
12
public class Cat extends Animal{
    Cat(){
        // 생성자
    }
     public Cat(String string) {
         return;
    }
    // 이하 Cat의 메소드들
    public String toString(){
        return "Cat";
    }
}
cs


Map.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.util.Iterator;
import java.util.LinkedList;
 
public class Map {
    
    public static void main(String[] args){
        
        // 저장된 파일로부터 동물을 가져오는 '맵 로드' 기능을 
        // lnkList에 초기 샘플데이터 넣었다고 가정
        LinkedList<String> lnkList = new LinkedList<String>();
        lnkList.add("Dog");
        lnkList.add("Cat");
 
        // Map은 말 그대로 맵의 구현 방법에 대해서만 서술되어야 하는데, 
        // 파일을 읽는 부분에서 '동물을 분류하는' 추가적인 책임이 포함되어있다. 
        // 만약 새 동물 Rat을 넣어야 한다면 전혀 상관없는 Map 클래스를 수정해야 할 것이다.
        
        // 1. 개선 전 방법 
        Iterator<String> iter = lnkList.iterator();        
        while (iter.hasNext()) {
            if(iter.equals("Dog")){
                Dog dog = new Dog();
                System.out.println(dog);
            }else if(iter.equals("Cat")){
                Cat cat = new Cat();
                System.out.println(cat);
            }
        }
 
        // 그래서 다양한 하위 클래스들을 생성하는(Factory : 공장) 클래스를 만들어 
        // 그 클래스에 책임을 위임하는 것이다.
        // 그러면 새 클래스 AnimalFactory를 만들어보자.
        
        // 2. 개선 후 방법 
        Iterator<String> factoryIter = lnkList.iterator();    
        while (factoryIter.hasNext()) {
           Animal animal = AnimalFactory.create(factoryIter.next());
           System.out.println(animal);
        }
    }
}
cs


AnimalFactory.java


1
2
3
4
5
6
7
8
9
10
11
12
13
public class AnimalFactory {
    static Animal create(String string) {
        if(string.equals("Dog")) {
            return new Dog(string);
        } else if(string.equals("Cat")) {
            return new Cat(string);
        } else if(string.equals("Rat")){
//            return new Rat(string);
        }
       //기타 동물의 생성자들
        return null;
    }
}
cs



팩토리를 사용하면 새 동물을 추가하는지의 여부에 상관없이 

다른 클래스를 수정할 필요가 없어져 단일 책임 원칙을 잘 지키는 코드가 된다.



2.2 빌더(Builder)


빌더 클래스는 인스턴스를 생성자를 통해 직접 생성하지 않고, 빌더라는 내부 클래스를 통해 간접적으로 생성하게 하는 패턴이다.


Jacket.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Jacket {
 
    int number;
    String name;
    double size;
 
    private Jacket(int number, String name, double size) {
        //Jacket 클래스 초기화
        this.number = number;
        this.name = name;
        this.size = size;
    }
 
    // 빌더 클래스는 인스턴스를 생성자를 통해 직접 생성하지 않고, 
    // 빌더라는 내부 클래스를 통해 간접적으로 생성하게 하는 패턴이다
    public static class Builder {
        int number=0;
        String name=null;
        double size=0d;
 
        public Builder() {
            //Builder 초기화
        }
 
        public Builder setNumber(int number) {
            this.number = number;
            return this;
        }
 
        public Builder setName(String name) {
            this.name = name;
            return this;
        }
 
        public Builder setSize(double size) {
            this.size = size;
            return this;
        }
 
        public Jacket build() {
            return new Jacket(number, name, size);
        }
    }
}
cs


JacketProduction.java


1
2
3
4
5
6
7
8
9
10
public class JacketProduction {
    public static void main(String args[]){
        createJacket();
    }
 
    public static void createJacket() {
        Jacket jacket = new Jacket.Builder().setNumber(1).setName("NorthFace").setSize(105).build();
        System.out.println(jacket.number+"  "+ jacket.name +"  "+ jacket.size);
    }
}
cs


1
1  NorthFace  105.0
cs


Builder 사용 이유


1. 클래스와 사용 대상의 결합도를 낮추기 위해


private 등의 접근 제한자로 제한하여 외부에서 임의로 접근하는 것을 막아 클래스와 사용대상의 결합도를 떨어뜨리고, 

대신 Builder라는 내부 클래스를 통해 해당 클래스를 간접적으로 생성한다.


2. 생성자에 전달하는 인수에 의미를 부여하기 위해


예를 들어서, 위에 제시된 예시에서 빌더 패턴이 없다고 가정하고 인스턴스를 생성하려면 

Jacket jacket = new Jacket(number, name, size); 이렇게 코드를 작성하여야 한다.


위의 예시에서는 인수가 세 개니까 그냥 저렇게 써도 큰 문제는 없지만, 

생성자에 전달하는 인수의 가짓수가 열 종류 가까이 되는 클래스의 경우에는 

고전적인 생성자 패턴으로는 인수를 전달하는 것이 상당히 비직관적이 된다.

(인수의 종류를 외워서 써넣어야되는 것뿐만 아니라, 인수의 순서까지 고려해야 한다!)

그래서 빌더 패턴을 통해 setXXX 형식으로 인수를 전달하면 한 눈에 보기에도 이것이 무슨 인수인지를 파악하기가 쉽다.




2.3 싱글톤(Singleton)



1
2
3
4
5
6
7
8
9
10
11
12
class Singleton {
 
    static final Singleton instance = new Singleton();
 
    private Singleton() {
        //초기화
    }
 
    public Singleton getInstance() {
        return instance;
    }
}
cs


키보드 리더, 프린터 스풀러, 점수기록표 등 클래스의 객체를 하나만 만들어야 하는 경우 사용한다. 

클래스 내에서 인스턴스가 단 하나뿐임을 보장하므로, 프로그램 전역에서 해당 클래스의 인스턴스를 바로 얻을 수 있고, 

불필요한 메모리 낭비를 최소화한다.


이 패턴에서는 생성자를 클래스 자체만 사용할 수 있도록 private 등의 접근제한자를 통하여 제한하여야 한다. 

생성자를 다른 곳에서도 사용할 수 있으면 그 곳에서도 인스턴스를 만들 수 있기 때문.


싱글톤 패턴을 사용하기 위해서는 반드시 접근제한자를 이용하여 외부의 접근을 막거나, 

final로 reference를 변경 불가능하게 설정하여야 한다. 

물론 생성자에 접근제한자를 사용하면 최소한 다른 인스턴스로 레퍼런스시키지는 못하겠지만, 

ClassName.singleton = null; 처럼 레퍼런스 자체를 지워버릴 수 있기 때문.

구현 방법에는 사전 초기화, 늦 초기화 등이 있다.


Eager initialization(사전 초기화)


클래스 로딩시에 인스턴스를 생성하는 방법이다. 위의 예시가 사전 초기화. 멀티스레드 환경에서의 

이중 객체 생성 문제가 없지만, 인스턴스를 호출하지 않아도 무조건 클래스를 초기화하기에 메모리 효율이나 연산 효율은 낮다.

Java에서는 static block initialization이라는 변종도 있다. 

클래스가 로딩될 때 최초 1회만 실행되는 static block을 통해 싱글톤 인스턴스를 초기화하는 방법인데, 구조적으로는 크게 다르지 않다.


Lazy initialization(늦 초기화)


인스턴스를 실제로 사용할 시점에서 인스턴스를 생성하는 방법이다. 세심한 방법을 쓰지 않으면 

위에 언급한 이중 객체 생성 문제가 발생할 가능성이 높으나, 

인스턴스를 실제로 사용하지 않는다면 메모리와 연산량을 아낄 수 있다는 장점이 있다.



3. 구조 패턴(객체 결합)


3.1 파사드(Facade)


파사드는 프랑스어 Façade에서 차용된 단어로 보통 건물의 출입구로 이용되는 정면 외벽 부분을 가리키는 말이다.

파사드 패턴은 시스템의 복잡성을 감추고, 사용자(Client)가 시스템에 접근할 수 있는 인터페이스(Interface)를 사용자(Client)에게 제공한다. 

따라서 파사드 패턴은 기존의 시스템에 인터페이스를 추가함으로써, 

복잡성을 감추기 위해 사용된다. 파사드 패턴은 구조적 패턴(Structural Pattern)에 포함된다.


1단계: 인터페이스를 생성한다.


1
2
3
public interface Coffee {
    void make();
}
cs



2단계: 그 인터페이스를 구현하기 위한 구체적인 클래스를 생성한다.


Americano.java


1
2
3
4
5
6
public class Americano implements Coffee {
   @Override
   public void make() {
      System.out.println("Americano::make()");
   }
}
cs


BanillaLatte.java


1
2
3
4
5
6
public class BanillaLatte implements Coffee {
   @Override
   public void make() {
      System.out.println("BanillaLatte::make()");
   }
}
cs


Cappuccino.java


1
2
3
4
5
6
public class Cappuccino implements Coffee {
   @Override
   public void make() {
      System.out.println("Cappuccino::make()");
   }
}
cs



3단계: 파사드 클래스를 생성한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CoffeeMaker {
    private Coffee americano;
    private Coffee banillaLatte;
    private Coffee cappuccino;
 
    public CoffeeMaker() {
        americano = new Americano();
        banillaLatte = new BanillaLatte();
        cappuccino = new Cappuccino();
    }
 
    public void makeAmericano(){
        americano.make();
    }
    
    public void makeBanillaLatte(){
        banillaLatte.make();
    }
 
    public void makeCappuccino(){
        cappuccino.make();
    }
}
cs



4단계: 다양한 종류의 형태를 만들기 위해 파사드를 사용한다.


1
2
3
4
5
6
7
8
9
public class FacadePatternDemo {
    public static void main(String[] args) {
        CoffeeMaker coffeeMaker = new CoffeeMaker();
 
        coffeeMaker.makeAmericano();
        coffeeMaker.makeBanillaLatte();
        coffeeMaker.makeCappuccino();
    }
}
cs



5단계: 결과값을 확인한다.


1
2
3
Americano::make()
BanillaLatte::make()
Cappuccino::make()
cs



4. 행위 패턴(객체 간 커뮤니케이션)


4.1 반복자(iterator)


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
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
 
public class IteratorTest {
    public static void main(String args[]){
        
        // List for each
        List<Integer> list = new ArrayList<Integer>();
        list.add(1); list.add(2); list.add(3);
        
        for(Integer a : list){
            System.out.println(a);
        }
        
        // Iterator
        LinkedList<String> lnkList = new LinkedList<String>();
        lnkList.add("Dog"); lnkList.add("Cat");
        
        Iterator<String> iter = lnkList.iterator();
        
        while(iter.hasNext() == true) {
          Object object = iter.next();
          System.out.println(object);
        }
    }
}
cs


1
2
3
4
5
1
2
3
Dog
Cat
cs



5. 기타


5.1 MVC


최근 애플리케이션 개발에 있어 상당히 중요한 모델이 된 패턴으로 Model, View, Controller 세 가지 부분으로 이루어져 있다. 

Model은 자료(Data)를 생성, 저장, 처리하는 역할을 하는 부분이다. 

View는 Model로부터 받은 자료를 여러 가지 형태로 사용자에게 보여주는 역할을 한다. 

Controller란 소프트웨어의 흐름을 제어하는 것으로 View와 Model 사이에서 관계를 설정하여 주는 부분을 말한다. 

Controller는 Model이나 View가 바뀌더라도 수정 없이 작동되어야 한다.



테스트 작성한 소스 첨부


myTest5.zip




+ Recent posts