spring boot 테스트 프로젝트로 swagger를 적용해보았다.

 

1. gradle에 해당 springfox-swagger를 명시해준다.

 

 

 

swagger2, swagger-ui, swagger-annotations, swagger-models를 받아주는데

해당 정보는 mvn repository에서 확인할 수 있다.

 

mvnrepository.com/artifact/io.springfox/springfox-swagger2/2.9.2

mvnrepository.com/artifact/io.springfox/springfox-swagger-ui/2.9.2

mvnrepository.com/artifact/io.swagger/swagger-annotations/1.5.21

mvnrepository.com/artifact/io.swagger/swagger-models/1.5.21

 

gradle 설정이 완료되면

 

ToySwaggerConfig 파일을 하나 생성해준다.

 

 

먼저 WebMvcConfigurer를 implements 받고 addResourceHandlers를 구현해준다.

swagger-ui가 보여질 화면의 리소스 위치를 설정해주는 것이다.

 

다음은 swagger를 만들 Docket을 bean으로 등록하고 생성할 Docket을 만들어준다.

 

.apis는 api를 탐색할 위치(com.shlee.toy1,controller)

.path 경로로 시작되는 위치(controller에서 @RequestMapping("/v1")으로 시작되는 uri)

.directModelSubstitute로 날짜형식을 맞춰주고

.globalResponseMessage로 요청을 받았을때 어떻게 처리할지를 정해준다.

 

.additionalModels의 typeResolver.resolve(ResponseEntity.class)는 ModelRef에서 지정한.

ex) CommonError, CommonResponse, string, ResponseEntity 같은 사용자 지정 모델값을 넣어주면 된다.

필요 하지 않다면 다음과 같이 작성해도 된다.

 

 

마지막으로 .apiInfo를 통해 api정보를 지정해준다.

 

 

그리고 서버를 실행후 http://localhost:8080/swagger-ui.html 에 접속하면

controller와 연동된 api 목록이 나타나게 된다.

 

 

test 시도 시 response데이터가 잘 오는 것을 확인 할 수 있었다.

 

 

 

최근 스프링 부트 기본 설정을 테스트로 잡아보면서 작업하는데

DB 비밀번호를 간단하게 설정(숫자 0882) 하여 application.yml 파일을 작성하였더니 해당 오류가 발생하였다.

 

 

 

패스워드는 대문자, 소문자, 숫자, 특수문자를 포함한 암호 길이 8자 이상으로 설정해야 한다고 한다.

그래서 아래와 같이 패스워드를 조합하여 설정하였더니 해당 오류가 발생하지 않았다. 

 

 

 

또한 스프링 2.3.x 버전에서는 

application-local.yml profile을 local

application-dev.yml profile은 dev

application-prd.yml profile을 prd로 

파일단위별로 관리하여

 

 

 

해당하는 active profiles가 무엇인지 설정한 뒤 실행하였다.

 

2.4.x 버전 부터 profile을 설정할 시 deprecated 오류가 발생하지만

이전 설정을 spring.config.use-legacy-processing=true 값을 통해

2.3.x의 동일한 설정을 사용할 수 있다.

  

 


프론트에 Vuejs를 사용해 사용자가 키워드를 넣어 크롤링 할수 있게 구현해보려고 한다.


이미 Vuejs 기본셋팅하는 방법을 포스팅한 적이 있다.

https://shlee0882.tistory.com/282?category=903333


해당 포스팅을 참고하여  Vuejs 프로젝트 생성하고 온다.



검색 엔진 ID와 API_KEY를 gitignore에 추가하기 위해 따로 API_DATA.js 파일을 뺐다.


Home.vue에는 구글키워드 이미지 크롤링 텍스트와 ImageList 컴포넌트를 갖고 있다.


사용한 오픈소스는

파싱을 위한 url, js를 쉽게 쓸수 있게 도와주는 jquery, 이미지 get을 위한 axios

zip파일 생성 및 체크를 위한 JSZip, JSZipUtils 을 사용했다.


구글이미지를 크롤링하는 js코드가 구현되어 있고 크롤링 진행상황에 대한 progress bar가 추가 되었다.

또한 수집된 url이 리스트형태로 텍스트 출력되는 것과 zip파일로 사용자에게 다운을 제공한다.

그 전 포스팅과 달라진 점은 fs을 사용하지 않는다는 것이다.


프론트에서는 fs를 사용할 수 없어 이미지 api로 json 데이터로 response 받으면

이미지 url을 axios request하여 이미지 데이터를 blob 구조로 받아와 이미지 리스트를 만들고

이미지 리스트를 zip파일로 묶어서 사용자에게 반환한다.


효율적인 크롤링과 acess-control-allow-origin 문제 해결을 위해 

크롬 extention에서 간단한 플러그인을 설치해준다.





https://github.com/shlee0882/vuejs-nodejs-google-image-crawling


배포된 url로 들어가 테스트 해본다.

크롤링이 잘되어 zip파일로 이미지를 다운받아지는 것을 확인 할 수 있다.







마지막으로 해당 소스의 배포된 url과 github repository를 올린다.


클라이언트


https://github.com/shlee0882/vuejs-nodejs-google-image-crawling


https://shlee0882.github.io/vuejs-nodejs-google-image-crawling/#/


서버쪽 


https://github.com/shlee0882/node-crawling-google-image


구글 검색엔진 API, Node js, Vue js를 활용하여 간단하게 이미지 크롤링하는 프로젝트를 만들어 보려고 한다.



본인은 구글 이미지를 크롤링 할 수 있는 오픈소스가 있는지 검색하였고

github에서 google-images라는 오픈소스를 발견하여 npm을 이용하여 해당 오픈소스를 설치하여 활용하였다.



1. 일단 제일 먼저 Nodejs프로젝트를 생성해보자. 



폴더를 하나 만들고 cmd에서 해당폴더의 경로로 이동 후 npm init을 한다.




그럼 package.json이 생성되고 해당 폴더경로로 VSC(visual studio code)로 오픈한다.



2. VSC에서 TERMINAL창을 열고 해당 위치에서 필요한 package를 설치한다.





3. 구글 커스텀 검색 엔진을 생성한다.


https://cse.google.com/cse


들어가서 간단하게 구글 검색엔진을 생성할 수 있고 

생성하면 검색엔진 ID가 나오게 된다.


이미지 검색 부분을 가능하게 설정해주고

해당 검색엔진 ID를 보관한다.





4. 구글 개발자 콘솔로 접속하여 프로젝트를 생성 및 연결하기


https://console.developers.google.com/


구글 개발자 콘솔로 접속하여 프로젝트를 생성한다.

본인은 googleSearchApiTest라는 프로젝트를 생성했다. 




프로젝트 생성 이후 라이브러리로 이동하여 custom search api를 검색하여 api 사용해보기를 설정한다.




그다음 사용자 인증정보 탭으로 이동 후 API 키를 하나 생성한다.


여기서 생성한 API_KEY를 보관한다.




우리는 총 2개의 값 (검색엔진 ID와 API_KEY) 를 갖게 되었다.


오픈소스 google-images 레퍼런스

https://github.com/vadimdemedes/google-images



5. 다시 프로젝트로 돌아온다.



getGoogleImg.js라는 자바스크립트 파일을 하나 생성한다.



6. 구글검색엔진으로 이미지 크롤링 하기 위한 코드를 작성한다.


getGoogleImg.js 파일에 해당 코드를 작성한다.


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
const GoogleImages = require('google-images');
let url = require('url');
let request = require('request');
const fs = require('fs');
var zip = new require('node-zip')();
 
// ** 8라인 본인의 검색엔진 ID와 API_KEY로 교체 필수
const client = new GoogleImages('검색엔진 ID''API_KEY');
 
const keyWord = "조현영";
const pageStVal = 1;
const pageEndVal = 201;
 
let saveDir = __dirname + "/"+keyWord;
 
if(!fs.existsSync(saveDir)){
    fs.mkdirSync(saveDir);
}
 
// 이미지 검색
const searchFunc = (pageStVal) =>{
    client.search(keyWord,  {page: pageStVal, size: 'large'}).then(images => {
        images.forEach(img => {
            console.log(img);
            let filePath = url.parse(img.url).pathname;
            let newFilePath = filePath.replace(/[^a-zA-Z0-8\.]+/g, '_');
            let localFilePath = saveDir + "/" + newFilePath;
            let pattern = /\.(jpg|png|gif)\b/
            
            // 파일길이가 200 미만이고 이미지 파일인지 체크
            if(newFilePath.length<200 && pattern.test(newFilePath)){
                try {
                    request.get(img.url).on('error'function(err) {
                        console.log('request error1:', err);
                    }).pipe(
                        fs.createWriteStream(localFilePath).on('close'function() {})
                    );
                } catch (err) {
                    console.log('request error2:', err);
                }
            };
        });
        compareTwoVal(pageStVal, pageEndVal);
    }).catch(error => {
        console.log(">>>>>>>>>>>>>>>>>>>"+error);
        console.log("모든 이미지를 수집했습니다.");
        makeImgToZip();
        return;
    });
}
 
// 이미지 압축파일 만들기
const makeImgToZip = () =>{
    var zipName = keyWord+".zip";
    var someDir = fs.readdirSync(__dirname+"/"+keyWord);
    var newZipFolder = zip.folder(keyWord);
    
    for(var i = 0; i < someDir.length; i++){
        newZipFolder.file(someDir[i], fs.readFileSync(__dirname+"/"+keyWord+"/"+someDir[i]),{base64:true});
    }
    var data = zip.generate({base64:false,compression:'DEFLATE'}); 
    fs.writeFile(__dirname +"/"+ zipName, data, 'binary'function(err){
        if(err){
            console.log(err);
        }
    });
}
 
// 페이징 검색
const compareTwoVal = (pageStVal, pageEndVal) =>{
    if(pageStVal<=pageEndVal){
        setTimeout(function() {
            pageStVal += 10;
            console.log("pageStVal: >>>>>>>>"+pageStVal);
            searchFunc(pageStVal);
        }, 500);
    }else{
        console.log("모든 이미지를 수집했습니다.");
        makeImgToZip();
        return;
    }
}
 
// 이미지 수집 시작
searchFunc(pageStVal);
cs



코드를 작성 및 저장 후 

termial에서 node getGoogleImg.js 로 실행한다.





정상적으로 동작한다면 

크롤링한 이미지 데이터를 json형태로 return받게 된다.


이제 이 많은 데이터가 

키워드의 폴더 하위로 로컬컴퓨터에 저장하게 되고 zip파일도 만든다.


레인보우 조현영의 이미지 데이터가 잘 크롤링 된 것을 확인 할 수 있다. 






Nodejs로 크롤링 테스트가 완료되었으니


프론트에 Vuejs를 사용해 사용자가 키워드를 넣어 크롤링 할수 있게 구현해보자.


[수정된 코드 github url]


https://github.com/shlee0882/node-crawling-google-image



Vue js와 Node js 사용해보기 - 핵심 1 을 이어서 내용을 진행하겠다.

1편 ( https://shlee0882.tistory.com/282?category=797808 )


visual studio code를 실행하여 2개의 파일을 수정할 것이다.


1. src/views/Home.vue



[Home.vue]


17라인에 HelloWorld를 다시 21라인에서 hello-world 컴포넌트로 재정의한다.

3라인에 hello-world 태그로 들어가고 HelloWorld.vue 파일에

바인딩 될 데이터를 셋팅해주고 있다


23라인에 초기 데이터를 선언 및 초기화해주고

30라인에서는 계산된 결과값을 가져오고

35라인에서는 뷰가 생성될때 시작되는 함수이다.


2. src/components/HelloWorld.vue



[HelloWorld.vue]


13라인 props에서 부모에게 받은 바인딩 된 데이터를 어떻게 받아올지 선언하고

받아온 데이터를 3~5라인에서 출력해주고 있다.


6라인에 changeAddress 클릭 이벤트가 있는데

21라인에 부모의 이벤트를 받는 $emit을 사용해 부모의 change-address를 찾아가 데이터를 전달해준다.




위 두 파일을 수정하여 띄워보면


라우팅에 따라 localhost:8080/, localhost:8080/about 에 다른 페이지가 출력되는 것을 확인 할 수 있다.




위 실습을 통해 Vue js에서 많이 사용하는 아래 항목들에 대해 


- data-bind

- on-click

- props

- components

- data()

- computed

- created()

- methods

- $emit


알아 볼 수 있게 되었다.



마지막으로 해당 소스와 배포된 URL을 추가한다.


https://shlee0882.github.io/vuejs-nodejs-basic-setting


https://github.com/shlee0882/vuejs-nodejs-basic-setting









vue js와 node js를 활용하여 vue js의 핵심 개념을 정리해 보려고 한다.


1. nodejs를 설치


https://nodejs.org/ko/download/



2. node js 프로젝트 생성


node js 프로젝트 폴더 생성 한다.

본인은 vueJs_nodeJs 라는 폴더로 생성하였다.

cmd로 생성한 프로젝트 경로로 진입한다.


아래 명령어를 입력하여 설치한다.


1
npm install -g @vue/cli
cs



위와 같이 설치가 완료되면 

아래 명령어를 입력한다.


1
vue create client
cs


위와 같이 명령어를 치면 선택할수 있는 항목이 주어진다.

Vue 버전을 선택할수도 있고 직접 커스텀하게 설정할 수 도 있다.

Manually select features 선택한다.



스페이스를 눌러 Babel, Router, Linter / Formatter 를 선택하고 엔터를 친다.

Babel 자바스크립트 컴파일러 설치해주고

Router 라우팅, linter는 코딩스타일 문법체크 정도로 생각할 수 있다.

여기서 취향대로 빼거나 추가할 수 있다.



라우터에 대한 히스토리 모드는 Y를 입력후 엔터를 친다.

히스토리 모드를 N으로 입력한다면 경로에 /#/이 붙는 현상이 발생한다.



Lint는 선택사항이라 아무거나 해도 된다.

ESLint + Airbnb config 설정을 선택한다.



Lint에 대한 설정 2개를 모두 선택한다.



config에 관한 설정을 in package.json 에 두겠다는 것이다.



다음 프로젝트에 이 설정 저장 N으로 설정한다.



설치가 완료되면 다음과 같이 뜨게 된다.

cd client를 하고 npm run serve를 한다.



vue js기반의 node js서버가 띄워지게 되었다.

해당 서버가 잘 띄워 졌는지 확인해보자.



멋지게 뜬것을 확인 할 수 있었다.


이제 코드를 봐야하므로 visual studio code를 실행하여 프로젝트 구조를 살펴본다.


만약 eslint를 설정을 해제하고 싶다면 package.json 위치와 같은 경로에 

.eslintignore 파일을 생성 후 해제하고 싶은 소스 경로를 명시해준다.



이제 이 설정을 가지고 vuejs를 실습하고 테스트 해보겠다.


2편 ( https://shlee0882.tistory.com/283?category=797808 )


Git 커밋 조작하기, 히스토리 조작, 잔디밭 조작하기


1일 1커밋을 놓쳤을때 잔디밭이 비게 되는데 비어있는 잔디밭에 색깔을 칠해보자. 

비어있는 잔디는 git 커밋 히스토리의 날짜 조작을 통해 

과거의 커밋을 한 것처럼 조작 할 수 있다.  



1. 먼저 새로운 git-history를 조작할 테스트 repository를 만든다.





2. Git Bash를 열고 로컬 repository와 연결된 위치로 이동한다.






3. git log로 커밋한 내용을 확인한다.



1
git log
cs


commit 다음 나오는 문자열(7dc220b~로 시작하는) 해쉬값을 복사한다.






4. git commit 기록을 수정하는 첫번째 방법은 git 필터링을 활용하는 방법이다.





1
2
3
4
5
6
git filter-branch --env-filter \
    'if [ $GIT_COMMIT = 복사한해쉬값 ]
     then
         export GIT_AUTHOR_DATE="Tue Aug 4 11:00:00 2020 +0900"
         export GIT_COMMITTER_DATE="Tue Aug 4 11:00:00 2020 +0900"
     fi'
cs


위에는 간단하게 지정한 해쉬값이 같을 경우 날짜를 바꾼다는 얘기다.

나는 8월 17일에 커밋과 푸시를 했지만 8월 4일에 커밋한 것으로 바꿀 것이다.


GIT_AUTHOR_DATE와 GIT_COMMITTER_DATE에 바꾸고 싶은 날짜를 수정한다.

(앞뒤 공백을 꼭 포함해야 한다.)



1
2
3
4
'변경 내용은 :wq로 저장한다.'
git pull origin master --allow-unrelated-histories
 
git push origin master
cs




push를 완료하면


github에 8월 4일에 조작된 날짜로 잔디밭이 표시된 것을 확인 할 수 있다.



다음은 git 필터링을 활용하는 방법이 아닌 git rebase를 활용하여 커밋을 조작해보자.



5. git rebase를 활용한 조작방법


일단 날짜 형식의 200805, 200806 md파일을 준비하고 1개씩 커밋 푸시 하였다. 

그리고 git log를 통해 조작할 커밋 해쉬값을 복사한다.



git log를 보면 모두 8월 17일에 커밋되었다는 것을 알려주고 있다.


git rebase -i [해쉬값] 를 입력한다.





수정할 해쉬값 앞에 edit를 넣는다.


edit [해쉬값] [커밋 메세지] 





1
2
3
4
5
6
7
8
9
git rebase -i 해쉬값
 
GIT_COMMITTER_DATE="Aug 6 11:00 2020 +0900" git commit --amend --date="Aug 6 11:00 2020 +0900"
 
git rebase --continue
 
git pull origin master --allow-unrelated-histories
 
git push origin master
cs




위와 같이 입력 후 girhub repository를 확인하면 

커밋날짜가 조작되어 잔디밭에 색깔이 칠해진 것을 확인 할 수 있다.



8월 17일에 커밋한 파일날짜를 8월 6일로 수정하여

커밋한 파일의 타이틀과 내용 날짜 모두 동일하게 커밋된 것을 확인 할 수 있었다.

git rebase를 활용하여 커밋조작이 가능했다.




git filtering을 사용하는 방법과 git rebase를 사용해 커밋날짜 조작을 해보았다.


해당 내용은 stackoverflow를 참고하여 작성했다.


(https://stackoverflow.com/questions/454734/how-can-one-change-the-timestamp-of-an-old-commit-in-git)


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



 


+ Recent posts