컬렉션을 안전하게 복사하려면 어떻게해야합니까?


9

과거에는 컬렉션을 안전하게 복사한다고 말했습니다.

public static void doThing(List<String> strs) {
    List<String> newStrs = new ArrayList<>(strs);

또는

public static void doThing(NavigableSet<String> strs) {
    NavigableSet<String> newStrs = new TreeSet<>(strs);

그러나 이러한 "복사"생성자, 비슷한 정적 생성 방법 및 스트림은 실제로 안전하며 규칙은 어디에 지정되어 있습니까? 안전하다는 말은 합리적으로 백업되고 결함이 없다고 가정 할 때 악의적 인 호출자에 대해 시행되는 Java 언어 및 컬렉션에서 제공 하는 기본 시맨틱 무결성 보장 SecurityManager입니다.

나는이 방법을 던지는 행복 해요 ConcurrentModificationException, NullPointerException, IllegalArgumentException, ClassCastException, 등, 또는 심지어 매달려.

String불변 유형 인수의 예로 선택 했습니다. 이 질문에 대해, 나는 자신의 문제가있는 가변 유형의 컬렉션에 대한 깊은 사본에 관심이 없습니다.

(명확하게하기 위해, 나는 오픈 JDK 소스 코드를 보면서 대한 대답의 몇 가지 종류가있다 ArrayList하고 TreeSet.)


2
안전 이란 무엇을 의미 합니까? 일반적으로 컬렉션 프레임 워크의 클래스는 javadocs에 지정된 예외를 제외하고 비슷하게 작동하는 경향이 있습니다. 복사 생성자는 다른 생성자와 마찬가지로 "안전"합니다. 컬렉션 카피 생성자가 안전한지 묻는 것이 매우 구체적으로 들리기 때문에 염두에 두어야 할 것이 있습니까?
카야 만

1
글쎄, NavigableSet다른 Comparable기반 컬렉션은 때로는 클래스가 compareTo()올바르게 구현되지 않고 예외를 throw하는지 감지 할 수 있습니다 . 신뢰할 수없는 주장에 의해 당신이 무엇을 의미하는지는 불분명합니다. 당신은 악당이 나쁜 줄의 모음을 만들어 내고 그것을 당신의 모음으로 복사 할 때 나쁜 일이 발생했음을 의미합니까? 아니요, 컬렉션 프레임 워크는 매우 견고하며 1.2 이후로 사용되었습니다.
카야 만

1
당신이 자신의 내부, 해킹없이 표준 컬렉션 많이 손상시킬 수 @JesseWilson HashSet(그리고 일반적으로 다른 모든 해시 컬렉션)의 정확성 / 무결성에 의존하는 hashCode요소의 구현 TreeSetPriorityQueue에 따라 Comparator(그리고 당신도 할 수 없습니다 존재하는 경우) 사용자 정의 비교를 적용하지 않고 동등한 복사본을 만들 EnumSet특정의 무결성 신뢰 enum클래스 파일, 그래서 생성하지, 컴파일 후 확인되지 않습니다 유형 javac을 파괴하거나 손수.
Holger

1
당신의 예에서, 당신은 new TreeSet<>(strs)어디 strs입니다 NavigableSet. 결과적으로 TreeSet의미를 유지하는 데 필요한 소스의 비교기를 사용 하므로 대량 사본이 아닙니다 . 포함 된 요소를 처리하는 것만으로도 괜찮다면 toArray()갈 길입니다. 반복 순서를 유지합니다. “요소 가져 오기, 요소 유효성 검사, 요소 사용”에 능숙하면 복사 할 필요조차 없습니다. 모든 요소를 ​​확인하고 모든 요소를 ​​사용하려는 경우 문제가 시작됩니다. 그런 다음, 당신은 믿을 수 없다 TreeSet사용자 정의 비교 w 사본
홀거

1
checkcast각 요소에 대해 효과가있는 유일한 대량 복사 작업 toArray은 특정 유형입니다. 우리는 항상 끝납니다. 일반 컬렉션은 실제 요소 유형을 알지 못하므로 복사 생성자가 비슷한 기능을 제공 할 수 없습니다. 물론, 당신은 올바른 사용을 위해 수표를 연기 할 수는 있지만, 귀하의 질문이 무엇을 목표로하는지 모르겠습니다. 요소를 사용하기 직전에 확인하고 실패하면 "의미 적 무결성"이 필요하지 않습니다.
Holger

답변:


12

Collection API와 같은 일반 API의 동일한 JVM 내에서 의도적으로 악의적 인 코드가 실행되는 것을 방지 할 수는 없습니다.

쉽게 설명 할 수있는 것처럼 :

public static void main(String[] args) throws InterruptedException {
    Object[] array = { "foo", "bar", "baz", "and", "another", "string" };
    array[array.length - 1] = new Object() {
        @Override
        public String toString() {
            Collections.shuffle(Arrays.asList(array));
            return "string";
        }
    };
    doThing(new ArrayList<String>() {
        @Override public Object[] toArray() {
            return array;
        }
    });
}

public static void doThing(List<String> strs) {
    List<String> newStrs = new ArrayList<>(strs);

    System.out.println("made a safe copy " + newStrs);
    for(int i = 0; i < 10; i++) {
        System.out.println(newStrs);
    }
}
made a safe copy [foo, bar, baz, and, another, string]
[bar, and, string, string, another, foo]
[and, baz, bar, string, string, string]
[another, baz, and, foo, bar, string]
[another, bar, and, foo, string, and]
[another, baz, string, another, and, foo]
[string, and, another, foo, string, foo]
[baz, string, foo, and, baz, string]
[bar, another, string, and, another, baz]
[bar, string, foo, string, baz, and]
[bar, string, bar, another, and, foo]

보시다시피, List<String>실제로 String인스턴스 목록을 얻는 것이 보장되지는 않습니다 . 유형 삭제 및 원시 유형으로 인해 목록 구현 측에서 가능한 수정조차 없습니다.

또 다른 것은 ArrayList생성자를 비난 할 수 있는 것은 들어오는 컬렉션의 toArray구현 에 대한 신뢰입니다 . TreeMap같은 방식으로 영향을받지 않지만,의 구성에서와 같이 배열을 통과하면 성능이 향상되지 않기 때문입니다 ArrayList. 어느 클래스도 생성자에 대한 보호를 보장하지 않습니다.

일반적으로 구석 구석에 의도적으로 악의적 인 코드가 있다고 가정하여 코드를 작성하려고 할 때 아무런 의미가 없습니다. 모든 것을 막기 위해 할 수있는 일이 너무 많습니다. 이러한 보호는 악의적 인 호출자에게 무언가에 대한 액세스 권한을 부여 할 수있는 동작을 실제로 캡슐화하는 코드에만 유용하며이 코드 없이는 아직 액세스 할 수 없습니다.

특정 코드에 대한 안전이 필요한 경우

public static void doThing(List<String> strs) {
    String[] content = strs.toArray(new String[0]);
    List<String> newStrs = new ArrayList<>(Arrays.asList(content));

    System.out.println("made a safe copy " + newStrs);
    for(int i = 0; i < 10; i++) {
        System.out.println(newStrs);
    }
}

그런 다음 newStrs문자열 만 포함하고 생성 후 다른 코드로 수정할 수 없음을 확인할 수 있습니다.

또는 List<String> newStrs = List.of(strs.toArray(new String[0]));Java 9 이상과 함께 사용
Java 10 List.copyOf(strs)과 동일한 기능을 수행하지만 설명서에는 수신 콜렉션의 toArray메소드 를 신뢰하지 않는다고 명시되어 있지 않습니다 . 따라서 List.of(…)배열 기반 목록을 반환하는 경우 복사본을 만드는을 호출 하는 것이 더 안전합니다.

호출자가 방법을 변경할 수 없으므로 배열이 작동하고 들어오는 컬렉션을 배열에 덤프 한 다음 새 컬렉션을 채우면 항상 사본을 안전하게 만듭니다. 컬렉션은 위에서 설명한 것처럼 반환 된 배열에 대한 참조를 보유 할 수 있으므로 복사 단계에서이를 변경할 수 있지만 컬렉션의 복사본에는 영향을 줄 수 없습니다.

따라서 특정 요소가 배열에서 검색된 후 또는 결과 컬렉션에서 전체적으로 일관성 검사를 수행해야합니다.


2
Java의 보안 모델은 스택에있는 모든 코드의 권한 세트의 교차점을 코드에 부여하여 작동하므로 코드 호출자가 의도하지 않은 작업을 수행 할 때 여전히 초기보다 많은 권한을 얻지 못합니다. 따라서 코드없이 악성 코드가 수행 할 수있는 작업 만 코드에서 수행 할 수 있습니다. 당신은 당신이 의도 상승 권한을 통해 실행하는 코드 강화해야한다 AccessController.doPrivileged(…)등 그러나 긴 애플릿 보안 관련 버그 목록 우리에게이 기술이 포기 된 이유에 대한 힌트를 제공합니다 ...
홀거

1
그러나“컬렉션 API와 같은 일반 API에”를 삽입해야했습니다. 왜냐하면 그것이 대답에 집중했기 때문입니다.
Holger

2
악의적 인 컬렉션 구현이 허용되는 권한있는 코드에 대해 보안과 관련이없는 코드를 강화해야하는 이유는 무엇입니까? 이 가상의 발신자는 코드를 호출하기 전과 후에도 악의적 인 동작을 겪을 수 있습니다. 코드가 올바르게 동작하는 유일한 코드라는 것도 알지 못합니다. new ArrayList<>(…)올바른 컬렉션 구현을 가정하면 복사 생성자로 사용 하는 것이 좋습니다. 이미 너무 늦었을 때 보안 문제를 해결하는 것은 귀하의 의무가 아닙니다. 손상된 하드웨어는 어떻습니까? 운영 체제? 멀티 스레딩은 어떻습니까?
Holger

2
사실“보안이 없음”을 옹호하는 것이 아니라 올바른 환경에서 보안을 유지하는 대신 사실이 깨어진 환경을 고치려고하지 않습니다. “ 수퍼 타입을 정확하게 구현하지 못하는 컬렉션이 많이 있다”는 흥미로운 주장 이지만, 증명을 요구하기에는 이미 너무 멀리 가서 이것을 더 확장시켰다. 원래 질문은 완전히 답변되었습니다. 당신이 지금 가져 오는 포인트는 결코 그것의 일부가 아닙니다. 말했듯이, List.copyOf(strs)명백한 가격으로 그와 관련하여 들어오는 컬렉션의 정확성에 의존하지 않습니다. ArrayList매일 매일 타협해야합니다.
Holger

4
모든“유사한 정적 생성 방법 및 스트림”에 대해 그러한 사양이 없음을 분명히 밝힙니다. 당신이 절대적으로 안전을 원한다면, 당신은 전화를해야 toArray()배열과 같은 배열의 수집 사본을 작성하여 다음 오버라이드 (override) 행동 가질 수 없기 때문에, 직접 new ArrayList<>(Arrays.asList( strs.toArray(new String[0])))또는 List.of(strs.toArray(new String[0])). 둘 다 요소 유형을 강제하는 부작용이 있습니다. 나는 개인적으로 그들이 copyOf불변의 컬렉션을 타협 할 수 없다고 생각 하지만 대안에는 대안이 있습니다.
Holger

1

오히려이 정보를 의견에 남기고 싶지만 평판이 충분하지 않습니다. 죄송합니다 :) 최대한 많은 정보를 설명하려고 노력할 것입니다.

constJava에서는 객체 내용을 수정하지 않는 멤버 함수를 표시하기 위해 C ++에서 사용 된 수정 자 와 같은 것이 아니라 원래 "불변성"이라는 개념이 사용되었습니다. 캡슐화 (또는 OCP, Open-Closed Principle)는 객체의 예기치 않은 돌연변이 (변경)를 방지해야했습니다. 물론 리플렉션 API는 이것을 둘러 봅니다. 직접 메모리 액세스도 동일합니다. 그것은 자신의 다리를 쏘는 것에 관한 것입니다 :)

java.util.Collection그 자체는 변경 가능한 인터페이스입니다 : add컬렉션을 수정 해야하는 메소드가 있습니다. 물론 프로그래머는 컬렉션을 던질 무언가로 랩핑 할 수 있습니다 ... 다른 프로그래머가 컬렉션을 변경할 수 없다는 javadoc을 읽을 수 없기 때문에 모든 런타임 예외가 발생합니다.

java.util.Iterable인터페이스에서 변경 불가능한 컬렉션을 노출 하기 위해 유형 을 사용하기로 결정 했습니다. 의미 적 Iterable으로 "돌연변이 성"과 같은 수집 특성이 없습니다. 여전히 스트림을 통해 기본 컬렉션을 수정할 수 있습니다.


JIC, 불변의 방식으로 맵을 노출 java.util.Function<K,V>하는 데 사용할 수 있습니다 (맵의 get방법이이 정의에 적합 함)


읽기 전용 인터페이스와 불변성의 개념은 직교합니다. C ++ 및 C의 요점은 시맨틱 무결성을 지원하지 않는다는 것 입니다. 또한 복사 객체 / 구조체 인수-const &는 그 점에 대한 독창적 인 최적화입니다. 통과하면 Iterator실제로 요소 단위로 사본을 작성하지만 좋지 않습니다. forEachRemaining/를 사용하는 forEach것은 분명히 완전한 재앙이 될 것입니다. (또한 그 방법 Iterator이 있음을 언급해야 합니다 remove.)
Tom Hawtin-tackline

Scala 컬렉션 라이브러리를 보면 변경 가능한 인터페이스와 변경 불가능한 인터페이스가 엄격하게 구분됩니다. 비록 완전히 다른 이유로 만들어졌지만 여전히 안전을 달성 할 수있는 방법을 보여줍니다. 읽기 전용 인터페이스는 의미 상 불변성을 가정합니다. 이것이 바로 말하려는 것입니다. ( Iterable실제로 불변이 아니지만에 관한 문제는 보이지 않습니다. forEach*)
Alexander
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.