IT이야기

Java 8의 새로운 java.util.Arrays 메소드가 모든 기본 유형에 대해 오버로드되지 않는 이유

cyworld 2021. 10. 7. 21:05
반응형

Java 8의 새로운 java.util.Arrays 메소드가 모든 기본 유형에 대해 오버로드되지 않는 이유는 무엇입니까?


Java 8에 대한 API 변경 사항을 검토하고 java.util.Arrays있으며 모든 기본 요소에 대해 의 새 메서드 가 오버로드되지 않는 것으로 나타났습니다 . 내가 알아 차린 방법은 다음과 같습니다.

현재 이러한 새 메서드 intlong, 및 double기본 형식 만 처리 합니다.

int, long, 그리고 double아마도 가장 널리 사용되는 프리미티브일 것이므로 API를 제한해야 한다면 이 세 가지를 선택하는 것이 이해가 되지만 API를 제한해야 하는 이유는 무엇입니까?


이 특정 시나리오뿐만 아니라 전체 질문을 해결하기 위해 우리 모두가 알고 싶어합니다....

Java 8에 인터페이스 오염이 있는 이유

예를 들어, C#과 같은 언어에는 선택적 반환 유형( FuncAction 각각이 서로 다른 유형의 최대 16개 매개변수 T1, T2, T3, ..., T16) 으로 인수를 허용하는 미리 정의된 함수 유형 집합이 있습니다 . 그러나 JDK 8에서 우리가 가지고 있는 것은 다른 이름과 다른 메소드 이름을 가진 다른 기능 인터페이스 세트 이며, 추상 메소드는 잘 알려진 함수 속성 (즉, 널, 단항, 이진, 삼항 등) 의 하위 집합을 나타냅니다 . 그리고 기본 유형을 다루는 경우가 폭발적으로 늘어나고 더 많은 기능적 인터페이스를 폭발적으로 일으키는 다른 시나리오도 있습니다.

유형 삭제 문제

따라서 어떤 면에서는 두 언어 모두 일종의 인터페이스 오염(또는 C#의 대리자 오염)으로 고통받습니다. 유일한 차이점은 C#에서는 모두 같은 이름을 갖는다는 것입니다. 불행히도 Java에서는 유형 삭제 로 인해 Function<T1,T2>Function<T1,T2,T3>또는 사이에 차이가 없기 Function<T1,T2,T3,...Tn>때문에 단순히 이름을 모두 같은 방식으로 지정할 수 없었고 가능한 모든 유형의 함수 조합에 대해 창의적인 이름을 제시해야 했습니다.

전문가 그룹이 이 문제와 씨름하지 않았다고 생각하지 마십시오. 람다 메일링 리스트 에 있는 Brian Goetz의 말 :

[...] 하나의 예로 함수 유형을 살펴보겠습니다. devoxx에서 제공하는 람다 스트로맨에는 함수 유형이 있습니다. 나는 그것들을 제거해야 한다고 주장했고, 이것이 나를 인기 없게 만들었습니다. 그러나 함수 유형에 대한 나의 반대는 내가 함수 유형을 좋아하지 않는다는 것이 아니라 함수 유형을 좋아한다는 것입니다. 그러나 함수 유형은 Java 유형 시스템의 기존 측면인 삭제와 심하게 싸웠습니다. 지워진 함수 유형은 두 세계 모두에서 최악입니다. 그래서 우리는 이것을 디자인에서 제거했습니다.

그러나 나는 "자바에는 함수 유형이 없을 것"이라고 말하고 싶지는 않습니다. (자바에는 함수 유형이 없을 수도 있음을 알고 있습니다.) 함수 유형에 도달하려면 먼저 삭제를 처리해야 한다고 생각합니다. 그것은 가능할 수도 있고 불가능할 수도 있습니다. 그러나 구체화된 구조적 유형의 세계에서는 함수 유형이 훨씬 더 의미가 있기 시작합니다. [...]

이 접근 방식의 장점은 원하는 만큼 많은 인수를 허용하는 메서드를 사용하여 자체 인터페이스 유형을 정의할 수 있고 이를 사용하여 적절하다고 생각하는 대로 람다 식과 메서드 참조를 만들 수 있다는 것입니다. 다시 말해, 우리는 훨씬 더 새로운 기능적 인터페이스로 세상을 오염시킬있는 힘을 가지고 있습니다. 또한 JDK의 이전 버전 또는 이와 같은 SAM 유형을 정의한 자체 API의 이전 버전에 있는 인터페이스에 대해서도 람다 식을 생성할 수 있습니다. 그래서 지금 우리가 사용하는 힘이 Runnable하고 Callable기능적인 인터페이스로.

그러나 이러한 인터페이스는 모두 이름과 방법이 다르기 때문에 암기하기가 더 어려워집니다.

그럼에도 불구하고 Function0, Function1, Function2, ..., FunctionN. 아마도 내가 반대할 수 있는 유일한 주장은 앞에서 언급한 API의 이전 버전에서 인터페이스에 대한 람다 식을 정의할 수 있는 가능성을 최대화하기를 원했다는 것입니다.

값 유형 부족 문제

따라서 분명히 유형 삭제는 여기에서 하나의 원동력입니다. 그러나 유사한 이름과 메서드 서명을 가진 이러한 추가 기능 인터페이스가 모두 필요한 이유와 기본 유형의 사용만 다른 이유를 궁금해하는 사람 중 하나라면 Java에서는 다음 과 같은 값 유형 부족하다는 것을 상기시켜 드리겠습니다. C#과 같은 언어로 된 것들. 즉, 제네릭 클래스에서 사용되는 제네릭 형식은 기본 형식이 아닌 참조 형식만 될 수 있습니다.

즉, 우리는 이것을 할 수 없습니다:

List<int> numbers = asList(1,2,3,4,5);

그러나 우리는 실제로 이것을 할 수 있습니다:

List<Integer> numbers = asList(1,2,3,4,5);

그러나 두 번째 예는 기본 유형에서 앞뒤로 래핑된 객체의 박싱 및 언박싱 비용을 발생시킵니다. 이것은 원시 값의 컬렉션을 다루는 작업에서 정말 비용이 많이 들 수 있습니다. 그래서 전문가 그룹 은 다양한 시나리오를 처리하기 위해 폭발적인 인터페이스만들기로 결정했습니다 . 상황을 "덜 나쁘게" 만들기 위해 int, long 및 double의 세 가지 기본 유형만 다루기로 결정했습니다.

람다 메일링 리스트 에서 Brian Goetz의 말 인용 :

[...] 더 일반적으로: 전문화된 기본 스트림(예: IntStream)을 갖는 철학은 불쾌한 절충안으로 가득 차 있습니다. 한편으로는 추악한 코드 중복, 인터페이스 오염 등이 많이 있습니다. 다른 한편으로는 boxed ops에서 모든 종류의 산술이 형편없고 int를 줄이는 데 대한 스토리가 없다는 것은 끔찍할 것입니다. 그래서 우리는 어려운 구석에 있고 더 악화되지 않으려고 노력하고 있습니다.

상황을 악화시키지 않기 위한 트릭 #1: 우리는 8가지 기본 유형을 모두 수행하지 않습니다. 우리는 int, long 및 double을 수행하고 있습니다. 다른 모든 것들은 이것들에 의해 시뮬레이션될 수 있습니다. 틀림없이 우리는 int를 제거할 수도 있지만 대부분의 Java 개발자는 이에 대한 준비가 되어 있지 않다고 생각합니다. 예, Character에 대한 호출이 있을 것이며 대답은 "int에 고정"입니다. (각 전문화 영역은 JRE 공간에 대해 ~100K로 예상됩니다.)

트릭 #2는 기본 스트림을 사용하여 기본 도메인에서 가장 잘 수행되는 작업(정렬, 축소)을 노출하지만 박스형 도메인에서 할 수 있는 모든 것을 복제하려고 하지 않는다는 것입니다. 예를 들어 Aleksey가 지적한 대로 IntStream.into()가 없습니다. (있는 경우 다음 질문은 "IntCollection은 어디에 있습니까? IntArrayList? IntConcurrentSkipListMap?) 의도는 많은 스트림이 참조 스트림으로 시작하여 기본 스트림으로 끝날 수 있지만 그 반대의 경우는 아닙니다. 괜찮습니다. 필요한 변환 횟수를 줄입니다(예: int -> T에 대한 맵 오버로드 없음, int -> T에 대한 Function 전문화 없음 등) [...]

전문가 그룹으로서는 어려운 결정임을 알 수 있습니다. 이것이 멋지다는 데 동의하는 사람은 거의 없을 것이며 우리 대부분은 이것이 필요하다는 데 동의할 것입니다.

확인된 예외 문제

상황을 더욱 악화시킬 수 있었던 세 번째 원동력이 있었습니다. 바로 Java가 두 가지 유형의 예외(checked 및 unchecked)를 지원한다는 사실입니다. 컴파일러는 확인된 예외를 처리하거나 명시적으로 선언해야 하지만 확인되지 않은 예외는 필요하지 않습니다. 따라서 대부분의 기능 인터페이스의 메서드 서명이 예외를 throw하도록 선언하지 않기 때문에 흥미로운 문제가 발생합니다. 예를 들어 다음은 불가능합니다.

Writer out = new StringWriter();
Consumer<String> printer = s -> out.write(s); //oops! compiler error

write작업이 확인된 예외(예: IOException)를 throw 하기 때문에 수행할 수 없지만 Consumer메서드 의 서명은 예외를 전혀 throw한다고 선언하지 않습니다. 따라서 이 문제에 대한 유일한 해결책은 더 많은 인터페이스를 만드는 것이었습니다. 일부는 예외를 선언하고 일부는 그렇지 않은 것입니다(또는 예외 투명성을 위해 언어 수준에서 또 다른 메커니즘을 생각해 냅니다. 다시 말하지만, 상황을 "덜 악화"시키려면 그룹은 이 경우 아무것도 하지 않기로 결정했습니다.

람다 메일링 리스트 에 있는 Brian Goetz의 말 :

[...] 예, 자신만의 뛰어난 SAM을 제공해야 합니다. 그러나 람다 변환은 잘 작동합니다.

The EG discussed additional language and library support for this problem, and in the end felt that this was a bad cost/benefit tradeoff.

Library-based solutions cause a 2x explosion in SAM types (exceptional vs not), which interact badly with existing combinatorial explosions for primitive specialization.

The available language-based solutions were losers from a complexity/value tradeoff. Though there are some alternative solutions we are going to continue to explore -- though clearly not for 8 and probably not for 9 either.

In the meantime, you have the tools to do what you want. I get that you prefer we provide that last mile for you (and, secondarily, your request is really a thinly-veiled request for "why don't you just give up on checked exceptions already"), but I think the current state lets you get your job done. [...]

So, it's up to us, the developers, to craft yet even more interface explosions to deal with these in a case-by-case basis:

interface IOConsumer<T> {
   void accept(T t) throws IOException;
}

static<T> Consumer<T> exceptionWrappingBlock(IOConsumer<T> b) {
   return e -> {
    try { b.accept(e); }
    catch (Exception ex) { throw new RuntimeException(ex); }
   };
}

In order to do:

Writer out = new StringWriter();
Consumer<String> printer = exceptionWrappingBlock(s -> out.write(s));

Probably, in the future (maybe JDK 9) when we get Support for Value Types in Java and Reification, we will be able to get rid of (or at least no longer need to use anymore) some of these multiple interfaces.

In summary, we can see that the expert group struggled with several design issues. The need, requirement or constraint to keep backwards compatibility made things difficult, then we have other important conditions like the lack of value types, type erasure and checked exceptions. If Java had the first and lacked of the other two the design of JDK 8 would probably have been different. So, we all must understand that these were difficult problems with lots of tradeoffs and the EG had to draw a line somewhere and make a decisions.

ReferenceURL : https://stackoverflow.com/questions/22918847/why-are-new-java-util-arrays-methods-in-java-8-not-overloaded-for-all-the-primit

반응형