Q.

SELECT 조회를 수행하면서 조회 결과로 출력되는 데이터집합의 총 ROW수를 어떻게 구해야할까? 

 

 

A.

  • 다른 컬럼을 조회하면서 동시에 결과 집합의 총 행 수를 구하려면 COUNT() 함수와 윈도우 함수를 함께 사용할 수 있다. 
SELECT
    column1,
    column2,
    COUNT(*) OVER () AS total_rows
FROM
    your_table
WHERE
    your_condition;

 

 

  • your_table은 대상 테이블이고, your_condition은 원하는 행을 선택하기 위한 조건이다. 
  • 이 쿼리는 your_table에서 your_condition을 만족하는 각 행에 대해 column1과 column2를 조회하면서, 동시에 전체 행 수를 나타내는 total_rows를 반환한다. 

 

 

 

 


 

 

 

 

COUNT(*) OVER () 란? 


  • COUNT(*) OVER ()는 SQL에서 사용되는 윈도우 함수(Window Function) 중 하나로, 전체 결과 집합의 총 행 수를 각 행에 포함된 결과로 반환하는데 사용된다. 
  • 이 함수는 각 행에 동일한 값을 부여하며, 그 값은 전체 결과 집합의 행 수다.
  • 즉, COUNT(*)는 각 행이 속한 전체 결과 집합의 행 수를, OVER ()는 윈도우 함수가 전체 결과 집합에 대해 적용되도록 지정한다.

 

 

 

 


 

 

 

 

그 외의 윈도우 함수(Window Function)


 

  • SQL에서 사용되는 윈도우 함수는 다양하며, 데이터를 파티션별로 나누고 정렬된 순서대로 처리하는데 사용된다. 

 

 

 

ROW_NUMBER(): 결과 집합 내에서 각 행에 대한 순서 번호를 할당합니다.
SELECT
    column1,
    column2,
    ROW_NUMBER() OVER (ORDER BY some_column) AS row_num
FROM
    your_table;



 

 

RANK(): 결과 집합 내에서 값이 동일한 행을 하나의 등급으로 묶어 순서 번호를 할당합니다. 같은 값이 여러 번 나타날 경우 같은 등급이 부여됩니다.
SELECT
    column1,
    column2,
    RANK() OVER (ORDER BY some_column) AS rank_num
FROM
    your_table;



 

 

DENSE_RANK(): RANK()와 비슷하지만, 같은 값이 여러 번 나타날 경우 중복된 등급이 부여되지 않습니다.
SELECT
    column1,
    column2,
    DENSE_RANK() OVER (ORDER BY some_column) AS dense_rank_num
FROM
    your_table;

 

 

 

SUM() OVER(): 결과 집합 내의 각 행에 대해 누적 합계를 계산합니다.
SELECT
    column1,
    column2,
    SUM(column2) OVER (ORDER BY some_column) AS cumulative_sum
FROM
    your_table;




 

AVG() OVER(): 결과 집합 내의 각 행에 대해 평균을 계산합니다.

 

SELECT
    column1,
    column2,
    AVG(column2) OVER (ORDER BY some_column) AS average
FROM
    your_table;



 

 

LEAD(): 현재 행 다음에 나오는 값을 가져옵니다.
SELECT
    column1,
    column2,
    LEAD(column2) OVER (ORDER BY some_column) AS next_value
FROM
    your_table;

 

 

 

LAG(): 현재 행 이전에 나오는 값을 가져옵니다.
SELECT
    column1,
    column2,
    LAG(column2) OVER (ORDER BY some_column) AS previous_value
FROM
    your_table;



 

 

 

 

 

 

 

 

 

 

Q.

스프링에서 서비스에서 쿼리에 보낼 변수를 임의 지정후 매퍼를 호출할때 @param을 붙여야 하는 이유가 뭘까?  왜 @param을 붙이지 않으면 변수를 못찾는 걸까? 

 

 

 

A.


  • 스프링에서 서비스에서 쿼리에 보낼 변수를 임의로 지정하고 매퍼를 호출할 때, @Param 어노테이션을 사용하는 이유는 MyBatis와 관련이 있다.
  • MyBatis는 SQL 쿼리를 실행할 때, 매개변수를 명시적으로 지정하지 않으면 자동으로 매핑을 수행한다.
  • 그래서 메서드의 매개변수가 하나일 때는 @Param 어노테이션을 사용하지 않아도 MyBatis가 매개변수를 찾아 매핑할 수 있습니다.
  • 하지만 메서드의 매개변수가 여러 개일 경우에는 MyBatis가 어떤 매개변수를 사용하여 SQL 쿼리의 파라미터로 매핑해야 하는지 알 수 없다.
  • 따라서 @Param 어노테이션을 사용하여 명시적으로 어떤 매개변수를 어떤 파라미터에 매핑해야 하는지 알려주는 것이 필요한 것이다.

 

 


 

 

 

  • 예를 들어, 다음과 같은 메서드 시그니처가 있다고 가정해본다.
public void updateData(String name, int age);

 

 

  • 이 경우에는 다음과 같이 MyBatis XML에서 쿼리를 작성할 수 있다.
<update id="updateData" parameterType="map">
    UPDATE your_table
    SET name = #{name},
        age = #{age}
</update>

 

 

 

  • 하지만 이때, MyBatis는 어떤 매개변수를 name에, 어떤 매개변수를 age에 매핑해야 하는지 알 수 없다.
  • 이런 경우에 @Param 어노테이션을 사용하여 메서드의 각 매개변수에 명시적으로 이름을 부여하여 사용한다.
public void updateData(@Param("name") String name, @Param("age") int age);

 

 

 

 

 

 

 

 

 

 

[Spring] @param 사용이유

public void countQuery(@Param("name")String parameter); 이렇게 @Param 어노테이션을 붙이면 본인이 원하는 명으로 mapper에서 사용할 수 있다. 위와 같은 경우는 #{name}이 되겠다. 사실 위의 코드같은경운 파라미

popo015.tistory.com

 

 

 

 

 

 

 

 

 

  • if~ else if 조건문의 공식은 다음과같다고 배웠다. 

 

  • 그러나 가끔 if 조건문을 작성하다가 조건의 경우와 가짓수가 명확해서 마지막 조건을 else if로 끝맺는 경우가 있었다.
  • 주로 조건에 해당하지않는 예외처리가 없는 경우 이랬다. 
  • 그렇다고 switch 문을 쓰기에도 적절하지않아 어떻게 해야하나 고민했다. 

 

  • 간단한 예시다.
  • 위와같이 코드를 작성후 컴파일하거나 실행해도 문제가 없어서 찜찜하지만 그대로 코드를 작성하곤했다.
  • 회사차원에서 코드 리뷰를 하거나 사수분이 여유가 있었다면 여쭤봤을텐데 안타깝게도 모두 해당하지않아서 매번까먹었다.
  • 그러나 우리의 GPT 선생님이 계시므로 생각난김에 물어봤다. 

 

 

 

 

 

 

 

가능은 하다.

 

  • else if 블록을 사용하여 여러 조건을 처리할 때, 마지막 else 블록이 없더라도 코드는 여전히 올바르게 동작할 수 있습니다. 마지막 else 블록은 선택적이며, 모든 if 또는 else if 조건에 맞지 않을 때 실행되는 기본 동작을 정의하는 역할을 합니다.
  • 따라서, 아래와 같이 else 블록이 빠져도 코드는 성립합니다:

 

 


 

하지만 논리적 명확성은 떨어질 수 있다. 

  • 하지만 else 블록을 빼게 되면, 위의 코드에서 num이 50 이하인 경우에 대한 처리가 없으므로 모든 경우에 대한 명시적인 처리가 없게 됩니다.
  • 따라서 프로그램의 논리를 명확하게 전달하기 위해 가능한 모든 경우에 대한 처리를 포함하는 것이 중요합니다.




 

 

 

 

 

 

시작하며

짧은 사이드 프로젝트를 하면서 내 로컬에서만 올렸다 내렸다 하는 단계를 지나 개발서버를 동작시켜보는 경험을 하게 되었다. 그 과정에서 푸티(putty)를 사용해보았는데 복기할겸, 다음 사용시 참고할겸 기록을 남겨본다.  

 

 

 

 

푸티(Putty)를 이용해 프로젝트를 개발서버에 올리기
  • 우선 가장 최신화된 프로젝트를 개발서버에 올려보는 단계이다.
  • EXPORT 하고자 하는 파일을 .war 확장자로 내보내 본다.

 

  • 프로젝트 > Export

 

 

 

  • 확장자는 .war 로 한다. > Next

 

 

 

  • 프로젝트 명을 설정하고 파일을 내보낼 경로도 설정한다. 
  • 만약 새로 내보내려는 파일이 기존 경로의 이름/파일과 중복된다면 Overwrite existing file을 체크해준다.
  • 작성 후 > Finish

 

 

 

  • 그러면 내가 선택한 경로에 .war 확장자를 가진 파일이 생성된다. 

 

 

 

  • FileZilla를 실행하여 호스트주소, 사용자명, 비밀번호, 포트번호를 입력후 빠른연결로 접속 시도한다.

 

 

 

  • 뭐.. 다 모자이크해서 사진이 의미가 있겠냐만 여기서 보여주고 싶은건 왼쪽 구역의 새로운 파일을 오른쪽 영역 개발 경로에 덮어 씌워준다는 것이다. 
  • 왼쪽에서 파일명을 드래그해 동일한 파일명이 존재하는 오른쪽 경로에 덮어씌워준다.

 

 

 

  • 푸티를 실행한다.

 

 

 

  • 호스트 네임, 혹은 아이피 주소를 입력 후 Port 번호까지 입력한다.
  • 이후 Open을 누르면 접속이 완료된다. 

 

 

 

  • 로그인시 아이디를 입력하면 비밀번호를 치라는 다음줄이 나올것인데
  • 비밀번호는 아무리 입력해도 눈에 보이지 않는다.
  • 그냥 감으로 맞게 치고 엔터를 누르면 다음과같이 로그인 이력이 나타난다. 

 

 

 

$ cd + 경로명 : 해당 경로로 이동하겠다는 명령어이다.
$ ll : 현재 위치한 파일 구조를 보이도록 하는 명령어이다. (영문 소문자 L엘)

 

 

 

  • 즉 cd home 폴더로 이동 후 'll '명령어를 입력하면 다음과같이 폴더의 모습이 나온다. 

 

 

 

  • 참고로 어떤 경로명을 일일이 치기 힘들어 복사를 하고싶다면 
  • 1. 푸티가 아닌 메모장에 복사, 붙여넣기 한 뒤
  • 2. 푸티로 옮겨와 cd 를 입력후 + '마우스 오른쪽 버튼' 을 클릭하면 자동으로 붙여넣어진다.  

 

 

 

  • 원하는 폴더경로로 이동했다면 이젠 방금 덮어씌웠던 war 확장자 파일의 압축을 풀어준다.
  • 압축을 푸는 명령어는 다음과 같다. 
  • $ jar -xfv [파일명].[확장자]

 

 

 

  • 서버를 올렸다 내릴땐 다음과같은 [./] 표식을 붙여준 후 실행한다. 

 

 

 

  • 정리하면, 다음과 같은 단계로 최신파일을 개발 서버에 올렸다가 내렸다.  
1. 깃에서 최신파일을 땡겨받는다 
2. 파일 경로와 globals.properties 내 경로 확인 후 war 파일로 만다
3. 파일질라에 업데이트 한다 
4. 푸티에 로그인한다. 서버 주소를 입력한다. 
5. 아이디 tomcat, 비밀번호 tomcat 을 친다. 
6. cd + 파일경로를 입력한다. 
7. $ ll을 쳤을때 나오는 경로가 현재폴더 내 들어와서 보이는 파일들 이다. 
8. 보이는 것중 하나로 들어가기 위해서 $ cd + 눈에보이는 경로를 친다. 

 

 

 

더보기

 

1. 올리려는 파일 압축하기 
( 기존에 있던 파일은 날짜 적어서 이름 바꾸기_ 같은 파일이라고 인식 못하도록)
이클립스 - export - war

2. was 경로 맞춰서 로컬에서 압축한 war파일을 filezilla(sftp)로 개발서버에 올리기

3. 개발 서버에 파일 올린 후 putty로 압축파일 풀기
war 파일 풀기 : jar -xvf klac.war
(-xvf : 압축풀기 명령어)

4. 서버 재기동 
cd /home/tomcat/apache-tomcat-9.0.75/bin/shutdown.sh - 중지
cd tomcat/apache-tomcat-9.0.75/bin/startup.sh - 시작

4-1. putty창 하나 더 켜서 로그 확인하기
로그경로
cd /home/tomcat/apache-tomcat-9.0.75/logs/catalina.out
cd /home/tomcat/apache-tomcat-9.0.75/logs/  이 경로에서 tail -f catalina.out

 

 

 

 

 

 

 

관련에러


JBWEB000236 : Servlet.service() for servlet jsp threw exception : java.lang.NumberFormatException : For input string : "lsNum1"

 

 

  • 조회 해올 쿼리에서 총 건수를 가져오는 화면을 만드는 도중 다음과같은 에러 메세지가 발생했다.
    결론만 말하자면 List로 날리고 있던 쿼리를 for문을 돌려서 가져오는게 아니라 특정값만 가져오려했기에 발생하는 문제이다.
  • 해결을 위해 객체.변수명의 형태가 아니라 객체[인덱스].변수명 의 형태로 인덱스를 기재해주어야 한다. 
  • 구글링 해보니 같은 오류의 또다른 원인으로는 ' ' 과 " " 의 위치혼동도 있다고 하니 참고해둘것

 

 

  • 다음과같이 작성했을때 나타나던 오류를 
<td class="class1">
	<c:out value="list.lsNum1"/>
</td>

 



  • 과 같이 작성하니 잘 해결되었다. 
<td class="class1">
	<c:out value="list[0].lsNum1"/>
</td>




 

 

 

 

 

 

 

 

collect()와 Collectors


  • collect()는 스트림의 요소를 수집하는 최종 연산으로 앞서 배운 리듀싱 (reducing)과 유사하다.
  • collect()가 스트림의 요소를 수집하려면, 어떻게 수집할 것인가에 대한 방법이 정의되어 있어야 하는데 이 방법을 정의한 것인 바로 컬렉터(collector)이다.
  • 컬렉터는 Collector인터페이스를 구현한 것이다.
  • Collectors클래스는 미리 작성된 다양한 종류의 컬렉터를 반환하는 static메서드를 가지고 있다.
collect() 	스트림의 최종연산. 매개변수로 컬렉터를 필요로 한다.
Collector 	인터페이스, 컬렉터는 이 인터페이스를 구현해야한다.
Collectors 	클래스, static메서드로 미리 작성된 컬렉터를 제공한다.

 

  • collect()는 매개변수의 타입이 Collector인데,매개변수가 Collector를 구현한 클래스의 객체이어야 한다는 뜻이다. 그리고 collect()는 이 객체에 구현된 방법대로 스트림의 요소 를 수집한다

 

 

 

 

 

 

 

스트림을 컬렉션과 배열로 변환- toList(),toSet( ),toMap( ),toCollection( ),toArray()


  • 스트림의 모든 요소를 컬렉션에 수집하려면, Collectors클래스의 toList()와 같은 메서드를 사용하면 된다.
  • List나 Set이 아닌 특정 컬렉션을 지정하려면, toCollection()에 해당 컬렉션의 생성자 참조를 매개변수로 넣어주면 된다.
  • Map은 키와 값의 쌍으로 저장해야하므로 객체의 어떤 필드를 키로 사용할지와 값으로 사용할지를 지정해줘야 한다.
  • 예시화면과같이 요소의 타입이 Person인 스트림에서 사람의 주민번호(regld)를 키로 하고, 값으로 Person객체를 그대로 저장하는 식이다.

 

 

 

 

  • 스트림에 저장된 요소들을 'T[ ]’타입의 배열로 변환하려면, toArray()를 사용하면 된다.
  • 단, 해당 타입의 생성자 참조를 매개변수로 지정해줘야 한다.
  • 만일 매개변수를 지정하지 않았다면, 반환받는 배열의 타입은 'Object[ ]’이다.

 

 

 

 

 

통계 - counting(), summinglnt(), averaginglnt( ),maxBy(), minBy()


  • 앞서 살펴보았던 최종 연산들이 제공하는 통계 정보를 collectO로 똑같이 얻을 수 있다.
  • 이후groupingBy()와 함께 사용할 때 해당 메서드들이 유용하게 쓰인다. 그룹별로 통계정보를 얻을 수 있기 때문이다. 

 

  • counting()앞에는 Collectors가 생략되어있고 제대로 쓴다면 Collectors.counting() 로 작성해야 한다.
  • 보다 간결한 코드를 위해 Collectors의 static메서드를 호출할 때는 ‘Collectors.’를 생략하였다.
  • static import되어 있다고 가정하자.

 

 

 

 

리듀싱 - reducing()


  • 리듀싱 역시 collect()로 가능하다
  • collect()의 reducing은 앞선 reduce()와는 달리 '그룹별로' 리듀싱이 가능하다는 차이점이 있다.
  • 전자는 전체에 대한 리듀싱만 가능하다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

스트림의 최종 연산


  • 최종 연산은 스트림의 요소를 소모해서 결과를 만들어낸다.
  • 그래서 최종 연산후에는 스트 림이 닫히게 되고 더 이상 시용할 수 없다.
  • 최종 연산의 결과는 스트림 요소의 합과 같은 단일 값이거나, 스트림의 요소가 담긴 배열 또는 컬렉션일 수 있다

 

 

 

 

forEach()


  • forEach()는 peek()와 달리 스트림의 요소를 소모하는 최종연산이다.
  • 반환 타입이 void 이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.
  • forEachOrdered는 순서대로 결과를 출력한다. 
  • sequential()은 직렬로 스트림을 연산한다. 
  • pararell()은 병렬로 스트림을 연산한다.

 

 

 

조건 검사 - allMatch(), anyMatch(), noneMatch(), findFirst(), findAny()


  • 스트림의 요소에 대해 지정된 조건에 모든 요소가 일치하는지, 일부가 일치하는지 아니면 어떤 요소도 일치하지 않는지 확인하는데 사용할 수 있는 메서드들이다.
  • 이 메서드들은 모두 매개변수로 Predicate(조건식)를 요구하며, 연산결과로 boolean을 반환한다.
  • allMatch()모든 요소가 조건을 만족시키면 true를 반환한다.
  • anyMatch()하나라도 조건을 만족시키면  true를 반환한다.
  • noneMatch모든 요소가 조건을 만족시키지 않을 때  true를 반환한다.

 

 

 

findFirst()와 findAny()


  • 조건에 일치하는 첫 번째 것을 반환하는 findFirst()가 있는데, 주로 filter()와 함께 사용된다.
  • 이는 조건에 맞는 스트림의 요소가 있는지 확인하는데 사용된다.
  • 병렬 스트림인 경우에는 findFirst()대신 findAny()를 사용해야 한다.

 

 

 

 

 

 

리듀싱 reduce()


  • 스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다.
  • 그래서 매개변수의 타입이 BinaryOperator다.
  • 처음 두 요소를 가지고 연산한 결과를 가지고 그 다음 요소와 연산한다.
  • 이 과정에서 스트림의 요소를 하나씩 소모하게 되며, 스트림의 모든 요소를 소모하게 되면 그 결과를 반환한다.

 

  • 연산결과의 초기값(identity)을 갖는 reduce()도 있는데,
  • 이 메서드들은 초기값과 스트림의 첫 번째 요소로 연산을 시작한다.
  • 스트림의 요소가 하나도 없는 경우, 초기값이 반환되므로,반환 타입이 Optional<T>가 아니라 T다.

 

 

  • forEach는 배열 strArr 내 요소들을 출력한다. 

 

  • 이때 이를병렬로 처리할 수 있는데 중간연산으로 parallel()을 사용하면 된다.
  • 출력 결과가 순서와 상관없이 출력되고있는 것을 확인할 수 있다. 

 

  • 병렬로 처리하되 최종연산으로 forEachOrdered()를 사용하면 순서대로 출력된다. 

 

  • noneMatch는 predicate 조건문을 사용함으로서 조건에 일치하는 결과가 하나도 없을때 true를 반환한다.
  • 조건대로 길이가 0인 배열의 요소는 없으므로 true가 출력되었다. 

 

 

  • 이때 배열요소의 길이를 3으로 조건 수정시, false를 반환한다.
  • "sum"이있어 해당조건을 일치하는 요소가 하나이상 있기 때문이다. 

 

  • findFirst를 사용함으로서 첫 글자가 's'인 배열의 요소중 첫번째로 발견한 것을 찾는다.
  • 순서대로 찾기에 "stream"이 가장먼저 출력되었다. 

 

  • 이 때 중간연산으로 parallel()을 사용하면, 순서와 상관없이 첫번째 조건에 맞는 결과를 반환하게 되므로
  • "sum" 역시 출력될 수 있다.

 

 

  • mapToInt 와 map은 똑같이 변환을 시켜주지만 전자는 기본형으로 변환이 된다는 차이점이 있다.
  • 즉, 같은 연산이어도 mapToInt는 Stream<String>을 IntStream으로 변환하지만 
  • map은 Stream<String>을 Stream<Integer>로 변환한다.

 

 

 

 

 

 

 

 

 

Optional<T>와 Optionallnt


  • 최종 연산의 결과 타입이 Optional인 경우가 있다.
  • Opti0nal<T>은 지네릭 클래스로 ‘T타입의 객체’를 감싸는 래퍼 클래스이다.
  • 그래서 Optional타입의 객체에는 모든 타입의 참조변수를 담을 수 있다.
  • 최종 연산의 결과를 그냥 반환하는 게 아니라 Optional객체에 담아서 반환하는 것이다.
  • 이처럼 객체에 담아서 반환을 하면, 반환된 결과가 null인지 매번 if문으로 체크하는 대신 Optional에 정의된 메서드를 통해서 간단히 처리할 수 있다. 널 체크를 위한 if문 없이도 NullPointerException이 발생하지 않는 코드를 작성하는 것이 가능해진다.
public final class Optional<T> {
	private final T value; // T타입의참조변수
}

 

 



Optional<T>


  • Optional 클래스란, T타입 객체의 래퍼 클래스이다.
  • Optional객체를 생성할 때는 of() 또는 ofNullable()을 사용한다
  • 그동안 null을 다룰때, 직접적으로 다루는것은 NullPointerException을 발생시킬 수 있어 위험했다.
  • (1) 그래서 null을 ""을 이용하여 간접적으로 다루거나
  • (2) if문을 사용하여 null을 체크하곤 했는데,
  • Optional<T>는 이러한 점을 해결하고 null을 간접적으로 다루기 위해 사용될 수 있는 래퍼클래스이다.

 

 

  • 만일 참조변수의 값이 null일 가능성이 있으면, of()대신 ofNullable()을 사용해야한다.
  • of()는 매개변수의 값이 null이면 NullPointerException일 발생하기 때문이다.
  • Optional〈T>타입의 참조변수를 기본값으로 초기화할 때는 empty()를 사용한다.
  • null로 초기화하는 것이 가능하지만, empty()로 초기화 하는 것이 바람직하다

 

 

 

 

  • 즉 null을 그대로 참조하는 것이 아닌, 주소값이 있는 객체에 담고 다시 다른 객체에서 앞선 주소를 참조하면,
  • 해당 null값은 항상 null이 아니게 되는 것이다.

 

 



Optional<T> 객체를 생성하는 다양한 방법


  • of 스태틱 메소드를 이용해 Optional 객체를 생성할 수 있다.
  • 단, of 메소드에 null을 그대로 담고 다루는 것은 NullPointException이 발생한다.
  • 이를 방지하기 위해 ofNullable 메소드를 사용하는데, 이때는 null을 그대로 담아도 예외가 발생하지 않는다. 
  • Optional 객체 생성시 null을 그대로 넣을 수는 있지만 바람직하지않고
  • empty() 메소드를 이용해 빈 객체로 초기화하는 방법을 사용할 수 있다.

 

 

 

  • 마찬가지로 배열또한 Object[] objArr = null로 넣는 것이 아니라 new Object[0]을 이용해 빈채로 넣어주는등 
  • 널 포인트 익셉션을 줄이기 위한 방법으로 접근해야 한다.

 

 

 

Optional<T> 객체의 값 가져오기 


  • Optional 객체에 저장된 값을 가져올 때는 get()을 사용한다.
  • 값이 null일 때는 NoSuchElementException이 발생하며, 이를 대비해서 orElse()로 대체할 값을 지정할 수 있다.
  • 이때 orElse("") 즉, 저장된 값이 null 일경우 지정된 문자열을 반영시키는 메소드를 사용할 수 있다.
  • orElse( )의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet( )
  • null일 때 지정된 예외를 발생시키는 orElseThrow( )가 있다.
  • orElseGet() 메소드의 경우 매개변수안에 람다식을 사용할 수 있다. 
  • orElseThrow() 메소드로는 null일시 예외까지 지정해 다루어 줄 수 있다. 
  • orElseGet(), orElseThrow()는 입력값없이 반환값만 계속 생성하는 Supplier 클래스에 속해있다. 

  • Stream처럼 Optional객체에도 filter(), map(), flatMap()을 사용할 수 있다.
  • map()의 연산결과가 Optional〈Optional〈T〉〉일 때, flatMap()을 시용하면 Optional〈T〉를 결과로 얻는다.
  • 만일 Optional객체의 값이 null이면, 이 메서드들은 아무 일도 하지 않는다.

 

 

  • isPresent()메소드는 null 일시 boolean 타입으로 결과를 반환한다. 
  • ifPresent() 메소드는 null이 아닐때만 작업을 수행하며, null일경우 아무 작업도 하지 않는다. 

 

 

  • 예제를 보자 
  • 먼저, 배열에 빈값을 넣고싶다면 arr = { }; 와같이 작성해 줄 수 있는데 
  • 이경우 콘솔 출력시 배열 안에 아무것도 없으므로 길이는 0이 출력된다. 

 

 

  • 그러나 배열에 직접 null을 작성해 다뤄줄 시 에러가 발생하게 된다. 
  • 빈값인데 길이를 반환하려고 했기 때문이다.  

 

 

  • 이때 arr 참조변수 안에 new int[0] 와같이 작성해주면 에러를 발생시키지 않고도 빈값을 채워넣을 수 있다. 

 

 

  • Optional 객체를 생성할때, 빈값을 넣어주고 싶다면 null을 직접 넣고 다루는 것은 바람직하지 않다. 
  • empty 메소드를 넣으면 빈값인 채로 객체를 생성할 수 있다. 

 

 

  • 그러나 앞서 언급했듯 get() 메소드를 이용시, null에 접근하는 get은 예외를 반환시킨다. 

 

 

  • get메소드를 사용해야 겠다면 try~ catch 문으로 감싸 예외 발생시 작성할 구문을 또다시 작성해주면 된다. 
  • 예외 발생시 "" 빈문자열로 초기화되도록 작성되었다. 

 

 

  • 그러나 이렇게 예외구문으로 감쌀시 코드가 길고 지저분해지므로 
  • 손쉽게 orElse() 메소드를 사용할 수 있다. 
  • orElse는 접근한 객체의 값이 null일 경우 () 안에 작성된 지정된 문자열을 반환한다. 

 

 

  • orElse("EMPTY")를 작성시 빈값에 대해 EMPTY문자열이 대신 출력되었다.  

 

 

  • 메서드 참조식을 람다식으로 바꾸는 연습도 계속 해보아야 한다. 

 

 

  • orElse 메서드의 클래스 명세서를 살펴보면 삼항연산자로 정의되어있는 것을 확인할 수 있다. 
  • 해당 메서드를 사용하면 매번 삼항연산자를 사용하는 수고를 덜 수 있다. 

 

 

 

 

OptionalInt, OptionalLong, OptionalDouble 


  • Optional<T>를 사용해도 무방하지만 해당 클래스들은 기본형만을 다뤄 성능을 향상시키기위해 쓰인다. 
  • 기본형을 감싸는 래퍼 클래스 들이다. 
  • opt와 opt2에 똑같이 빈값을 담았지만 메소드 비교시 다르게 간주됨을 확인할 수 있다.
  • opt에 담긴것은 int형 정수 0이고, empty에 담긴것은 빈객체 null이다. 
  • 그러므로 값이 존재하는지 묻는 is Present() 메소드를 사용하면
  • 0값을 가진 opt는 true를, null을 가진 opt2는 false를 반환한다.  
  • 두 빈값이 같은지 묻는 equals() 메소드 사용시 역시 false를 반환하게 된다. 

 

 

 

 

  • 메소드 참조를 람다식으로 변경하면 다음과 같다. 

 

 

  • result1변수엔 "123"을 담은뒤 null 값을 체크하는 filter 중간연산을 통과해 int형으로 반환하여 정수 123이 출력된다.
  • 그러나 result2에는 "" 빈값을 담았기에 filter 중간연산에서 null 로 간주된다. 그러므로 null값일시 괄호안의 내용을 반환하는 orElse 연산자의 도움으로 map 메소드에서 -1로 반환된다.  
  • result3 역시 값이 "456" 존재하므로 ifPresent 연산자를 거쳐 값이 잘 출력된다. 

 

 

  • 값 존재 여부에 따라 참,거짓을 반환하는 isPresent메소드의 활용이다. 
  • 0은 값을 존재한다고 간주해 true를 반환하고, 빈 객체만을 생성한 empty()는 null로 간주해 false를 반환한다. 

 

 

  • null값에 getAsInt() 메소드 사용시 NoSuchElementException이 발생한다.

 

 

  • 0값과 null 값은 다르기에 두 객체가 동일한지 묻는 equal 메소드 사용시 false가 출력된다. 

 

 

 

 

 

 

 

 

 



* 스트림의 중간연산 지난 수업 복습

 

 



스트림의 요소 변환하기 - map()


  • 스트림의 요소에 저장된 값 중에서 원하는 필드만 뽑아내거나 특정 형태로 변환해야 할 때가 있다.
  • 이 때 사용하는 것이 바로 map()이다.
  • 이 메서드의 선언부는 아래와 같으며 매개변수로 T타입을 R타입으로 변환해서 반환하는 함수를 지정해야한다.

 

  • 스트림의 요소 T를 R로 변환해준다.
  • Stream<File> -> Stream<String>

  • map() 역시 중간 연산이므로, 연산결과는 String을 요소로 하는 스트림이다.
  • map()으로 Stream〈File〉을 Stream〈String〉으로 변환했다고 볼 수 있다
  • map()도 filter()처럼 하나의 스트림에 여러 번 적용할 수 있다.

 

 

  • 메소드 참조 File::getName은 클래스명::메소드명이다.
  • 메소드 참조를 람다식으로 변환하면 (참조변수) ->  참조변수.getName(); 가 된다.

 

 

  • 확장자만 추출하는 예제이다.
  • 스트림 변환 후 중간연산으로는 확장자 파일만 filter로 걸러내고 map으로 확장자만 잘라 변경, 대문자로 변경, 중복제거, 최종연산의 과정을 거쳤다.

 

  • 중간, 최종연산을 거친후 JAVA, BAK, TXT 확장자가 출력되었다.

 

 

  • 중간연산 확장자 추출과 대문자 변환 중복제거를 주석처리하면 다음과같이 콘솔창에 출력된다.

 

 




스트림의 요소를 소비하지않고 엿보기


  • 스트림을 출력하는 것으론 최종연산 forEach가 있었다.
  • 그런데 이는 최종연산으로서 스트림을 소모하여 재활용이 불가능하다.
  • peek은 스트림의 요소를 소비하지않고 현재 연산된 값을 출력해 확인해볼 수 있는 중간연산이다.
  • forEach()와 달리 스트림의 요소를 소모하지 않으므로 연산 사이에 여러 번 끼워 넣어도 문제가 되지 않는다
  • 주로 디버깅 용으로 사용된다.





 

'스트림의 스트림'을 '스트림'으로 변환 - flatMap()


  • 스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우, 즉 스트림의 타입이 Stream〈T[ ]>인 경우, Stream〈T>로 다루는 것이 더 편리할 때가 있다. 그럴 때는 map() 대신 flatMap()을 사용하면 된다.
  • Stream〈String[]〉을 ‘map(Arrays::stream)’으로 변환한 결과는 Stream〈String〉이 아닌, Stream〈Stream〈String〉〉이다.
  • 즉, 스트림의 스트림인 것이다.

 

 

  • 스트림을 flatMap으로 변환 시 '스트림의 스트림'화 되지않고 '스트림'으로 잘 담긴다.
  • flatMap()은 map()과 달리 아래의 그림처럼,스트림의 스트림이 아닌 스트림으로 만들어 준다.

 

 

 

  • 그러므로 중간연산 및 최종연산이 진행될 수 있다. 

 

 

  • 스트림을 스트림으로 변환하려고 할시 Stream<Stream<String> 참조변수> 로 바뀐다.
  • 이러한 스트림의 스트림화를 막아주는 것이 flatMap 이다. 

 

 

 

  • 아래와 같이 여러 문장을 요소로 하는 스트림이 있을 때, 이 문장들을 split()으로 나눠서 요소가 단어인 스트림을 만들고 싶다면 어떻게 해야 할 까?
  • map()은 Stream〈String〉이 아니라 Stream〈Stream 〈String〉〉을 결과로 돌려준다.
  • 이럴 때도 map()대신 flatMap()으로 원하는 결과를 얻을 수 있다

 

\

 

  • map( )과 flatMap( )의 차이를 간단히 정리하면 다음과 같다.

 

 

 

 

 

 

스트림의 중간연산

 

 

 

스트림 자르기 skip(), limit()


  • skip()과 limitO은 스트림의 일부를 잘라낼 때 사용하며, 사용법은 아주 간단하다.
  • skip(3)은 처음 3개의 요소를 건너뛰고,
  • limit(5)는 스트림의 요소를 5개로 제한한다.

 

 

스트림의 요소 걸러내기 


  • distinct()스트림에서 중복된 요소들을 제거하고,
  • filter()는 주어진 조건(Predicate)에 맞지 않는 요소를 걸러낸다.

 

 

 

스트림 정렬하기


  • 스트림을 정렬할 때는 sorted()를 사용하면 된다.
  • Comparator를 지정하지 않으면 스트림 요소의 기본 정렬 기준(Comparable)으로 정렬한다.
  • 단, 스트림의 요소가 Comparable을 구현한 클래스가 아니면 예외가 발생한다.

 

 

 

  • Comparator의 comparing()으로 정렬기준을 제공
  • 정렬에 사용되는 메서드의 개수가 많지만,가장 기본적인 메서드는 comparing()이다.

 

 

  • 추가 정렬기준을 제공하려면 thenComparing()을 사용하여 중간연산시 정렬을 이어가야 한다.

 

 

  • 예제를 보자. 다음의 예제는 학생의 성적을 반별 오름차순, 총점별 내림차순으로 정렬하여 출력한다
  • 기본적인 출력은 다음과 같다.

 

  • 기본정렬은 총점의 내림차순이다. 

 

 

 

 

 

 

 

 

 

 

(+)

메소드 참조를 다시 람다식으로 바꿀 수 있다.

 

 

 

 

+ Recent posts