Java 8 Functional Tutorial

Java 8 Functinal Programming Tutorial.


Java 8 은 Lambda 표현식과 Stream API 를 이용하여 Functinal 프로그래밍을 지원한다.
이 튜토리얼에서 사전에 정의된 Functional interfaces와 Collctions API 그리고 Stream API를 통해
Java8 이 어떤식으로 Functioanl 프로그래밍을 지원하는지 알아본다.


1. Java8 Functional Programming 소개

Java 는 객체 지향 프로그래밍 언어이다.
Java8 은 Lambda 표현식을 통해 Functional programming 을 지원한다.

람다 표현식은 다음의 문법으로 특정 지어 진다.
(콤마로 구분된 여러가지의 파라메터) -> {하나 이상의 바디 처리}

JDK 의 컴파일러가 type interface 를 지원하기에 람다 표현식은 두가지 방법으로 짧은 표현이 가능하다.

  • 파라메터의 타입을 제외 할수 있다. 컴파일러는 파라메터의 값으로 타입을 유추 할수 있다.
  • 람다 처리시 바디가 단일 표현식이면 return 키워드를 제외시킬수 있다.

또한 다음의 상황에서 좀 더 단순표현이 가능하다.

  • 단일 파라메터에서 괄호를 생략 할수 있다.
  • 표현식의 바디가 단일 처리일 경우 중괄호는 생략 할수 있다.

Functional programming 은 파라메터나 Function 자체로 반환하여 전달하는 고기능 Function을 지원한다.
또한 새로운 stream API 또한 고기능 Function을 지원한다.

해당 튜토리얼에서는 Java8 에서 Functional 프로그래밍을 기본적으로 사전에 정의된
인터페이스들과 Collections, 그리고 Stream API 로 설명한다.

2. 사용법

현재 글의 예제는 다음의 버전으로 빌드되고 실행된다.

  • Java 1.8.101
  • Maven 3.3.9
  • Eclipse Oxygen
  • JUnit 4.12

2.1. Maven Project

이번에는 Junit 라이브러리를 포함한 Maven Project 를 생성한다.

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<project xmlns="http://maven.apache.org/POM/4.0.0"<br>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"<br>
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><br>
<modelVersion>4.0.0</modelVersion><br>
<groupId>zheng.jcg.demo</groupId><br>
<artifactId>java8-demo</artifactId><br>
<version>0.0.1-SNAPSHOT</version><p></p>
<p> <dependencies><br>
<dependency><br>
<groupId>junit</groupId><br>
<artifactId>junit</artifactId><br>
<version>4.12</version><br>
<scope>test</scope><br>
</dependency><br>
</dependencies><br>
<build><br>
<plugins><br>
<plugin><br>
<artifactId>maven-compiler-plugin</artifactId><br>
<version>3.3</version><br>
<configuration><br>
<source>1.8</source><br>
<target>1.8</target><br>
</configuration><br>
</plugin><br>
</plugins><br>
</build><br>
</project></p>

3. 사전 정의된 Functional Interfaces

Functional Interface (FI) 는 java.lang.object 로부터 상속받지 않으며 하나의 abstract 메소드를
가진 인터페이스 이다.

Functional Programming 의 한가지 특징은 순수 Function 이다.
순수 Function 은 입력과 출력을 처리한다.
또한 해당 처리는 어떠한 상황에서도 변하지 않는 것을 목적으로 한다.
그러므로 해당 처리는 어떠한 사이드 이팩트도 발생하지 않는다.
항상 동일한 출력과 입력을 처리한다.

Java 8 은 40가지 이상의 사전에 정의된 Functional interfaces 를 제공한다. Consumer 를 제외한 모든 FI 는 순수 Function들이다.

Java8 참조 메소드는 람다식의 단 한가지 실행을 위해 짧게 표현할수 있다.
개발자는 람다식이나 참조 메소드를 FI 로 사용할수 있다.
Java8 Stream API 는 사전 정의된 FI들을 Steam 을 위해 사용할수 있다.

이번 장에서는 공통 Functional Interface들을 어떤식으로 사용할지 JUnit 클래스를 생성하여 보여준다.

3.1. Function

Function FI 는 하나의 파라메터 받아 들이며 하나의 결과를 반환한다.
Function FI 는 abstract 메소드인 apply(Object) 를 호출한다.

Java8 은 원시 데이터 타입을 위해 각각 사용하기 용이한 FI들을 제공한다

제공 인터페이스: IntFunction, DoubleFunction, IntToDoubleFunction, IntToLongFunction, DoubleToIntFunction, DoubleToLongFunction, LongToDoubleFunction, 그리고 LongToIntFunction.

BitFunction FI 는 두가지의 파라메터를 매개변수로 받으며 처리된 결과를 리턴한다.
BitFunction FI 는 abrtract 메소드인 apply(Object, Object) 를 호출한다.

Java8 은 또한 두가지 파라메터를 매게 변수로 받으며 처리된 결과를 Double값, Int값, Long 값을 반환하는
ToDoubleBiFunction, ToIntBiFunction, ToLongBiFunction 를 제공한다.

이번 장에서는 FunctionTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • Integer 클래스를 String 클래스로 변환
  • 문자열의 길이를 Integer 형으로 반환
  • 두가지 Function을 새로운 Function으로 결합
  • 리스트 내의 속성들을 Stream-map(Function <T, R>) 를 통하여 변환한다.
  • IntFunction, DoubleFunction, 등등의 사용법을 보여준다.

FunctionTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.DoubleFunction;
import java.util.function.DoubleToIntFunction;
import java.util.function.DoubleToLongFunction;
import java.util.function.Function;
import java.util.function.IntFunction;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.LongToDoubleFunction;
import java.util.function.LongToIntFunction;
import java.util.function.ToDoubleBiFunction;
import java.util.function.ToIntBiFunction;
import java.util.function.ToLongBiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.Test;

import com.zheng.demo.DataUtil;
import com.zheng.demo.model.LoginUser;

public class FunctionTest {

@Test
public void BiFunction_concat_two_String() {
BiFunction<String, String, String> concat = (a, b) -> a + b;
String combinedStr = concat.apply("Today is", " a wonderful day");
assertEquals("Today is a wonderful day", combinedStr);
}

@Test
public void BiFunction_multiple_two_int() {
BiFunction<Integer, Integer, Integer> concat = (a, b) -> a * b;
Integer product = concat.apply(3, 4);
assertEquals(12, product.intValue());
}

@Test
public void DoubleFunction_convertDoubleToString_via_lambda() {
DoubleFunction<String> doubleToString = num -> Double.toString(num);

assertEquals("123.456", doubleToString.apply(123.456));
}

@Test
public void DoubleToIntFunction_convertDoubleToInt_via_lambda() {
DoubleToIntFunction doubleToInt = num -> (int) num;

assertEquals(123, doubleToInt.applyAsInt(123.456));
}

@Test
public void DoubleToLongFunction_convertDoubleToLong_via_lambda() {
DoubleToLongFunction doubleToLongFunc = num -> (long) num;

assertEquals(123789008080l, doubleToLongFunc.applyAsLong(123789008080.456));
}

@Test
public void Function_combine_TwoFunctions() {
Function<LoginUser, String> getUser = LoginUser::getUsertName;
Function<String, String> toUpper = String::toUpperCase;

Function<LoginUser, String> userNameMustBeUppercase = getUser.andThen(toUpper);

assertEquals("MARY", userNameMustBeUppercase.apply( DataUtil.buildLoginUser("Mary", "pwd123")));
}

@Test
public void Function_convertStringToInteger_via_methodReference() {
Function<String, Integer> convertToWordCount = String::length;
List<String> words = Arrays.asList("The", "That", "John", "Thanks");

List<Integer> wordsCounts = words.stream().map(convertToWordCount).collect(Collectors.toList());

assertEquals(3, wordsCounts.get(0).intValue());
assertEquals(4, wordsCounts.get(1).intValue());
assertEquals(4, wordsCounts.get(2).intValue());
assertEquals(6, wordsCounts.get(3).intValue());
}

@Test
public void IntFunction_convertIntegerToString_via_lambda() {
IntFunction<String> intToString = num -> Integer.toString(num);

assertEquals("123", intToString.apply(123));
}

@Test
public void IntFunction_via_lambda() {
IntFunction<Integer> powerValue = num -> num * num;

assertEquals(9, powerValue.apply(3).intValue());
}

@Test
public void IntToDoubleFunction_convertIntToDouble_via_lambda() {
IntToDoubleFunction intToDoubleFunc = num -> (double) num;

assertEquals(123, intToDoubleFunc.applyAsDouble(123), 0.1);
}

@Test
public void IntToLongFunction_convertIntToLong_via_lambda() {
IntToLongFunction intToLongFunc = num -> (long) num;

assertEquals(123456, intToLongFunc.applyAsLong(123456));
}

@Test
public void LongToDoubleFunction_convertLongToDouble_via_lambda() {
LongToDoubleFunction longToDoubleFunc = num -> (double) num;

assertEquals(123456, longToDoubleFunc.applyAsDouble(123456), 0.1);
}

@Test
public void LongToIntFunction_convertLongToInt_via_lambda() {
LongToIntFunction longToIntFun = num -> (int) num;

assertEquals(123456, longToIntFun.applyAsInt(123456));
}
@Test
public void stream_map_via_methodReference() {
Map<String, List<String>> awards = new HashMap<>();
awards.put("Mary", Arrays.asList("Math", "Spelling Bee"));
awards.put("Tom", Arrays.asList("Basketball", "Spelling Bee"));
awards.put("Allen", Arrays.asList("English", "Spelling Bee"));

Function<String, String> convertKeyToUppercase = String::toUpperCase;

List<String> uppercaseKeys = awards.entrySet().stream().map(e -> convertKeyToUppercase.apply(e.getKey()))
.collect(Collectors.toList());

assertTrue(uppercaseKeys.contains("MARY"));
assertTrue(uppercaseKeys.contains("TOM"));
assertTrue(uppercaseKeys.contains("ALLEN"));
}

@Test
public void stream_map_with_lambda() {
List<String> collected = Stream.of("Java", "Rocks").map(string -> string.toUpperCase())
.collect(Collectors.toList());

assertTrue(collected.contains("JAVA"));
assertTrue(collected.contains("ROCKS"));
}

@Test
public void ToDoubleBiFunction_power_two_int() {
ToDoubleBiFunction<Integer, Integer> concat = (a, b) -> Math.pow(a, b);
double powerRet = concat.applyAsDouble(5, 3);
assertEquals(125.0, powerRet, 0.1);
}

@Test
public void ToIntBiFunction_multiple_two_int() {
ToIntBiFunction<Integer, Integer> concat = (a, b) -> a * b;
Integer product = concat.applyAsInt(3, 4);
assertEquals(12, product.intValue());
}

@Test
public void ToLongBiFunction_power_two_int() {
ToLongBiFunction<Integer, Integer> concat = (a, b) -> (long) Math.pow(a, b);
Long powerRet = concat.applyAsLong(5, 3);
assertEquals(125, powerRet.intValue());
}
}

3.2. Predicate

Predicate FI 는 하나의 매게변수를 받아 들이며 Boolean 값을 반환한다.
Predicate FI 의 abstract 메소드는 test(Object) 이다.
BiPredicate FI 는 두가지의 매게 변수를 받아들이며 Boolean 값을 반환한다.
Java8 은 또한 원시 데이터 타입을 위한 IntPredicate, LongPredicate, 그리고 DoublePredicate 을 제공한다.

이번 장에서는 다음을 설명하기 위해 PredicateTest.java 를 생성한다.

  • Integer가 숫자 형태가 맞는지 체크한다.
  • 리스트의 속성을 Stream - filter(Predicate <T, R>) 을 이용하여 필터링 한다.
  • 두가지 Predicate를 하나의 Predicate로 결합한다.
  • Long 값을 3으로 나눌수 있는지 체크한다.
  • Double 값을 값에 맞게 처리되는지 체크한다.
  • 최초 매게 변수 Integer 값이 두번째 매게변수 값보다 큰지 체크한다.
  • IntPredicateDoublePredicate 의 사용법을 보여준다.

PredicateTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import java.util.function.BiPredicate;
import java.util.function.DoublePredicate;
import java.util.function.IntPredicate;
import java.util.function.LongPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;

import org.junit.Test;

public class PredicateTest {

@Test
public void BiPredicate_whichIsBigger() {
BiPredicate<Integer, Integer> isBigger = (x, y) -> x > y;
assertTrue(isBigger.test(5, 4));
assertTrue(isBigger.negate().test(4, 5));
}

@Test
public void DoublePredicate_test_isPositive() {
DoublePredicate isPositive = x -> x > 0;
assertTrue(isPositive.test(1.5));
assertFalse(isPositive.test(-1.7));
}

@Test
public void IntPredicate_test_isNagative() {
IntPredicate isNagative = x -> x < 0;
assertTrue(isNagative.test(-1));
assertFalse(isNagative.test(1));
}

@Test
public void LongPredicate_test_isDivisibleByThree() {
LongPredicate isDivisibleBy3 = x -> x % 3 == 0;

assertTrue(isDivisibleBy3.test(12));
assertFalse(isDivisibleBy3.test(11));
}

@Test
public void Predicate_combine_two_predicates() {
// takes one argument and return a boolean
Predicate<String> stringIsLongerThanTen = s -> s.length() > 10;
assertTrue(stringIsLongerThanTen.test("This string is longer than 10"));
assertFalse(stringIsLongerThanTen.test("short"));

Predicate<String> stringStartWithA = s -> s.startsWith("A");
assertTrue(stringStartWithA.test("Apple is a fruit"));

Predicate<String> startWithAandLongerThan10 = stringIsLongerThanTen.and(stringStartWithA);
assertTrue(startWithAandLongerThan10.test("Apple is a fruit which grows everywhere."));
}

@Test
public void Predicate_test_integer_isEven() {
Predicate<Integer> isEven = s -> s % 2 == 0;
assertTrue(isEven.test(4));
assertFalse(isEven.test(5));
}

@Test
public void stream_filter_via_lambda() {
Stream.of("Apple", "Pear", "Banana", "Cherry", "Apricot").filter(fruit -> {
System.out.println("filter:" + fruit);
return fruit.startsWith("A");
}).forEach(fruit -> System.out.println("Started with A:" + fruit));
}

}

3.3. Supplier

Supplier FI 는 매게변수가 없으나 결과 값은 반환한다.
Supplier FI 의 abstract method 는 get() 이다.
보통, Java8 에서 원시 데이터 타입을 사용하기 편하게 하위의 Interface를 제공한다

제공 인터페이스 : IntSupplier, DoubleSupplier, BooleanSupplier, 그리고 LongSupplier.

이번장에서는 SupplierTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • string 값을 반환한다.
  • true 값을 반환한다.
  • 최대 Integer 값을 반환한다.
  • 최대 Long 값을 반환한다.
  • pi 값을 반환한다.

SupplierTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.function.BooleanSupplier;
import java.util.function.DoubleSupplier;
import java.util.function.IntSupplier;
import java.util.function.LongSupplier;
import java.util.function.Supplier;

import org.junit.Test;

public class SupplierTest {

@Test
public void BooleanSupplier_getAsBoolean() {
BooleanSupplier booleanSupplier = () -> true;
assertTrue(booleanSupplier.getAsBoolean());
}

@Test
public void DoubleSupplier_getAsDouble() {
DoubleSupplier pi = () -> Math.PI;
assertEquals(3.14, pi.getAsDouble(), 0.01);
}

@Test
public void IntSupplier_getAsInt() {
IntSupplier maxInteger = () -> Integer.MAX_VALUE;
assertEquals(2147483647, maxInteger.getAsInt());
}

@Test
public void LongSupplier_getAsLong() {
LongSupplier maxLongValue = () -> Long.MAX_VALUE;
assertEquals(9223372036854775807l, maxLongValue.getAsLong());
}

@Test
public void Supplier_AString() {
Supplier<String> message = () -> "Mary is fun";
assertEquals("Mary is fun", message.get());
}
}

3.4. Consumer

Consumer FI 는 하나의 매게 변수를 가지지만 결과값을 반환하지 않는다.
Consumer FI 의 abstract method 는 accept(Object) 이다.
보통, Java 또한 원시 데이터 타입을 사용하기 편하게 하위 Interface들을 제공한다

제공 인터페이스: IntConsumer, LongConsumer, DoubleConsumer, BiConsumer, ObjtIntConsumer, ObjLongConsumer, and ObjDoubleconsumer.

주의: XXConsumer FI들은 사이드 이팩트를 허용하게끔 설계 되어 있다.

이번 장에서는 ConsumerTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • 소문자를 변환한 이후 문자열을 출력한다.
  • 문자열을 출력한다.
  • 두가지의 문자열을 출력한다.
  • 사람들과 만난 일 이후의 년도를 출력한다.
  • 구의 둘레를 계산한다.

ConsumerTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.DoubleConsumer;
import java.util.function.IntConsumer;
import java.util.function.LongConsumer;
import java.util.function.ObjDoubleConsumer;
import java.util.function.ObjIntConsumer;
import java.util.function.ObjLongConsumer;

import org.junit.Test;

import com.zheng.demo.DataUtil;
import com.zheng.demo.model.Contact;

public class ConsumerTest {

@Test
public void BiConsumer_printout() {
BiConsumer<String, String> echo = (x, y) -> {
System.out.println(x);
System.out.println(y);
};
echo.accept("This is first line.", "Here is another line");
}

@Test
public void Consumer_convertToLowercase_via_lambda() {
Consumer<String> convertToLowercase = s -> System.out.println(s.toLowerCase());
convertToLowercase.accept("This Will convert to all lowercase");
}

@Test
public void Consumer_print_prefix() {
Consumer<String> sayHello = name -> System.out.println("Hello, " + name);
for (String name : Arrays.asList("Mary", "Terry", "John")) {
sayHello.accept(name);
}
}

@Test
public void Consumer_print_via_methodreferce() {
Consumer<String> output = System.out::println;
output.accept("This will be printed out.");
}

@Test
public void DoubleConsumer_printout() {
DoubleConsumer echo = System.out::println;
echo.accept(3.3);
}

@Test
public void IntConsumer_printout() {
IntConsumer echo = System.out::println;
echo.accept(3);
}

@Test
public void LongConsumer_printout() {
LongConsumer echo = System.out::println;
echo.accept(3l);
}

@Test
public void ObjDoubleConsumer_caculate_circle_circumference() {
ObjDoubleConsumer<Double> circleCircumference = (r, p) -> System.out.println("Circumference: " + 2 * r * p);

circleCircumference.accept(new Double(4.0), Math.PI);
}

@Test
public void ObjIntConsumer_alterContactAge() {
ObjIntConsumer<Contact> addThreeYear = (c, a) -> {
c.setAge(c.getAge() + a);
System.out.println("Updated contact" + c);
};

addThreeYear.accept(DataUtil.buildContact("mzheng", "pwd", 40), 3);

}

@Test
public void ObjLongConsumer() {
ObjLongConsumer<String> appendex = (m, l) -> {
System.out.println("Append " + m + l);
};
appendex.accept("test message", 10l);
}

}

3.5. UnaryOperator

UnaryOperator FI 는 특별히 피 연산자와 결과가 동일한 Function 이다.
UnaryOperator FI 의 abstract method 는 apply(Object) 이다.
보통, Java8은 원시 타입을 위해 각각의 클래스를 제공한다.

제공 클래스: IntUnaryOperator, DoubleUnaryOperator, 그리고 LongUnaryOperator.

이번 장에서는 UnaryOperatorTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • 문자열을 대문자 포멧으로 변환한다.
  • 문자열을 다른 문자열과 합친다.
  • 숫자열 값을 Double 값으로 반환한다.
  • 매게변수의 값을 연산이후 Long 형값으로 반환한다.
  • 매게변수의 값을 연산이후 Double 형값으로 반환한다.

UnaryOperatorTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

import static org.junit.Assert.assertEquals;

import java.util.function.DoubleUnaryOperator;
import java.util.function.IntUnaryOperator;
import java.util.function.LongUnaryOperator;
import java.util.function.UnaryOperator;

import org.junit.Test;

public class UnaryOperatorTest {

@Test
public void UnaryOperator_convertToUppdercase_via_lamdba() {
UnaryOperator<String> convertToUppercase = msg -> msg.toUpperCase();

String uppString = convertToUppercase.apply("this will be all uppercase");

assertEquals("THIS WILL BE ALL UPPERCASE", uppString);
}

@Test
public void UnaryOperator_concatString_via_methodReference() {
UnaryOperator<String> sayHi = "Hi, "::concat;

String concatString = sayHi.apply("Mary");

assertEquals("Hi, Mary", concatString);
}

@Test
public void IntUnaryOperator_doubleIt() {
IntUnaryOperator doubledIt = x -> x * 2;
assertEquals(24, doubledIt.applyAsInt(12));
}

@Test
public void LongUnaryOperator_squareIt() {
LongUnaryOperator squareIt = x -> x * x;
assertEquals(144, squareIt.applyAsLong(12));
}

@Test
public void DoubleUnaryOperator_squareIt() {
DoubleUnaryOperator squareIt = x -> x * x;
assertEquals(144, squareIt.applyAsDouble(12), 0.1);
}

}

3.6. BinaryOperator

BinaryOperator FI 는 특별히 피 연산자와 결과가 동일한 Function 이다.
BinaryOperator FI 의 abstract method 는 apply(Object) 이다.
보통, Java8은 int, long, double 각각의 데이터 타입으로서 IntBinaryOperator, LongBinaryOperator, 그리고 DoubleBinaryOperator 를 제공한다.

이번 장에서는 BinaryOperatorTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • 두개의 숫자를 더한다.
  • 다양한 숫자 타입을 더한다.
  • 두 숫자의 거듭 제곱을 처리한다.

BinaryOperatorTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import static org.junit.Assert.assertEquals;

import java.util.function.BinaryOperator;
import java.util.function.IntBinaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.DoubleBinaryOperator;

import org.junit.Test;

public class BinaryOperatorTest {

@Test
public void BinaryOperator_add_via_lambda() {
BinaryOperator<Integer> add = (a, b) -> a + b;

Integer sum = add.apply(10, 12);

assertEquals(22, sum.intValue());
}

@Test
public void IntBinaryOperator_add_two_numbers() {
IntBinaryOperator add2 = (a, b) -> a + b;

int sum = add2.applyAsInt(10, 12);

assertEquals(22, sum);
}

@Test
public void LongBinaryOperator_mutiple_two_numbers() {
LongBinaryOperator add2 = (a, b) -> a * b;

long product = add2.applyAsLong(10, 12);

assertEquals(120, product);
}

@Test
public void DoubleBinaryOperator_power_two_number() {
DoubleBinaryOperator add2 = (a, b) -> Math.pow(a, b);

double powerRet = add2.applyAsDouble(10, 2);

assertEquals(100, powerRet, 001);
}

}

4. Functional Inteface 들을 상횡에 맞게 사용

Java8 은 인터페이스를 FI 로 표시하는 새로운 어노테이션 @FunctionalInterface 를 제공한다.
Java 컴파일러는 표시된 인터페이스가 하나이상의 abstract 메소드를 가질때 예외를 던진다.

이번 장에서는 두개의 FI를 생성하여 Java 어플리케이션에서 사용되는지 다음의 사례로 보여진다.

  • IntegerCalculator 클래스는 @FunctionalInterface 어노테이션을 가진다.
  • GreetFunction 클래스는 @FunctionalInterface 어노테이션 없이 처리된다.

4.1. IntegerCaculator

이번 장에서는 IntegerCalcuator.java 를 하나의 메소드인 calcuate 와 함께 생성한다.

IntegerCalcuator.java

1
2
3
4
5
 
@FunctionalInterface
public interface IntegerCalculator {
int caculate(int x, int y);
}

4.2. GreetingFunction

이번 장에서는 GreetingFunction.java 를 하나의 메소드인 speak 와 함께 생성한다.
Java 컴파일러는 해당 클래스를 @FunctionalInterface 어노테이션 없이 FI 로 취급한다.

GreetingFunction.java

1
2
3
public interface GreetingFunction {
void speak(String message);
}

4.3. Demo

이번 장에서는 FPDemo.java 클래스를 다음을 설명하기 위해 생성한다.

  • 두개의 숫자가 더하기, 빼기, 곱하기 계산이 되는지에 대해 처리
  • 어떤식으로 사람을 초대하는지

FPDemo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FPDemo {
public static void main(String[] args) {
GreetingFunction greeting = message -> System.out.println("Hello " + message + "!");
greeting.speak("Tom");
greeting.speak("Mary");

caculateTwoNumbers(3, 4);
caculateTwoNumbers(3, 0);
}

private static void caculateTwoNumbers(int x, int y) {
IntegerCalculator add = (a, b) -> a + b;
IntegerCalculator diff = (a, b) -> a - b;
IntegerCalculator divide = (a, b) -> (b == 0 ? 0 : a / b);

System.out.println(x + " + " + y + " = " + add.caculate(x, y));
System.out.println(x + " - " + y + " = " + diff.caculate(x, y));
System.out.println(x + " / " + y + " = " + divide.caculate(x, y));
}
}

5. Java8 확장.

Java8 은 Collection 클래스를 sort, max, min 메소드를 활용하여 확장 한다.
해당 메소드 들은 comparator 를 파라메터로 Functional interface 를 처리할수 있다.

Java8 Stream 클래스 API 는 매게 변수로 Functional interface 를 받아 들이는 메소드인
map, filter, sorted, min, max, reduce 를 제공한다.

5.1. Collections 과 Comparator

Collections 클래스와 Comparator 인터페이스는 Java8 에서 확장 되었다.
Comparator@FunctionalInterface 어노테이션이 포함되었다.
또한 Collectionssort 메소드는 Comparator의 매게 변수로 처리 된다.

이번 장에서는 CollectionsTest.java 를 다음의 경우를 설명하기 위해 생성한다.

  • 연락처를 나이순으로 정렬한다.
  • 문자열의 리스트를 정렬한다.
  • JDK7에서의 정렬처리와 비교한다.

CollectionsTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.junit.Test;

import com.zheng.demo.dao.ContactDao;
import com.zheng.demo.model.Contact;

public class CollectionsTest {
private ContactDao contDao = new ContactDao();

@Test
public void Collections_sort_by_contact_age() {
Comparator<Contact> contactComparator = Comparator.comparing(Contact::getAge);
List<Contact> contacts = contDao.findAllContacts();

Collections.sort(contacts, contactComparator);

System.out.println("Sorted contact");
contacts.stream().forEach(System.out::println);

Contact oldertContact = Collections.max(contacts, contactComparator );
assertEquals(57, oldertContact.getAge());

Contact youngestContact = Collections.min(contacts, contactComparator );
assertEquals(21, youngestContact.getAge());

}

@Test
public void Collections_sortWithInferType() {
List<String> names = Arrays.asList("Allen", "Matt", "Mary", "Megan", "Alex");
Collections.sort(names, (a, b) -> a.compareTo(b));
System.out.println("Sorted names: " + names);
}

@Test
public void sortBeforeJava8() {
List<String> names = Arrays.asList("Allen", "Matt", "Mary", "Megan", "Alex");

Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
}

}

5.2. Stream

Java8 Stream API 는 속성들의 컬렉션 처리와 반복문을 직관적으로 처리한다.
개발자는 더이상 find, search, 객체의 속성을 컬렉션에서 필터링 하는것을 사용하지 않아도 된다.

이번 장에서는 StreamTest.java 클래스를 다음을 설명하기 위해 생성한다.

  • For 반복문으로 객체 속성들을 반복처리 한다.
  • Iterator 로 객체 속성들을 반복 처리한다.
  • Stream API 의 foreach(Consumer<T>) 로 처리한다.
  • 객체의 리스트를 특정 객체 속성으로 필터링 처리한다.
  • 객체의 리스트 안에 있는 객체 속성들을 변경한다.
  • 객체의 리스트에서 최소값과 최대값의 값들을 필터링 하고 또한 정렬한다.

StreamTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.junit.Before;
import org.junit.Test;

public class StreamTest {
private List<String> userNames;

@Test
public void search() {
Predicate<String> startWithA = name -> name.startsWith("a");
List<String> startWithANames = userNames.stream().filter(startWithA).collect(Collectors.toList());
assertEquals("aWang", startWithANames.get(0));
}

@Test
public void IntStream_sum() {
int sum = IntStream.of(1, 3, 5, 7, 9).sum();
assertEquals(25, sum);
}

@Test
public void tranform() {
List<String> uppercaseNames = userNames.stream().map(String::toUpperCase).collect(Collectors.toList());
assertTrue(uppercaseNames.contains("MZHENG"));
assertTrue(uppercaseNames.contains("AWANG"));
assertTrue(uppercaseNames.contains("TCHANG"));
}

@Test
public void min() {
Comparator<String> comparator = Comparator.comparing(String::length);
Optional<String> shortestName = userNames.stream().min(comparator );
assertTrue(shortestName.isPresent());
assertEquals("aWang", shortestName.get());

Optional<String> longestName = userNames.stream().max(comparator );
assertTrue(longestName.isPresent());
assertEquals("mzheng", longestName.get());

}

@Test
public void print_elelments_via_loop() {
for (String name : userNames) {
System.out.println(name);
}
}

@Test
public void print_elements_via_Iterator() {
Iterator<String> i = userNames.iterator();
while (i.hasNext()) {
System.out.println(i.next());
}
}

@Test
public void print_elemetns_via_Stream() {
// Internal iteration
userNames.stream().forEach(System.out::println);
}

@Before
public void setup() {
userNames = Stream.of("mzheng", "tChang", "aWang").collect(Collectors.toList());
}

@Test
public void sort() {
List<String> sortedNames = userNames.stream().sorted().collect(Collectors.toList());
assertEquals("aWang", sortedNames.get(0));
assertEquals("mzheng", sortedNames.get(1));
assertEquals("tChang", sortedNames.get(2));
}

}

6. 예제

해당 블로그 글에서 각각의 Java 사전 정의된 Function interface 들을 테스트 했었고,
두개의 각각 수정된 Functiona interface 들을 테스트 했으며,
Comparator, Collections, 그리고 Stream 의 처리를 확인했다.
이번 장에서는 실제로 사용되는 사례를 설명 한다.

해당 예제의 어플리케이션에서는 다음 두가지의 요구사항이 있다고 가정한다.

  • 데이터 베이스에서 연락처를 가져온다.
  • 로그인 유저의 연락처를 변경한다.

6.1. Contact

이번 장에서는 성, 이름, 나이, 유저 이름, 비밀번호를 포함한 Contact.java 클래스를 생성한다.

Contact.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class Contact {

private String firstName;
private String lastName;
private String userName;
private String password;
private int age;

public Contact() {
super();
}

public Contact(String firstName, String lastName, String userName, String password, int age) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.userName = userName;
this.password = password;
this.age = age;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Contact [firstName=" + firstName + ", lastName=" + lastName + ", userName=" + userName + ", password="
+ password + ", age=" + age + "]";
}

}

6.2. LoginUser

이번 장에서는 username 과 password를 포함한 LoginUser.java 클래스를 생성한다.

LoginUser.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class LoginUser {
private String userName;
private String password;

public String getUsertName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Override
public String toString() {
return "LoginUser [userName=" + userName + ", password=" + password + "]";
}
}

6.3. DataUtil

이번 장에서는 DataUtil.java 클래스를 생성한다.

DataUtil.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

import java.util.ArrayList;
import java.util.List;

public class DataUtil {
public static List<Contact> getListOfContacts() {
List<Contact> contacts = new ArrayList<>();
contacts.add(new Contact("Becky", "Zheng", "bzheng", "pwd1234@", 48));
contacts.add(new Contact("Alex", "Change", "aChange", "pwd987$", 21));
contacts.add(new Contact("Caleb", "Wang", "cWang", "pwd2345#", 57));
return contacts;
}

public static Contact buildContact(String username, String pwd, int age) {
Contact cnt = new Contact();
cnt.setUserName(username);
cnt.setPassword(pwd);
cnt.setAge(age);
return cnt;
}

public static LoginUser buildLoginUser(String userName, String pwd) {
LoginUser user = new LoginUser();
user.setUserName(userName);
user.setPassword(pwd);
return user;
}

public static LoginUser toUser(Contact contact) {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}
}

6.4. ContactDao

이번 장에서는 모든 연락처를 찾는 메소드를 포함한 ConctactDao.java 클래스를 생성한다.

ContactDao.java

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.zheng.demo.dao;

import java.util.List;

import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;

public class ContactDao {

public List<Contact> findAllContacts(){
return DataUtil.getListOfContacts();
}
}

6.4.1. ContactDaoTest

이번 장에서는 ContactDaoTest.java 클래스를 생성한다.

ContactDaoTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import static org.junit.Assert.assertEquals;

import java.util.List;

import org.junit.Test;

import com.zheng.demo.model.Contact;

public class ContactDaoTest {

private ContactDao testClass = new ContactDao();

@Test
public void findAllContacts() {
List<Contact> allContacts = testClass.findAllContacts();
assertEquals(3, allContacts.size());
}
}

6.5. Data Mapper

이번 장에서는 Contact 클래스를 LoginUser 클래스로 변환하는 DataMapper 클래스를 생성한다.

DataMapper.java

1
2
3
4
5
6
7
8
9
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;
import com.zheng.demo.model.LoginUser;

public class DataMapper {
public LoginUser toUser(Contact contact) {
return DataUtil.toUser(contact);
}
}

6.5.1. DataMapperTest

이번 장에서는 DataMapperTest.java 클래스를 생성한다.

DataMapperTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import org.junit.Test;

import com.zheng.demo.model.Contact;
import com.zheng.demo.model.LoginUser;

public class DataMapperTest {

private DataMapper dto = new DataMapper();

@Test
public void toUser() {
Contact contact = new Contact("firstName", "lastName", "userName", "password", 40);
LoginUser user = dto.toUser(contact);
assertNotNull(user);
assertEquals("USERNAME", user.getUsertName());
assertEquals("password", user.getPassword());
}
}

6.6. LoginUserService

이번 장에서는 Contact 클래스를 LoginUser 클래스로 변환할수 있는 여러가지 방법이 처리되는 LoginUserService.java 클래스를 생성한다.
해당 클래스는 각각의 기능이 있다.

  • Java 8 메소드 참조 (세가지 포멧이 포함된 버전)
  • Java 8 람다 표현식 (세가지 포멧이 포함된 버전)
  • Java 8 람다 표현식으로 명명된 기능
  • Java For 반복문

LoginUserService.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.zheng.demo.dao.ContactDao;
import com.zheng.demo.model.Contact;
import com.zheng.demo.model.DataUtil;
import com.zheng.demo.model.LoginUser;

public class LoginUserService {

ContactDao contactDao = new ContactDao();
DataMapper dto = new DataMapper();

public List<LoginUser> getAllUser_java8Style_Lambda_1() {
return contactDao.findAllContacts().stream().map(contact -> {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_Lambda_2() {
return contactDao.findAllContacts().stream().map(c -> {
return toUser(c);
}).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_Lambda_3() {
return contactDao.findAllContacts().stream().map(c -> toUser(c)).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_methodReference_1() {
return contactDao.findAllContacts().stream().map(DataUtil::toUser).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_methodReference_2() {
return contactDao.findAllContacts().stream().map(this::toUser).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_methodReference_best() {
return contactDao.findAllContacts().stream().map(dto::toUser).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_java8Style_namedLambda() {
Function<Contact, LoginUser> convertContactToLoginUser = contact -> {
return toUser(contact);
};
return contactDao.findAllContacts().stream().map(convertContactToLoginUser).collect(Collectors.toList());
}

public List<LoginUser> getAllUser_loopStyle() {
List<Contact> allContacts = contactDao.findAllContacts();
List<LoginUser> allUser = new ArrayList<>();
for (Contact contact : allContacts) {
allUser.add(toUser(contact));
}
return allUser;
}

private LoginUser toUser(Contact contact) {
LoginUser user = new LoginUser();
user.setPassword(contact.getPassword());
user.setUserName(contact.getUserName().toUpperCase());
return user;
}

}

6.6.1. LoginUserServiceTest

이번 장에서는 LoginUserServiceTest.java 클래스를 생성한다.

LoginUserServiceTest.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import static org.junit.Assert.assertTrue;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.junit.Test;

import com.zheng.demo.model.LoginUser;

public class LoginUserServiceTest {

private LoginUserService testService = new LoginUserService();

@Test
public void getAllUser_java8Style_Lambda_1() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_1();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_Lambda_2() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_2();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_Lambda_3() {
List<LoginUser> allusers = testService.getAllUser_java8Style_Lambda_3();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_methodReference_1() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_1();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_methodReference_2() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_2();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_methodReference_best() {
List<LoginUser> allusers = testService.getAllUser_java8Style_methodReference_best();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_java8Style_namedLambda() {
List<LoginUser> allusers = testService.getAllUser_java8Style_namedLambda();
assertTrue(allusers.size() == 3);

validate(allusers);
}

@Test
public void getAllUser_loopStyle() {
List<LoginUser> allusers = testService.getAllUser_loopStyle();
assertTrue(allusers.size() == 3);

validate(allusers);
}

private void validate(List<LoginUser> allusers) {
Consumer<LoginUser> printOutUser = System.out::println;
allusers.stream().forEach(printOutUser );

Predicate<LoginUser> foundMary = e -> e.getUsertName().equalsIgnoreCase("bzheng") ;
List<LoginUser> foundusers = allusers.stream().filter(foundMary ).collect(Collectors.toList());
assertTrue(foundusers.size() == 1);
}
}

7. 요약.

해당 튜토리얼에서는 사전에 정의된 functional interface 들을 어떤식으로 사용하는지에 대해 보여줬으며,
또한 각각 맞춤 Functional interface 의 사용법을 보여줬다.

이후 Stream API 를 다루어 보았으며, 이후 실 처리에 사용할 수 있는 예제들을 처리했다.

Functional interface 를 지원하는 Java 8 은 2014년 3월 18일에 릴리즈 되었다.
Java 는 원래 Functional 프로그램을 지원하지 않았다.

Functional programming 은 Function 에서 결과를 리턴하는것에 포커싱 되어 있으며 객체 지향보다
좀 더 좋은 성능을 보여준다.

8. 원문

Java 8 Functional Programming Tutorial By Mary Zheng

영어 공부를 위해 퍼왔습니다. 저작권이 문제가 된다면 삭제 하겠습니다.

9. 다운로드 예제 코드

해당 예제는 메이븐 프로젝트로 구성되어 있으며, JUnit test와 사전 정의된 Functional interface와
Collections, Stream API 가 포함되어 있다.

GitHub

Share