KOSA FullStack 교육/java
KOSA fullstack 교육(Lambda, Collection Stream)
로미로미로
2025. 5. 14. 12:32
람다(Lambda)식
::
- 함수형 프로그램
- 자바 8버전에서 새롭게 추가된 기술
::
- 익명함수(함수명이 없다)
- 코드가 간결하고 성능상 좋다.
- Collection 안에 데이터를 처리할 때 유용하다.
- 병렬처리 가능
ex) 기본 ver
int add (int x, int y) {
return x+y;
}
람다식 ver
(x,y)->x+y;
람다식은 함수형 인터페이스(Functional Interface)에서 주로 사용됨
interface MyFunction{
public int max(int a, int b);
}
class Test{
public static void main(String[] args){
MyFunction mf = new MyFunction(){
public int max(int a, int b){
return a>b?a:b;
}
};
}
람다식 적용!!
class Test{
public static void main(String[] args){
MyFunction mf = (a,b)->a>b?a:b;
mf.max(1,20);
}
함수형 인터페이스 구성요소
- 추상메소드 -> 이게 핵심
- defualt 메소드
- static 메소드 :::
- 대표적인 Functional 인터페이스
- 매개변수 ㄴ 반환타입 ㄴ ------> Runnable void run()
- 매개변수 ㅇ 반환타입 ㄴ ------> Consumer void accept(T)
- 매개변수 ㄴ 반환타입 ㅇ ------> Supplier R get()
- 매개변수 ㅇ 반환타입 ㅇ ------> Function R apply(T)
- 매개변수 ㅇ 반환타입 ㅇ(boolean) -----> Predicate boolean test(T)
함수 인터페이스 예제
public class FunctionalTest2 {
public static void main(String[] args) {
System.out.println("-----Runnable void run()-----");
Runnable runnable = () -> System.out.println("run... 함수");
runnable.run();
System.out.println("-----Supplier R get()-----");
Supplier<Integer> supplier = () -> (int) (Math.random()*100)+1;
List<Integer> list = new ArrayList<>();
makeRandomList(list, supplier);
System.out.println(list);
System.out.println("-----Consumer void accept(T)-----");
Consumer<Integer> consumer = (a) -> System.out.println(a);
System.out.println("-----Function R apply(T)-----");
Function<Integer, Integer> f = (i) -> i/10 * 10;
List<Integer> returnList= doSomething(list, f);
System.out.println(returnList);
System.out.println("-----Predicate boolean test(T)-----");
//람다식
Predicate<Integer> p1 = (i)->i%2 == 0;
System.out.println("짝수입니까?"+p.test(230));
printEvenNumber(list, p, consumer);
}
private static void makeRandomList(List<Integer> list,Supplier<Integer> s) {
//10회를 반복해서 Supplier로부터 임의의 값을 받고 그 값을 list에 저장
for(int i=0; i<10; i++) {
list.add(s.get());
}
}
private static List<Integer> doSomething(List<Integer> list, Function<Integer, Integer> function){
List<Integer> newList = new ArrayList<Integer>(list.size());
for(int i: list) {
newList.add(function.apply(i));
}
return newList;
}
private static void printEvenNumber(List<Integer> list, Predicate<Integer> p, Consumer c) {
for(int i: list) {
if(p.test(i)) {
c.accept(i);
}
}
}
}
함수형 인터페이스/람다식 워크샵 문제 해결
package self.test;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
class Employee{
int empNo;
String nameString;
double salary;
public Employee(int empNo, String nameString, double salary) {
super();
this.empNo = empNo;
this.nameString = nameString;
this.salary = salary;
}
public int getEmpNo() {
return empNo;
}
public void setEmpNo(int empNo) {
this.empNo = empNo;
}
public String getNameString() {
return nameString;
}
public void setNameString(String nameString) {
this.nameString = nameString;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee [empNo=" + empNo + ", nameString=" + nameString + ", salary=" + salary + "]";
}
}
public class WorkshopTest {
public static void main(String[] args) {
List<Employee> employees = Arrays.asList(
new Employee(1, "kuromi", 58000.0),
new Employee(2, "kitty", 20000.0),
new Employee(3, "mymello", 40000.0),
new Employee(4, "sinamoroll", 50000.0));
System.out.println("---직원 급여를 15% 인상한 정보를 출력(forEach)---");
employees.forEach(e -> {
e.setSalary(e.getSalary() * 1.15);
System.out.println(e);
});
System.out.println("---직원 급여를 15% 인상한 정보를 출력(Consumer)---");
Consumer<Employee> consumer = (a) -> {
a.setSalary(a.getSalary() * 1.15);
};
raiseSalary(employees, consumer.andThen(System.out::println));
}
private static void raiseSalary(List<Employee> emps, Consumer<Employee> c) {
for (Employee e : emps) {
c.accept(e);
}
}
}
consumer.andThen(System.out::println);
→ andThen: consumer 연산 후에 그 다음에 괄호 안의 행동을 하겠다는 의미
Collection Stream
: 객체를 저장하는 방법론, 가장 원시적인 저장법
- Set
- List
- Map
저장법이 다르다는 것은 그 안에 저장된 데이터를 뽑아낼때도 다른 방법으로 뽑혀진다는 것을 의미. 이것이 코드가 길어질 수 있고 복잡해질 수 있는 부분이 된다.
자바 8버전 이후에 Collection Stream, Lambda 기술이 제공되면서 이 부분이 해소
아래는 간단한 람다식 복습
public class FunctionalTest3 {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i=0; i<=10; i++) {
list.add(i);
}
for(int i: list) System.out.println(i);
Map<Integer, String> map = new HashMap<>();
map.put(111, "POCHACO");
map.put(222, "KUROMI");
map.put(333, "MYMELO");
Set<Integer> keySet = map.keySet();
Iterator<Integer> iterator = keySet.iterator();
while(iterator.hasNext()) {
int key = iterator.next();
String valueString = map.get(key);
System.out.println(key+"-------"+valueString);
}
}
}
이 코드에서 데이터를 뽑아내는 부분을 바꿔보자
다음과 같이 new Consumer 를 생략해서 람다식으로 표현할 수 있다.
System.out.println("1. 리스트에 담긴 객체를 출력 ----> forEach");
list.forEach(i->System.out.println(i));
System.out.println("2. Map에 담긴 객체(key, value)를 출력 ----> forEach");
map.forEach((k,v)->System.out.println("Key: "+k+", value: "+v));
Stream 개념 설명
Stream 의 기본 예제(대표적인 사용)
public class CollectionStreamTest1 {
public static void main(String[] args) {
List<String> fruits = Arrays.asList("Apple", "Melon", "Banana", "Apple", "Orange", "Grape", "Grape", "WaterMelon", "Banana");
Stream<String> stream= fruits.stream();
//중간 연산
List<String> last = stream.distinct().limit(3).sorted().collect(Collectors.toList());
//중복 제거, 여전히 스트림임
//limit으로 가지고 올 수를 제한함
//바로 sort로 정렬 가능
//반환형태가 R 로 나오는 게 최종연산임
System.out.println(last);
}
}
Stream 의 특성
- Read Only
System.out.println("원본 리스트1: "+fruits);
//1. 스트림 생성
Stream<String> stream = fruits.stream();
stream.distinct().limit(4).sorted().forEach(i->System.out.println(i));
System.out.println("원본 리스트2: "+fruits);
- 원본 데이터가 변하지 않는다.
- 일회성
String[] arr = {"aa","bb","cc","dds"};
//Stream<String> aStream = arr.stream(); 불가넝
Stream<String> aStream = Stream.of(arr); //이렇게 하면 가능
aStream.forEach(System.out::println);
long count = aStream.count();
System.out.println("스트림 안의 데이터 개수: "+count);
- 다음과 같이 스트림을 두 번 사용하면 에러가 뜨면서 강제종료 된다.
- 스트림은 꼭 한 번만 써야한다.
- 지연된 연산
IntStream intStream=new Random().ints(1,46);//1~45사이의 랜덤한 값을 반환
//intStream.forEach(System.out::println);
//중복된 값은 없애고 값 중에 6개만 추출, 정렬해서 출력
intStream.distinct().limit(6).sorted().forEach(System.out::println);
- intStream을 처음에 프린트 해보면 무한생성 되는 것을 볼 수 있다.
- 무한 생성 됨에도 불구하고 위의 스트림 연산이 잘 돌아가는 것을 보면
- 최종 연산 전까지 스트림의 중간 연산이 수행되지 않는다는 것을 알 수 있다.
Stream 워크샵 과제
List<String> endingCoffes=coffes.stream()
.filter(c->c.endsWith("o"))
.sorted(Comparator.reverseOrder())
.distinct()
.collect(Collectors.toList());
System.out.println(endingCoffes);
자주쓰는 연산자 정리
중간연산자
- map, filter, mapToInt
- peek
- distiinct
- limit
최종연산자
- anyMatch
- count
- average
- forEach
- collect
Optional
ex)
List<Integer> list = new ArrayList<Integer>(5); //초기 사이즈가 5인 리스트
Stream<Integer> stream = list.stream();
double avg=stream.mapToInt(Integer :: intValue)
.average()
.getAsDouble();
//list안에 데이터 없이 중간 처리 연산자를 가동시키면 NoSuchElementExceptioin 발생
//이럴 경우에 Optional이 필요하다
System.out.println(avg);
위와 같이 null인 값을 처리하려고 하면 예외가 발생한다. 이를 처리해주기 위해 Optional을 사용한다.
방법 1)
List<Integer> list = Arrays.asList(1,2,3,4,5);
OptionalDouble optional = list.stream()
.mapToInt(Integer::intValue)
.average();
if(optional.isPresent()) {
System.out.println("AVG1: "+optional.getAsDouble());
}else {
System.out.println("AVG1: "+0.0);
}
방법2)
double avg2 = list.stream()
.mapToInt(Integer :: intValue)
.average()
.orElse(0.0);
System.out.println("AVG2: "+avg2);
방법3)
list.stream()
.mapToInt(Integer :: intValue)
.average()
.ifPresent(System.out::println);
방법4) throw Exception
Song song1995 = songs.stream()
.filter(song->song.getYear()==1995)
.findFirst()
.orElseThrow(RuntimeException::new);
System.out.println(song1995);