[Java] 60. 람다 활용

2025. 7. 4. 23:51·Java/Modern Java(8~)

 

람다 활용

#Java/adv3


필터 만들기1

psvm {
	List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
	
	// 짝수만 거르기
    List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
	
	// 홀수만 거르기
	List<Integer> oddNumbers = filter(numbers, n -> n % 2 == 1);
}

static List<Integer> filter(List<Integer> numbers, Predicate<Integer> predicate) {
    List<Integer> filtered = new ArrayList<>();
    for (Integer number : numbers) {
        if (predicate.test(number)) {
            filtered.add(number);
        }
    }
    return filtered;
}

필터 만들기2

앞서 만든 filter() 메서드는 숫자 리스트에 있는 값을 필터링 하는 모든 곳에서 사용할 수 있다. 유틸리티 클래스로 만듬

public class IntegerFilter {

    public static List<Integer> filter(List<Integer> list, Predicate<Integer> predicate) {
        List<Integer> filtered = new ArrayList<>();
        for (Integer num : list) {
            if (predicate.test(num)) {
                filtered.add(num);
            }
        }
        return filtered;
    }
}
  • List<Integer> evenNumbers = IntegerFilter.filter(numbers, n  n % 2 == 0); 요렇게 써주면 된다.
  • 범용성 있게 다양한 곳에서 사용할 수 있는 IntegerFilter 클래스를 만들었다.

필터5 - 제네릭 도입

제네릭을 사용하면 클래스 코드의 변경 없이 다양한 타입을 적용할 수 있다.

public class GenericFilter {

    public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
        List<T> result = new ArrayList<>();
        for (T num : list) {
            if (predicate.test(num)) {
                result.add(num);
            }
        }
        return result;
    }
}
  • List<Integer> numbersResult = GenericFilter.filter(numbers, n -> n % 2 == 0);
  • List<String> stringsResult = GenericFilter.filter(strings, s -> s.length() >= 2);
  • (GenericFilter.filter에 전달되는 매개변수 타입을 보고 타입 추론이 된다.)
  • 제네릭을 도입한 덕분에 Integer, String 같은 다양한 타입의 리스트에 필터링 기능을 사용할 수 있게 되었다. 모든 타입의 리스트를 람다 조건으로 필터링 할 수 있다.

맵 만들기1

  • 맵(map)은 대응, 변환을 의미하는 매핑(mapping)의 줄임말이다. 매핑은 어떤 것을 다른 것으로 변환하는 과정을 의미한다.
  • 프로그래밍에서는 각 요소를 다른 값으로 변환하는 작업을 매핑(mapping, map)이라 한다.
    • 어떤 하나의 데이터를 다른 데이터로 변환하는 작업
static List<Integer> mapStringToInteger(List<String> list) {
    List<Integer> numbers = new ArrayList<>();
    for (String s : list) {
        Integer value = Integer.valueOf(s);
        numbers.add(value);
    }
    return numbers;
}

static List<Integer> mapStringToLength(List<String> list) {
    List<Integer> numbers = new ArrayList<>();
    for (String s : list) {
        Integer value = s.length();
        numbers.add(value);
    }
    return numbers;
}


위 코드를 남다를 이용해서 개선해보자. 무언가를 전달받아서 적용한 값을 돌려준다 Function<T, R> 인터페이스

psvm {
    // 문자열을 숫자로 변환
    List<Integer> numbers = map(list, s -> Integer.valueOf(s));
    
    // 문자열의 길이
    List<Integer> lengths = map(list, s -> s.length());
}

static List<Integer> map(List<String> list, Function<String, Integer> mapper) {
    List<Integer> numbers = new ArrayList<>();
    for (String s : list) {
        numbers.add(mapper.apply(s));
    }
    return numbers;
}

맵 만들기2

필터를 개선한 것 처럼, Map도 별도의 유틸리티 클래스로 빼고, 제네릭을 도입해보자.

psvm {
	List<String> fruits = List.of("apple", "banana", "orange");
	
	// String -> String
    List<String> upperFruits = GenericMapper.map(fruits, s -> s.toUpperCase());

    // String -> Integer
    List<Integer> lengthFruits = GenericMapper.map(fruits, s -> s.length());

    // Integer -> String
    List<Integer> integers = List.of(1, 2, 3);
    List<String> starList = GenericMapper.map(integers, n -> "*".repeat(n));
}

public class GenericMapper {

    public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
        List<R> result = new ArrayList<>();
        for (T s : list) {
            result.add(mapper.apply(s));
        }
        return result;
    }
}

  • 제네릭 <T, R> 을 선언하고, String 으로 되어 있는 부분을 T 로, Integer 로 되어 있는 부분을 R 로 변경하면 된다.
    • T: 입력, R: 출력(반환)
  • String.repeat(int count) 는 자바 11에 추가된 메서드이다. 같은 문자를 count 수 만큼 붙여서 반환한다.
  • 제네릭을 도입한 덕분에 다양한 타입의 리스트의 값을 변환(매핑) 사용할 수 있게 되었다.
  • GenericMapper 는 제네릭을 사용할 수 있는 모든 타입의 리스트를 람다 조건으로 변환(매핑) 할 수 있다. 따라서 매우 유연한 매핑(변환) 기능을 제공한다.

필터와 맵 활용1

  • 리스트에 있는 값 중에 짝수만 남기고, 남은 짝수 값의 2배를 반환해라.
  • direct() 에 람다, 앞서 작성한 유틸리티를 사용하지 말고, for , if 등으로 코드를 직접 작성해라.
  • lambda() 에 앞서 작성한 필터와 맵 유틸리티를 사용해서 코드를 작성해라.
public class Ex1_Number {

    public static void main(String[] args) {
        // 짝수만 남기고, 남은 값의 2배를 반환
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        List<Integer> directResult = direct(numbers);
        System.out.println("directResult = " + directResult);

        List<Integer> lambdaResult = lambda(numbers);
        System.out.println("lambdaResult = " + lambdaResult);
    }

    static List<Integer> direct(List<Integer> numbers) {
        List<Integer> result = new ArrayList<>();
        for (Integer number : numbers) {
            if (number % 2 == 0) { // 짝수 필터링
                int numberX2 = number * 2;
                result.add(numberX2); // 2배로 변환하여 추가
            }
        }
        return result;
    }

    static List<Integer> lambda(List<Integer> numbers) {
        List<Integer> filteredList = GenericFilter.filter(numbers, n -> n % 2 == 0);
        List<Integer> mappedList = GenericMapper.map(filteredList, n -> n * 2);
        return mappedList;
    }
}

선언형 프로그래밍과 명령형 프로그래밍
direct() 와 lambda() 는 서로 다른 프로그래밍 스타일을 보여준다.

  • direct() 는 프로그램을 어떻게 수행해야 하는지 수행 절차를 명시한다.
    • 쉽게 이야기해서 개발자가 로직 하나하나를 어떻게 실행해야 하는지 명시한다.
    • 이런 프로그래밍 방식을 명령형 프로그래밍이라 한다.
    • 명령형 스타일은 익숙하고 직관적이나, 로직이 복잡해질수록 반복 코드가 많아질 수 있다.
  • lambda() 는 무엇을 수행해야 하는지 원하는 결과에 초점을 맞춘다.
    • 쉽게 이야기해서 특정 조건으로 필터하고, 변환하라고 선언하면, 구체적인 부분은 내부에서 수행된다.
    • 개발자는 필터하고 변환하는 것 즉 무엇을 해야 하는가에 초점을 맞춘다.
      • 예를 들어 실제 어떻게 for문과 if문 등을 사용해서 필터하고 변환할지를 개발자가 크게 신경쓰지 않는다.
    • 이런 프로그래밍 방식을 선언적 프로그래밍이라 한다.
    • 선언형 스타일은 무엇을 하고자 하는지가 명확히 드러난다. 따라서 코드 가독성과 유지보수가 쉬워진다.
      • 여기서는 필터하고 변환하는 것에만 초점을 맞춘다. 실제 어떻게 필터하고 변환할지는 해당 기능을 사용하는 입장에서는 신경쓰지 않는다.

명령형 vs 선언적 프로그래밍

명령형 프로그래밍 (Imperative Programming)

  • 정의: 프로그램이 어떻게(How) 수행되어야 하는지, 즉 수행 절차를 명시하는 방식이다.
  • 특징:
    • 단계별 실행: 프로그램의 각 단계를 명확하게 지정하고 순서대로 실행한다.
    • 상태 변화: 프로그램의 상태(변수 값 등)가 각 단계별로 어떻게 변화하는지 명시한다.
    • 낮은 추상화: 내부 구현을 직접 제어해야 하므로 추상화 수준이 낮다.
    • 예시: 전통적인 for 루프, while 루프 등을 명시적으로 사용하는 방식
    • 장점: 시스템의 상태와 흐름을 세밀하게 제어할 수 있다.

선언적 프로그래밍 (Declarative Programming)

  • 정의: 프로그램이 무엇(What)을 수행해야 하는지, 즉 원하는 결과를 명시하는 방식이다.
  • 특징:
    • 문제 해결에 집중: 어떻게(how) 문제를 해결할지보다 무엇을 원하는지에 초점을 맞춘다.
    • 코드 간결성: 간결하고 읽기 쉬운 코드를 작성할 수 있다.
    • 높은 추상화: 내부 구현을 숨기고 원하는 결과에 집중할 수 있도록 추상화 수준을 높인다.
    • 예시: filter , map 등 람다의 고차 함수를 활용, HTML, SQL 등
  • 장점: 코드가 간결하고, 의도가 명확하며, 유지보수가 쉬운 경우가 많다.

필터와 맵 활용2

public class Student {
    private String name;
    private int score;
	// Getter, Setter, toString()
}

점수가 80점 이상인 학생의 이름을 추출하기

private static List<String> direct(List<Student> students) {
    List<String> highScoreNames = new ArrayList<>();
    for (Student student : students) {
        if (student.getScore() >= 80) {
            String name = student.getName();
            highScoreNames.add(name);
        }
    }
    return highScoreNames;
}

// 1. filter: 점수가 80점 이상인 학생들만 필터
// 2. map: 필터링된 학생 객체에서 이름만 추출
// 선언적 스타일
private static List<String> lambda(List<Student> students) {
    List<Student> filtered = GenericFilter.filter(students, s -> s.getScore() >= 80);
    List<String> mapped = GenericMapper.map(filtered, s -> s.getName());
    return mapped;
}

스트림 만들기1

기존 코드의 번거로움: 필터와 맵 기능을 별도의 유틸리티에서 각각 따로 제공했고, 이로 인해 두 기능을 함께 사용할 때, 필터된 결과를 다시 맵에 전달하는 번거로운 과정을 거쳐야했다.

List<Student> filtered = GenericFilter.filter(students, s -> s.getScore() >= 80);
List<String> mapped = GenericMapper.map(filtered, s -> s.getName());

리스트를 매번 전달하지 않고도 여러 번 필터, 맵 할 수 있는 방법을 생각해보자.


스트림1

필터와 맵을 사용할 때를 떠올려보면 데이터들이 흘러가면서 필터되고, 매핑된다. 그래서 마치 데이터가 물 흐르듯이 흘러간다는 느낌을 받았을 것이다. 이렇듯 데이터가 흘러가면서 필터도 되고, 매핑도 되는 클래스의 이름을 스트림(Stream)이라고 짓자.


public class MyStreamV1 {
    private List<Integer> internalList;

    public MyStreamV1(List<Integer> internalList) {
        this.internalList = internalList;
    }

    public MyStreamV1 filter(Predicate<Integer> predicate) {
        List<Integer> filtered = new ArrayList<>();
        for (Integer element : internalList) {
            if (predicate.test(element)) {
                filtered.add(element);
            }
        }
        return new MyStreamV1(filtered);
    }

    public MyStreamV1 map(Function<Integer, Integer> mapper) {
        List<Integer> mapped = new ArrayList<>();
        for (Integer element : internalList) {
            mapped.add(mapper.apply(element));
        }
        return new MyStreamV1(mapped);
    }

    public List<Integer> toList() {
        return internalList;
    }
}

  • 예제에서 스트림은 자신의 데이터 리스트를 가진다. 여기서는 쉽게 설명하기 위해 Integer 를 사용했다.
  • 스트림은 자신의 데이터를 필터(filter)하거나 매핑(map)해서 새로운 스트림을 만들 수 있다.
    • 예를 들어서 필터를 하면 필터된 데이터를 기반으로 새로운 스트림이 만들어진다.
    • 예를 들어서 매핑을 하면 매핑된 데이터를 기반으로 새로운 스트림이 만들어진다.
  • 스트림은 내부의 데이터 리스트를 toList() 로 반환할 수 있다.
  • filter() , map() 에 앞서 개발한 GenericFilter , GenericMapper 의 기능을 사용해도 되지만, 여기서는 직접 작성하겠다.

위 클래스를 이용해서 개발해보자

public class MyStreamV1Main {

    public static void main(String[] args) {
        // 짝수만 남기고, 남은 값의 2배를 반환
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        returnValue(numbers);
    }

    static void returnValue(List<Integer> numbers) {
        MyStreamV1 stream = new MyStreamV1(numbers);
        MyStreamV1 filteredStream = stream.filter(n -> n % 2 == 0);
        MyStreamV1 mappedStream = filteredStream.map(n -> n * 2);
        List<Integer> result = mappedStream.toList();
        System.out.println("result1 = " + result);
    }
}

위 코드를, 메서드 체이닝을 이용하여 개선해보자. MyStreamV1은 filter, map을 호출할 때 자기 자신의 타입을 반환하므로, 메서드를 연결하여 호출하는 메서드 체이닝을 사용할 수 있다.


// 추가
    static void methodChain(List<Integer> numbers) {
        List<Integer> result = new MyStreamV1(numbers)
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .toList();
        System.out.println("result2 = " + result);
    }
  • 덕분에 지저분한 중간 변수들을 제거하고, 깔끔하게 필터와 맵을 사용할 수 있게 되었다.

스트림 만들기2: 정적 팩토리(static factory) 메서드를 추가하자.

// static factory 추가
public class MyStreamV2 {
    private List<Integer> internalList;

    private MyStreamV2(List<Integer> internalList) {
        this.internalList = internalList;
    }

    // static factory
    public static MyStreamV2 of(List<Integer> internalList) {
        return new MyStreamV2(internalList);
    }

	// 기존과 동일
}
  • 기존 생성자를 외부에서 사용하지 못하도록 private 으로 설정했다.
  • 이제 MyStreamV2 를 생성하려면 of() 메서드를 사용해야 한다.

public class MyStreamV2Main {

    public static void main(String[] args) {
        // 짝수만 남기고, 남은 값의 2배를 반환
        List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

		// 정적 팩토리 메서드로 스트림 생성
        List<Integer> result = MyStreamV2.of(numbers)
                .filter(n -> n % 2 == 0)
                .map(n -> n * 2)
                .toList();
        System.out.println("result = " + result);
    }
}

정적 팩토리 메서드 - static factory method

  • 정적 팩토리 메서드는 객체 생성을 담당하는 static 메서드로, 생성자(constructor) 대신 인스턴스를 생성하고 반환하는 역할을 한다. 즉, 일반적인 생성자(Constructor) 대신에 클래스의 인스턴스를 생성하고 초기화하는 로직을 캡슐화하여 제공하는 정적(static) 메서드이다.
    • 정적 메서드: 클래스 레벨에서 호출되며, 인스턴스 생성 없이 접근할 수 있다.
    • 객체 반환: 내부에서 생성한 객체(또는 이미 존재하는 객체)를 반환한다.
      • if (이미 존재하는 객체가 있거나 재활용 가능하다) return 이미 존재하는 객체;
    • 생성자 대체: 생성자와 달리 메서드 이름을 명시할 수 있어, 생성 과정의 목적이나 특징을 명확하게 표현할 수 있다.
    • 유연한 구현: 객체 생성 과정에서 캐싱, 객체 재활용, 하위 타입 객체 반환 등 다양한 로직을 적용할 수 있다.
  • 생성자는 이름을 부여할 수 없다. 반면에 정적 팩토리 메서드는 의미있는 이름을 부여할 수 있어, 가독성이 더 좋아지는 장점이 있다.
  • 정리하자면, 생성자를 private로 만들고, 생성자 감싸서 구현하고, 이렇게 함으로써 무엇을 생성하는지 정적 메서드 일므을 통해 명확하게 드러낼 수 있고, 또 생성자 호출을 위임하면서 객체 생성 시 다양한 로직을 추가할 수 있다.
  • 참고로 인자들을 받아 간단하게 객체를 생성할 때는 주로 of(...) 라는 이름을 사용한다.

예시) 회원 등급별 생성자가 다른 경우

// 일반 회원 가입시 이름, 나이, 등급
new Member("회원1", 20, NORMAL);
// VIP 회원 가입시 이름, 나이, 등급, 선물 주소지
new Member("회원1", 20, VIP, "선물 주소지");

// 일반 회원 가입시 인자 2개
Member.createNormal("회원1", 20)
// VIP 회원 가입시 인자 3개
Member.createVip("회원2", 20, "선물 주소지")
  • 정적 팩토리를 사용하면 메서드 이름으로 명확하게 회원과 각 회원에 따른 인자를 구분할 수 있다.
  • 정적 팩토리 메서드의 대표적인 예시가 Integer.valueof()이다.
    • 예) Integer.valueOf() : -128 ~ 127 범위는 내부에 가지고 있는 Integer 객체를 반환한다.
    • 이런식으로 추가로 객체를 생성하기 전에 이미 있는 객체를 찾아서 반환하는 로직을 넣는 것도 가능하다.
  • 정적 팩토리 메서드를 사용하면 보통 가독성이 더 좋아진다. 하지만 반대로 이야기하면 이름도 부여해야 하고, 준비해야 하는 코드도 더 많다.

스트림 만들기3: 제네릭으로 개선

  • MyStreamV3 은 내부에 List<T> internalList 를 가진다. 따라서 MyStreamV3<T> 로 선언한다.
  • map() 은 T 를 다른 타입인 R 로 반환한다. R 을 사용하는 곳은 map 메서드 하나이므로 map 메서드 앞에 추가로 제네릭 <R> 을 선언한다.
// 점수가 80점 이상인 학생의 이름을 추출해라.
static List<String> ex1(List<Student> students) {
    return MyStreamV3.of(students)
            .filter(s -> s.getScore() >= 80)
            .map(s -> s.getName())
            .toList();
}

// 점수가 80점 이상이면서, 이름이 5글자인 학생의 이름을 대문자로 추출해라.
static List<String> ex2(List<Student> students) {
    return MyStreamV3.of(students)
            .filter(s -> s.getScore() >= 80)
            .filter(s -> s.getName().length() == 5)
            .map(s -> s.getName())
            .map(name -> name.toUpperCase())
            .toList();
}

스트림 만들기4: 스트림의 최종 결과까지 스트림에서 처리하도록 개선

List<String> result = MyStreamV3.of(students)
        .filter(s -> s.getScore() >= 80)
        .map(s -> s.getName())
        .toList();

// 외부 반복
for (String s : result) {
    System.out.println("name: " + s);
}

이 경우 결과 리스트를 for 문을 통해 하나씩 반복하며 출력하면 된다.
filter , map 등도 스트림 안에서 데이터 리스트를 하나씩 처리(함수를 적용)하는 기능이다. 따라서 최종 결과를 출력하는 일도 스트림 안에서 처리할 수 있을 것 같다. 리스트를 반환하지 않는 구조 Consumer를 스트림 안에 넣어보자.


기존 MyStreamV3 에 스트림에 forEach() 라는 메서드를 추가하자.

    // 추가
    public void forEach(Consumer<T> consumer) {
        for (T element : internalList) {
            consumer.accept(element);
        }
    }

MyStreamV3.of(students) // V4 사용 가정
        .filter(s -> s.getScore() >= 80)
        .map(s -> s.getName())
        .forEach(name -> System.out.println("name: " + name));
  • forEach() 에 함수를 전달해서 각각의 데이터 리스트를 출력하도록 했다. 이것은 최종 데이터이므로 반환할 것은 없고, 각각의 데이터를 받아서 소비만 하면 된다. 따라서 Consumer 를 사용했다.

내부 반복 vs 외부 반복

스트림을 사용하기 전에 일반적인 반복 방식은 for 문, while 문과 같은 반복문을 직접 사용해서 데이터를 순회하는 외부 반복(External Iteration) 방식이었다. 예를 들어 다음 코드처럼 개발자가 직접 각 요소를 반복하며 처리한다.
외부 반복

List<String> result = ...
for (String s : result) {
    System.out.println("name: " + s);
}

스트림에서 제공하는 forEach() 메서드로 데이터를 처리하는 방식은 내부 반복(Internal Iteration) 이라고 부른다. 외부 반복처럼 직접 반복 제어문을 작성하지 않고, 반복 처리를 스트림 내부에 위임하는 방식이다. 스트림 내부에서 요소들을 순회하고, 우리는 처리 로직(람다)만 정의해주면 된다.


내부 반복

MyStreamV3.of(students) // V4 사용 가정
        .filter(s -> s.getScore() >= 80)
        .map(s -> s.getName())
        .forEach(s -> System.out.println("name: " + s)); // 내부 반복
  • 반복 제어를 스트림이 대신 수행하므로, 사용자는 반복 로직을 신경 쓸 필요가 없다.
  • 코드가 훨씬 간결해지며, 선언형 프로그래밍 스타일을 적용할 수 있다.

내부 반복 vs 외부 반복 선택

외부 반복을 선택하는 것이 더 나은 경우

  • 단순히 한두 줄 수행만 필요한 경우
    • 단순히 순회해서 필터링, 맵 이런 기능 안쓰고, 출력만 하는 경우.
  • 반복 제어에 대한 복잡하고 세밀한 조정이 필요할 경우
    • 예를 들어 반복 중에 특정 조건을 만나면 바로 반복을 멈추거나, 일부만 건너뛰고 싶다면 break , continue 등을 사용하는 외부 반복이 단순하다.

내부 반복을 선택하는 것이 더 나은 경우

  • 연속적인 필터링, 매핑, 집계가 필요하면 스트림을 이용한 내부 반복이 선언적이고 직관적이다.


Ref) 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 강의 | 김영한 - 인프런

'Java > Modern Java(8~)' 카테고리의 다른 글

[Java] 62. 메서드 참조  (0) 2025.07.04
[Java] 61. 람다 vs 익명 클래스  (0) 2025.07.04
[Java] 59. 함수형 인터페이스  (0) 2025.07.04
[Java] 58. 람다  (0) 2025.07.04
[Java] 57. 람다가 필요한 이유  (0) 2025.07.04
'Java/Modern Java(8~)' 카테고리의 다른 글
  • [Java] 62. 메서드 참조
  • [Java] 61. 람다 vs 익명 클래스
  • [Java] 59. 함수형 인터페이스
  • [Java] 58. 람다
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (457)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Java] 60. 람다 활용
상단으로

티스토리툴바