Overview

Java 8 Stream 에는 count() 라는 종결 함수가 있습니다.

현재 Stream 의 원소 갯수를 카운트 해서 long 타입으로 리턴합니다.


count 의 중간 연산으로 peek 사용

public class NotePad {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 5)
              .peek(System.out::println)
              .count();
    }
}

위 코드는 어떻게 동작할까요?

아무것도 나오지 않습니다.

원인을 몰라서 구글링 해봤더니 Java 9 Stream API Reference 에 다음과 같은 글이 있었습니다.


The number of elements covered by the stream source, a List, is known and the intermediate operation, peek, does not inject into or remove elements from the stream (as may be the case for flatMap or filter operations). Thus the count is the size of the List and there is no need to execute the pipeline and, as a side-effect, print out the list elements.


요약하자면 count() 라는 종결함수는 Stream 의 갯수를 세기 때문에 효율을 위해 중간 연산을 생략하기도 한다는 뜻입니다.

그래서 중간 연산을 강제로 실행시키고 싶다면 filterflatMap 과 같이 Stream 요소의 갯수를 변화시킬 수 있는 중간 연산을 추가하면 됩니다.


filter 로 강제 출력

public class NotePad {
    public static void main(String[] args) {
        Stream.of(1, 2, 3, 4, 5)
              .filter(e -> e > 0)
              .peek(System.out::println)
              .count();
    }
}

filter 를 추가하면 peek 도 정상적으로 동작합니다.


1
2
3
4
5

Reference

Overview

함수형 인터페이스란 1 개의 추상 메소드를 갖는 인터페이스를 말합니다.

Java8 부터 인터페이스는 기본 구현체를 포함한 디폴트 메서드 (default method) 를 포함할 수 있습니다.

여러 개의 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스입니다.

자바의 람다 표현식은 함수형 인터페이스로만 사용 가능합니다.


1. Functional Interface

함수형 인터페이스는 위에서도 설명했듯이 추상 메서드가 오직 하나인 인터페이스를 의미합니다.

추상 메서드가 하나라는 뜻은 default method 또는 static method 는 여러 개 존재해도 상관 없다는 뜻입니다.

그리고 @FunctionalInterface 어노테이션을 사용하는데, 이 어노테이션은 해당 인터페이스가 함수형 인터페이스 조건에 맞는지 검사해줍니다.

@FunctionalInterface 어노테이션이 없어도 함수형 인터페이스로 동작하고 사용하는 데 문제는 없지만, 인터페이스 검증과 유지보수를 위해 붙여주는 게 좋습니다.


1.1. Functional Interface 만들기

@FunctionalInterface
interface CustomInterface<T> {
    // abstract method 오직 하나
    T myCall();

    // default method 는 존재해도 상관없음
    default void printDefault() {
        System.out.println("Hello Default");
    }

    // static method 는 존재해도 상관없음
    static void printStatic() {
        System.out.println("Hello Static");
    }
}

위 인터페이스는 함수형 인터페이스입니다.

default method, static method 를 넣어도 문제 없습니다.

어차피 함수형 인터페이스 형식에 맞지 않는다면 @FunctionalInterface 이 다음 에러를 띄워줍니다.

Multiple non-overriding abstract methods found in interface com.practice.notepad.CustomFunctionalInterface


1.2. 실제 사용

CustomInterface<String> customInterface = () -> "Hello Custom";

// abstract method
String s = customInterface.myCall();
System.out.println(s);

// default method
customInterface.printDefault();

// static method
CustomFunctionalInterface.printStatic();

함수형 인터페이스라서 람다식으로 표현할 수 있습니다.

String 타입을 래핑했기 때문에 myCall()String 타입을 리턴합니다.

마찬가지로 default method, static method 도 그대로 사용할 수 있습니다.

위 코드를 실행한 결과값은 다음과 같습니다.

Hello Custom
Hello Default
Hello Static

2. Java 에서 기본적으로 제공하는 Functional Interfaces

매번 함수형 인터페이스를 직접 만들어서 사용하는 건 번거로운 일입니다.

그래서 Java 에서는 기본적으로 많이 사용되는 함수형 인터페이스를 제공합니다.

기본적으로 제공되는 것만 사용해도 웬만한 람다식은 다 만들 수 있기 때문에 개발자가 직접 함수형 인터페이스를 만드는 경우는 거의 없습니다.


함수형 인터페이스 Descripter Method
Predicate T -> boolean boolean test(T t)
Consumer T -> void void accept(T t)
Supplier () -> T T get()
Function<T, R> T -> R R apply(T t)
Comparator (T, T) -> int int compare(T o1, T o2)
Runnable () -> void void run()
Callable () -> T V call()

2.1. Predicate

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate 는 인자 하나를 받아서 boolean 타입을 리턴합니다.

람다식으로는 T -> boolean 로 표현합니다.


2.2. Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer 는 인자 하나를 받고 아무것도 리턴하지 않습니다.

람다식으로는 T -> void 로 표현합니다.

소비자라는 이름에 걸맞게 무언가 (인자) 를 받아서 소비만 하고 끝낸다고 생각하면 됩니다.


2.3. Supplier

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

Supplier 는 아무런 인자를 받지 않고 T 타입의 객체를 리턴합니다.

람다식으로는 () -> T 로 표현합니다.

공급자라는 이름처럼 아무것도 받지 않고 특정 객체를 리턴합니다.


2.4. Function

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function 은 T 타입 인자를 받아서 R 타입을 리턴합니다.

람다식으로는 T -> R 로 표현합니다.

수학식에서의 함수처럼 특정 값을 받아서 다른 값으로 반환해줍니다.

T 와 R 은 같은 타입을 사용할 수도 있습니다.


2.5. Comparator

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

Comparator 은 T 타입 인자 두개를 받아서 int 타입을 리턴합니다.

람다식으로는 (T, T) -> int 로 표현합니다.


2.6. Runnable

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable 은 아무런 객체를 받지 않고 리턴도 하지 않습니다.

람다식으로는 () -> void 로 표현합니다.

Runnable 이라는 이름에 맞게 "실행 가능한" 이라는 뜻을 나타내며 이름 그대로 실행만 할 수 있다고 생각하면 됩니다.


2.7. Callable

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable 은 아무런 인자를 받지 않고 T 타입 객체를 리턴합니다.

람다식으로는 () -> T 로 표현합니다.

Runnable 과 비슷하게 Callable 은 "호출 가능한" 이라고 생각하면 좀 더 와닿습니다.


Supplier vs Callable

SupplierCallable 은 완전히 동일합니다.

아무런 인자도 받지 않고 특정 타입을 리턴해줍니다.

둘이 무슨 차이가 있을까..?

사실 그냥 차이가 없다고 생각하시면 됩니다.

단지 CallableRunnable 과 함께 병렬 처리를 위해 등장했던 개념으로서 ExecutorService.submit 같은 함수는 인자로 Callable 을 받습니다.


3. 두 개의 인자를 받는 Bi 인터페이스

특정 인자를 받는 Predicate, Consumer, Function 등은 두 개 이상의 타입을 받을 수 있는 인터페이스가 존재합니다.


함수형 인터페이스 Descripter Method
BiPredicate (T, U) -> boolean boolean test(T t, U u)
BiConsumer (T, U) -> void void accept(T t, U u)
BiFunction (T, U) -> R R apply(T t, U u)

4. 기본형 특화 인터페이스

지금까지 확인한 함수형 인터페이스를 제네릭 함수형 인터페이스라고 합니다.

자바의 모든 형식은 참조형 또는 기본형입니다.

  • 참조형 (Reference Type) : Byte, Integer, Object, List
  • 기본형 (Primitive Type) : int, double, byte, char

Consumer<T> 에서 T 는 참조형만 사용 가능합니다.

Java 에서는 기본형과 참조형을 서로 변환해주는 박싱, 언박싱 기능을 제공합니다.

  • 박싱 (Boxing) : 기본형 -> 참조형 (int -> Integer)
  • 언박싱 (Unboxing) : 참조형 -> 기본형 (Integer -> int)

게다가 개발자가 박싱, 언박싱을 신경쓰지 않고 개발할 수 있게 자동으로 변환해주는 오토박싱 (Autoboxing) 이라는 기능도 제공합니다.

예를 들어 List<Integer> list 에서 list.add(3) 처럼 기본형을 바로 넣어도 사용 가능한 것도 오토박싱 덕분입니다.

하지만 이런 변환 과정은 비용이 소모되기 때문에, 함수형 인터페이스에서는 이런 오토박싱 동작을 피할 수 있도록 기본형 특화 함수형 인터페이스 를 제공합니다.

IntPredicate, LongPredicate 등등 특정 타입만 받는 것이 확실하다면 기본형 특화 인터페이스를 사용하는 것이 더 좋습니다.

아래에서 소개하는 인터페이스 외에 UnaryOperatorBi 인터페이스에도 기본형 특화를 제공합니다.


4.1 Predicate (T -> boolean)

기본형을 받은 후 boolean 리턴

  • IntPredicate
  • LongPredicate
  • DoublePredicate

4.2. Consumer (T -> void)

기본형을 받은 후 소비

  • IntConsumer
  • LongConsumer
  • DoubleConsumer

4.3. Function (T -> R)

기본형을 받아서 기본형 리턴

  • IntToDoubleFunction
  • IntToLongFunction
  • LongToDoubleFunction
  • LongToIntFunction
  • DoubleToIntFunction
  • DoubleToLongFunction

기본형을 받아서 R 타입 리턴

  • IntFunction<R>
  • LongFunction<R>
  • DoubleFunction<R>

T 타입 받아서 기본형 리턴

  • ToIntFunction<T>
  • ToDoubleFunction<T>
  • ToLongFunction<T>

4.4. Supplier (() -> T)

아무것도 받지 않고 기본형 리턴

  • BooleanSupplier
  • IntSupplier
  • LongSupplier
  • DoubleSupplier

Conclusion

Java 8 에서 람다에 활용 가능한 함수형 인터페이스를 제공하고 있습니다.

직접 만들어서 쓸 수도 있지만 이미 제공하는 인터페이스로도 대부분 처리 가능하므로 어떤 게 있는지 잘 파악해서 활용해야 합니다.


Reference

Overview

Java 설치 방법과 여러 개의 버전을 사용할 때 어떤 식으로 변경하는지 알아봅시다.

여러 Java (JDK) 버전을 사용하는 경우 원하는 버전을 기본으로 설정할 수 있습니다.


1. Java (OpenJDK) 설치

1.1. adoptopenjdk/openjdk 저장소 추가

$ brew tap adoptopenjdk/openjdk

1.2. cask 가 없다면 설치

$ brew install cask

1.3. OpenJDK 8 과 11 을 설치

$ brew install --cask adoptopenjdk8
$ brew install --cask adoptopenjdk11
  • 하나의 버전만 사용한다면 하나만 설치하면 됩니다.

1.4. 설치 여부 확인

$ java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)
  • Java 버전 확인을 확인해서 잘 나오면 설치가 완료된 겁니다.

2. Java (JDK) 버전 변경

2.1. 설치된 JDK 버전 확인

$ /usr/libexec/java_home -V

Matching Java Virtual Machines (2):
    11.0.11 (x86_64) "AdoptOpenJDK" - "AdoptOpenJDK 11" /Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
    1.8.0_292 (x86_64) "AdoptOpenJDK" - "AdoptOpenJDK 8" /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
/Library/Java/JavaVirtualMachines/adoptopenjdk-11.jdk/Contents/Home
  • 저는 현재 JDK 11 과 JDK 8 버전이 설치되어 있습니다.

2.2. JDK 버전 변경

# 1.8 버전으로 변경
$ export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)

# 11 버전으로 변경
$ export JAVA_HOME=$(/usr/libexec/java_home -v 11)

2.3. JDK 변경 확인

$ java -version
openjdk version "1.8.0_292"
OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)

$ echo $JAVA_HOME
/Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
  • $JAVA_HOME 도 변경된 것을 확인 할 수 있습니다.

3. 기본 Java 버전 적용

프로젝트에 따라서 여러 개의 Java 버전을 사용해야 하는 경우가 있습니다.

자주 사용하는 Java 버전을 기본으로 세팅하고 싶다면 bash 를 사용하는 경우 ~/.bash_profile, zsh 를 사용하는 경우 ~/.zshrc 파일 가장 하단에 아래 코드를 한줄 추가해주면 됩니다.

JDK 버전 변경 때 사용했던 명령어와 동일합니다.

# 1.8 버전을 기본으로
$ export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)

# 11 버전을 기본으로
$ export JAVA_HOME=$(/usr/libexec/java_home -v 11)

Collection 의 합을 구하는 방법

Collection 의 합을 구하는 방법은 reducesum 두 가지가 존재합니다.

단, Stream 에서 sum() 을 사용하려면 IntStream, LongStream, DoubleStream 와 같은 기본형 (Primitive Type) 특화 스트림을 사용해야 합니다.

그래서 보통 mapToInt, mapToLong, mapToDouble 같은 메소드로 스트림을 변환시키고 사용합니다.

 

reduce

reduce(초기값, 연산) 형식으로 사용합니다.

초기값부터 시작하여 각 원소를 차례대로 순회하며 연산을 수행합니다.

이전 연산의 결과를 다음 초기값으로 넘기면서 연산의 결과를 누적해서 총 결과값을 구하는 메서드입니다.

int sum = Stream.of(1, 2, 3).reduce(0, Integer::sum);

 

sum

Stream 의 총합을 구하는 메서드입니다.

기본형 특화 스트림에서만 사용 가능합니다.

int sum = Stream.of(1, 2, 3).mapToInt(e -> e).sum();

 

그렇다면 합을 구할 때 어떤걸 사용할까?

처음 생각 할 때는 reduce 로 한번에 하는게 Stream 처리가 적어서 더 빠를거라고 생각했습니다.

가독성도 크게 차이 안납니다.

그런데 실제로 테스트 해보니 결과는 달랐습니다.

 

Test Code

public static void main(String[] args) {
    Map<String, Integer> hashmap = new HashMap<>();

    for (int i = 0; i < 10000000; i++) {
        hashmap.put(i + "", i);
    }

    // for-loop 시간측정
    long start1 = System.currentTimeMillis();
    int sum1 = 0;
    for (String key : hashmap.keySet()) {
        sum1 += hashmap.get(key);
    }
    long end1 = System.currentTimeMillis();

    // stream mapToInt 시간측정
    long start2 = System.currentTimeMillis();
    int sum2 = hashmap.values().stream().reduce(0, Integer::sum);
    long end2 = System.currentTimeMillis();

    // stream reduce 시간측정
    long start3 = System.currentTimeMillis();
    int sum3 = hashmap.values().stream().mapToInt(i -> i).sum();
    long end3 = System.currentTimeMillis();

    System.out.println("for-loop: " + (end1 - start1));
    System.out.println("int reduce: " + (end2 - start2));
    System.out.println("mapToInt: " + (end3 - start3));
}

Result

for-loop: 303
reduce: 368
mapToInt: 310

 

원인 & 결론

reduce 에는 박싱, 언박싱 비용이 들어갑니다.

내부적으로 합계를 계산하기 위해 Integerint 형으로 언박싱 하고 다시 int -> Integer 로 박싱하는 과정이 숨겨져 있어서 시간이 더 오래 걸립니다.

그래서 reduce 의 성능이 더 느리니 IntStream 에서 제공하는 메서드가 있는 경우에는 해당 메서드를 사용하는 게 유용합니다.

 

Reference

'Language > Java' 카테고리의 다른 글

Java Stream count() 의 비밀  (0) 2021.04.18
Java 8 함수형 인터페이스 (Functional Interface)  (1) 2021.04.10
Mac OS Java (OpenJDK) 설치 및 버전 변경  (2) 2021.03.08
[Java] Static  (0) 2020.09.19
[Java] Map, HashMap, LinkedHashMap  (0) 2020.04.25

1. Collection Sort

Kotlin 에서는 Collection 을 정렬하기 위한 여러가지 유틸리티들을 제공합니다.


1.1. Sort, Sorted

가장 쉬운 방법은 sort 메소드를 호출하는 겁니다.

기본적으로 오름차순으로 정렬합니다.

val list = mutableListOf(1, 2, 7, 6, 5, 6)
list.sort()
println(list)  // [1, 2, 5, 6, 6, 7]

sort 메소드는 해당 Collection 의 원소 위치가 변경됩니다.

기존 Collection 은 그대로 둔 채 새로운 Collection 으로 받길 원한다면 sorted 메소드를 사용해야 합니다.

sorted 메소드를 사용하면 기존 Collection 은 변하지 않습니다.

val list = mutableListOf(1, 2, 7, 6, 5, 6)
val sorted = list.sorted()
println(sorted)  // [1, 2, 5, 6, 6, 7]
println(list)    // [1, 2, 7, 6, 5, 6] (sorted 를 사용했기 때문에 변하지 않음)

내림차순으로 정렬하고 싶다면 sortByDescending 를 사용하거나 reverse 메소드를 사용하면 됩니다.

마찬가지로 sortedByDescending 를 사용하면 원래 Collection 의 변경 없이 내림차순으로 정렬된 값을 구할 수 있습니다.

// 1. sortByDescending 로 내림차순 정렬
list.sortByDescending { it }

val sorted = list.sortedByDescending { it }

// 2. reverse 사용해서 정렬 후 뒤집기
list.sort()
list.reverse()

val sorted = list.sorted().reversed()

1.2. SortBy

만약 Object 의 특정 Property 들을 기준으로 정렬하고 싶다면 sortBy 메소드를 사용하면 됩니다.

sortBy 메소드는 Object 를 받아서 Property 를 반환하는 Lamdba 식을 파라미터로 받습니다.

val list = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
list.sortBy { it.second }
println(list)  // [(1, a), (2, b), (7, c), (5, c), (6, d), (6, e)]

sort 와 마찬가지로 기존 Collection 의 변경 없이 정렬된 값을 받고 싶다면 sortedBy 를 사용하면 됩니다.

그리고 내림차순을 지원하는 sortByDescending 도 있습니다.


1.3. SortWith

sortWith 메소드를 사용하면 여러 가지 조건을 섞어서 정렬할 수 있습니다.

sortWith 메소드는 Comparator 를 파라미터로 받습니다.

(Kotlin 에서 Comparator 를 생성하는 여러가지 방법은 다음 챕터에서 다룹니다)

val list = mutableListOf(1 to "a", 2 to "b", 7 to "c", 6 to "d", 5 to "c", 6 to "e")
list.sortWith(compareBy({it.second}, {it.first}))
println(list)  // [(1, a), (2, b), (5, c), (7, c), (6, d), (6, e)]

위 Collection 은 it.second(문자) 로 먼저 정렬된 후에 it.first(숫자) 로 정렬됩니다.

그리고 역시 sortedWith 메소드가 존재하며, 역순으로 정렬할때는 reverse 를 사용하거나 Comparator 를 반대로 수정하면 됩니다.


2. Comparison

Kotlin 은 Comparator 를 만들기 위해 kotlin.comparisons 라는 유용한 패키지를 제공합니다.

이 챕터에서는 아래 컨텐츠를 다룹니다.

  • Comparator creation
  • Handling of null values
  • Comparator rules extension

2.1. Comparator Creation

Kotlin 은 Comparator 를 생성하는 여러 팩토리 메서드를 제공합니다.


2.1.1. naturalOrder

가장 간단한 생성 메서드는 naturalOrder() 입니다.

아무런 파라미터를 필요로 하지 않으며 오름차순을 기본으로 합니다.

val ascComparator = naturalOrder<Long>()

2.1.2. compareBy

여러 개의 속성을 사용하고 싶다면 compareBy 메소드를 사용하면 됩니다.

파라미터로는 Comparable 를 리턴하는 정렬 규칙을 여러 개 사용할 수 있습니다.

그럼 넘겨진 규칙들은 순차적으로 호출 되며 원소들을 정렬합니다.

만약 먼저 나온 규칙에서 원소의 우열이 가려져 정렬 처리가 되었다면 뒤의 규칙들은 확인하지 않습니다.

val complexComparator = compareBy<Pair<Int, String?>>({it.first}, {it.second})

위 코드에서 it.first 값을 사용해 먼저 비교를 하고 값이 같은 경우에만 it.second 비교까지 이루어집니다.


2.1.3. Comparator

간단하게 new Comparator 를 선언해서 만들 수도 있습니다.

자바와 마찬가지로 두 원소에 대한 비교 조건을 넣어줘야 합니다.

val caomparator = Comparator<Int> { a, b -> a.compareTo(b) }

2.2. Handling of null Values

정렬하려는 Collection 이 null 값을 갖고 있을 수도 있습니다.

nullsFirst 또는 nullsLast 와 함께 Comparator 를 사용하면 null 값을 가장 처음 또는 가장 마지막에 위치하도록 설정할 수 있습니다.

val list = mutableListOf(4, null, 1, -2, 3)

list.sortWith(nullsFirst())  // [null, -2, 1, 3, 4]

list.sortWith(nullsLast())  // [-2, 1, 3, 4, null]

list.sortWith(nullsFirst(reverseOrder()))  // [null, 4, 3, 1, -2]

list.sortWith(nullsLast(compareBy { it }))  // [-2, 1, 3, 4, null]

2.3. Comparator Rules Extension

Comparator 오브젝트는 추가적인 정렬 규칙과 혼합되거나 확장할 수 있습니다.

kotlin.comparable 패키지에 있는 then 키워드를 활용하면 됩니다.

첫 번째 비교의 결과가 동일할 때만 두번째 비교가 이루어집니다.

val students = mutableListOf(21 to "Helen", 21 to "Tom", 20 to "Jim")

val ageComparator = compareBy<Pair<Int, String?>> {it.first}
val ageAndNameComparator = ageComparator.thenByDescending {it.second}

// [(20, Jim), (21, Tom), (21, Helen)]
println(students.sortedWith(ageAndNameComparator))

위 코드는 나이가 어린 순으로 먼저 정렬하고 나이가 같으면 이름을 알파벳 역순으로 정렬합니다.


Reference

'Language > Kotlin' 카테고리의 다른 글

Kotlin Collections 와 Sequences 의 차이점 (feat. Java Stream)  (1) 2022.01.27
Kotlin Enum  (0) 2021.10.06
[Kotlin] Swap  (0) 2020.11.11
[Kotlin] For 문  (0) 2020.11.11
[Kotlin] String - drop, dropLast, dropWhile, dropLastWhile  (0) 2020.09.25

Swap

Python 만큼은 아니지만 Kotlin 에서도 문법을 활용하여 값의 Swap 을 쉽게 짤 수 있다.

var a = 1
var b = 2

a = b.also { b = a }

println(a) // print 2
println(b) // print 1

For

Kotlin 에서의 for 문은 자바와 마찬가지로 iterator 를 사용합니다.

따라서 iterator(), next(), hasNext() 을 제공하는 모든 컬렉션에서 for 문을 사용할 수 있습니다.

기본 for 문은 아래와 같습니다.

for (item in collection) {
    print(item)
}

특정 범위 (range) 를 지정해서 사용할 수도 있습니다.

.. 을 사용하면 마지막 숫자까지 포함하until 을 사용하면 마지막 숫자는 포함하지 않습니다.

// 1, 2, 3
for (i in 1..3) {
    println(i)
}

// 1, 2
for (i in 1 until 3) {
    println(i)
}

downTo 를 이용하면 감소하는 for 문을 만들 수 있고 step 을 사용하면 증가되는 양을 수정할 수 있습니다.

// 6, 4, 2, 0
for (i in 6 downTo 0 step 2) {
    println(i)
}

배열을 순회할 때는 index 만 뽑아내거나 (index, value) 형태로 순회 가능합니다.

for (i in array.indices) {
    println(array[i])
}
for ((index, value) in array.withIndex()) {
    println("the element at $index is $value")
}

컬렉션 타입에서는 forEach, forEachIndexed 를 사용하여 람다식으로 표현할 수 있습니다.

// 1, 2, 3
listOf(1, 2, 3).forEach { println(it) }

/**
 * index: 0, value: 4
 * index: 1, value: 5
 * index: 2, value: 6
 */
listOf(4, 5, 6).forEachIndexed { index, value -> 
    println("index: $index, value: $value")
}

drop, dropLast, dropWhile, dropLastWhile

drop 은 문자열의 앞이나 뒷부분을 자를 때 사용된다.

내부적으로는 substring 으로 구현되어 있다.

  • drop : 앞에서부터 n 개의 문자를 제거한 String 을 반환
  • dropLast: 뒤에서부터 n 개의 문자를 제거
  • dropWhile, dropLastWhile: 조건을 만족하지 않는 문자가 나올때까지 제거


/* definition */
fun String.drop(n: Int): String

fun String.dropLast(n: Int): String

fun String.dropWhile(
  predicate: (Char) -> Boolean
): String

fun String.dropLastWhile(
  predicate: (Char) -> Boolean
): String


/* exmaple */
val string = "<<<First Grade>>>"

println(string.drop(6)) // st Grade>>>
println(string.dropLast(6)) // <<<First Gr
println(string.dropWhile { !it.isLetter() }) // First Grade>>>
println(string.dropLastWhile { !it.isLetter() }) // <<<First Grade


Reference

String

'Language > Kotlin' 카테고리의 다른 글

Kotlin Collections 와 Sequences 의 차이점 (feat. Java Stream)  (1) 2022.01.27
Kotlin Enum  (0) 2021.10.06
Kotlin Collection Sorting (정렬)  (0) 2021.02.19
[Kotlin] Swap  (0) 2020.11.11
[Kotlin] For 문  (0) 2020.11.11

+ Recent posts