자바의 람다식에 대해 학습하세요.
식별자없이 실행가능한 함수
장점:
단점:
(매개변수)-> 표현바디
(매개변수)-> {표현바디}
()-> {표현바디}
()-> 표현바디
기본예제
Setting
package me.whiteship.livestudy.week15;
@FunctionalInterface
public interface Setting {
void setUp();
}
LeagueOfLegend
package me.whiteship.livestudy.week15;
public class LeagueOfLegend {
public void running(Setting setting){
setting.setUp();
System.out.println("LeagueOfLegend running");
}
}
LambdaSample
package me.whiteship.livestudy.week15;
public class LambdaSample {
public static void main(String[] args) {
LeagueOfLegend leagueOfLegend = new LeagueOfLegend();
leagueOfLegend.running(new Setting() {
@Override
public void setUp() {
System.out.println("leageOfLegend is setup");
}
});
}
}
output
leageOfLegend is setup
LeagueOfLegend running
Process finished with exit code 0
람다예제
lambdaSample
package me.whiteship.livestudy.week15;
public class LambdaSample {
public static void main(String[] args) {
LeagueOfLegend leagueOfLegend = new LeagueOfLegend();
leagueOfLegend.running(()-> System.out.println("leagueOfLegend is setup"));
}
}
output
leageOfLegend is setup
LeagueOfLegend running
Process finished with exit code 0
@FunctionalInterface
예제
package me.whiteship.livestudy.week15;
@FunctionalInterface
public interface Functional {
int calc(int a,int b);
}
사용예제2
various 사용
Functional add = (int a,int b)->{return a+b;};
Functional add1 = (int a,int b)->a+b;
Functional add2 = Integer::sum;
package me.whiteship.livestudy.week15;
public class App {
public static void main(String[] args) {
Functional add = (int a,int b)->{return a+b;};
Functional add1 = (int a,int b)->a+b;
Functional add2 = Integer::sum;
int result = add.calc(1,1);
int result1 = add.calc(1,1);
int result2 = add.calc(1,1);
System.out.println(result+ "," +result1+"," +result2);
}
}
output
2,2,2
바이트코드
기본예제 바이트코드
// class version 52.0 (52)
// access flags 0x21
public class me/whiteship/livestudy/week15/LambdaSample {
// compiled from: LambdaSample.java
// access flags 0x8
static INNERCLASS me/whiteship/livestudy/week15/LambdaSample$1 null null
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lme/whiteship/livestudy/week15/LambdaSample; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW me/whiteship/livestudy/week15/LeagueOfLegend
DUP
INVOKESPECIAL me/whiteship/livestudy/week15/LeagueOfLegend.<init> ()V
ASTORE 1
L1
LINENUMBER 8 L1
ALOAD 1
NEW me/whiteship/livestudy/week15/LambdaSample$1
DUP
INVOKESPECIAL me/whiteship/livestudy/week15/LambdaSample$1.<init> ()V
INVOKEVIRTUAL me/whiteship/livestudy/week15/LeagueOfLegend.running (Lme/whiteship/livestudy/week15/Setting;)V
L2
LINENUMBER 15 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE leagueOfLegend Lme/whiteship/livestudy/week15/LeagueOfLegend; L1 L3 1
MAXSTACK = 3
MAXLOCALS = 2
}
익명 객체를 람다식으로 대체가 가능하다.
람다식의 매개변수 타입과 개수 그리고 반환값이 익명 객체와 일치하기 때문이다.
람다예제 바이트코드
// class version 52.0 (52)
// access flags 0x21
public class me/whiteship/livestudy/week15/LambdaSample {
// compiled from: LambdaSample.java
// access flags 0x19
public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup
// access flags 0x1
public <init>()V
L0
LINENUMBER 3 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
RETURN
L1
LOCALVARIABLE this Lme/whiteship/livestudy/week15/LambdaSample; L0 L1 0
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x9
public static main([Ljava/lang/String;)V
L0
LINENUMBER 6 L0
NEW me/whiteship/livestudy/week15/LeagueOfLegend
DUP
INVOKESPECIAL me/whiteship/livestudy/week15/LeagueOfLegend.<init> ()V
ASTORE 1
L1
LINENUMBER 7 L1
ALOAD 1
INVOKEDYNAMIC setUp()Lme/whiteship/livestudy/week15/Setting; [
// handle kind 0x6 : INVOKESTATIC
java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
// arguments:
()V,
// handle kind 0x6 : INVOKESTATIC
me/whiteship/livestudy/week15/LambdaSample.lambda$main$0()V,
()V
]
INVOKEVIRTUAL me/whiteship/livestudy/week15/LeagueOfLegend.running (Lme/whiteship/livestudy/week15/Setting;)V
L2
LINENUMBER 8 L2
RETURN
L3
LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
LOCALVARIABLE leagueOfLegend Lme/whiteship/livestudy/week15/LeagueOfLegend; L1 L3 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x100A
private static synthetic lambda$main$0()V
L0
LINENUMBER 7 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
LDC "leagueOfLegend is setup"
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
RETURN
MAXSTACK = 2
MAXLOCALS = 0
}
@FunctionalInterface
public interface Functional {
int calc(int a,int b);
}
Functional func = (a,b) -> {return a+b};
System.out.println(func.calc(1,2));
// 결과
// f3
이렇게 사용이 가능하다
Runnable
Runnable runnable = () -> System.out.println("runnable run");
runnable.run();
output
runnable run
Runnable은 Run()을 호출해야 한다. 함수형 인터페이스마다 run()과 같은 실행 메서드 이름이 다르다.
인터페이스 종류마다 만들어진 목적이 다르고, 인터페이스 별 목적에 맞는 실행 메서드 이름을 정하기 때문이다.
Supplier<T>
은 인자를받지않고 T타입의 객체를 리턴한다.Supplier
@FunctionalInterface
public interface Supplier<T> {
T get();
}
SupplierSample
package me.whiteship.livestudy.week15;
import java.util.function.Supplier;
public class SupplierSample {
public static void main(String[] args) {
Supplier<String> supplier = ()->"Supplier Sample";
System.out.println(supplier.get());
}
}
인터페이스AB = 인터페이스A.andThen(인터페이스B);
최공결과 인터페이스 AB.method();
인터페이스 AB의 method()를 호출하면 우선 인터페이스 A부터 처리하고 결과를 인터페이스 B의 매개값으로 제공한다.
인터페이스 B는 제공받은 매개값을 가지고 처리한 후 최종결과를 리턴.
인터페이스AB의 method()를 호출하면 먼저 인터페이스B로부터 처리하고 결과를 인터페이스 A의 매개값으로 제공한다. 인터페이스 A는 제공받은 매개값을 가지고 최종 처리한후 결과를 리턴.
Consumer<T>
는 T타입의 객체를 인자로 받고 리턴 값은 없다.Consumer
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
/**
* Returns a composed {@code Consumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
*
* @param after the operation to perform after this operation
* @return a composed {@code Consumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
ConsumerSample
package me.whiteship.livestudy.week15;
import java.util.function.Consumer;
public class ConsumerSample {
public static void main(String[] args) {
Consumer<String> print = str -> System.out.println("This is " + str + " interface");
print.accept("Consumer");
}
}
output
This is Consumer interface
andThen()을 사용하면 두 개 이상의 Consumer를 사용할수있다.
package me.whiteship.livestudy.week15;
import java.util.function.Consumer;
public class ConsumerSample {
public static void main(String[] args) {
Consumer<String> print = str -> System.out.println("This is " + str + " interface");
Consumer<String> print1 = str -> System.out.println("ok");
print.andThen(print1).accept("Consumer");
}
}
output
This is Consumer interface
ok
Function<T,R>
은 T 타입의 인자를 받아, R 타입의 객체로 리턴한다.Function
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
FunctionSample1
package me.whiteship.livestudy.week15;
import java.util.function.Function;
public class FunctionSample {
public static void main(String[] args) {
Function<Integer,Integer> add = (value)->value+value;
System.out.println(add.apply(5));
}
}
FunctionSample2
package me.whiteship.livestudy.week15;
import java.util.function.Function;
public class FunctionSample {
public static void main(String[] args) {
Function<Integer,Integer> f1 = (value)->value-2;
Function<Integer,Integer> f2 = (value)->value+5;
Function<Integer,Integer> addAndSub = f2.compose(f1);
Integer result = addAndSub.apply(10);
System.out.println(result);
}
}
output
13
순차적 연결
Function과 Operator 종류의 함수적 인터페이스는 먼저 실행한 함수적 인터페이스의 결과를 다음 함수적 인터페이스의 매개값으로 넘겨주고 최종 처리결과를 리턴한다.
예를 들어서 Function<Member,Address>와 Function<Address,String>을 순차적으로 연결해서 Function<Member,String>을 생성한다고 하면, Function<Address,String>은 매개값으로 제공되는 Address로 부터 String을 리턴한다. 이 둘을 andThen() 이나 compose()로 연결하면
Function<Member,Address>에서 리턴한 Address를 Function<Address,String>의 매개값으로 넘겨서 최종 String 타입을 리턴하는 Function<Address,String>을 생성해낸다.
Address
package me.whiteship.livestudy.week15.Sequence;
public class Address {
private String country;
private String city;
public Address(String country, String city) {
this.country = country;
this.city = city;
}
public String getCountry() {
return country;
}
public String getCity() {
return city;
}
}
Member
package me.whiteship.livestudy.week15.Sequence;
public class Member {
private String name;
private String id;
private Address address;
public Member(String name, String id, Address address) {
this.name = name;
this.id = id;
this.address = address;
}
public String getName() {
return name;
}
public String getId() {
return id;
}
public Address getAddress() {
return address;
}
}
FunctionMethodExample
package me.whiteship.livestudy.week15.Sequence;
import java.util.function.Function;
public class FunctionMethodExample {
public static void main(String[] args) {
Function<Member,Address> f1 = (m) -> m.getAddress();
Function<Address,String> f2 = (a) -> a.getCity();
// 1. andThen
Function<Member,String> f3 = f1.andThen(f2);
String city = f3.apply(new Member("yyh","5",new Address("Korea","Incheon")));
System.out.println(city);
// 2. compose
Function<Member,String> f4 = f2.compose(f1);
city = f4.apply(new Member("yyh","5",new Address("USA","New York")));
System.out.println(city);
}
}
output
Incheon
New York
Predicate<T>
는 T타입 인자를 받고 결과로 boolean으로 리턴한다. @FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
PredicateExample
package me.whiteship.livestudy.week15;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isSmallerThan = num -> num<10;
System.out.println(isSmallerThan.test(5));
}
}
output
true
another example
package me.whiteship.livestudy.week15;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<Integer> isBiggerThan = num -> num>20;
Predicate<Integer> isSmallerThan = num -> num<10;
System.out.println(isBiggerThan.and(isSmallerThan).test(5));
System.out.println(isBiggerThan.or(isSmallerThan).test(5));
}
}
output
false
true
another example
package me.whiteship.livestudy.week15;
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<String> isEqual = Predicate.isEqual("TheWing");
System.out.println(isEqual.test("TheWing"));
}
}
output
true
package me.whiteship.livestudy.week15.useInterface;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
public class ex1 {
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;
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<>(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());
}
}
}
output
[6, 88, 93, 57, 62, 10, 89, 54, 94, 63]
[6, 88, 62, 10, 54, 94, ]
[0, 80, 90, 50, 60, 10, 80, 50, 90, 60]
Process finished with exit code 0
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
BiConsumer<T,U> | void accept(T t , U u) | 두개의 매개변수,반환값은 없음 |
BiFunction<T,U,R> | R apply(T t , U u) | 두 개의 매개변수를 받아 결과를 반환 |
BiPredicate | boolean test(T t , U u) | 조건식을 표현하고 boolean을 반환 |
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
UnaryOperator | T apply(T t) | Function의 자손, Function과 달리 매개변수와 결과타입 같음. |
BiryOperator | T apple (T t , T t ) | BiFunction의 자손. BiFunction과 달리 매개변수와 결과 타입같음. |
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
DoubleToIntFunction | int applyAsInt(double value) | AtoB Function 은 A 타입 출력이 B 타입 |
ToIntFunction | int applyAsInt(T value) | ToBFunction은 출력이 B타입, 입력이 제네릭 타입 |
IntFunction | T apply (T t , U u) | AFunction은 입력이 A타입, 출력은 제네릭 |
ObjIntConsumer | void accept(T t , U u ) | ObjAFunction은 입력이 T, A 타입이고 출력은 없음 |
package me.whiteship.livestudy.week15.useInterface;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.*;
public class Ex2 {
public static void main(String[] args) {
IntSupplier s = ()->(int)(Math.random() * 100) + 1;
IntConsumer c= i -> System.out.print( i + " ");
IntPredicate p = i -> (i%2) == 0;
IntUnaryOperator op = i -> i/10 * 10;
int [] arr = new int[10];
makeRandomList(s,arr);
System.out.println(Arrays.toString(arr));
printEvenNum(p,c,arr);
System.out.println();
int [] newArr = doSomething(op,arr);
System.out.println(Arrays.toString(newArr));
}
static int[] doSomething(IntUnaryOperator f, int[] list) {
// List<T> newList = new ArrayList<>(list.size());
int[] newList = new int[list.length];
for (int i = 0; i < list.length; i++) {
newList[i] = f.applyAsInt(list[i]);
}
return newList;
}
static void printEvenNum(IntPredicate p, IntConsumer c, int[] list) {
for (int i : list) {
if(p.test(i)){
c.accept(i);
}
}
}
static void makeRandomList(IntSupplier s, int[] list) {
for (int i = 0; i < list.length; i++) {
list[i] = s.getAsInt();
}
}
}
output
[30, 73, 4, 40, 70, 85, 9, 4, 57, 81]
30 4 40 70 4
[30, 70, 0, 40, 70, 80, 0, 0, 50, 80]
Process finished with exit code 0
조금더 쉽게 설명하면
클래스의 멤버 메서드의 매개변수와 이 메서드 실행 블록 내부의 지역변수는 JVM의 stack에 생성되고 메서드 실행이 끝나면 stack에서 사라진다.
new 연산자를 사용해서 생성한 객체는 JVM의 HEAP 영역에 객체가 생성되고 GC(garbage collector)에 의해 관리되며, 더이상 사용하지 않는 객체에 대해 필요한 경우 메모리에서 제거한다.
heap에 생성된 객체가 stack의 변수를 사용하려고 하는데, 사용하려는 시점에 stack에 더이상 해당 변수가 존재하지 않을 수 있다. 왜냐하면 stack은 메서드 실행 이 끝나면 매개변수나 지역 변수를 제거하기 때문이다. 그래서 더 이상 존재하지 않는 변수를 사용하려고 하기에 오류가 발생한다.
자바는 위 문제를 Variable Capture 라고 하는 값 복사를 사용해서 해결하고 있다.
즉, 람다에서 Local variable은
예제코드
package me.whiteship.livestudy.week15.VariableCapture;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.function.Supplier;
public class Foo {
public static void main(String[] args) {
Foo foo = new Foo();
foo.run();
}
private void run() {
// 참조는 할수있다.
// java8부터는 final 키워드를 생략할수있는 케이스가 있다.
// 이 변수가 사실상 final인 경우이다.(변수를 변경하지 않기때문에)
int baseNumber = 10;
// baseNumber = 20; 으로 변경할시 로컬클래스,익명클래스,람다 모두 컴파일 오류 발생
// 1. 로컬 클래스
class LocalClass{
void PrintBaseNumber(){
System.out.println(baseNumber);
}
}
// 2. 익명클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(baseNumber);
}
};
// 3. 람다
Supplier<Integer> printInt = ()->{
return baseNumber;
};
System.out.println(printInt.get());
}
}
로컬클래스와 익명 클래스<> 람다와 다른점 -> 쉐도윙(가려지는 것, 덮어지는 것)
int baseNumber = 10;
// 1. 로컬 클래스
class LocalClass{
void PrintBaseNumber(){
int baseNumber = 11;
// baseNumber 값은 11이 찍힐 것이다. (scope)
// run 메소드에서 선언한 baseNumber에 대해 쉐도잉이 발생
System.out.println(baseNumber);
}
}
// 2. 익명 클래스
Consumer<Integer> integerConsumer = new Consumer<Integer>() {
@Override
public void accept(Integer baseNumber) {
// 파라미터로 전달받은 baseNumber 가 찍힐 것이다.
// run 메소드에서 선언한 baseNumber에 대해 쉐도잉이 발생
System.out.println(baseNumber);
}
};
int baseNumber= 10;
// 3. 람다
IntConsumer printInt = (baseNumber) -> {
System.out.println(baseNumber);
};
위와 같이 선언하게 되는 경우 에러발생
→ Variable 'baseNumber' is already defined in the scope
-> 람다식 내부에서 사용하는 변수는 Variable Capture가 발생하며, 이 값은 final이거나 effective final 처럼 사용해야한다.
예제
package me.whiteship.livestudy.week15.reference;
public class MethodReferenceSample {
public static void main(String[] args) {
MethodReferenceInterface methodReferenceInterface = MethodReferenceSample::multiplyPrint;
methodReferenceInterface.multiply(13);
}
public static void multiplyPrint(int value){
System.out.println(value*2);
}
}
클래스이름::new
String::new
() -> new String
클래스이름::메서드이름
(매개변수)-> Class.staticMethod(매개변수)
String::valueOf
str -> String.valueOf(매개변수)
(매개변수)-> obj.instanceMethod(매개변수)
obj::instanceMethod
object::toString
()->object.toString()
// 생성자 참조
String::new // ClassName::new
()->new String();
// static 메서드 참조
String::valueOf // Classname :: staticMethodName
(str) -> String.valueOf(str)
// instance 메서드 참조 클로저
x::toString // instanceName:: instanceMethodName
() -> "TheWing".toString()
// instance 메서드 참조 람다
String :: toString // ClassName :: instanceMethodName
(str) -> str.toString()
package me.whiteship.livestudy.week15.reference;
import java.util.function.BiFunction;
import java.util.function.IntBinaryOperator;
public class Example {
public static void main(String[] args) {
// int 타입 두개를 받아 int 타입을 반환하는 표준 api 사용
IntBinaryOperator op;
// static method 참조
op = (num1,num2)-> MyReference.add_static(num1,num2);
System.out.println(op.applyAsInt(10,20));
op = MyReference::add_static;
System.out.println(op.applyAsInt(20,30));
// instance method 참조
MyReference mr = new MyReference();
op = (num1,num2) -> mr.add_instance(num1,num2);
System.out.println(op.applyAsInt(30,40));
op = mr::add_instance;
System.out.println(op.applyAsInt(40,50));
// 람다식의 매개변수로 접근 가능한 메서드 참조
// 만약 (x,y) -> x.instanceMethod(y) 인 경우가 있는데
// 이런 경우 사용할 수 있는 방법은 아래와 같다.
// 아래 코드는 x 문자열에 y문자열이 포함되어 있는지 결과를 반환하는 예제이다.
// 이경우 static method 참조와 형태가 매우 유사해 보이지만
// x의 타입에 속하는 클래스 다음에 :: 연산자를 사용해서 메서드 참조를 한다.
BiFunction<String,String,Boolean> myBiFunction;
myBiFunction= (x,y) -> x.contains(y);
System.out.println(myBiFunction.apply("java study","java"));
myBiFunction= String::contains;
System.out.println(myBiFunction.apply("java study","python"));
}
}
class MyReference{
// static method
public static int add_static(int num1,int num2){
return num1+num2;
}
// instance method
public int add_instance(int num1,int num2){
return num1+num2;
}
}
생성자 참조의 예제
package me.whiteship.livestudy.week15.reference;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
public class Example1 {
public static void main(String[] args) {
BiFunction<String,Integer,ConstructorRefTest> bf;
Function<String,ConstructorRefTest> f;
Supplier<ConstructorRefTest> s;
bf = (p1,p2)-> new ConstructorRefTest(p1,p2);
System.out.println(bf.apply("aaaaa",19).toString());
System.out.println();
s= ConstructorRefTest::new;
System.out.println("기본 생성자 : " +s.get().toString());
f=ConstructorRefTest::new;
System.out.println("String 하나를 받는 생성자: " + f.apply("aaaaa").toString());
bf = ConstructorRefTest::new;
System.out.println("String,int 두개를 받는 생성자: "+ bf.apply("qweqweqwe",1).toString());
}
}
class ConstructorRefTest{
String name;
int age;
public ConstructorRefTest() {
}
public ConstructorRefTest(String name) {
this.name = name;
}
public ConstructorRefTest(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString(){
return "ConstructorRefTest{"+
"NAME='"+name+'\''+
",age=" + age +
'}';
}
}
output
ConstructorRefTest{NAME='aaaaa',age=19}
기본 생성자 : ConstructorRefTest{NAME='null',age=0}
String 하나를 받는 생성자: ConstructorRefTest{NAME='aaaaa',age=0}
String,int 두개를 받는 생성자: ConstructorRefTest{NAME='qweqweqwe',age=1}
Process finished with exit code 0
다음의 세가지 조건을 모두 만족하는것들을 말한다.
자바에서 메소드를 변수에 담아 전달해본적이 있었나?
메서드는 클래스에 종속되어 객체로 전달하거나 객체를 반환한 적은 있어도 메서드 자체를 전달해본적은 없다.
package me.whiteship.livestudy.week15.reference;
public class Example2 {
public static void main(String[] args) {
//변수에 저장
MyInterface mi = ()-> System.out.println("변수에 저장된 람다식");
mi.print();
// 매개변수로 전달
doProc(()->System.out.println("매개변수로 전달된 람다식"));
// 반환값으로 사용
getProc().print();
}
public static void doProc(MyInterface mi){
mi.print();
}
public static MyInterface getProc(){
return ()-> System.out.println("반환 값으로 사용된 람다식");
};
}
interface MyInterface{
void print();
}
output
변수에 저장된 람다식
매개변수로 전달된 람다식
반환 값으로 사용된 람다식
람다식이 세가지 조건을 만족하기 때문에 1급 시민 객체인것은 알겠으나 뭐가 좋은건가?
heap 영역에 생성된 객체가 stack 영역의 변수를 안정적으로 사용하기 위해 final 또는 final 성격을 가져야 한다.
즉, 변할수있는것을 변하지 않도록 제한을 둬야한다. 이것을 불변상태(immutable)로 만든다고 한다.
불변의 상태로 만든다는 것은 조금 더 쉬운 말로 '외부의 상태에 독립적' 이라고 표현할수있다.
불변상태로 만들면 지역 변수에 대해 변하지 않는 상수를 사용하기 때문에 동일한 입력에 대해 동일한 결과를 기대할수있다.
동일한 입력에 대해 일관된 결과를 받아볼 수 있다는 것은 다시 말하면 다수의 쓰레드가 동시에 공유해서 사용한다고 하더라도 일관된 결과를 받아볼 수 있다는 것으로 쓰레드와 관련된 동시성 문제가 생길 원인을 미리 방지할수있다.
`