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);
}

함수형 인터페이스 구성요소

  1. 추상메소드 -> 이게 핵심
  2. defualt 메소드
  3. static 메소드 :::
  4. 대표적인 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 의 특성

  1. 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);
  • 원본 데이터가 변하지 않는다.
  1. 일회성
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);
  • 다음과 같이 스트림을 두 번 사용하면 에러가 뜨면서 강제종료 된다.
  • 스트림은 꼭 한 번만 써야한다.
  1. 지연된 연산
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);