[java] Java 8에서 String.chars ()가 int 스트림 인 이유는 무엇입니까?

Java 8 에는 문자 코드를 나타내는 s ( ) String.chars()스트림을 리턴 하는 새로운 메소드 가 있습니다. 많은 사람들이 대신 스트림을 기대할 것입니다. 이런 식으로 API를 디자인하려는 동기는 무엇입니까?intIntStreamchar



답변

다른 사람들이 이미 언급했듯이, 그 뒤에 디자인 결정은 방법과 클래스의 폭발을 막는 것이 었습니다.

여전히 개인적으로 나는 이것이 매우 나쁜 결정이라고 생각하며, 그들이 CharStream대신하고 합리적인 다른 방법 을 만들고 싶지 chars()않다고 생각해야합니다.

  • Stream<Character> chars(), 이는 일련의 상자 문자를 제공하며 약간의 성능 저하가 있습니다.
  • IntStream unboxedChars()성능 코드에 사용될 것입니다.

그러나 현재 이러한 방식으로 수행 되는 이유 에 중점을 두는 대신 이 답변은 Java 8에서 얻은 API로 수행하는 방법을 보여주는 데 중점을 두어야한다고 생각합니다.

Java 7에서는 다음과 같이했습니다.

for (int i = 0; i < hello.length(); i++) {
    System.out.println(hello.charAt(i));
}

Java 8에서 합리적인 방법은 다음과 같습니다.

hello.chars()
        .mapToObj(i -> (char)i)
        .forEach(System.out::println);

여기에서을 가져 IntStream와서 lambda 통해 객체에 매핑 i -> (char)i하면 자동으로 상자에 상자를 넣은 Stream<Character>다음 원하는 것을 수행 할 수 있으며 여전히 메서드 참조를 플러스로 사용합니다.

주의 당신이 비록 있어야mapToObj당신이 잊고 사용하는 경우, map다음, 아무것도 불평하지 않습니다,하지만 당신은 여전히 끝낼 것입니다 IntStream, 당신이 그것을 대신 문자를 나타내는 문자열의 정수 값을 인쇄 왜 궁금 중단 될 수 있습니다.

Java 8의 다른 추악한 대안 :

에 남아서 IntStream궁극적으로 인쇄하려고하면 더 이상 메소드 참조를 인쇄에 사용할 수 없습니다.

hello.chars()
        .forEach(i -> System.out.println((char)i));

또한 자신의 메서드에 대한 메서드 참조를 사용하면 더 이상 작동하지 않습니다! 다음을 고려하세요:

private void print(char c) {
    System.out.println(c);
}

그리고

hello.chars()
        .forEach(this::print);

변환 손실이있을 수 있으므로 컴파일 오류가 발생합니다.

결론:

API는 때문에 추가하고자하는하지의이 방법으로 설계되었습니다 CharStream, 제가 개인적으로 방법은 반환해야한다고 생각 Stream<Character>하고, 해결 방법은 현재 사용하는 것입니다 mapToObj(i -> (char)i)IntStream그들과 함께 제대로 작동 할 수 있도록.


답변

Skiwi답변은 이미 많은 주요 사항을 다루었습니다. 조금 더 배경을 채울 것입니다.

모든 API의 디자인은 일련의 트레이드 오프입니다. Java에서 어려운 문제 중 하나는 오래 전에 만들어진 디자인 결정을 다루는 것입니다.

기본 요소는 1.0 이후 Java로 사용되었습니다. 프리미티브는 객체가 아니기 때문에 Java를 “불완전한”객체 지향 언어로 만듭니다. 프리미티브의 추가는 객체 지향 순도를 희생하면서 성능을 향상시키기위한 실용적인 결정이었다고 생각합니다.

이것은 거의 20 년이 지난 지금도 여전히 우리와 함께하고있는 절충안입니다. Java 5에 추가 된 오토 박싱 기능은 대부분 박싱 및 언 박싱 메소드 호출로 소스 코드를 복잡하게 만들 필요가 없었지만 여전히 오버 헤드가 있습니다. 많은 경우 눈에 띄지 않습니다. 그러나 내부 루프 내에서 권투 또는 언 박싱을 수행하는 경우 상당한 CPU 및 가비지 콜렉션 오버 헤드가 발생할 수 있음을 알 수 있습니다.

Streams API를 디자인 할 때 프리미티브를 지원해야한다는 것이 분명했습니다. boxing / unboxing 오버 헤드는 병렬 처리의 성능 이점을 없애줍니다. 우리는 API에 많은 양의 혼란을 가중 시켰기 때문에 모든 프리미티브 를 지원하고 싶지 않았습니다 . (실제로 ShortStream?를 사용하는 것을 볼 수 있습니까 ?) “모두”또는 “없음”은 디자인하기에 편안한 장소이지만 수용 할 수있는 것은 아닙니다. 그래서 우리는 “일부”의 합리적인 가치를 찾아야했습니다. 우리는 원시적 전문으로 결국 int, long하고 double. (개인적으로 나는 int제외했을 것입니다. 그러나 그것은 단지 나입니다.)

들어 CharSequence.chars()우리가 돌아 고려 Stream<Character>(초기 프로토 타입이 구현했을 수 있습니다)하지만 때문에 권투 오버 헤드 거부되었습니다. 문자열에 char프리미티브 값이 있다고 가정하면 호출자가 값에 대해 약간의 처리를하고 문자열로 다시 상자를 풀 때 무조건 복싱을 부과하는 것은 실수로 보입니다.

우리는 또한 CharStream원시 전문화를 고려 했지만 API에 추가 할 대량의 양에 비해 사용 범위가 상당히 좁은 것 같습니다. 그것을 추가하는 것이 가치가 없었습니다.

이것이 발신자에게 부과되는 처벌 은로 표시된 값을 IntStream포함 하고 캐스팅이 적절한 장소에서 수행되어야한다는 것을 알아야한다는 것입니다. 오버로드 된 API 호출이 있고 동작이 현저하게 다르기 때문에 이는 혼동을 일으 킵니다 . 호출이 또한 값을 반환 하지만 포함 된 값이 상당히 다르기 때문에 추가적인 혼란이 발생할 수 있습니다.charintsPrintStream.print(char)PrintStream.print(int)codePoints()IntStream

따라서 이것은 여러 대안 중에서 실용적으로 선택하는 것으로 요약됩니다.

  1. 우리는 기본 전문화를 제공 할 수 없어 단순하고 우아하며 일관된 API를 만들 수 있지만 고성능 및 GC 오버 헤드가 발생합니다.

  2. API를 어지럽히고 JDK 개발자에게 유지 보수 부담을 부과하는 비용으로 완전한 기본 전문화 세트를 제공 할 수 있습니다. 또는

  3. 우리는 프리미티브 전문화의 부분 집합을 제공하여 상당히 좁은 범위의 유스 케이스 (char processing)에서 호출자에게 상대적으로 적은 부담을주는 적당한 크기의 고성능 API를 제공 할 수 있습니다.

우리는 마지막 것을 선택했습니다.


답변