[JAVA] chapter 14 람다와 스트림 - 1

WOOK JONG KIM·2022년 10월 3일

자바의 정석

목록 보기
20/25
post-thumbnail

ch14-1~4 람다식

  • 람다식은 메서드를 하나의 식으로 표현한 것. 함수를 간략하면서도 명확한 식으로 표현할 수 있게 해준다.

  • 메서드를 람다식으로 표현하면 메서드의 이름반환값이 없어지므로 람다식을 익명함수라고도 한다. -> 메서드를 변수처럼 다루게 해줌

int[] arr = new int[5];
Arrays.setAll(arr, (i) ->(int)(Math.random()*5)+1);

//위의 람다식은 아래의 함수와 같은 역할을 수행한다.
int method(i){
    return (int) (Math.random()*5)+1;
}
  • 람다식은 메서드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통 { } 사이에 ->를 추가한다

  • 반환 값이 있는 경우 return문 대신 식의 연산결과가 자동적으로 반환값이 되도록 할 수 있다. 이때는 끝에 ;를 붙이지 않는다

  • 람다식에 선언된 매개변수의 타입은 추론이 가능한 경우 생략할 수 있다. 반환타입이 없는 이유도 추론이 가능하기 때문.
    (단, 매개변수중 어느 하나의 타입만 생략하는 것은 허용되지 않는다.)

  • 선언된 매개변수가 하나뿐인 경우에는 괄호를 생략할 수 있다. 단, 매개변수의 타입이 있으면 괄호를 생략할 수 없다.

int max(int a,int b) { return a>b? a:b}
-> (a,b) -> a>b? a:b

void printVar(String name, int i) { System.out.println(name+"-"+i);}
-> (name, i) -> System.out.println(name + "=" + i)

int roll(){ return (int)(Math.random() * 6) ; }
-> () -> (int)(Math.randomm() * 6)

람다식은 익명 클래스의 객체와 동등

(int a, int b) -> a>b ? a:b

// 밑과 동일

new Object(){
	int max(int a, int b){
    	return a>b? a:b;
    }
}

// 호출시에는 참조변수를 통해

타입 f = (int a, int b)-> a>b ? a:b;

// 참조변수인 f의 타입은 클래스 또는 인터페이스가 가능

ch14-5~6 함수형 인터페이스

람다식을 다루기 위한 인터페이스를 함수형 인터페이스라 부르며 이를 통해 람다식을 다룬다

// max가 선언된 MyFunction 인터페이스가 있다고 가정
interface Myfunction{
	public abstract int max(int a, int b);
}


// 이 인터페이스를 구현한 익명 클래스의 객체는 다음과 같다
MyFunction f = new MyFunction(){
					public int max(int a, in b){
                    	return a>b? a:b;
                    }
               };
int big = f.max(5,3)

/* 여기서 MyFunction인터페이스에 정의된 메서드 max()는 
람다식 (int a, int b) ->a > b? a:b와 메서드의 선언부가 일치 하기에 람다식으로 
대체가능 */

MyFunction f = (int a, int b) -> a > b? a:b;
int big = f.max(5,3);

// max와 람다식의 매개변수 타입과 개수, 반환값이 일치하기에 가능

@FunctionalInterface
interface MyFunction{
	// 람다식과 인터페이스가 1:1로 연결되기 위해 하나의 추상메서드만 정의되어 있어야함
	public abstract int max(int a, int b);
}

메서드의 매개변수함수형 인터페이스라면 메서드를 호출할 때 람다식을 참조하는 참조변수를 매개변수로 지정해야한다.
(참조변수 없이 직접 람다식을 매개변수로 지정하는 것도 가능하다.)

@FunctionalInterface
interface Myfunction{
	void myMethod(); // 추상 메서드
}

void aMethod(MyFunction f){// 매개변수 타입이 함수형 인터페이스
	f.myMethod(); // Myfunction에 정의된 메서드 호출
}
	...

MyFunction f = () -> System.out.println("myMethod()");
aMethod(f)

aMethod(() -> System.out.println("Mymethod()"))); 

메서드의 반환타입이 함수형 인터페이스타입이라면 함수형 인터페이스의 추상메서드와 동등한 람다식을 가리키는 참조변수를 반환하거나 람다식을 직접 반환할 수 있다.

MyFunction myMethod(){
	MyFunction f = () -> {};
    return f; // 한줄로 하면 return () -> {};
}
  • 람다식은 익명 객체이고 익명 객체는 타입이 없다. 따라서 대입 연산자의 양변의 타입을 일치시키기 위해 형변환이 필요하다.

  • 람다식은 인터페이스를 구현한 클래스 객체와 완전히 동일하기 때문에 동일한 인터페이스로의 형변환을 허용한다. 이러한 형변환은 생략가능하다.

  • 람다식은 객체이지만 Object타입으로 형변환 할 수 없다. 람다식은 오직 함수형 인터페이스로만 형변환이 가능하다.

  • 람다식 내에서 참조하는 지역변수는 final이 붙지 않았어도 상수로 간주된다. 반면 클래스 인스턴스 변수는 상수로 간주되지 않아 값을 변경할 수 있다. (외부 지역변수와 같은 이름의 람다식 매개변수는 허용되지 않는다.)

package ch14;

@FunctionalInterface
interface MyFunction{
	void run(); // public abstract void run();
}

public class Ex14_1 {
	
	static void execute(MyFunction f) { // 매개변수의 타입이 MyFunction인 메서드
		f.run();
	}
	
	static MyFunction getMyFunction() { // 반환 타입이 MyFunction인 메서드 
		MyFunction f = () -> System.out.println("f3.run()");
		return f;
	}

	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();
		
		execute(f1);
		execute( ()-> System.out.println("run()") );

	}

}

ch14-7~8 java.util.function 패키지

java.util.function 패키지에는 일반적으로 자주 쓰이는 형식의 메서드가 미리 정의되어 있다.

재사용성이나 유지보수 측면에서 가능하면 매번 새로운 함수형 인터페이스를 정의하지 말고, 이 패키지의 인터페이스를 활용하는 것이 좋다.

Predicate는 Function의 변형으로 반환타입이 boolean이라는 것만 다르다

Predicate<String> isEmptyStr = s -> s.length() == 0;
String s = "";

if(isEmptyStr.test(s))
	System.out.println("This is an empty String.");

UnaryOperator<T>BinaryOperator<T>는 Function의 자손 인터페이스로 매개변수의 타입반환 타입이 모두 일치한다.
(타입이 같아서 하나만 적음)

package ch14;

import java.util.function.*;

import java.util.*;

public class Ex14_2 {

	public static void main(String[] args) {
		Supplier<Integer> s = () -> (int)(Math.random()*100) + 1;
		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);
		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));
		}
		
		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());
		}
	}

}
[52, 45, 78, 44, 76, 42, 85, 63, 83, 53]
[52,78,44,76,42,]
[50, 40, 70, 40, 70, 40, 80, 60, 80, 50]

ch14-9~10 Predicate 결합

and(), or(), negate()를 연결하여 하나의 새로운 Predicate로 결합 가능

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;

// 여러 조건식을 하나로 합친 경우( 참조 변수 대신 조건식을 바로 넣어야됨)
// 100 <= i && (i < 200 || i %2 ==0 )
Predicate<Integer> all = notP.and(q.or(r));

// isEqual() -> 두 대상을 비교하는 Predicate를 만들 때 사용
// 
Predicate<String> p = Predicate.isEqual(str1)
boolean result = p.test(str2)

Predicate 끝에 negate()를 붙이면 조건식 전체가 부정

package ch14;

import java.util.function.*;

public class Ex14_3 {

	public static void main(String[] args) {
		Predicate<Integer>p = i -> i < 100;
		Predicate<Integer>q = i -> i <200;
		Predicate<Integer>r  = i -> i%2 ==0;
		Predicate<Integer> notP = p.negate();
		
		Predicate<Integer> all = notP.and(q.or(r));
		System.out.println(all.test(150));
		
		String str1 = "abc";
		String str2 = "abc";
		
		// isEqual로 매개변수로 비교대상 하나, test()로 나머지 한개 매개변수로 지정. 
		Predicate<String>p2 = Predicate.isEqual(str1);
		boolean result = p2.test(str2);
		System.out.println(result);

	}

}

ch14-11~12 컬렉션 프레임웍과 함수형 인터페이스

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}의 형식으로 출력
map.forEach((k, v) -> System.out.print("{"+k+", "+v+"}, "));

forEach로 예전에 Iterator로 while문 돌리던 컬렉션 프레임워크 코드를 아주 간단히 대체할 수 있음


ch14-13~14 메서드 참조

하나의 메서드만 호출하는 람다식은 클래스이름::메서드이름 또는 참조변수 :: 메서드이름 으로 바꾸기 가능

// 문자열을 정수로 변환하는 람다 식
Function<String, Integer> f = (String s) -> Integer.parseInt(s);

// 바꾼 후
Function<String, Integer> f = Integer::parseInt;

생성자의 메서드 참조

Supplier<MyClass> s = () -> new MyClass(); //생성자
Supplier<MyClass> s = MyClass::new; // 메서드 참조


// 매개변수가 있는 생성자
BiFunction<Integer, String, MyClass> bf = (i, s) -> new MyClass(i,s);

BiFunction<Integer, String, MyClass> bf2 = MyClass:: new; // 메서드 참조

// 배열 생성
Function<Integer, int[]> f = x-> new int[x];
Function<Integer, int[]> f2 = int[] :: new;
profile
Journey for Backend Developer

0개의 댓글