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



자바 배열 클래스 (1)

Java 8의 API 변경 사항을 검토 java.util.Arrays 의 새 메소드가 모든 프리미티브에 대해 오버로드되지 않았 음을 확인했습니다. 내가 발견 한 방법은 다음과 같습니다.

현재이 새로운 메소드는 int , longdouble 프리미티브 만 처리합니다.

int , longdouble 은 아마도 가장 널리 사용되는 프리미티브 일 수 있으므로 API를 제한해야한다면이 세 가지를 선택 하겠지만 API를 제한해야하는 이유는 무엇입니까?

https://code.i-harness.com


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

Java 8에서 인터페이스 오염이 발생하는 이유

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

유형 삭제 문제

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

전문가 그룹이이 문제로 어려움을 겪지 않았다고 생각하지 마십시오. 람다 메일 링리스트 의 브라이언 괴츠 (Brian Goetz)

[...] 한 가지 예로서 함수 유형을 살펴 보겠습니다. devoxx에서 제공되는 lambda strawman은 함수 유형을가집니다. 나는 우리가 그들을 제거 할 것을 주장했고, 이것은 나를 인기가 없었습니다. 그러나 기능 유형에 대한 나의 반대는 기능 유형을 좋아하지 않는다는 것만은 아니 었습니다. 기능 유형을 좋아하지만 기능 유형은 Java 유형 시스템의 기존 측면 인 삭제와 관련하여 나빴습니다. 지워지는 기능 유형은 두 세계 모두 최악입니다. 그래서 우리는 이것을 디자인에서 제거했습니다.

그러나 나는 "자바는 결코 함수 타입을 가질 수 없다"라고 말하기를 꺼려한다. (자바는 함수 타입을 가질 수 없다는 것을 알고있다.) 함수 타입을 얻으려면 우선 지우기를 처리해야한다고 생각한다. 그럴 수도 있고 아닐 수도 있습니다. 그러나 구조화 된 유형의 세계에서 함수 유형은 더 많은 의미를 갖기 시작합니다. [...]

이 접근법의 장점은 우리가 원하는만큼 많은 인자를 받아들이는 메소드를 가지고 우리 자신의 인터페이스 타입을 정의 할 수 있다는 것입니다. 우리가 알기에 그것들을 사용하여 람다 표현식과 메소드 참조를 만들 수 있습니다. 다시 말해, 우리는 훨씬 더 새로운 기능적 인터페이스로 세계를 오염시킬있는 힘을 가지고 있습니다. 또한 이전 버전의 JDK의 인터페이스 또는 이와 같은 SAM 유형을 정의한 이전 API 버전의 경우에도 람다 식을 만들 수 있습니다. 이제는 RunnableCallable 을 기능 인터페이스로 사용할 수 있습니다.

그러나 이러한 인터페이스는 모두 다른 이름과 방법을 가지고 있으므로 암기하기가 더 어려워집니다.

여전히 나는 Scala 에서처럼 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)이있는 뒤에있는 철학은 불쾌한 상충 관계가 있습니다. 한편으로는 추악한 코드 중복, 인터페이스 오염 등이 있습니다. 반면에 박스형 연산에 대한 모든 종류의 산술 연산은 ints를 줄이기위한 이야기가 없으면 끔찍합니다. 그래서 우리는 힘든 코너에 있으며 더 악화 시키려하지 않습니다.

트릭 # 1을 악화시키지 않는 것은 다음과 같습니다. 우리는 8 가지 기본 유형 모두를 수행하지 않습니다. 우리는 int, long, double을하고 있습니다. 모든 다른 것들은 이것들에 의해 시뮬레이션 될 수 있습니다. 아마 우리는 int도 제거 할 수 있지만, 대부분의 Java 개발자가 int를 준비하고 있다고 생각하지는 않습니다. 예, 문자에 대한 호출이있을 것이며 답변은 "int로 고정하십시오." (각 전문 분야는 JRE 풋 프린트에 ~ 100K 정도로 예상됩니다.)

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

이것이 전문가 그룹에게는 어려운 결정이라는 것을 알 수 있습니다. 나는 이것이 멋지다는 데 동의하는 사람이 거의 없을 것이라고 생각하며, 우리 중 대부분은 필요하다고 동의 할 가능성이 큽니다.

확인 된 예외 문제

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

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

write 작업이 확인 된 예외 (예 : IOException )를 throw하지만 Consumer 메서드의 서명이 아무 예외도 throw하지 않는다고 선언했기 때문에 수행 할 수 없습니다. 따라서이 문제를 해결할 수있는 유일한 방법은 더 많은 인터페이스를 만들고 일부는 예외를 선언하고 어떤 것은 선언하지 않는 것입니다 (또는 예외 투명성을 위한 언어 수준에서 또 다른 메커니즘을 제안합니다.) 다시 말하면, 전문가를 "덜 나쁘게"만드는 것입니다. 그룹은이 경우에 아무 것도하지 않기로 결정했습니다.

람다 메일 링리스트 의 브라이언 괴츠 (Brian Goetz)

[...] 네, 예외적 인 SAM을 제공해야합니다. 그러나 람다 변환은 그들과 잘 작동 할 것입니다.

EG는이 문제에 대한 추가 언어 및 라이브러리 지원에 대해 논의했으며, 결국 이것이 비용 / 이점의 절충안이라는 절충안을 느꼈습니다.

라이브러리 기반 솔루션은 SAM 유형 (예외적 또는 비보호)에서 2 배 폭발을 일으키며, 이는 원시적 전문화에 대한 기존의 조합 폭발과 나쁘게 상호 작용합니다.

사용 가능한 언어 기반 솔루션은 복잡성 / 가치 상실로 인한 손실입니다. 몇 가지 대체 솔루션이 있지만 우리는 계속 탐구 할 것입니다. 분명히 8은 아니지만 9도 마찬가지입니다.

그 동안에는 원하는 것을 할 수있는 도구가 있습니다. 나는 당신이 우리에게 마지막 마일을 제공하기를 더 좋아한다는 것을 얻는다. (또한, 부수적으로, 당신의 요청은 실제로 "이미 체크 예외를 포기하지 않는 이유"에 대한 가느 다란 가려진 요청이다.) 그러나 나는 현재 상태가 너는 네 일을 끝내야 해. [...]

따라서 우리는 개발자들에게 사안별로 다음과 같은 인터페이스 폭발 을 처리해야합니다.

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); }
   };
}

하기 위해서 :

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

아마도 Java (JDK 9) 에서 Java 및 Reification의 값 유형 지원을 받을 때 이러한 다중 인터페이스 중 일부를 제거 할 수있을 것입니다.

요약하면 전문가 그룹이 여러 가지 디자인 문제로 어려움을 겪고 있음을 알 수 있습니다. 이전 버전과의 호환성을 유지할 필요성, 요구 사항 또는 제약 조건으로 인해 작업이 어려워진 경우 값 유형, 유형 삭제 및 예외 검사 예외와 같은 다른 중요한 조건이 있습니다. Java가 첫 번째로 있고 다른 두 개가 부족하다면 JDK 8의 디자인이 달라졌을 것입니다. 그래서 우리 모두는 이러한 것들이 많은 상충 관계가있는 어려운 문제 였음을 이해해야하며 EG는 어딘가에서 선을 그어 결정해야했습니다.





java-8