본문 바로가기

개발기술/Java

Java 코딩구현심화 - 스트림,람다식


특수 클래스 : 내부클래스

  • 내부클래스 : 클래스 in 클래스 (클래스 안에 선언한 클래스)
    • class Outer {class Inner {}} 
    •  내부 클래스에서 외부클래스의 유효범위안에 들어가기 때문에 인스턴스 생성없이도 외부 클래스 인스턴스 멤버에 접근가능하나, 외부에서는 내부 클래스에 접근 불가함. 
    • 내부클래스가 외부클래스 '안에서만' 사용되는 클래스이기에, 굳이 바깥에 분리하여 둘필요가 없어 외부클래스의 멤버처럼 내부클래스를 캡슐화를 진행하는 개념이다. 
      • ex) Outerclass.Innerclass var = new Outer().new Innter() 
      • 원래 클래스에는 default와 public 밖에 사용되지 않는데, 내부클래스는 모든 접근제어자를 사용가능.

 

내부클래스의 종류 

  • 인스턴스 클래스 (instance class) : 외부 클래스의 인스턴스에 종속적인 내부 클래스입니다.  이러한 클래스는 외부 클래스의 인스턴스가 생성된 후에야 인스턴스화 될 수 있으며, 외부 클래스의 인스턴스 변수 및 메소드에 직접적으로 접근할 수 있는 권한을 가집니다. 주로 외부클래스 인스턴스의 필드멤버처럼 다루어지며, 인스턴스멤버들과의 작업을 위해 만들어짐.
  • 정적 클래스 (static class) : 외부 클래스의 인스턴스 생성과 관계없이 인스턴스를 생성할 수 있는 내부 클래스입니다.  정적 내부 클래스는 외부 클래스의 정적 멤버에 접근할 수 있습니다. ]
    • 보통 특정 클래스내에서만 사용될 인스턴스는 중첩정적클래스로 사용한다.
public class InnerRunnableMainV1 {
    public static void main(String[] args) {
        log("main() start");

        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        
        log("main() end");
    }

    static class MyRunnable implements Runnable{
        @Override
        public void run() {
            log( "run()");
        }
    }

 

 

  • ✓ 지역 클래스 (local class) : 클래스의 메소드 안에 클래스가 있는 경우이며 선언된 내부에서만 사용가능하다.

 

  • ✓ 익명 클래스 (anonymous class) : 부모로부터 상속받을 클래스의 선언과 인스턴스 생성이 동시에 되는 이름없는 일회용 클래스
    • 사용법 : 익명이기때문에 자신의 이름은 없고 대신해서 상속을 받는 부모나 인터페이스의 이름을 사용하여 개체를 생성하고 생성과 동시에 클래스의 내용을 채워넣는다.
public class InnerRunnableMainV2 {
    public static void main(String[] args) {
        log("main() start");

        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                log("run() start");
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();

        log("main() end");
    }
}

 

 

 

람다식과 함수형 인터페이스

  • 람다식 : 함수형인터페이스(함수/메서드를 나타내는 객체)를 사용하여 익명클래스를 만들때 간단하게 표현하는 방법.
    • type InterfaceName(parameter type a, b){ Overriding method statment;} 를 (a, b) -> overriding method statment로 간략화한것
      • 반환타입과 이름을 지우고 매개변수 () 사이에 '->'를 입력한다.  매개변수의 type이 추론이 가능하다면 type도 제거한다. {}내 문장에 한가지일때는 {}도 생략한다.
      • 함수는 클래스에 독립적이며 메서드는 클래스에 종속적이다. 자바에서는 함수(메서드)만 단독으로 존재할 수 없기때문에 람다식은 익명객체이다.  익명클래스를 생성하는 문법이 생략되어있다고 생각하면 됨. 이 객체를 담는 참조변수가 필요하고 이를 위해서 함수형 인터페이스가 도입된다.

 

public class InnerRunnableMainV4 {
    public static void main(String[] args) {
        log("main() start");

        Thread thread = new Thread(() -> log("run() start"));
        thread.start();

        log("main() end");
    }
}

함수형인터페이스

  •  함수형인터페이스 : 단 하나의 추상메서드만 선언된 인터페이스이며 @Fuctional Interface라는 annotaion을 사용가능하다.
    • 함수형인터페이스의 역할은 람다식을 호출하기 위해서 함수형인터페이스가 method의 이름을 대신 선언해주는 것이라고  이해하면 됨
      • 익명클래스를 사용한 원형 MyFunction f = new public abstract int max ( int a, int b) {return a>b ? a : b ; }
      • 람다식을 사용한 축약형  MyFunction f = (a ,b) -> a>b ? a: b;
        • ex :  Comparator interface is a functional interface used to order the elements

 

  •  대표적인 함수형 인터페이스 ; 
    •  Predicate<T>  :  입력값 T를 받아 boolean을 반환하는 함수형 인터페이스. 
      • 주요메서드 : boolean test(T t): 이 메소드는 객체 T를 매개변수로 받아, 정의된 조건에 따라 true 또는 false를 반환함
      • 부가메서드
        • and(Predicate<? super T> other): 두 개의 Predicate 조건을 논리적 AND 연산으로 결합합니다.
        • or(Predicate<? super T> other): 두 개의 Predicate 조건을 논리적 OR 연산으로 결합합니다.
        • negate(): Predicate 조건의 논리적 부정(NOT)을 수행합니다.
      • 람다식 표현 : Predicate<String> startsWithA = name -> name.startsWith("A");
    • Comparable<T> : 객체가 자신의 비교 규칙을 내장하여 자연스러운 정렬 순서를 제공할 수 있게 해주는 인터페이스. .sort(object) 호출은 클래스의 compareTo 메소드를 내부적으로 사용하여 각 Person 인스턴스를 나이에 따라 정렬합니다.
      • int compareTo(T o): 이 메소드는 객체 자신(this)를 매개변수로 전달된 객체 o와 비교합니다. 이 메소드의 반환 값은 다음과 같은 세 가지 경우 중 하나입니다:
        • 음수 반환: 객체 자신이 매개변수 객체보다 "작다"는 것을 의미합니다.
        • 0 반환: 두 객체가 같다는 것을 의미합니다.
        • 양수 반환: 객체 자신이 매개변수 객체보다 "크다"는 것을 의미합니다.
public class Person implements Comparable<Person> {
    private String name;
    private int age;

    @Override
    public int compareTo(Person other) {
        // 나이를 기준으로 비교
        return Integer.compare(this.age, other.age);
    }
}
    •  Comparato<T>  :  두 객체를 외부에서 주어진 기준으로 비교하는 데 사용됩니다.  객체의 기본 정렬 순서(Comparable 인터페이스를 통한 정렬)와 다른 방식으로 객체를 정렬하고 싶을 때 유용합니다.
      • 주요메서드 : int compare(T o1, T o2): 이 메소드는 두 객체 o1과 o2를 비교하고, o1이 o2보다 작으면 음수를, 같으면 0을, 크면 양수를 반환합니다. 이 반환 값은 Collections.sort()나 Arrays.sort() 같은 정렬 메서드에서 사용되어 객체들을 정렬하는 데 활용됩니다.
      • 추가메서드
        • reversed(): 현재 Comparator의 반대 순서로 비교하는 새 Comparator를 반환합니다.
        • thenComparing(Comparator<? super T> other): 현재 Comparator로 두 객체가 같다고 판단될 때, other Comparator를 사용하여 추가 비교를 수행합니다.
          • thenComparing을 안쓰더라도 if문으로 비교결과값 ==0이면 새로운 비교로직을 적용하면 thencomparing을 쓰지않고서도 2가지이상 비교조건을 구현할 수 있음.
        • static <T> Comparator<T> naturalOrder(): 객체의 자연적인 정렬 순서(Comparable을 구현한 경우)에 따라 비교하는 Comparator를 반환합니다.
        • static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator)  nullsLast(Comparator<? super T> comparator): null 값을 갖는 객체들을 각각 비교 시작이나 끝에 배치하는 Comparator를 반환합니다.
        • Comparator<T> comparing( Function<? super T, ? extends U> keyExtractor ) : comparing <T>은 주로 람다 표현식이나 메소드 참조를 인자로 받습니다. 이 인자는 객체에서 특정 키를 추출하는 함수로 작동합니다. 추출된 키는 자연 순서(Comparable을 구현해야 함)에 따라 비교됩니다
  •  
    •  Supplier<T> : 매개변수로 받는 값은 없고 T값을 반환함.
      • Method Signature: The Supplier<T> interface has a single method, get(), which does not take any parameters and returns a value of type T:
      • Supplier<LocalDate> todaySupplier = LocalDate::now;
    • Consumer<T> : 매개변수로 T를 받고, 특정 행위를 수행한 후 반환값은 없음
      • Consumer<String> printConsumer = System.out::println;
    • Function<T R> : 일반적인 함수로 인자 T를 받고 R값을 반환함.
      • Function<String, Integer> lengthFunction = String::length;
      • UnaryOperator<T>  :  operates on a single operand and returns a result of the same type as its operand. extention of Function<T, T>.
  • 메서드참조 : 하나의 메서드만을 호출하는 람다식은 메서드참조로 더 간단히 할 수 있다. 파라미터부분을 제거하고 사용되는 메서드는 문맥으로 프로그램은 파라미터에 대한 정보를 읽어들인다. 단, 아래의 불가예시와 같이 특정 인자를 사용해야되는 경우에는 람다식으로만 표현가능한 부분이 있음.
    • ex 1 : (x) -> ClassName.method(x) 에서 ClassName::method로 변환
    • ex 2 : (obj,x) -> obj.method(x) 에서 ClassName ::method로 변환
    • 불가 : name -> name.startsWith("A") 에서 String::startsWith("A) ; 메서드참조에 매개변수부분은 넣을수 없음.

Comparator 인터페이스는 주로 다음 메소드를 정의합니다:

  • int compare(T o1, T o2): 이 메소드는 두 객체 o1과 o2를 비교하고, o1이 o2보다 작으면 음수를, 같으면 0을, 크면 양수를 반환합니다. 이 반환 값은 Collections.sort()나 Arrays.sort() 같은 정렬 메서드에서 사용되어 객체들을 정렬하는 데 활용됩니다.

Comparator 인터페이스는 주로 다음 메소드를 정의합니다:

  • int compare(T o1, T o2): 이 메소드는 두 객체 o1과 o2를 비교하고, o1이 o2보다 작으면 음수를, 같으면 0을, 크면 양수를 반환합니다. 이 반환 값은 Collections.sort()나 Arrays.sort() 같은 정렬 메서드에서 사용되어 객체들을 정렬하는 데 활용됩니다.

 

스트림

 

  컬렉션, 배열과 같은 다양한 데이터소스를 표준화 된 방법으로 다루기 위한 객체.  배열, 컬렉션 등으로부터 데이터를 하나씩 참조하는 파이프라인. for 문의 사용을 줄여주어 간결한 코드 작성이 가능하다. 스트림은 원본으로부터 데이터를 읽기만할 뿐 원본을 바꾸지 않는다. 주로 람다식과 함께 쓰인다.

스트림은 크게 3가지로 구성됨 ; 1. Stream 생성 2. 중개 연산 3. 최종 연산 - 데이터소스객체.Stream생성().중개연산().최종연산();

 

 

1. 스트림 생성

  1. 컬렉션/배열에서 스트림 생성:
    • 객체 배열 스트림: Stream<Object> stream = Arrays.stream(arr);
    • 기본형 배열 스트림: IntStream stream = Arrays.stream(arr, fromIndex, toIndex);
    • 컬렉션에서 스트림: CollectionInstance.stream()
    객체 배열에는 sum, max와 같은 연산 메소드가 없으므로, 계산이 필요할 때는 기본형 배열 스트림 사용을 권장합니다.
  2. 스트림 빌더 사용:
    • Stream<Object> stream = Stream.builder().add(element1).add(element2)...build();
    • 타입을 지정한 빌더: Stream<Object> stream = Stream.<Object>builder().add(element).build();
  3. 무한 스트림 생성:
    • Stream.generate(Supplier Interface) 메소드를 사용하여 무한 스트림(요소가 무한히 생성되는 스트림)을 만들 수 있습니다.
    • 예: Stream.generate(() -> "ContentsToRepeat");

 

2. 중간연산

중간 연산은 스트림의 입력과 출력이 모두 스트림인 연산입니다. 즉, 중간 연산을 여러 번 반복적으로 적용할 수 있으며, 최종 연산이 수행될 때까지 실제 실행되지 않습니다.

 

  • Filter(Predicate - 판별식) - 조건이 참인 요소들만 걸러내는 메소드입니다.
    • IntStream.range(1, 10).filter(n -> n % 2 == 0); // 1부터 9까지의 짝수만 필터링
  • IntStream.range (a,b) : a부터 b-1까지의 숫자 스트림을 생성, b는 포함하지않음 
    • IntStream.range(1, 10).filter(n -> n % 2 == 0);
  • IntStream.limit(long maxSize) : 스트림의 요소를 특정개수까지만 제한하는 메소드
    • IntStream.range(1, 100).limit(5); // 1부터 5까지의 스트림 생성
  • sorted(): 스트림 내 요소를 정렬합니다. 기본적으로 오름차순으로 정렬됩니다.
  • map (function 연산) : 스트림의 각 요소에 대해 특정 연산을 수행하는 메소드입니다. Stream<T>형태로 반환하기 때문에 primitive data를 결과로 산출하는 function은 WrapperClass로 autoboxing되기때문에 mapToInt를 사용해야함.
    • IntStream.range(1, 10).map(n -> n + 1); // 1부터 9까지의 각 숫자에 1을 더함
  • mapToInt(Integer::parseInt) :  input stream을 특정 class(Integer)의 메소드(parseInt) input값으로 사용하여 변환하도록 표기
    • Stream<String> strStream = Stream.of("1", "2", "3");
    • strStream.mapToInt(Integer::parseInt); // 문자열을 정수로 변환
  • boxed() : 기본형(primitive) 데이터 타입을 래퍼(wrapper) 클래스로 변환합니다..
    • Arrays.stream(new int[]{1, 2, 3}).boxed().collect(Collectors.toList()); // int 배열을 리스트로 변환

 

3. 최종연산 :

연산결과가 스트림이 아닌 연산, 스트림은 사용되면 소모되기때문에 단 한번만 사용가능

  • Primitive type 생성 (Primitive type stream 전용 )
    • sum() - count() - average() - min()- max()
  • 객체생성
    • toArray() : create an array of Object or an array of a specific type via an array constructor reference.
      • ex : String[] array = stream.toArray(String[]::new);
    • collect()  : Collectors 클래스의 메소드를 사용하여 스트림을 collectin 객체로 변환해줌.
      • ex : stream.collect(Collectors.toList()); stream.collect(Collectors.toSet()); 
  • 공통 
    • foreach(Consumer - Class::method) : consumer functional interface를 parameter로하여 stream을 소모하여 class의 method를 실행시킴.
    • reduce : combine all elements of the stream into a single result. It's a model of a folding operation, where a binary operator is applied repeatedly to combine elements successively until a single value remains. first parameter is an initial value for the reduction process and as a default result if the stream is empty.
      • reduce(1, (a, b) -> a * b);
  • 사용 : 데이터소스객체.Stream생성().중개연산().최종연산();
IntStream.range(1,N+1).filter(i->i%2==1).toArray();

 

 

'개발기술 > Java' 카테고리의 다른 글

Deep Dive into 디버깅  (2) 2024.09.15
Java 쓰레드 관리  (0) 2024.08.12
Java Dependency - Marven, Gradle, exteral libraries(Lombok etc)  (0) 2024.07.05
Java 언어의 이해  (0) 2024.06.19
Java 코딩구현 - I/O System  (1) 2024.06.14