- 자바 스트림(Stream) API 사용하기 -


Java 8에서 추가된 스트림(Steam) API에 대해 알아보자.

 

자바에서 배열이나 컬렉션을 사용할 때 여기에 저장된 데이터에 접근하기 위해서는 반복문이나 반복자(Iterator)를 사용하여 데이터에 접근해야했는데,

그렇게 되면 코드가 너무 길어지고 가독성도 떨어지고, 코드의 재사용이 거의 불가능한 상태의 코드가 탄생한다.

 

이러한 문제점을 극복하기 위해 나온게 스트림 API다.

그러다보니 보다 간결해지고, 데이터 소스에 대한 공통된 접근 방식을 제공하기 때문에 자주 사용된다.

 

이제 스트림API의 특징과 사용법에 대해 하나씩 알아가보자.

 

아 들어가기에 앞서 스트림 API는 람다 표현식을 많이 사용하니 뭔지 알아두기라도 하자.

https://mine-it-record.tistory.com/476

 

[JAVA] 자바_람다식(Lambda Expression) (ft. 함수형 인터페이스, 메서드 참조)

- 자바 람다 표현식(Lambda Expression)이란? - Java 8에서 추가된 람다 표현식(Lambda Expression)과 함수형 인터페이스 그리고 메서드 참조에 대해 알아보자. 1. 람다식(Lambda Expression) - 람다식이란 익명..

mine-it-record.tistory.com


1. Stream API

1-1. 주요 특징과 동작 흐름

  • 스트림은 원본 데이터를 변경하지 않는다.
  • 스트림은 외부 반복을 통해 작업하는 컬렉션과는 달리 내부 반복(internal iteration)을 통해 작업을 수행한다.
  • 스트림은 재사용이 가능한 컬렉션과는 달리 단 한 번만 사용할 수 있다.
  • 스트림의 연산은 필터(filter)-맵(map) 기반의 API를 사용하여 지연(lazy) 연산을 통해 성능을 최적화한다.
  • 스트림은 parallelStream() 메서드를 통한 손쉬운 병렬 처리를 지원한다.
  • 스트림은 [스트림의 생성 -> 스트림의 중개 연산 -> 스트림의 최종 연산]세 가지 단계에 걸쳐서 동작하며, 중개 연산의 경우 Stream형태로 결과를 반환하기 때문에 연속적으로 연결해서 사용할 수 있다.

스트림 API 동작 흐름 (출처 : tcpschool)

 

1-2. 스트림의 생성

스트림 API는 다양한 데이터 소스 (컬렉션, 배열, 가변 매개변수, 지정된 범위의 연속된 정수, 특정 타입의 난수들, 람다 표현식, 파일, 빈 스트림)에서 생성할 수 있다.

 

▷ 1) 컬렉션

자바에서 제공하는 모든 컬렉션의 최고 상위 조상인 Collections 인터페이스에는 stream() 메서드가 정의되어 있다.

따라서 Collection 인터페이스를 구현한 모든 List와 Set 컬렉션 클래스에서도 stream()메서드로 스트림을 생성할 수 있다. 또한, parallelStream() 메서드를 사용하면 병렬 처리가 가능한 스트림을 생성할 수 있다.

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(4, 3, 2, 1);
        Stream<Integer> stream = list.stream();
        stream.forEach(System.out::println);
		
        // Collection 인터페이스를 가지진 않지만 컬렉션 프레임워크 번외로 사용 예제 추가
        // stream api를 활용한 map 출력
        Map<String, Integer> map = new HashMap<>();
        map.put("mine", 50);
        map.put("mine-it", 100);
        map.put("mine-it-record", 200);

        map.entrySet().stream()
            .filter(e -> e.getKey().contains("it"))
            .filter(e -> e.getValue() > 150)
            .forEach(e -> System.out.println(e.getKey() + " : " + e.getValue()));
    }
}

 

▷ 2) 배열

배열에 관한 스트림을 생성하기 위해 Arrays 클래스에는 다양한 형태의 stream() 메서드가 클래스 메서드로 정의되어 있다. 또한, 기본 타입인 int, long, double 형을 저장할 수 있는 배열에 관한 스트림이 별도로 정의되어 있다.

이러한 스트림은 java.util.stream 패키지의 intStream, LongStream, DoubleStream 인터페이스로 각각 제공된다.

public class Main {
    public static void main(String[] args) {

        String[] arr = new String[]{"넷", "둘", "셋", "하나"};

        // 배열에서 스트림 생성
        Stream<String> stream1 = Arrays.stream(arr);
        stream1.forEach(e -> System.out.print(e + " "));
        // stream1.forEach(System.out::println);
        System.out.println(); // 넷 둘 셋 하나

        // 배열의 특정 부분만을 이용한 스트림 생성
        Stream<String> stream2 = Arrays.stream(arr, 3, 4);
        stream2.forEach(e -> System.out.print(e + " ")); // 하나
    }
}

 

▷ 3) 가변 매개변수

Stream 클래스의 of() 메서드를 사용하면 가변 매개변수(variable parameter)를 전달받아 스트림을 생성할 수 있다.

public class Main {
    public static void main(String[] args) {

        // 가변 매개변수에서 스트림 생성
        Stream<Double> stream = Stream.of(4.2, 2.5, 3.1, 1.9);
        stream.forEach(System.out::println);
    }
}

 

▷ 4) 지정된 범위의 연속된 정수

지정된 범위의 연속된 정수를 스트림으로 생성하기 위해 IntStream나 LongStream 인터페이스에는 range()와 rangeClose() 메서드가 정의되어 있다.

  • range(int startInclusive, int endExclusive) : 명시된 시작 정수를 포함하지만, 명시된 마지막 정수는 포함하지 않는 스트림을 생성한다.
  • rangeCloase (int startInclusive, int endExclusive) : 명시된 시작 정수뿐만 아니라 명시된 마지막 정수까지도 포함하는 스트림을 생성한다.
public class Main {
    public static void main(String[] args) {

        // 지정된 범위의 연속된 정수에서 스트림 생성
        IntStream stream1 = IntStream.range(1, 4);
        stream1.forEach(e -> System.out.print(e + " ")); // 1 2 3 
        System.out.println();

        IntStream stream2 = IntStream.rangeClosed(1, 4);
        stream2.forEach(e -> System.out.print(e + " ")); // 1 2 3 4

    }
}

 

▷ 5) 특정 타입의 난수들

특정 타입의 난수로 이루어진 스트림을 생성하기 위해 Random 클래스에는 ints(), longs(), doubles()와 같은 메서드가 정의되어 있다.

  • 이 메서드들은 매개변수로 스트림의 크기를 long 타입으로 전달받을 수 있다.
  • 이 메서드들은 만약 매개변수를 전달받지 않으면 크기가 정해지지 않은 무한 스트림(infinite stream)을 반환한다. (이때에는 limit() 메서드를 사용하여 따로 스트림의 크기를 제한해야 한다.)
public class Main {
    public static void main(String[] args) {

        // 특정 타입의 난수로 이루어진 스트림 생성
        IntStream stream = new Random().ints(4);
        stream.forEach(System.out::println);

    }
}

 

▷ 6) 람다 표현식

람다 표현식을 매개변수로 전달받아 해당 람다 표현식에 의해 반환되는 값을 요소로 하는 무한 스트림을 생성하기 위해 Stream 클래스에는 iterate()와 generate() 메서드가 정의되어 있다.

  • iterate(T seed, UnaryOperator<T> f) : 시드로 명시된 값을 람다 표현식에 사용하여 반환된 값을 다시 시드로 사용하는 방식으로 무한스트림을 생성한다.
  • generate(Supplier<T> s) : 매개변수가 없는 람다 표현식을 사용하여 반환된 값으로 무한 스트림을 생성한다.
public class Main {
    public static void main(String[] args) {
        Stream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...
        stream.forEach(System.out::println);
    }
}

위 예제는 홀수만으로 이루어진 무한 스트림을 생성하는 예제다..

 

▷ 7) 파일

파일의 한 행(line)을 요소로 하는 스트림을 생성하기 위해 java.nio.file.Files 클래스에는 lines() 메소드가 정의되어 있다.

또한, java.io.BufferedReader 클래스의 lines() 메소드를 사용하면 파일뿐만 아니라 다른 입력으로부터도 데이터를 행(line) 단위로 읽어 올 수 있습니다.

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("D:\\mine.txt");
        Stream<String> stream1 = Files.lines(path);
        Stream<String> stream2 = Files.lines(path, Charset.forName("UTF-8"));
    }
}

 

▷ 8) 빈 스트림

아무 요소도 가지지 않는 빈 스트림은 Stream 클래스의 empty() 메서드를 사용하여 생성할 수 있다.

public class Main {
    public static void main(String[] args) throws IOException {
        // 빈 스트림 생성
        Stream<Object> stream = Stream.empty();
        System.out.println(stream.count()); // 스트림의 요소의 총 개수를 출력함.
        // 0
    }
}

 

1-3. 스트림의 중개 연산 (스트림의 변환)

- 스트림 API에 의해 생성된 초기 스트림은 중개 연산을 통해 또 다른 스트림으로 변환된다.

- 중개 연산은 스트림을 전달받아 스트림으로 반환하므로, 중개 연산은 연속으로 연결해서 사용할 수 있다.

- 스트림의 중개 연산은 필터(filter)-맵(map) 기반의 api를 사용함으로 지연(lazy) 연산을 통해 성능을 최적화할 수 있다.

- 스트림 API에서 사용할 수 있는 대표적인 중개 연산과 그에 따른 메서드는 다음과 같다.

  • (스트림 필터링) filter(Predicate<? super T> predicate) : 해당 스트림에서 주어진 조건(predicate)에 맞는 요소만으로 구성된 새로운 스트림을 반환.
  • (스트림 필터링) distinct() : 해당 스트림에서 중복된 요소가 제거된 새로운 스트림을 반환. (내부적으로 Object 클래스의 equals() 메서드를 사용함)
  • (스트림 변환) map(Functoin<? super T, ? extends R> mapper) : 해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값으로 이루어신 새로운 스트림을 반환.
  • (스트림 변환) flatMap(Functoin<? super T, ? extends Stream<? extends R>> mapper) : 해당 스트림의 요소가 배열일 경우, 배열의 각 요소를 주어진 함수에 인수로 전달하여, 그 반환값으로 이루어진 새로운 스트림을 반환.
  • (스트림 제한) limit(long maxSize) : 해당 스트림에서 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림을 반환.
  • (스트림 제한) skip(long n) : 해당 스트림의 첫 번째 요소부터 전달된 개수만큼의 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림을 반환.
  • (스트림 정렬) sorted(Comparator<? super T> comparator) : 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬한다. (비교자를 전달하지 않으면 영문사전 순(natural order)으로 정렬한다.)
  • (스트림 연산 결과 확인) peek(Consumer<? super T> action) : 결과 스트림으로부터 각 요소를 소모하여 추가로 명시된 동작(action)을 수행하여 새로운 스트림을 생성하여 반환함. (주로 연산과 연산 사이에 결과를 확인하고 싶을 때 사용한다. 따라서 개발자가 디버깅 용도로 많이 사용한다고 보면된다.)

▷ 예제 1 ) 스트림 중개 연산

public class Main {
    public static void main(String[] args) {
       
        // 중복을 제거하고(distinct) 홀수만을 골라낸다(filter)
        IntStream streamFilter = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
        streamFilter.distinct().filter(n -> n % 2 != 0).forEach(System.out::println);
        // 7 5 1 3
        
        // 배열의 각 요소를 변환하여 다시 하나로 합쳐 새로운 스트림으로 반환 (map)
        Stream<String> streamMap = Stream.of("Int", "Double", "Long");
        streamMap.map(s -> s.concat("Stream")).forEach(System.out::println);
        // IntStream DoubleStream LongStream

        // 여러 문자열이 저장된 배열을 각 문자열에 포함된 단어로 이루어진 스트림으로 변환후 반환(flatMap)
        // flatMap의 매개변수 타입을 보면 Stream타입인것을 명심하자.
        String[] arr = {"I study hard", "You study JAVA", "I am hungry"};
        Stream<String> streamFlatmap = Arrays.stream(arr);
        streamFlatmap.flatMap(s -> Stream.of(s.split(" "))).forEach(System.out::println);
        // I study hard You study JAVA I am hungry
        
        // 첫번째 요소부터 3개를 제외하고 (skip) 첫 번째 요소부터 5개의 요소만으로 이루어진 (limit) 스트림 반환 
        IntStream streamLimit = IntStream.range(0, 10); // 0 1 2 3 4 5 6 7 8 9
        streamLimit.skip(3).limit(5).forEach(n -> System.out.print(n + " "));
        // 3 4 5 6 7
        
        // 오름차순과 내림차순 정렬 (sort)
        Stream<String> streamSort1 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
        Stream<String> streamSort2 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");

        streamSort1.sorted().forEach(s -> System.out.print(s + " ")); // CSS HTML JAVA JAVASCRIPT
        streamSort2.sorted(Comparator.reverseOrder()).forEach(s -> System.out.print(s + " "));
        // JAVASCRIPT JAVA HTML CSS 
        
        // 연산과 연산 사이의 결과 확인 (peek)
        IntStream streamPeek = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
        streamPeek.peek(s -> System.out.println("원본 스트림 : " + s))
            .skip(2)
            .peek(s -> System.out.println("skip(2) 실행 후 : " + s))
            .sorted()
            .peek(s -> System.out.println("sorted() 실행 후 : " + s))
            .forEach(System.out::println);
    }
}

 

1-4. 스트림의 최종 연산 (스트림의 사용)

- 스트림 API에서 중개 연산을 통해 변환된 스트림은 마지막으로 최종 연산을 통해 각 요소를 소모하여 결과를 표시한다. 즉, 지연(lazy)되었던 모든 중개 연산들이 최종 연산 시에 모두 수행되는 것이다.

- 최종 연산 시에 모든 요소를 소모한 해당 스트림은 더는 사용할 수 없다.

- 스트림 API에서 사용할 수 있는 대표적인 최종 연산과 그에 따른 메서드는 다음과 같다.

  • (요소의 출력) forEach(Consumer<? super T> action) : 스트림의 각 요소에 대해 해당 요소를 소모하여 명시된 동작을 수행한다.
  •  
  • (요소의 소모) reduce(T identity, BinaryOperator<T> accumulator) : 처음 두 요소를 가지고 연산을 수행한 뒤, 그 결과와 다음 요소를 가지고 또다시 연산을 수행한다. 이런 식으로 해당 스트림의 모든 요소를 소모하여 연산을 수행하고, 그 결과를 반환한다. (인수로 초깃값(identity)를 전달하면 초깃값과 해당 스트림의 첫 번째 요소가 먼저 연산을 수행한다. 그리고 초깃값을 사용하냐 안하냐의 차이로 반환 타입이 바뀌니 유의하고 사용해야한다. (예제 참조))
  • (요소의 검색) findFirst() : 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환함.
  • (요소의 검색) findAny() : 해당 스트림에서 첫 번째 요소를 참조하는 Optional 객체를 반환함. (병렬 스트림일 때 주로 사용)
  • (요소의 검사) anyMatch(Predicate<? super T> predicate) : 해당 스트림의 일부 요소가 특정 조건을 만족할 경우에 true를 반환한다.
  • (요소의 검사) allMatch(Predicate<? super T> predicate) : 해당 스트림의 모든 요소가 특정 조건을 만족할 경우에 true를 반환한다.
  • (요소의 검사) noneMatch(Predicate<? super T> predicate) : 해당 스트림의 모든 요소가 특정 조건을 만족하지 않을 경우에 true를 반환한다.
  • (요소의 통계) count() :  해당 스트림의 요소의 개수를 반환한다.
  • (요소의 통계) max(Comparator<? super T> comparator) : 해당 스트림의 요소 중에서 가장 큰 값을 가지는 요소를 참조하는 Optional 객체를 반환한다.
  • (요소의 통계) min(Comparator<? super T> comparator) : 해당 스트림의 요소 중에서 가장 작은 값을 가지는 요소를 참조하는 Optional 객체를 반환한다.
  • (요소의 연산) sum() : 해당 스트림의 모든 요소에 대해 합을 구하여 반환한다.
  • (요소의 연산) average() : 해당 스트림의 모든 요소에 대해 평균값을 구하여 반환한다.
  • (요소의 수집) collect(Collector<? super T,A,R> collector) : 인수로 전달되는 Collectors 객체에 구현된 방법대로 스트림의 요소를 수집한다. (Collector 클래스에는 미리 정의된 다양한 방법이 클래스 메서드로 정의되어 있으며 이 외에도 사용자가 직접 Collector 인터페이스를 구현하여 자신만의 수집 방법을 정의할 수 있다.)

▷ 예제 1 ) 스트림 최종 연산

public class Main {
    public static void main(String[] args) {
       
        // 각 요소를 소모하여 명시된 동작을 수행한다. (forEach)
        IntStream streamEach = IntStream.of(1, 2, 4, 3);
        streamEach.forEach(System.out::println);
        // 1 2 4 3
        
        // 첫번째와 두번째 요소를 가지고 연산 후 세번째 네번째 등 연속으로 수행 (reduce)
        Stream<String> streamReduce1 = Stream.of("넷", "둘", "셋", "하나");
        Optional<String> reduce1 = streamReduce1.reduce((s1, s2) -> s1 + "! " + s2);
        reduce1.ifPresent(System.out::println);
        // 넷! 둘! 셋! 하나
        
        // reduce의 연산 수행에 초깃값 부여
        Stream<String> streamReduce2 = Stream.of("넷", "둘", "셋", "하나");
        String reduce2 = streamReduce2.reduce("시작", (s1, s2) -> s1 + "! " + s2);
        System.out.println(reduce2);
        // 시작! 넷! 둘! 셋! 하나
        
        // 모든 요소를 정렬한 후 첫 번째에 위치한 요소를 출력 (findFirst)
        // 여기서 findAny()를 써도 동일한 결과가 나오나 
        // findAny() 메서드는 병렬 스트림인 경우에 사용해야만 정확한 연산 결과를 반환한다.
        IntStream streamFind = IntStream.of(4, 2, 7, 3, 5, 1, 6);
        OptionalInt findResult = streamFind.sorted().findFirst();
        // OptionalInt findResult = streamFind.sorted().findAny();
        System.out.println(findResult.getAsInt());
        // 1
        
        // 특정 요소를 만족하거나 만족하지 못하거나 (anyMatch, allMatch, noneMatch)
        IntStream streamMatch = IntStream.of(30, 90, 70, 10);
        System.out.println(streamMatch.anyMatch(n -> n > 80)); // true
        // System.out.println(streamMatch.allMatch(n -> n > 80)); // false
        // System.out.println(streamMatch.noneMatch(n -> n > 90)); // true
        
        // 요소의 통계를 뽑아낸다. (개수 : count / 최댓값 : max / 최솟값 : min)
        IntStream streamStat = IntStream.of(30, 90, 70, 10);
        System.out.println(streamStat.count()); // 4
        // System.out.println(streamStat.max().getAsInt()); // 90
        // System.out.println(streamStat.min().getAsInt()); // 10
        
        // 요소의 합을 구한다. (sum)
        IntStream streamSum = IntStream.of(30, 90, 70, 10);
        System.out.println(streamSum.sum()); // 200

        // 요소의 평균을 구한다. (average)
        DoubleStream streamAvg = DoubleStream.of(30.3, 90.9, 70.7, 10.1);
        System.out.println(streamAvg.average().getAsDouble()); // 50.5
        
        // 스트림을 리스트로 수집한다. (collect)
        Stream<String> stream = Stream.of("넷", "둘", "하나", "셋");
        List<String> list = stream.collect(Collectors.toList());
        Iterator<String> iter = list.iterator();
        while(iter.hasNext()) {
            System.out.print(iter.next() + " ");
        }
    }
}

참고 : http://www.tcpschool.com/java/java_stream_concept

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

반응형

- 자바 람다 표현식(Lambda Expression)이란? -


Java 8에서 추가된 람다 표현식(Lambda Expression)함수형 인터페이스 그리고 메서드 참조에 대해 알아보자.


1. 람다식(Lambda Expression)

- 람다식이란 익명객체를 생성하기 위한 표현식을 말한다.

- 간단히 말해 메서드를 하나의 간결한 식으로 표현한 것이라고 할 수 있다.

- Java 8부터 사용 가능하며 람다 표현식을 사용하여 자바에서도 함수형 프로그래밍이 가능하게 되었다.

- 기존의 불필요한 코드를 줄여주고, 작성된 코드의 가독성을 높여준다.

1-1. 람다 표현식 작성하기

자바에서는 화살표(->) 기호를 사용하여 람다 표현식을 작성할 수 있다.

 

▷ 문법

(매개변수목록) -> {함수몸체}

 

▷ 예제 1 ) 기본 문법 예제

// 기존 방식
반환타입 메서드이름 (매개변수타입 변수){
	...
}

// 람다식
(매개변수타입 변수) -> {
	...
}

 

기본 문법에 대해 배워봤고, 그 다음에는 람다식에는 몇가지 특징이 존재하니 알고 넘어가자.

  • 메서드이름과 반환타입의 경우에는 생략할 수 있다.
  • 매개변수의 타입을 추론할 수 있는 경우에는 타입을 생략할 수 있다. (대부분 생략이 가능하다.)
  • 매개변수가 하나인 경우에는 괄호(())를 생략할 수 있다.
  • 함수의 몸체가 하나의 명령문만으로 이루어진 경우에는 중괄호({})를 생략할 수 있다. (이때 세미콜론은 붙이지 않음)
  • 함수의 몸체가 하나의 return 문으로만 이루어진 경우에는 중괄호({})를 생략할 수 없다.
  • return문 대신 표현식을 사용할 수 있으며, 이때 반환값은 표현식의 결괏값이 된다. (이때 세미콜론은 붙이지 않음)

▷ 예제 2 ) 람다식 작성 예제

// 1. 기존 메서드
int min(int x, int y) {
    return x < y ? x : y;
}

// 2. 메서드명과 반환 타입 생략
(int x, int y) -> {
    return x < y ? x : y;
}

// 3. return문 대신 표현식 사용 그리고 중괄호의 생략
// 이때는 문장이 아니므로 끝에 세미콜론은 생략한다.
(int x, int y) -> x < y ? x : y

// 4. 매개변수의 타입이 추론이 가능한 경우 생략
(x, y) -> x < y ? x : y

 

1-2. 함수형 인터페이스 (Functional Interface)

람다식을 다루기 위한 인터페이스로, 람다 표현식을 하나의 변수에 대입할 때 사용하는 참조 변수의 타입을 함수형 인터페이스라고 부른다.

 

▷ 문법

참조변수의타입 참조변수의이름 = 람다 표현식

 

람다식은 메서드 그 자체로 볼게 아니라 익명 클래스의 객체와 동등하다고 볼 수 있다.

// 람다 표현식
(x, y) -> x < y ? x : y

// 익명 함수
new Object() {
    int min(int x, int y) {
        return x < y ? x : y;
    }
}

 

익명 객체의 메서드와 람다식의 매개변수, 반환값이 일치하면 익명 객체를 람다식으로 대체할 수 있다.

람다식으로 정의된 익명 객체의 메서드를 호출하려면 참조변수가 필요하다.

 

이 때, 참조변수의 타입은 클래스 또는 인터페이스가 가능한데, 람다식과 동등한 메서드가 정의되어 있어야한다.

여기서 우리는 함수형 인터페이스를 사용할 것이다.

 

▷ 예제 1 ) 함수형 인터페이스 사용

public class Main {
	// 1. 함수형 인터페이스의 선언
	@FunctionalInterface
	interface LambdaFunction{
		int min(int x, int y);
	}
	
	public static void main(String[] args) {
		LambdaFunction lambdaFunction = (x, y) -> x < y  ? x : y; 	// 2. 추상 메서드의 구현
		System.err.println(lambdaFunction.min(3, 4)); 	// 3. 함수형 인터페이스의 사용
	}
}

 

함수형 인터페이스는 추상클래스와 달리 단 하나의 추상 메서드만을 가져야한다. (하지만 default와 static 메서드의 개수에는 제약이 없다.)

함수형 인터페이스 라는걸 명시해 주지 않으면 일반 인터페이스로 인식해 추후 코드가 꼬여 에러가 발생할 수 있으나,

이를 방지하기 위해 @FunctionalInterface 를 선언해 주는 것이다.

 

@FunctionalInterface 어노테이션은 해당 인터페이스가 함수형 인터페이스라는것을 선언해주어 저렇게 명시된 함수형 인터페이스에 두 개 이상의 메서드가 선언되면 자바 컴파일러는 오류를 발생시킨다.

 

그리고 2.번으로 가게되면 함수형 인터페이스의 추상 메서드를 람다식을 사용해 간결하게 구현하였으며,

3번으로가 해당 함수형 인터페이스를 사용한 것이다.

 

자바에서는 기본적으로 4가지의 함수형 인터페이스를 지원하고있다.

  • Supplier<T> : 매개변수 없이 반환값 만을 갖는 함수형 인터페이스이다. (T get()를 추상 메서드로 가진다.)
  • Consumer<T>  : 객체 T를 매개변수로 받아서 사용하며, 반환값은 없는 함수형 인터페이스이다. (void accept(T t)를 추상메서드로 가진다.)
  • Function<T, R>  : T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스이다. (R apply(T t)를 추상메서드로 가진다.)
  • Predicate<T> : T를 매개변수로 받아 처리한 후 Boolean을 반환한다. (Boolean test(T t)를 추상 메서드로 가진다.)

자세한 내용은 아래 오라클 공홈과 한글로 잘 설명해놓은 블로그를 참조하자.

 

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

 

java.util.function (Java Platform SE 8 )

Interface Summary  Interface Description BiConsumer Represents an operation that accepts two input arguments and returns no result. BiFunction Represents a function that accepts two arguments and produces a result. BinaryOperator Represents an operation u

docs.oracle.com

 

https://mangkyu.tistory.com/113

 

[Java] 람다식(Lambda Expression)과 함수형 인터페이스(Functional Interface) (2/5)

1. 람다식(Lambda Expression) 이란? Stream 연산들은 매개변수로 함수형 인터페이스(Functional Interface)를 받도록 되어있다. 그리고 람다식은 반환값으로 함수형 인터페이스를 반환하고 있다. 그렇기 때문

mangkyu.tistory.com

 

1-3. 메서드 참조 (Method Reference)

- 메서드 참조는 람다 표현식이 단 하나의 메서드만을 호출하는 경우에 해당 람다 표현식에서 불필요한 매개변수를 제거하고 사용할 수 있도록 도와준다.

- 함수형 인터페이스를 람다식이 아닌 일반 메서드를 참조시켜 선언하는 방법이다.

- 참조가능한 메서드는 일반 메서드, static 메서드, 생성자가 있다.

- 메서드 참조를 사용하면 람다식과 마찬가지로 함수형 인터페이스로 반환된다.

 

▷ 문법

클래스이름::메소드이름
참조변수이름::메소드이름

 

▷ 예제 1 ) static 메서드 참조 (Math.min)

public class Main {
	@FunctionalInterface
	interface LambdaFunction{
		int min(int x, int y);
	}
	
	public static void main(String[] args) {
		// 1. 람다식 사용
		LambdaFunction lambdaFunction = (x, y) -> Math.min(x, y);
		System.err.println(lambdaFunction.min(3, 4)); 
	   
		// 2.메서드 참조 사용
		LambdaFunction lambdaFunction = Math::min;
		System.err.println(lambdaFunction.min(3, 8)); 
	}
}

 

위 예제를 보면 알겠지만 람다식은 단순히 두개의 값을 Math.min() 메서드의 매개값으로 전달하는 역할만 하는것뿐 별다른 의미는 없다.

 

이럴때 함수형 인터페이스의 매개변수 타입, 개수, 반환형과 사용하려는 메서드의 매개변수 타입, 개수, 반환형이 일치한다면 2번 처럼 메서드 참조를 사용하여 이를 간결하게 표현할 수 있다.

 

▷ 예제 2 ) 일반 메서드 참조

Consumer<String> consumer = System.out::println; 
consumer.accept("Hello World!!");

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.forEach(System.out::println);

System.out.println() 메서드는 반환값이 없고, 파라미터로 String을 받는 메서드라 Consumer라는 함수형 인터페이스에 참조시킬 수 있다.

 

그리고 forEach는 함수형 인터페이스인 Consumer를 매개변수로 받기 때문에 System.out 메서드 참조를 사용할 수 있다.

 

▷ 예제 3 ) 생성자 참조

- 단순히 객체를 생성하고 반환하는 람다 표현식은 생성자 참조로 변환할 수 있다.

Supplier<String> supplier = () -> new String(); // 람다식 표현
Supplier<String> supplier = String::new; // 메서드 참조

Supplier<Object> supplier = () -> new Object(); // 람다식 표현
Supplier<Object> supplier = Object::new; // 메서드 참조

1-4. 기타 예제

▷ 예제 1 ) Thread 표현식

new Thread(new Runnable() {
    public void run() {
        System.out.println("전통적인 방식의 일회용 스레드 생성");
    }
}).start();
 
new Thread(()->{
    System.out.println("람다 표현식을 사용한 일회용 스레드 생성");
}).start();

 

▷ 예제 2 ) 람다 표현식과 메서드 참조의 비교

DoubleUnaryOperator oper;

oper = (n) -> Math.abs(n); // 람다 표현식
System.out.println(oper.applyAsDouble(-5));

oper = Math::abs; // 메소드 참조
System.out.println(oper.applyAsDouble(-5));

 

▷ 예제 3 ) 배열 생성 시 생성자 참조

Function<Integer, double[]> func1 = a -> new double[a]; // 람다 표현식
Function<Integer, double[]> func2 = double[]::new;      // 생성자 참조

참고 : http://www.tcpschool.com/java/java_lambda_concept

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com

반응형

- <a> tag download Attribute -


<a>태그를 통해 간단하게 파일 다운로드 하는 방법에 대해 알아보자.

<a>태그에는 download 라는 속성이 존재하는데 이는 href에 지정된 파일을 다운로드해주는 기능이다.

 

이렇듯 복잡한 기능의 다운로드로는 사용을 못하고 일반 이미지, 엑셀 템플릿 등 고정된 것들에 한해서만 사용하기에 좋다.

 

▷ 구문

<a href='filepath' download>

 

구문까지 익혔으니 실제로 어떤식으로 사용되는지 봐보자.


▷ 예제1) <a> 태그 download 속성 기본 사용법

<a href="/template/excel_mine_sample.xlsx" download>

 

사용법은 구문과 별 다를게 없이 아주 간단하다.

예제에서 사용된 엑셀 뿐만 아니라 다른 여러 확장자의 파일 역시 가능하다.

 

▷ 예제2) 다른 이름으로 다운로드 받기

<a href="/template/excel_mine_sample.xlsx" download="record_sample">

 

download 속성에는 filename 옵션이 존재하는데,

download="filename" 을 입력함으로 실제 파일과 다른 이름으로 다운로드 제공이 가능하다.

 

1번 예제처럼 생략할 경우 실제 파일 이름으로 다운로드 받아진다.

그래서 결과는 excel_mine_sample.xlsx 로 받아졌어야 하는 파일이 record_sample.xlsx로 받아진다.


여기까지가 <a> 태그의 download 속성에 대한것이나 하나 알아둬야할게 있는데,

역시 download 속성은 IE에서는 사용이 불가능하다.

 

만약 IE에서도 가볍게 다운로드받는 기능을 사용하고 싶다면 아래 링크를 참조해보자.

 

[SPRING] 엑셀(Excel) 서식(템플릿) 다운로드 구현하기

 

[SPRING] 엑셀(Excel) 서식(템플릿) 다운로드 구현하기

- 스프링 엑셀(Excel) 서식 템플릿 다운로드 구현하기 - 일반적으로 웹 개발을 한다고 하면 엑셀 업로드 및 다운로드 기능을 자주 접하게 되는데 그럴 때마다 사용되는 게 Apache POI API를 사용하고는

mine-it-record.tistory.com


참고 : https://www.w3schools.com/tags/att_a_download.asp

반응형

- 변수 타입에 따른 기본값 (ft. 기본형 참조형) -


자바에서는 변수를 선언할 경우 초기화를 하지 않더라도 변수의 타입별로 기본값이 존재하는데,

이는 컴파일러(Compiler)에 의해 초기화가 되는 값이니

실제로 코드를 작성한다면 직접적으로 값을 할당하여 초기화 시켜주는게 좋다.

 

그리고 지역변수는 초기값이 들어가지 않기 때문에 여러모로 혼란스러울수 있으니 꼭 값을 할당해서 초기화 시켜주자.

그래도 일단 궁금하니 변수 타입에 따른 기본값은 아래 표로 정리해둔다.

 

▷ 변수 타입에 따른 기본값

자료형(변수 타입) 기본값
byte 0
short 0
int 0
long 0L
float 0.0f
double 0.0d
char '\n0000'
boolean false
참조형 변수(String or any Object) null

 

▶ 코드 테스트

import java.util.Map;

public class Test {

	static byte byteT;
	static short shortT;
	static int intT;
	static long longT;
	static float floatT;
	static double doubleT;
	static char charT;
	static boolean booleanT;
	static String stringT;
	static Map mapT;
	
	public static void main(String[] args) {
		System.out.println(byteT); //0
		System.out.println(shortT); //0
		System.out.println(intT); //0
		System.out.println(longT); //0
		System.out.println(floatT); //0.0
		System.out.println(doubleT); //0.0
		System.out.println(charT); //
		System.out.println(booleanT); //false
		System.out.println(stringT); //null
		System.out.println(mapT); //null
	}

}

▶ 결과

 

반응형

- json-simple을 사용한 JSON 파일 READ / WRITE -


JAVA에서 JSON을 파일로 만들거나 기존에 있는 JSON 파일을 읽는 방법에 대해 알아보자.

우선 JSON을 다루기 위해 json-simple 라이브러리를 사용할 것인데

 

maven 설정을 해주어야 한다.

 

▷ json-simple 설정

<dependency>
	<groupId>com.googlecode.json-simple</groupId>
	<artifactId>json-simple</artifactId>
	<version>1.1</version>
</dependency>

https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple/1.1.1

 

maven을 사용하기 싫거나 maven 환경이 아니라면 .jar 파일을 직접 받아서 사용해주면 된다.

https://code.google.com/archive/p/json-simple/downloads

 

Google Code Archive - Long-term storage for Google Code Project Hosting.

 

code.google.com

 

이제 json-simple 환경이 잡혔다면 코드 예제를 통해 접근해보자.


▷ JSON 파일 쓰기(Write) / 파일로 저장하기

import java.io.FileWriter;
import java.io.IOException;

import org.json.simple.JSONObject;

public class JsonSimple {

	public static void main(String[] args) {
		
		JSONObject obj = new JSONObject();
		obj.put("name", "mine-it-record");
		obj.put("mine", "GN");
		obj.put("year", 2021);

		try {
			FileWriter file = new FileWriter("c:/mine_data/mine.json");
			file.write(obj.toJSONString());
			file.flush();
			file.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		System.out.print(obj);

	}

}

 

▶결과

JSONObject 형식의 파일을 FileWriter를 가지고 저장하는 원리이다.

JSONObject 안에 배열이나 이런걸 넣어도 잘 저장될 것이다.

 

▷ JSON 파일 읽기(Read)

package test.my.only;

import java.io.FileReader;
import java.io.IOException;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

public class JsonSimple {

	public static void main(String[] args) {
		
		JSONParser parser = new JSONParser();

		try {
			FileReader reader = new FileReader("c:/mine_data/mine.json");
			Object obj = parser.parse(reader);
			JSONObject jsonObject = (JSONObject) obj;
			
			reader.close();
			
			System.out.print(jsonObject);
			
		} catch (IOException | ParseException e) {
			e.printStackTrace();
		}

	}
	
}

 

▶결과

{"mine":"GN","year":2021,"name":"mine-it-record"}

 

FileReader 로 JSON 파일을 읽은 뒤 파일을 simle-json에서 제공하는 JSONParser를 사용하여 파싱 해준것이다.

알고보면 참 쉬운 JSON 파일의 읽고 쓰는 방법이다.

반응형

- <datalist> 태그란 무엇일까? -


<datalist> 태그는 <input> 요소에서 사용하기 위한 옵션들의 리스트를 미리 정의하여 보여준다.

 

쉽게 설명하자면 <input> 태그와 <select> 태그가 짬뽕됐다고 생각해도 좋을듯하다.

제목과 마찬가지로 input에 drop down 목록을 보여주며, 미리 정의된 목록을 대상으로는 자동완성 기능도 제공된다.

 

예제를 통해 바로 접근해보도록하자.

 

▷예제1)

<input list="cars"/>
<datalist id="cars">
    <option value="Avante">
    <option value="Sonata">
    <option value="Tesla">
    <option value="Volvo">
</datalist>

 

▶결과


코드를 보면 알겠지만

사용방법은 <input> 태그list 속성<datalist> 태그id 속성값일치시켜 사용하는 것이며

결과 처럼 input 태그와 datalist의 목록을 제공하며, 자동완성 혹은 목록중 하나를 선택하는것 역시 가능하다.

 

HTML5에서 추가된 기능이며 생각보다 사용할 수 있는곳이 많아보인다.

인터넷에 input + selectbox 기능을 제공하는 라이브러리들이 많이 존재하는데 

비슷하다면 비슷한 기능이기에 이정도로도 충분해 보인다.

반응형