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


198

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


4
@RohitJain 특정 스트림을 의미하지 않았습니다. CharStream존재하지 않는 경우 추가하는 데 문제점이 있습니까?
Adam Dyga

5
@AdamDyga : 다른 유형 (char, short, float)은 큰 영향을주지 않고 더 큰 동등 (int, double)로 표시 될 수 있기 때문에 설계자는 기본 스트림을 3 가지 유형으로 제한하여 클래스 및 메소드의 폭발을 피하기로 선택했습니다. 성능 저하.
JB 니 제트

3
@JBNizet 알겠습니다. 그러나 몇 가지 새로운 클래스를 저장하기 위해 여전히 더러운 솔루션처럼 느껴집니다.
Adam Dyga

9
@JB Nizet : 우리는 이미 같은 나에게 그것은 외모 과부하 모든 스트림뿐만 아니라 주어진 인터페이스의 폭발 모든 기능 인터페이스를 ...
홀거

5
네, 기본 스트림 전문화가 세 개 뿐인 경우에도 이미 폭발이 발생했습니다. 8 가지 프리미티브 모두에 ​​스트림 전문화가 있다면 어떨까요? 대격변? :-)
Stuart Marks

답변:


218

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

여전히 개인적으로 나는 이것이 매우 나쁜 결정이라고 생각하며, 그들이 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그들과 함께 제대로 작동 할 수 있도록.


7
내 결론 : API 의이 부분은 의도적으로 설계되었습니다. 그러나 광범위한 답변에 감사드립니다
Adam Dyga

27
+1, 그러나 내 제안은 codePoints()대신에 사용 하는 chars()것이므로 이미 모든 int코드 char메소드 java.lang.Character를 비롯하여 코드 포인트를 추가로 수용하는 많은 라이브러리 함수를 찾을 수 있습니다 StringBuilder.appendCodePoint.이 지원은 이후에 존재합니다 jdk1.5.
Holger

6
코드 포인트에 대한 좋은 지적. 그것들을 사용하면 보충 문자를 처리 할 수 ​​있으며, 이는 a String또는 로 대리 쌍으로 표시됩니다 char[]. 대부분의 char처리 코드가 대리 쌍을 잘못 처리 한다고 생각 합니다.
스튜어트 마크

2
@skiwi를 정의한 void print(int ch) { System.out.println((char)ch); }다음 메소드 참조를 사용할 수 있습니다.
스튜어트 마크

2
Stream<Character>거부 된 이유 는 내 답변을 참조하십시오 .
스튜어트 마크

90

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를 제공 할 수 있습니다.

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


1
좋은 대답입니다! 두 가지 다른 방법이있을 수없는 이유 그러나이 응답하지 않습니다 chars(), 반환이있는 한 가지 Stream<Character>다른 존재 (작은 성능 저하와 함께) IntStream,이 또한 고려되었다? 사람들이 Stream<Character>편의상 성능 저하에 비해 그 가치가 있다고 생각한다면 사람들은 어쨌든 그것을 매핑 할 가능성이 큽니다 .
skiwi

3
미니멀리즘이 여기에 들어옵니다. 에 chars()char 값을 반환하는 메소드가 이미있는 경우 IntStream동일한 값을 얻지 만 상자 형식의 다른 API 호출을 많이 추가하지는 않습니다. 호출자는 많은 어려움없이 값을 박스에 넣을 수 있습니다. 이 경우 (아마 드문 경우)에는이 작업을 수행하지 않아도되지만 API에 혼란을 추가하는 것이 더 편리합니다.
스튜어트 마크

5
중복 질문 덕분에 나는 이것을 발견했습니다. 나는 이 방법이 거의 사용되지 않았다는 사실을 감안할 때 chars()귀환 IntStream은 큰 문제가되지 않는다는 데 동의한다 . 그러나 다시 변환하는 내장 된 방법이 좋은 것입니다 IntStream받는 사람을 String. 그것은 할 수 .reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()있지만 정말 길다.
Tagir Valeev 2016 년

7
@TagirValeev 예, 다소 번거 롭습니다. 코드 포인트 스트림 (IntStream)을 사용하면 나쁘지 않습니다 collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString(). 실제로 짧지는 않지만 코드 포인트를 사용하면 (char)캐스트를 피하고 메소드 참조를 사용할 수 있습니다. 또한 대리자를 제대로 처리합니다.
스튜어트 마크 5

2
@IlyaBystrov 불행히도 같은 프리미티브 스트림 IntStream에는을 사용하는 collect()메소드 가 없습니다 Collector. collect()이전 의견에서 언급했듯이 3 개의 인수 방법 만 있습니다.
스튜어트 마크
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.