https://winterbe.com/posts/2014/03/16/java-8-tutorial/ ๋ฅผ ๋ฒ์ญํ์์ต๋๋ค.
- Default Methods for Interfaces (๋ํดํธ ๋ฉ์๋)
- Lambda Expressions (๋๋ค ์)
- Functional Interfaces (ํจ์ํ ์ธํฐํ์ด์ค)
- Method and Constructor References (๋ฉ์๋/์์ฑ์ ๋ ํผ๋ฐ์ค)
- Lambda Scopes (๋๋ค ๋ฒ์)
- Built-in Functional Interfaces (๋ด์ฅ ํจ์ํ ์ธํฐํ์ด์ค)
- Streams (์คํธ๋ฆผ)
- Parallel Streams (๋ณ๋ ฌ ์คํธ๋ฆผ)
Java 8์ default ํค์๋๋ฅผ ์ด์ฉํด interface์ non-abstract ๋ฉ์๋ ๊ตฌํ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์ด ๊ธฐ๋ฅ์ Extension Methods๋ผ๊ณ ๋ ํฉ๋๋ค.
์๋์ ์์๋ฅผ ๋ณด๋ฉด, Formula์ธํฐํ์ด์ค์์ abstract๋ฉ์๋ calculate๋ง ๊ตฌํํ๋ฉด ๋ฉ๋๋ค.
interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } } Formula formula = new Formula() { @Override public double calculate(int a) { return sqrt(a * 100); } }; formula.calculate(100); // 100.0 formula.sqrt(16); // 4.0
Java 8์๋ anonymous objects๋ฅผ ๋ง๋๋ ๋์ , ํจ์ฌ ๋ ์งง์ syntax๋ฅผ ๊ฐ์ง Lambda expression์ ์ ๊ณตํฉ๋๋ค.
์๋์ ์ผ์ชฝ๊ณผ ๊ฐ์ code๋ฅผ Lambda๋ฅผ ์ฌ์ฉํ๋ฉด์ ์ค๋ฅธ์ชฝ๊ณผ ๊ฐ์ด ์ค์ผ ์ ์์ต๋๋ค. Java ์ปดํ์ผ๋ฌ๊ฐ parameter type์ ์๊ณ ์๊ธฐ ๋๋ฌธ์ ์์ฑ์ ์๋ตํ ์ ์์ต๋๋ค.
Java8 ์ด์  ์ฝ๋
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator<String>() { @Override public int compare(String a, String b) { return b.compareTo(a); } });
Lambda ex 1
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
์ด code๋ฅผ ๋ ์งง๊ฒ ์ค์ผ ์๋ ์์ต๋๋ค.
Lambda ex 2
Collections.sort(names, (String a, String b) -> b.compareTo(a));
์ฌ๊ธฐ์ ๋ ์ค์ธ๋ค๋ฉด
Lambda ex 3
Collections.sort(names, (a, b) -> b.compareTo(a));
์ด๋ป๊ฒ Lambda๊ฐ type์ ์๊น์? ๊ฐ Lambda๋ interface๋ก ์ง์ ๋ type์ ๋์์ด ๋ฉ๋๋ค. ์ด๊ฑธ Functional Interface๋ผ๊ณ ๋ถ๋ฅด๋๋ฐ, ์ ํํ๊ฒ ํ๋์ abstract ๋ฉ์๋ ์ ์ธ์ ํฌํจํด์ผ ํฉ๋๋ค. ์ด๋ฌํ type์ Lambda๋ ์์ ๋งํ abstract ๋ฉ์๋์ ๋งค์นญ์ด ๋ฉ๋๋ค. ๋ํ default ๋ฉ์๋๋ ์ถ์์ ์ด์ง ์๊ธฐ ๋๋ฌธ์ functional interface์ ์์ ๋กญ๊ฒ ์ถ๊ฐํด๋ ๋ฉ๋๋ค.
ํ๋์ abstract ๋ฉ์๋๋ง์ ํฌํจํ ์์์ ์ธํฐํ์ด์ค๋ฅผ Lambda๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ธํฐํ์ด์ค์ @FunctionalInterface ์ด๋ ธํ ์ด์ ์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค. ์ปดํ์ผ๋ฌ๊ฐ ์ด๋ ธํ ์ด์ ์ ์์์ฐจ๋ ค์ ๋๋ฒ์งธ ์ถ์ ๋ฉ์๋๋ฅผ ๋ง๋ค ๋ ์ค๋ฅ๋ฅผ ๋ฐ์์ํฌ ๊ฒ์ ๋๋ค.
@FunctionalInterface interface Converter<F, T> { T convert(F from); } Converter<String, Integer> converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
functional interfaces์์ ์ฌ์ฉํ ์์  code๋ static ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ์ฌ ๋ ๋จ์ํํ ์ ์์ต๋๋ค.
Java8์์๋ :: ํค์๋๋ฅผ ํตํด ๋ฉ์๋๋ ์์ฑ์์ ์ฐธ์กฐ๋ฅผ ์ ๋ฌํ ์ ์์ต๋๋ค. ์๋์ ์์๊ฐ static method๋ฅผ ์ฐธ์กฐํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
Converter<String, Integer> converter = (from) -> Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
object ๋ฉ์๋๋ฅผ ์ฐธ์กฐํ ์๋ ์์ต๋๋ค.
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
:: ํค์๋๋ฅผ ์ด์ฉํด์ ์์ฑ์์ ๋ํ ์์ ๋ ํ ์ ์์ต๋๋ค. ์๋์ ๊ฐ์ด Person ํด๋์ค๋ฅผ ๋ง๋ค๊ณ PersonFactory ์ธํฐํ์ด์ค๋ฅผ ๋ง๋ค์์ ๋, ์์ฑ์ ์ฐธ์กฐ๋ฅผ ํตํด ์๋์ผ๋ก ์ฐ๊ฒฐํด์ค๋๋ค.
Person::new๋ฅผ ํตํด Person ์์ฑ์์ ๋ํ ์ฐธ์กฐ๋ฅผ ๋ง๋ญ๋๋ค. ๊ทธ๋ผ Java ์ปดํ์ผ๋ฌ๋ PersonFactory.create์ ๋งค์นญํ์ฌ ์ฌ๋ฐ๋ฅธ ์์ฑ์๋ฅผ ์๋์ผ๋ก ๋งค์นญํด์ค๋๋ค.
class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } PersonFactory<Person> personFactory = Person::new; Person person = personFactory.create("Peter", "Parker");
lambda ์์์ ์ธ๋ถ ๋ฒ์(outer scope)๋ฅผ ๊ฐ์ง ๋ณ์์ ์ ๊ทผํ๋ ๊ฒ์ anonymous object์ ์ ์ฌํฉ๋๋ค. ๋ก์ปฌ ์ธ๋ถ ์ค์ฝํ ๋ฟ๋ง ์๋๋ผ instance ํ๋, static ๋ณ์์์ final ๋ณ์์ ์ ๊ทผํ ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด lambda ํํ์์ ์ธ๋ถ ๋ฒ์์์ final ๋ก์ปฌ ๋ณ์๋ฅผ ์ฝ์ ์ ์์ต๋๋ค. ์ด๋ final์ ์ ์ธํ์ง ์์ ๋ณ์๋ ์ปดํ์ผ ๋ฉ๋๋ค. ํ์ง๋ง, ์๋ฌต์ ์ผ๋ก final์ผ๋ก ์ธ์ํ๊ณ ์ฝ๋๊ฐ ์ปดํ์ผ๋๋ค๋ ์ ์ ์ธ์งํ๊ณ ์์ด์ผํฉ๋๋ค.
int num = 1; //final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); //implicitly final stringConverter.convert(2); // 3 num = 3 //COMPILE ERROR!!
์ธ์คํด์ค ํ๋์ static ๋ณ์๋ ๋ก์ปฌ ๋ณ์์ ๋ฐ๋๋ก lambda ์ ๋ด์์์ ์ฝ๊ณ ์ฐ๊ธฐ ์ ๊ทผ์ด ๊ฐ๋ฅํฉ๋๋ค.
class Lambda4 { static int outerStaticNum; //static variable int outerNum; //instance field void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
deafult ๋ฉ์๋๋ lambda ์์์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์๋์ ์์ ๋ ์ปดํ์ผ์ด ์๋๋ฉฐ, Formula๋ Deafult Method์์ ์ฌ์ฉํ ์์ ์ ๋๋ค.
Formula formula = (a) -> sqrt( a * 100); //COMPILE ERROR!!
JDK 1.8 API๋ ๋ง์ ๋ด์ฅ functional interface๋ค์ ๊ฐ์ง๊ณ ์์ต๋๋ค. Comparator ๋ Runnable ์ ๊ฐ์ ์ผ๋ถ๋ Java์ ๊ตฌ ๋ฒ์ ์์ ๋ณผ ์ ์๋ ๊ฒ๋ค์ ๋๋ค. ์ด ๊ธฐ์กด์ ์ธํฐํ์ด์ค๊ฐ ํ์ฅ๋์ด @FunctionalInterface ์ด๋ ธํ ์ด์ ์ ํตํด Lambda๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์ง๋ง JAVA 8 API๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋ ์๋ก์ด functional interface๋ค์ด ๋ง๊ณ , ์ด๋ฌํ ์ธํฐํ์ด์ค๋ค ์ค ์ผ๋ถ๋ Google Guava ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ ์๊ฐ๋์ด ์์ต๋๋ค. ์ด๋ฌํ ์ธํฐํ์ด์ค๋ค์ด ์ด๋ป๊ฒ ์ ์ฉํ๊ฒ ๋ฉ์๋๊ฐ ํ์ฅ๋๋์ง์ ๋ํด์ ์ ์๊ณ ์์ด์ผํฉ๋๋ค.
Predicate๋ ํ๋์ ๋งค๊ฐ๋ณ์๋ฅผ boolean์ ๋ฐํํ๋ ํจ์ ์ ๋๋ค. ์ด ์ธํฐํ์ด์ค๋ ๋ ผ๋ฆฌ ์ฐ์ฐ์ด ๊ฐ๋ฅํ default ๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. (and, or, negate)
Predicate<String> predicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate<Boolean> nonNull = Objects::nonNull; nonNull.test(null); //false
Function์ ํ๋์ ๋งค๊ฐ๋ณ์๋ฅผ ๋ฐํ๊ฐ์ผ๋ก ๋ณํํ๋ ์ญํ ์ ํฉ๋๋ค. Function <๋งค๊ฐ๋ณ์ ํ์ , ๋ฐํ๊ฐ ํ์ >๊ณผ ์ฌ์ฉํ๋ฉฐ, apply ๋ฉ์๋๋ฅผ ํตํด ๊ฐ์ ๋ฐํํฉ๋๋ค. deafault ๋งค์๋๋ค์ chain multiple functions๋ก ํจ๊ป ์ฌ์ฉํ ์ ์์ต๋๋ค. (compose, andThen)
Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
Supplier๋ ์ฃผ์ด์ง generic ํ์ ์ ๊ฒฐ๊ณผ๋ฅผ ์ ๊ณตํ๋ฉฐ, get ๋ฉ์๋๋ฅผ ํตํด ๋ฐํ๋ฉ๋๋ค. Functions์๋ ๋ฌ๋ฆฌ ๋งค๊ฐ๋ณ์๊ฐ ์๋ ๊ฒ์ด ํน์ง์ ๋๋ค.
Supplier<Person> personSupplier = Person::new; personSupplier.get(); // new Person
Consumer๋ ํ๋์ ๋งค๊ฐ๋ณ์์ ๋ํด ์ํํ๋ ์์ ์ ํฉ๋๋ค. Lambda ์์ ํจ๊ณผ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ์ฅ์ ์ด ์์ต๋๋ค.
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker")); //Hello, Luke ์ถ๋ ฅ
Comparator๋ ๋น๊ต ๊ท์น์ ์ ํ๋ ์ธํฐํ์ด์ค๋ก, ์ ๋ ฌ ๊ท์น์ ์ ํ ๋ ๋ง์ด ์ฌ์ฉ๋ฉ๋๋ค. ๊ตฌ๋ฒ์ ์ Java์์๋ ์ฌ์ฉ๋์๊ณ , Java8์์ ์ธํฐํ์ด์ค์ ๋ค์ํ default method๊ฐ ์ถ๊ฐ๋์์ต๋๋ค.
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Person p1 = new Person("John", "Doe"); Person p2 = new Person("Alice", "Wonderland"); comparator.compare(p1, p2); // > 0 comparator.reversed().compare(p1, p2); // < 0
Optional์ functional interface๊ฐ ์๋๋ผ NullPointerException์ ๋ฐฉ์งํ๊ธฐ ์ํ niffy ์ ํธ๋ฆฌํฐ์ ๋๋ค. null์ด ์ฌ ์ ์๋ ๊ฐ์ ๊ฐ์ธ๋ Wrapper ํด๋์ค๋ก, NPE๊ฐ ๋ฐ์ํ์ง ์๋๋ก ๋์์ค๋๋ค.
Optional<String> optional = Optional.of("bam"); //of():null์ด ์๋ ๋ช ์๋ ๊ฐ์ ๊ฐ์ง๋ Optional ๊ฐ์ฒด ๋ฐํ optional.isPresent(); // true //Optional ๊ฐ์ฒด์ ์ ์ฅ๋ ๊ฐ์ด null์ธ์ง ํ์ธ optional.get(); // "bam" //Optional ๊ฐ์ฒด์ ์ ์ฅ๋ ๊ฐ ์ ๊ทผ optional.orElse("fallback"); // "bam" //์ ์ฅ๋ ๊ฐ์ด ์กด์ฌ-> ๊ฐ๋ฐํ, ์์ผ๋ฉด-> ์ธ์ ๊ฐ ๋ฐํ optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
java.util.Stream์ ํ๋ ์ด์์ ์์ ์ ์ํํ ์ ์๋ elements๋ค์ sequence(์์๊ฐ ์๋ ์ฐ๊ฒฐ?)์ ๋ํ๋ ๋๋ค. Stream ์ฐ์ฐ์์ intermediate์ด๊ฑฐ๋ terminal ์ด ์์ต๋๋ค.
terminal ์ฐ์ฐ์ ํน์  ํ์ ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ๋ฐ๋ฉด, intermediate ์ฐ์ฐ์ Stream ์์ฒด๋ฅผ ๋ฐํํ๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ ๋ฉ์๋ ํธ์ถ์ ์ฐ์์ ์ผ๋ก ์ฐ๊ฒฐํ ์ ์์ต๋๋ค. Stream์ list๋ set (map์ ์๋จ)๊ณผ ๊ฐ์ java.util.Collection๋ฅผ source๋ก ํด์ ๋ง๋ค์ด์ง๋ฉฐ, sequential ํ๊ฑฐ๋ parallelํ๊ฒ ์คํ๋ ์ ์์ต๋๋ค.
์๋๋ ์ผ๋ฐ์ ์ผ๋ก ์ฌ์ฉ๋๋ ์ ๋ช ํ stream ์ฐ์ฐ๋ค์ ๋๋ค. Filter, Sorted, Map์ intermediate ์ฐ์ฐ์ด๊ณ , Match, Count, Reduce๋ terminal ์ฐ์ฐ์ ๋๋ค. ์ฝ๋๋ก ์ฝ๊ฒ ์ดํด๊ฐ ๊ฐ๊ธฐ ๋๋ฌธ์, ์์ธํ ์ค๋ช ์ ์๋ตํ๊ฒ ์ต๋๋ค.
//stringCollection ์ List๋ก, {"ddd2", "aaa2", "bbb1", "aaa1", "bbb3", "ccc", "bbb2", "ddd1"}์ ์์๋ฅผ ๊ฐ์ง๊ณ ์์
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2"
sorted๋ ์ ๋ ฌ๋ view๋ง ๋ณด์ฌ์ค ๋ฟ, ์์๊ฐ ์ ๋ ฌ๋ ์ํ๋ก ์ ์ฅ๋์ง๋ ์์
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false
- anyMatch ์ฌ์ฉ: true
- nonMatch ์ฌ์ฉ: true
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3
Optional<String> reduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
์์์ ์ธ๊ธํ ๊ฒ๊ณผ ๊ฐ์ด Stream์ Sequentialํ๊ฑฐ๋ Parallelํ ์ ์์ต๋๋ค. Sequential Stream์ ๋ํ ์ฐ์ฐ์ ๋จ์ผ ์ค๋ ๋์์ ์คํ๋๋ ๋ฐ๋ฉด, Parallel Stream์ ๋ฉํฐ ์ค๋ ๋์์ ๋์์ ์ํ ๋ ์ ์์ต๋๋ค.
๋ค์ ์์๋ Parallel Stream์ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ผ๋ง๋ ์ฝ๊ฒ ๋์ผ ์ ์๋์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค.
large list ์์ฑ
int max = 1000000; List<String> values = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
Sequential Sort
long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 ms
Parallel Sort
long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); //stream์ parallelStream์ผ๋ก ๋ฐ๊ฟ System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 ms
Stream ๊ด๋ จํด์ ์๋ ๋งํฌ์ ๋ ์์ธํ๊ฒ ์ค๋ช ๋์ด ์์ต๋๋ค.