람다식(Lambda Expression)
- 함수(메서드)를 간단한 식으로 표현하는 방법
int max(int a, int b){
return a>b?a:b;
}
//람다식
(a,b) -> a>b? a: b
- 익명 함수(anonymous function) : 반환 타입과 이름을 지우고 화살표 사용
-함수와 메서드의 차이 : 함수는 일반적 용어/ 메서드는 객체지향개념 용어
-함수는 클래스에 독립적, 메서드는 클래스에 종속적 (자바는 메서드 밖에 없음- 자바에서 모든 메서드는 클래스안에 존재해야함)
-람다식 작성하기
1. 메서드의 이름과 반환타입을 제거하고 '->'를 블록{}앞에 추가
2. 반환 값이 있는 경우, 식이나 값만 적고 return문 생략 가능 (끝에 ;안붙임)
3. 매개변수의 타입이 추론가능하면 생략가능(대부분의 경우 생략가능)
- 주의 사항
1. 매개변수가 하나인 경우, 괄호 생략가능(타입이 없을때만)
a -> a*a //OK
int a -> a*a // ERROR (int a) -> a*a 로 사용해야함
2. 블록 안의 문장이 하나뿐일때 괄호 생략가능 (단, 하나뿐인 문장이 return 문이면 괄호 생략불가)
(int i) -> {
System.out.println(i);
}
//위를 아래 처럼 사용
(int i) -> System.out.println(i)
람다식은 익명함수가 아니라 익명객체
- 자바에서 람다식은 익명함수가 아니라 익명객체임 (자바에서 메서드만 따로 존재할 수 없기 때문)
(a,b)-> a>b?a:b
// 익명 클래스의 객체 -> 객체의 선언과 생성을 동시에
Object obj = new Object(){
int max(int a,int b){
return a>b?a:b;
}
}
int value =obj.max(3,5);// ERROR : 참조변수 obj의 타입은 Object인데 Object클래스에 max가 없음
- 람다식(익명 객체)을 다루기 위한 참조변수가 필요함
-> 참조변수의 타입은 ? Object를 사용하면 안됨 -> 함수형 인터페이스 사용
- 익명객체에서 메서드 부분을 표현한 것이 람다식! 자바에서는 메서드만 존재할 수 없기 때문에 익명객체가 존재하며 람다식은 이를 간단히 표현한 것
함수형 인터페이스
- 단 하나의 추상 메서드만 선언된 인터페이스 = 람다식을 다루기 위해 사용
@FunctionalInterface // 애너테이션 사용시 컴파일러가 함수형 인터페이스를 제대로 사용했는지 확인해줌
interface MtFunction{
public abstract int max(int a, int b);
}
//구현
MyFunction f = new MyFunction(){ //익명 클래스의 선언 & 객체 생성 동시에
public int max(int a, int b){
return a>b?a:b;
}
};
obj.max(3,5)는 에러이지만 f.max(3,5)는 가능
즉, 함수형 인터페이스를 선언하고 함수형 인터페이스 타입의 참조변수로 람다식을 다룰 수 있음!
MyFunction f = (a,b)->a>b?a:b;
int value = f.max(3,5) //실제로는 람다식이 호출됨
public class Ex14_0 {
public static void main(String[] args) {
// Object obj = (a,b) -> a>b? a:b; //람다식 , 익명 객체
// MyFunction2 f = new MyFunction2() {
// public int max(int a, int b) { //public생략 불가 : 오버라이딩 -접근제어자는 좁계 못바꿈
// return a>b ? a:b;
// }
//
// };
//람다식(익명개체)을 다루기 위한 참조변수의 타입은 함수형 인터페이스로 함
MyFunction2 f= (a,b) -> a>b?a:b;
int value=f.max(3,5);
System.out.println("value="+value);
}
}
@FunctionalInterface
interface MyFunction2{
int max(int a, int b);
}
람다식에는 이름이 생략됨 / 함수형 인터페이스에 있는 추상메서드와 람다식 연결 /
추상메서드를 통해 람다식을 호출한 것으로 생각하면 됨!
함수형 인터페이스 타입의 매개변수, 반환타입
- 함수형 인터페이스 타입의 매개변수 : 람다식에 이름을 붙여주고 호출
메서드의 매개변수로 함수형 인터페이스 == 메서드의 매개변수로 람다식을 받겠다는 뜻
void aMethod(MyFunction f) {
f.myMethod(); //람다식 호출// MyFunction에 정의된 메서드 호출
//f에 저장된 MyFunction객체의 myMethod를 호출하는 역할을 함
}
@FunctionalInterface
interface MyFunction {
void myMethod();//람다식에 이름을 붙여줌
}
//MyFunction f = () -> System.out.println("myMethod()"); //람다식을 참조변수(f)에 담고
//aMethod(f); //메서드안에서 람다식 호출
aMethod(()->System.out.println("myMethod()"));// 람다식을 직접 집어넣기
( 람다 표현식을 사용하여 MyFunction 객체를 생성하고 aMethod를 호출 )
- 함수형 인터페이스 타입의 반환 타입
//MyFunction myMethod(){ //이 메서드는 람다식을 반환
// MyFunction f= ()-> {};
// return f;
//}
MyFunction myMethod() {
return ()->{};
}
@FunctionalInterface
interface MyFunction {
void run(); // public abstract void run();
}
class Ex14_1 {
static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
f.run();
}
static MyFunction getMyFunction() { // 반환 타입이 MyFunction(함수형 인터페이스)인 메서드
MyFunction f = () -> System.out.println("f3.run()");
return f;
//return () -> System.out.println("f3.run()"); 으로 줄일 수 있음
}
public static void main(String[] args) {
// 람다식으로 MyFunction의 run()을 구현
MyFunction f1 = ()-> System.out.println("f1.run()"); //매개변수와 반환 타입 일치
//함수형 인터페이스 직접 구현
MyFunction f2 = new MyFunction() { // 익명클래스로 run()을 구현
public void run() { // public을 반드시 붙여야 함
System.out.println("f2.run()");
}
};
MyFunction f3 = getMyFunction(); //반환 타입인 함수형 인터페이스
f1.run();
f2.run();
f3.run(); //람다식을 호출하려면 함수형 인터페이스의 선언된 이름(run)을 사용
execute(f1); // 람다식을 매개변수로 받아서 호출
execute( ()-> System.out.println("run()") ); //람다식을 매개변수로 직접 넘겨줌
}
}
java.util.function 패키지
- 자주 사용되는 다양한 함수형 인터페이스를 제공 (표준화 된다는 장점이 있음)
Predicate<String> isEmptyStr=a->a.length()==0;
String a ="";
if(isEmptyStr.test(a)) //if(s.length()==0) //위 람다식에 붙여진 이름이 test / 람다식을 호출하려면 test를 사용해야함
System.out.println("This is an empty String.");
- 매개변수가 2개인 함수형 인터페이스
입력을 3개 이상 받는 함수형 인터페이스가 필요하면 직접 만들어야함
@FunctionalInterface
interface TriFunction(T,U,V,R){
R apply(T t, U u, V v);
}
- 매개변수타입과 반환 타입이 일치하는 함수형 인터페이스(단항연산자/이항연산자)
import java.util.function.*;
import java.util.*;
class Ex14_2 {
public static void main(String[] args) {
Supplier<Integer> s = ()-> (int)(Math.random()*100)+1; //1~100사이의 난수 반환
Consumer<Integer> c = i -> System.out.print(i+", "); //콘솔에 출력
Predicate<Integer> p = i -> i%2==0; //짝수인지 조사
Function<Integer, Integer> f = i -> i/10*10; // i의 일의 자리를 없앤다.
List<Integer> list = new ArrayList<>();
makeRandomList(s, list);//list를 절댓값으로 채움
System.out.println(list);
printEvenNum(p, c, list); //짝수 출력
List<Integer> newList = doSomething(f, list);
System.out.println(newList);
}
static <T> List<T> doSomething(Function<T, T> f, List<T> list) {
List<T> newList = new ArrayList<T>(list.size());
for(T i : list) {
newList.add(f.apply(i)); //1의 자리를 없애서 새로운 list에 저장
}
return newList;
}
static <T> void printEvenNum(Predicate<T> p, Consumer<T> c, List<T> list) {
System.out.print("[");
for(T i : list) {
if(p.test(i))//짝수인지 검사
c.accept(i);
}
System.out.println("]");
}
static <T> void makeRandomList(Supplier<T> s, List<T> list) {
for(int i=0;i<10;i++) {
list.add(s.get());
}
}
}
Predicate의 결합
- and(),or(),negate()로 두 Predicate를 하나로 결합(default 메서드)
+ Predicate는 함수형 인터페이스= default메서드, static메서드, *추상메서드*가질수 있음
Predicate<Integer> p = i -> i<100;
Predicate<Integer> q =i -> i<200;
Predicate<Integer> r = i -> i %2 ==0 ;
Predicate<Integer> notP = p.negate(); //i>=100
Predicate<Integer> all = notP.and(q).or(r);//100<=i<200||i%2==0
Predicate<Integer> all2 = notP.and(q.or(r));//i>=100&&(i<200||i%2==0)
//predicate를 사용할때 test라는 메서드를 사용 (predicate가 가지고 있는 추상 메서드)
System.out.Println(all.test(2);//true
System.out.println(all2.test(2);//false
- 등가비교를 위한 Predicate의 작성에는 isEqual()를 사용(static 메서드)
Predicate.isEqual() 메서드:
- Predicate.isEqual(T targetRef)는 주어진 targetRef와 동등한지 비교하는 Predicate 객체 생성.
이것은 함수형 프로그래밍에서 주어진 값과 다른 값과의 동등성을 테스트하기 위해 사용됨 - p.test(str2)를 호출하면 str2와 str1이 동등한 경우에 true를 반환하고, 그렇지 않으면 false를 반환
Predicate<String> p = Predicate.isEqual(str1) // isEquals()은 static 메서드
Boolean result = p.test(str2);//str1과 str2가 같은지 비교한 결과를 반환
boolean result = Predicate.isEqual(str2).test(str2) //줄여서 쓸 수 있음 str1.equals(str2)와 같음
import java.util.function.*;
class Ex14_3 {
public static void main(String[] args) {
Function<String, Integer> f = (s) -> Integer.parseInt(s, 16); //문자열을 16진수로
Function<Integer, String> g = (i) -> Integer.toBinaryString(i); // 이진수 문자열
Function<String, String> h = f.andThen(g); //함수 f와 g를 연결 f의 출력이 g의 입력으로 들어감(둘이 같아야 연결 가능)
Function<Integer, Integer> h2 = f.compose(g); // g.andThen(f)과 같음
System.out.println(h.apply("FF")); // "FF" → 255 → "11111111"
System.out.println(h2.apply(2)); // 2 → "10" → 16
Function<String, String> f2 = x -> x; // 항등 함수(identity function)
System.out.println(f2.apply("AAA")); // AAA가 그대로 출력됨
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i%2 == 0;
Predicate<Integer> notP = p.negate(); // i >= 100
Predicate<Integer> all = notP.and(q.or(r));
System.out.println(all.test(150)); // true
String str1 = "abc"; new String("abc")로 바꾸었을때도 turn -> 등가비교 연산자가 이닌 equals사용
String str2 = "abc";
// str1과 str2가 같은지 비교한 결과를 반환
Predicate<String> p2 = Predicate.isEqual(str1);
boolean result = p2.test(str2);
System.out.println(result);
}
}
컬렉션 프레임웍과 함수형 인터페이스
(jdk1.8에서 람다식을 이용해서 작업을 쉽게 처리할 수 있는 메서드가 추가됨 )
- 함수형 인터페이스를 사용하는 컬렉션 프레임웍의 메서드(와일드 카드 생략)
list forEach(i->System.out.print(i+",")); //list의 모든 요소 출력
list.removeIf(x->x%2==0||x%3==0); //2또는 3의 배수 출력
list.replaceAll(i->i*10); // 모든 요소에 10을 곱함
//map의 모든 요소를 {k,v}형식으로 출력
list.forEach((k,v)-> System.put.print("{"+k+","+v+"}");
메서드 참조(method reference)
-하나의 메서드만 호출하는 람다식은 '메서드 참조'로 더 간단히 할 수 있음
1. static 메서드 참조
Ingeger method(String s ) {
return Integer.parseInt(s);
}
Function<String,Integer>f=(String s) -> Integer.parseInt(s); // 람다식
Function<String,Integer> f = Interger::parseInt; //메서드 참조
생성자의 메서드 참조
-생성자와 메서드 참조 : 생성자에서 메서드 참조가 사용될 수 있음
//Supplier은 입력X, 출력O
Supplier<MyClass> s = () -> new MyClass();
Supplier<MyClass> s = MyClass::new; //메서드 참조
Function<Integer, MyClass> s = (i)-> new MyClass(i); //생성자의 매개변수가 있는경우
Function<Integer, MyClass> s = MyClass::new;
- 배열과 메서드 참조
Function<Integer, int[]> f = x-> new int (x); //람다식 (배열길이(Integer)을 주면 그 길이에 해당하는 int 배열을 만들어줌)
Function<Integer,int[]> f2= int[]::new;//메서드 참조
import java.util.function.Supplier;
public class Ex14_0 {
public static void main(String[] args) {
//Supplier<MyClass> s =()->new MyClass(); //람다식
Supplier<MyClass> s = MyClass::new; //메서드 호출
MyClass mc = s.get();
System.out.println(mc);
System.out.println(s.get());
}
}
class MyClass {}
-생성자 추가
import java.util.function.Function;
import java.util.function.Supplier;
public class Ex14_0 {
public static void main(String[] args) {
//Function<Integer,MyClass> f =(i)->new MyClass(i); //람다식
Function<Integer, MyClass> f = MyClass::new; //메서드 호출
MyClass mc = f.apply(100);
System.out.println(mc.iv);
System.out.println(f.apply(200).iv);
}
}
class MyClass {
int iv;
MyClass(int iv){
this.iv=iv;
}
}
-배열 생성
import java.util.function.Function;
import java.util.function.Supplier;
public class Ex14_0 {
public static void main(String[] args) {
//Function<Integer,int[]> f2 = (i)->new int[i]; //람다식
Function<Integer,int[]> f2= int[]::new; //메서드 참조
int[] arr=f2.apply(100);
System.out.println("arr.length ="+arr.length);
}
}
class MyClass {
int iv;
MyClass(int iv){
this.iv=iv;
}
}
스트림(Stream)
- 다양한 데이터 소스(컬렉션, 배열등 )를 표준화된 방법으로 다루기 위한것
- 데이터 소스로 stream을 만들고, 그럼 똑같은 방식으로 작업을 처리하게됨
스트림을 이용한 작업
1. 스트림 만들기
2. 중간영상(n번)
3. 최종연산(1번)
- 다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
- 스트림이 제공하는 기능 -중간연산과 최종연산
-- 중간연산 : 연산결과가 스트림인 연산. 반복적으로 적용가능
-- 최종연산 - 연산결고가 스트림이 아닌 연산. 단 한번만 적용가능(스트림의 요소를 소모)
Stream의 특정
- 스트림은 데이터 소스로부터 데이터를 읽기만 할 뿐 변경하지 않음 (read only)
- 스트림은 lterator처럼 일회용(필요시 다시 스트림을 생성해야함
- 최종 연산 전까지 중간연산이 수행되지 않는다 -지연된 연산
- 스트림은 작업을 내부 반복으로 처리
- 스트림의 작업을 병렬로 처리 - 병렬스트림
- 기본형 스트림 : IntStream. LongStream, DoubleStream (: 안써도되긴 하는데 성능이 좋아짐)
-- 오토박싱&언박싱의 비효율이 제거됨 (Stream<Integer>대신 IntStream사용)
-- 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공
--> 기본형 스트림을 쓰는 이유: 시간이 많이걸리거나 해서 성능 개선을 위해..
스트림 만들기 -컬렉션
- Collection인터페이스의 steam()으로 컬렉션을 스트림으로 변환
스트림 만들기 - 배열
-객체 배열로부터 스트림 생성하기
- 기본형 배열로부터 스트림 생성하기 : 위에는 숫자 배열로 만들면 Integer stream이 생성되는데 기본형 배열 가지고 stream을 만들면 기본형 stream이 생김
int[] intArr={1,2,3,4,5}; //기본형 배열
IntStream.intStream=Arrays.stream(intArr); //intStream생성
intStream.forEach(System.out.println);
System.out.println(intStream.count()); //OK
System.out.println(intStream.sum()); //OK
Integer[] intArr ={1,2,3,4};// 자동으로 오토박싱이되서 integer객체배열이 됨
Stream<Integer> intStream=Arrays.stream(intArr); //integer객체 스트림
System.out.println(intStream.count()); //OK
System.out.println(intStream.sum()); //ERROR : integer 객체 스트림은 count만 있음
Stream<T>는 숫자 외에도 여러타입의 스트림이 가능해야하므로 숫자스트림에마 사용할 수 있는 sum, average()를 넣지 않은 것
--> 숫자 스트림을 다룰때에는 기본형스트림을 사용하면 속도도 향상되고 추가적으로 편리한 메서드들을 사용할 수 있음
-스트림 만들기 -임의의수
--난수를 요소로 갖는 스트림 생성하기
ints(),longs(),doubles()는 랜덤클래스에 정의되어 있으며 각타입의 범위안에 속하는 난수를 요소로 갖는 스트림을 생성함
모두 무한 스트림을 생성 하며로 limit로 자르거나 크기를 미리 지정해줌
-- 지정된 범위의 난수를 요소로 갖는 스트림을 생성하는 메서드 (Ramdom클래스)
스트림 만들기- 특정 범위의 정수
--특정 범위의 정수를 요소로 갖는 스트림 생성하기 (IntStream, LongStream)
스트림 만들기 -람다식 iterate(),generate()
-iterate()는 이전 요소를 seed로 하여 다음 요소 계산
-generate는 seed를 사용하지 않음 (각 요소가 서로 독립/ 서로 상관 없음)
//iterate(T seed,UnaryOprator f) 단항 연산자
Stream<Integer> intStream = Steram.iterate(1, n->n+2);
intStream.limit(10).forEach(System.out::println);
//generate(Supplier s) : 주기만 하는 것 입력X, 출력 o
Stream<Integer> oneStream = Stream.generate(()->1);
oneStream.limit(10).forEach(System.out.println);
스트림 만들기 -파일과 빈 스트림
--파일을 소스로 하는 스트림 생성하기
-- 비어있는 스트림 생성하기 (자주 쓰이지는 않음 -있다는 정도만 알아두기)
스트림의 연산
- 스트림이 제공하는 기능 - 중간 연산과 최종연산
스트림의 중간 연산
--스트림 건너뛰기 / 자르기 skip().limit()
--스트림의 요소 걸러내기 filter(), distinct()
- 스트림 정렬하기 sorted()
-- comparator의 comparing()으로 정렬 기준을 제공
-- comparing()메서드의 반환타입은 comparator임. sorted메서드의 매개변수로 comparator을 넣어야함
--comparing메서드를 사용하면 comparator를 반환하고, sorted가 반환된 comparator를 사용
--정렬기준을 제공할때는 thenComparing()을 사용
import java.util.*;
import java.util.stream.*;
class Ex14_5 {
public static void main(String[] args) {
Stream<Student> studentStream = Stream.of(
new Student("이자바", 3, 300),
new Student("김자바", 1, 200),
new Student("안자바", 2, 100),
new Student("박자바", 2, 150),
new Student("소자바", 1, 200),
new Student("나자바", 3, 290),
new Student("감자바", 3, 180)
);
studentStream.sorted(Comparator.comparing(Student::getBan) // 반별 정렬
.thenComparing(Comparator.naturalOrder())) // 기본 정렬
.forEach(System.out::println);
}
}
class Student implements Comparable<Student> {
String name;
int ban;
int totalScore;
Student(String name, int ban, int totalScore) {
this.name =name;
this.ban =ban;
this.totalScore =totalScore;
}
public String toString() {
return String.format("[%s, %d, %d]", name, ban, totalScore);
}
String getName() { return name;}
int getBan() { return ban;}
int getTotalScore() { return totalScore;}
// 총점 내림차순을 기본 정렬로 한다.
public int compareTo(Student s) {
return s.totalScore - this.totalScore;
}
}
- Map () : Stream 요소 번환
import java.io.*;
import java.util.stream.*;
class Ex14_6 {
public static void main(String[] args) {
File[] fileArr = { new File("Ex1.java"), new File("Ex1.bak"),
new File("Ex2.java"), new File("Ex1"), new File("Ex1.txt")
};
Stream<File> fileStream = Stream.of(fileArr); //stream의 각 요소가 file객체
// map()으로 Stream<File>을 Stream<String>으로 변환
Stream<String> filenameStream = fileStream.map(File::getName); //((f)->f.getName())
filenameStream.forEach(System.out::println); // 모든 파일의 이름을 출력
fileStream = Stream.of(fileArr); // 스트림을 다시 생성
fileStream.map(File::getName) // Stream<File> → Stream<String>
.filter(s -> s.indexOf('.')!=-1) // 확장자가 없는 것은 제외
.map(s -> s.substring(s.indexOf('.')+1)) // 확장자만 추출
.map(String::toUpperCase) // 모두 대문자로 변환
.distinct() // 중복 제거
.forEach(System.out::print); // 출력 : JAVABAKTXT
System.out.println();
}
}
Ex1.java
Ex1.bak
Ex2.java
Ex1
Ex1.txt
JAVABAKTXT
-peek (forEach()의 중간연산버전)
--스트림의 요소를 소비하지않고 엿보기
-flatmap : 스트림의 스트림을 스트림으로 변환
-> 원래는 String stream이 되길 원함
import java.util.*;
import java.util.stream.*;
class Ex14_7 {
public static void main(String[] args) {
Stream<String[]> strArrStrm = Stream.of(
new String[]{"abc", "def", "jkl"},
new String[]{"ABC", "GHI", "JKL"}
);
// Stream<Stream<String>> strStrmStrm = strArrStrm.map(Arrays::stream);
Stream<String> strStrm = strArrStrm.flatMap(Arrays::stream); //map말고 flatMap을 사용하면 하나의 stream으로 합쳐짐
strStrm.map(String::toLowerCase)//소문자로 바꾸기
.distinct()//중복제거
.sorted()//정렬
.forEach(System.out::println);
System.out.println();
String[] lineArr = {
"Believe or not It is true",
"Do or do not There is no try",
};
Stream<String> lineStream = Arrays.stream(lineArr);
// string(Stream<String>)을 문자열 배열로 바꾸는데 map을 사용하면 Stream<Stream<String>>가 됨
// flatMap을 사용하면 Stream의 string이 Stream의 String이 됨
lineStream.flatMap(line -> Stream.of(line.split(" +"))) //line.split으로 하나 이상의 공백으로 나눔
.map(String::toLowerCase)
.distinct()
.sorted()
.forEach(System.out::println);
System.out.println();
}
}
Optional<T>
- T타입 객체의 래퍼클래스 -Optional<T>
public final class Option<T>{
private final T value; //T타입의 참조변수를 가지고 있음-> 모든 종류의 객체 저장가능(null포함)
...
}
null포함이 유용한 이유
- null을 직접 다루는것은 위험 --> NullPointException발생 가능
- null 체크시 if문 필수 -> 코드가 지저분
(null을 option에 집어넣음으로써 null을 다룸-> NPE가능성 없어짐 + 코드가 간결해짐 )
Optional<T> 객체 생성하기
--Optional<T>객체를 생성하는 다양한 방법
-- null대신 빈 Optional<T>객체를 사용하자
Optional<String> optVal = null; //Null로 초기화. 가능하지만 바람직하지 않음
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
Optional<T>객체의 값 가져오기
- Optional 객체의 값 가져오기 - get(),orElse(), orElseGet(), orElseThrow()
- isPresent() : Optional객체의 값이 null이면 false, 아니면 true를 반환 /ifPresent : Optional객체의 값이 null이 아닐떄만 작업 수행
-- OptionalInt, OptionalLong, OptionalDouble (높은 성능을 요구할때)
--기본형 값을 감싸는 래퍼클래스
--OptionalInt의 값 가져오기 -int getAsInt()
--빈 Optional객체와의 비교
import java.util.*;
class Ex14_8 {
public static void main(String[] args) {
Optional<String> optStr = Optional.of("abcde");//optional 객체 생성
Optional<Integer> optInt = optStr.map(String::length); //optional객체를 map으로 변경
System.out.println("optStr="+optStr.get());
System.out.println("optInt="+optInt.get());
int result1 = Optional.of("123")
.filter(x->x.length() >0)
.map(Integer::parseInt).get();
int result2 = Optional.of("") //빈문자열
.filter(x->x.length() >0) //false
.map(Integer::parseInt).orElse(-1); // orElse사용 -> 이경우 null이므로 -1
System.out.println("result1="+result1);
System.out.println("result2="+result2);
Optional.of("456").map(Integer::parseInt)
.ifPresent(x->System.out.printf("result3=%d%n",x)); //값이 있으면 출력
OptionalInt optInt1 = OptionalInt.of(0); // 0을 저장
OptionalInt optInt2 = OptionalInt.empty(); // 빈 객체를 생성
System.out.println(optInt1.isPresent()); // true
System.out.println(optInt2.isPresent()); // false
System.out.println(optInt1.getAsInt()); // 0
// System.out.println(optInt2.getAsInt()); // NoSuchElementException
System.out.println("optInt1="+optInt1);
System.out.println("optInt2="+optInt2);
System.out.println("optInt1.equals(optInt2)?"+optInt1.equals(optInt2));
}
}
스트림의 최종 연산
- forEach()
- 스트림의 모든 요소에 지정된 작업을 수행 -forEach(), forEachOrdered()
sequential() : 직렬스트림
parallel() : 병렬 스트림 - > 여러 쓰레드가 나눠서 병렬로 처리 / 순서보장이 안됨< --> forEachOrdered는 순서 보장이 됨
--> 직렬일때는 동일하지만, 병렬일때 forEach를 쓰면 순서가 보장이 안됨
순서가 관계 없을때는 forEach를 사용하는게 좋음(성능이 forEach가 더 좋음)
스트림의 최종연산 -조건 검사
조건 검사 - allMatch(), anyMatch(), noneMatch()
조건에 일치하는 요소 찾기 -findFirst(), findAny()
reduce()
- 스트림의 요소를 하나씩 줄여가며 누적연산 수행 - reduce()
identity : 초기값 / accumulator : 이전 연산결과와 스트림의 요소에 수행할 연산 /(combiner : 병렬처리된 결과를 합치는데 사용할 연산(병렬 스트림))
import java.util.*;
import java.util.stream.*;
class Ex14_9 {
public static void main(String[] args) {
String[] strArr = {
"Inheritance", "Java", "Lambda", "stream",
"OptionalDouble", "IntStream", "count", "sum"
};
Stream.of(strArr).
.parallel() //병렬로 처리
.forEachOrdered(System.out::println);// 순서를 유지하고 싶을때 forEachOrdered사용
boolean noEmptyStr = Stream.of(strArr).noneMatch(s->s.length()==0);//String길이가 0인게 없어서 false
Optional<String> sWord = Stream.of(strArr)
.filter(s->s.charAt(0)=='s').findFirst(); //filter에 맞는 것중 가장 첫번째(filter가 없으면 그냥 가장 첫번째 반환)
System.out.println("noEmptyStr="+noEmptyStr);
System.out.println("sWord="+ sWord.get());
// Stream<String>을 IntStream으로 변환 (IntStream은 기본형 스트림 : Integer객체로 다루는 것과 동일하나 성능이 더 좋음 )
IntStream intStream1 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream2 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream3 = Stream.of(strArr).mapToInt(String::length);
IntStream intStream4 = Stream.of(strArr).mapToInt(String::length);
int count = intStream1.reduce(0, (a,b) -> a + 1); //요소 개수 확인 //초기값이 있어서 결과가 없을 수 없음 -> 반환값이 int
int sum = intStream2.reduce(0, (a,b) -> a + b); //단어의 길이를 모두 sum함
OptionalInt max = intStream3.reduce(Integer::max); //결과가 없을수도 있어서 OptionalInt 반환
OptionalInt min = intStream4.reduce(Integer::min);
System.out.println("count="+count);
System.out.println("sum="+sum);
System.out.println("max="+ max.getAsInt()); //get호출시 결과 없으면 에러
System.out.println("max="+ max.orElse(0)); //결과가 없으면 0반환
System.out.println("max="+ max.orElseGet(()->0)); // 람다식 사용가능
System.out.println("min="+ min.getAsInt());
}
}
collect()와 Collectors
- collect()는 Collector를 매개변수로 하는 스트림의 최종연산 / reduce와 다르게 그룹별 리듀싱이 가능!
- collector는 수집(collect)에 필요한 메서드를 정의해놓은 인터페이스
- Collectors클래스는 다양한 기능의 컬렉터(Collector를 구현한 클래스)를 제공
정리 --> collect()는 최종연산 / Collector는 인터페이스로 최종연산에 필요한걸 제공해줌 -필요한 매개변수를 묶어서 매개변수 하나로 만들어준것 ! / Collectors는 클래스로 Collector를 구현한 구현체를 제공.
스트림을 컬렉션으로 변환 -toList(),toSet(),toMap(),toCollection()
스트림을 배열로 변환 -toArray()
-스트림의 통계 - counting(),summingInt()
-- 스트림의 통계정보 제고 - countiong(),summingInt(),maxBy(),minBy(),
스트림을 리듀싱 -reducing()
--스트림을 리듀싱 -reducing() : collectors가 제공하는 reducing메서드 / reduce()는 전체 리듀싱이고 reducing메서드는 그룹별 리듀싱이 가능함
-문자열 스트림의 요소를 모두 연결 - joining()
- 스트림의 그룹화와 분할
-- partitioningBy()는 스트림을 2분할함 (n분할할때보다 성능좋음)
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
class Student2 {
String name;
boolean isMale; // 성별
int hak; // 학년
int ban; // 반
int score;
Student2(String name, boolean isMale, int hak, int ban, int score) {
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban;
this.score = score;
}
String getName() { return name; }
boolean isMale() { return isMale; }
int getHak() { return hak; }
int getBan() { return ban; }
int getScore() { return score; }
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점]",
name, isMale ? "남":"여", hak, ban, score);
}
// groupingBy()에서 사용
enum Level { HIGH, MID, LOW } // 성적을 상, 중, 하 세 단계로 분류
}
class Ex14_10 {
public static void main(String[] args) {
Student2[] stuArr = {
new Student2("나자바", true, 1, 1, 300),
new Student2("김지미", false, 1, 1, 250),
new Student2("김자바", true, 1, 1, 200),
new Student2("이지미", false, 1, 2, 150),
new Student2("남자바", true, 1, 2, 100),
new Student2("안지미", false, 1, 2, 50),
new Student2("황지미", false, 1, 3, 100),
new Student2("강지미", false, 1, 3, 150),
new Student2("이자바", true, 1, 3, 200),
new Student2("나자바", true, 2, 1, 300),
new Student2("김지미", false, 2, 1, 250),
new Student2("김자바", true, 2, 1, 200),
new Student2("이지미", false, 2, 2, 150),
new Student2("남자바", true, 2, 2, 100),
new Student2("안지미", false, 2, 2, 50),
new Student2("황지미", false, 2, 3, 100),
new Student2("강지미", false, 2, 3, 150),
new Student2("이자바", true, 2, 3, 200)
};
System.out.printf("1. 단순분할(성별로 분할)%n");
Map<Boolean, List<Student2>> stuBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale));
List<Student2> maleStudent = stuBySex.get(true); // 남학생 출력
List<Student2> femaleStudent = stuBySex.get(false); // 여학생 출력
for(Student2 s : maleStudent) System.out.println(s);
for(Student2 s : femaleStudent) System.out.println(s);
System.out.printf("%n2. 단순분할 + 통계(성별 학생수)%n");
Map<Boolean, Long> stuNumBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale, counting()));
System.out.println("남학생 수 :"+ stuNumBySex.get(true));
System.out.println("여학생 수 :"+ stuNumBySex.get(false));
System.out.printf("%n3. 단순분할 + 통계(성별 1등)%n");
Map<Boolean, Optional<Student2>> topScoreBySex = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,
maxBy(comparingInt(Student2::getScore))
));
System.out.println("남학생 1등 :"+ topScoreBySex.get(true));
System.out.println("여학생 1등 :"+ topScoreBySex.get(false));
Map<Boolean, Student2> topScoreBySex2 = Stream.of(stuArr)
.collect(partitioningBy(Student2::isMale,
collectingAndThen(
maxBy(comparingInt(Student2::getScore)), Optional::get
)
));
System.out.println("남학생 1등 :"+ topScoreBySex2.get(true));
System.out.println("여학생 1등 :"+ topScoreBySex2.get(false));
System.out.printf("%n4. 다중분할(성별 불합격자, 100점 이하)%n");
Map<Boolean, Map<Boolean, List<Student2>>> failedStuBySex =
Stream.of(stuArr).collect(partitioningBy(Student2::isMale,
partitioningBy(s -> s.getScore() <= 100))
);
List<Student2> failedMaleStu = failedStuBySex.get(true).get(true);
List<Student2> failedFemaleStu = failedStuBySex.get(false).get(true);
for(Student2 s : failedMaleStu) System.out.println(s);
for(Student2 s : failedFemaleStu) System.out.println(s);
}
}
1. 단순분할(성별로 분할)
[나자바, 남, 1학년 1반, 300점]
[김자바, 남, 1학년 1반, 200점]
[남자바, 남, 1학년 2반, 100점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김자바, 남, 2학년 1반, 200점]
[남자바, 남, 2학년 2반, 100점]
[이자바, 남, 2학년 3반, 200점]
[김지미, 여, 1학년 1반, 250점]
[이지미, 여, 1학년 2반, 150점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[김지미, 여, 2학년 1반, 250점]
[이지미, 여, 2학년 2반, 150점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
2. 단순분할 + 통계(성별 학생수)
남학생 수 :8
여학생 수 :10
3. 단순분할 + 통계(성별 1등)
남학생 1등 :Optional[[나자바, 남, 1학년 1반, 300점]]
여학생 1등 :Optional[[김지미, 여, 1학년 1반, 250점]]
남학생 1등 :[나자바, 남, 1학년 1반, 300점]
여학생 1등 :[김지미, 여, 1학년 1반, 250점]
4. 다중분할(성별 불합격자, 100점 이하)
[남자바, 남, 1학년 2반, 100점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
-- groupingBy()는 스트림을 n분할함
--스트림 요소를 그룹화
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;
class Student3 {
String name;
boolean isMale; // 성별
int hak; // 학년
int ban; // 반
int score;
Student3(String name, boolean isMale, int hak, int ban, int score) {
this.name = name;
this.isMale = isMale;
this.hak = hak;
this.ban = ban;
this.score = score;
}
String getName() { return name; }
boolean isMale() { return isMale; }
int getHak() { return hak; }
int getBan() { return ban; }
int getScore() { return score; }
public String toString() {
return String.format("[%s, %s, %d학년 %d반, %3d점]", name, isMale ? "남" : "여", hak, ban, score);
}
enum Level {
HIGH, MID, LOW
}
}
class Ex14_11 {
public static void main(String[] args) {
Student3[] stuArr = {
new Student3("나자바", true, 1, 1, 300),
new Student3("김지미", false, 1, 1, 250),
new Student3("김자바", true, 1, 1, 200),
new Student3("이지미", false, 1, 2, 150),
new Student3("남자바", true, 1, 2, 100),
new Student3("안지미", false, 1, 2, 50),
new Student3("황지미", false, 1, 3, 100),
new Student3("강지미", false, 1, 3, 150),
// 뒷 페이지에 계속됩니다.
new Student3("이자바", true, 1, 3, 200),
new Student3("나자바", true, 2, 1, 300),
new Student3("김지미", false, 2, 1, 250),
new Student3("김자바", true, 2, 1, 200),
new Student3("이지미", false, 2, 2, 150),
new Student3("남자바", true, 2, 2, 100),
new Student3("안지미", false, 2, 2, 50),
new Student3("황지미", false, 2, 3, 100),
new Student3("강지미", false, 2, 3, 150),
new Student3("이자바", true, 2, 3, 200)
};
System.out.printf("1. 단순그룹화(반별로 그룹화)%n");
Map<Integer, List<Student3>> stuByBan = Stream.of(stuArr)
.collect(groupingBy(Student3::getBan));
for(List<Student3> ban : stuByBan.values()) {
for(Student3 s : ban) {
System.out.println(s);
}
}
System.out.printf("%n2. 단순그룹화(성적별로 그룹화)%n");
Map<Student3.Level, List<Student3>> stuByLevel = Stream.of(stuArr)
.collect(groupingBy(s-> {
if(s.getScore() >= 200) return Student3.Level.HIGH;
else if(s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
}));
TreeSet<Student3.Level> keySet = new TreeSet<>(stuByLevel.keySet());
for(Student3.Level key : keySet) {
System.out.println("["+key+"]");
for(Student3 s : stuByLevel.get(key))
System.out.println(s);
System.out.println();
}
System.out.printf("%n3. 단순그룹화 + 통계(성적별 학생수)%n");
Map<Student3.Level, Long> stuCntByLevel = Stream.of(stuArr)
.collect(groupingBy(s-> {
if(s.getScore() >= 200) return Student3.Level.HIGH;
else if(s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
}, counting()));
for(Student3.Level key : stuCntByLevel.keySet())
System.out.printf("[%s] - %d명, ", key, stuCntByLevel.get(key));
System.out.println();
/*
for(List<Student3> level : stuByLevel.values()) {
System.out.println();
for(Student3 s : level) {
System.out.println(s);
}
}
*/
System.out.printf("%n4. 다중그룹화(학년별, 반별)");
Map<Integer, Map<Integer, List<Student3>>> stuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student3::getHak,
groupingBy(Student3::getBan)
));
for(Map<Integer, List<Student3>> hak : stuByHakAndBan.values()) {
for(List<Student3> ban : hak.values()) {
System.out.println();
for(Student3 s : ban)
System.out.println(s);
}
}
System.out.printf("%n5. 다중그룹화 + 통계(학년별, 반별 1등)%n");
Map<Integer, Map<Integer, Student3>> topStuByHakAndBan =
Stream.of(stuArr)
.collect(groupingBy(Student3::getHak,
groupingBy(Student3::getBan,
collectingAndThen(
maxBy(comparingInt(Student3::getScore))
, Optional::get
)
)
));
for(Map<Integer, Student3> ban : topStuByHakAndBan.values())
for(Student3 s : ban.values())
System.out.println(s);
// 뒷 페이지에 계속됩니다.
System.out.printf("%n6. 다중그룹화 + 통계(학년별, 반별 성적그룹)%n");
Map<String, Set<Student3.Level>> stuByScoreGroup = Stream.of(stuArr)
.collect(groupingBy(s-> s.getHak() + "-" + s.getBan(),
mapping(s-> {
if(s.getScore() >= 200) return Student3.Level.HIGH;
else if(s.getScore() >= 100) return Student3.Level.MID;
else return Student3.Level.LOW;
} , toSet())
));
Set<String> keySet2 = stuByScoreGroup.keySet();
for(String key : keySet2) {
System.out.println("["+key+"]" + stuByScoreGroup.get(key));
}
} // main의 끝
}
1. 단순그룹화(반별로 그룹화)
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이자바, 남, 1학년 3반, 200점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[이자바, 남, 2학년 3반, 200점]
2. 단순그룹화(성적별로 그룹화)
[HIGH]
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이자바, 남, 2학년 3반, 200점]
[MID]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[LOW]
[안지미, 여, 1학년 2반, 50점]
[안지미, 여, 2학년 2반, 50점]
3. 단순그룹화 + 통계(성적별 학생수)
[HIGH] - 8명, [MID] - 8명, [LOW] - 2명,
4. 다중그룹화(학년별, 반별)
[나자바, 남, 1학년 1반, 300점]
[김지미, 여, 1학년 1반, 250점]
[김자바, 남, 1학년 1반, 200점]
[이지미, 여, 1학년 2반, 150점]
[남자바, 남, 1학년 2반, 100점]
[안지미, 여, 1학년 2반, 50점]
[황지미, 여, 1학년 3반, 100점]
[강지미, 여, 1학년 3반, 150점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[김지미, 여, 2학년 1반, 250점]
[김자바, 남, 2학년 1반, 200점]
[이지미, 여, 2학년 2반, 150점]
[남자바, 남, 2학년 2반, 100점]
[안지미, 여, 2학년 2반, 50점]
[황지미, 여, 2학년 3반, 100점]
[강지미, 여, 2학년 3반, 150점]
[이자바, 남, 2학년 3반, 200점]
5. 다중그룹화 + 통계(학년별, 반별 1등)
[나자바, 남, 1학년 1반, 300점]
[이지미, 여, 1학년 2반, 150점]
[이자바, 남, 1학년 3반, 200점]
[나자바, 남, 2학년 1반, 300점]
[이지미, 여, 2학년 2반, 150점]
[이자바, 남, 2학년 3반, 200점]
6. 다중그룹화 + 통계(학년별, 반별 성적그룹)
[1-1][HIGH]
[2-1][HIGH]
[1-2][MID, LOW]
[2-2][MID, LOW]
[1-3][HIGH, MID]
[2-3][HIGH, MID]
'Study > java' 카테고리의 다른 글
chapter 10 (0) | 2023.11.05 |
---|---|
chapter 13 (0) | 2023.09.09 |
chapter 12 (0) | 2023.08.31 |
chapter 9 (0) | 2023.08.12 |
chapter 11 (0) | 2023.08.07 |