"varargs 매개 변수에 대해 T의 일반 배열이 생성됨"컴파일러 경고를 해결할 수 있습니까?


154

이것은 문제가되는 코드의 단순화 된 버전입니다. 한 제네릭 클래스는 제네릭 유형 매개 변수가있는 다른 클래스를 사용하고 제네릭 유형 중 하나를 varargs 매개 변수가있는 메서드에 전달해야합니다.

class Assembler<X, Y> {
    void assemble(X container, Y... args) { ... }
}

class Component<T> {
    void useAssembler(T something) {

        Assembler<String, T> assembler = new Assembler<String, T>();

        //generates warning:
        // Type safety : A generic array of T is
        // created for a varargs parameter
        assembler.assemble("hello", something);
    }

}

이 경고가 발생하지 않고 일반 매개 변수를 varargs 메서드에 전달하는 올바른 방법이 있습니까?

물론

assembler.assemble("hello", new T[] { something });

일반 배열을 만들 수 없기 때문에 작동하지 않습니다.


3
이상한 것. 컴파일러는 여기에서 완전한 유형 안전성을 보장 할 수 있어야하는 것 같습니다.
erickson

3
Angelika Langer의 Java Generics FAQ의 관련 항목 : angelikalanger.com/GenericsFAQ/FAQSections/…
Flow

답변:


88

을 추가하는 것 외에는 @SuppressWarnings("unchecked")그렇게 생각하지 않습니다.

버그 보고서 에는 더 많은 정보가 있지만 컴파일러는 제네릭 유형의 배열을 좋아하지 않습니다.


3
@SuppressWarnings ( "unchecked")를 피하고 싶었습니다. 그 버그 보고서는 저에게 희망이 거의 없습니다!
matt b

3
Joshua Bloch가 "Effective Java"에서 "제네릭과 어레이를 혼합하지 마십시오"라고 표현했습니다.
Timmos

20
그런 다음 암시 적으로 : Generics와 함께 Varargs를 사용하지 마십시오! 맞습니다 ... varargs를 Collection이 아닌 Array에 매핑하는 결정은 Java를 영원히 찌르는 것입니다. 고슬링 씨 잘하셨습니다.
bernstein 2014

57

Tom Hawtin은 주석에서 이것을 지적했지만 더 명확하게 말하면 네, 선언 사이트에서 해결할 수 있습니다 (잠재적으로 많은 호출 사이트가 아닌) : JDK7로 전환.

Joseph Darcy의 블로그 게시물 에서 볼 수 있듯이 Java 7에 대한 몇 가지 작은 점진적 언어 개선 사항을 선택하는 Project Coin 연습 은 메서드 측면에서이 경고가 알려진 상황에서이 경고 를 제거 할 수 있도록 허용하는 Bob Lee의 제안 을 수락 @SuppressWarnings("varargs")했습니다. 안전한.

이것은 이 커밋 으로 OpenJDK에서 구현되었습니다 .

이것은 당신의 프로젝트에 유용 할 수도 있고 아닐 수도 있습니다 (많은 사람들이 JVM의 불안정한 프리 릴리즈 버전으로 전환하는 것을 기뻐하지 않을 것입니다!). 아마도 그것은-또는 아마도 나중에이 질문을 발견하는 사람 일 것입니다 (JDK7이 나간 후) ) 유용합니다.


7
언급 된 프로젝트 코인 기능을 사용할 수 있습니다 - 볼 @SafeVarargs을 자바 7에
조지 호킨스

Bob의 제안에서 대안 E는 매력적입니다.
Christopher Perry

자바 8 나타납니다 (이하 "변수 인수") @SuppressWarnings 대신 @SafeVarargs를 사용하는
폴 Wintz

17

유창한 인터페이스를 원한다면 빌더 패턴을 시도해 볼 수 있습니다. varargs만큼 간결하지는 않지만 형식에 안전합니다.

일반 형식의 정적 메서드는 형식 안전성을 유지하면서 빌더를 사용할 때 일부 상용구를 제거 할 수 있습니다.

빌더

public class ArgBuilder<T> implements Iterable<T> {

    private final List<T> args = new ArrayList<T>();

    public ArgBuilder<T> and(T arg) {
        args.add(arg);
        return this;
    }

    @Override
    public Iterator<T> iterator() {
        return args.iterator();
    }

    public static <T> ArgBuilder<T> with(T firstArgument) {
        return new ArgBuilder<T>().and(firstArgument);
    }
}

그것을 사용

import static com.example.ArgBuilder.*;

public class VarargsTest {

    public static void main(String[] args) {
        doSomething(new ArgBuilder<String>().and("foo").and("bar").and("baz"));
        // or
        doSomething(with("foo").and("bar").and("baz"));
    }

    static void doSomething(Iterable<String> args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

1
구성의 힘. 나는 varargs보다 이것을 훨씬 더 좋아하고 더 표현력이 있습니다.
Christopher Perry

1
@ChristopherPerry 잘 당신은 당신의 코드베이스도 고려해야합니다. 기본 Collection(이 경우 ArrayList)은 호출자에게 강제로 적용되지만, a LinkedList가 더 적절하거나 변경 불가능한 배열 자체 (예 : OP 질문의 varargs)를 알 수 있습니다. 특수화되지 않은 사용 사례에서는 이것이 적절할 수 있지만,이를 둘러싼 코드와 사용자의 요구에 따라 이것이 제한 사항이라는 점을 지적하십시오.
searchengine27

5

vararg 메서드 호출에서 매개 변수를 Object로 명시 적으로 캐스팅하면 @SuppressWarnings에 의존하지 않고도 컴파일러가 만족할 수 있습니다.

public static <T> List<T> list( final T... items )
{
    return Arrays.asList( items );
}

// This will produce a warning.
list( "1", 2, new BigDecimal( "3.5" ) )

// This will not produce a warning.
list( (Object) "1", (Object) 2, (Object) new BigDecimal( "3.5" ) )

// This will not produce a warning either. Casting just the first parameter to 
// Object appears to be sufficient.
list( (Object) "1", 2, new BigDecimal( "3.5" ) )

여기서 문제는 컴파일러가 생성 할 구체적인 배열 유형을 파악해야한다는 것입니다. 메서드가 제네릭이 아닌 경우 컴파일러는 메서드의 형식 정보를 사용할 수 있습니다. 메서드가 제네릭이면 호출시 사용 된 매개 변수를 기반으로 배열 유형을 파악하려고합니다. 매개 변수 유형이 동종이면 해당 작업이 쉽습니다. 그들이 다양하다면 컴파일러는 내 생각에 너무 영리하려고 노력하고 유니온 유형의 제네릭 배열을 만듭니다. 그런 다음 그것에 대해 경고해야한다는 느낌을받습니다. 더 간단한 해결책은 유형을 더 잘 좁힐 수 없을 때 Object []를 만드는 것입니다. 위의 솔루션은 바로 그것을 강제합니다.

이를 더 잘 이해하려면 다음 list2 메서드와 비교하여 위의 목록 메서드를 호출 해보십시오.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}

예를 들면 다음과 같습니다. Iterator <?> it = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); } 예를 들어 알 수없는 유형의 배열에서 객체의 클래스를 가져옵니다.
ggb667

2

Java 7부터 메소드 에 @SafeVarargs 를 추가 할 수 있으며 클라이언트 코드에 주석을 달 필요가 없습니다.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}

1

메서드를 오버로드 할 수 있습니다. 이것은 문제를 해결하지 못하지만 경고 수를 최소화합니다 (예, 해킹입니다!).

class Assembler<X, Y> {
  void assemble(X container, Y a1) { ... }
  void assemble(X container, Y a1, Y a2) { ... }
  void assemble(X container, Y a1, Y a2, Y a3) { ... }
  void assemble(X container, Y a1, Y a2, Y a3, Y a4) { ... }
  void assemble(X container, Y... args) { ... }
}

23
Ew. 이것은 varargs가 방지해야하는 해킹의 종류입니다.
Amanda S

1
예를 들어 Guava의 ImmutableSet.of를 살펴보면 유효한 접근 방식이 될 수 있습니다 .
Jonathan

1

해결하기 매우 쉬운 문제입니다. 사용 List<T>!

참조 유형의 배열은 피해야합니다.

현재 Java 버전 (1.7)에서는 @SafeVargs호출자로부터 경고를 제거 할 메서드를 표시 할 수 있습니다 . 그래도 조심해서 레거시 어레이가없는 편이 낫습니다.

또한 참조 비 Reifiable에게 가변 인자 방법과 형식 매개 변수 사용하면 향상된 컴파일러 경고 및 오류를 기술 노트를.


6
이것은 varargs 매개 변수로 피할 수 없습니다.
matt b

4
경고 억제가 사용이 아닌 varargs 메소드 선언에서 진행되도록 JDK7에 대한 제안이 있습니다.
Tom Hawtin-tackline

11
이것은 저자의 질문의 중요한 측면을 완전히 무시합니다. varargs 매개 변수는 배열을 생성하고이 경고를 생성합니다.
Daniel Yankowsky 2010

2
@Tom Hawtin-tackline에 동의합니다. 자세한 내용은 Bloch << Effecive Java >> 항목 25 : 배열보다 목록 선호를 참조하십시오.
Stan Kurilin

2
나는 일반적으로 하나 블로흐 동의하지만 ... 규칙에 명확한 예외는 가변 인자
Joeri 핸드릭스 (Hendrickx)

0

제네릭 유형의 배열로 작업 할 때 제네릭 유형에 대한 참조를 전달해야합니다. 이를 통해 실제로 java.lang.reflect.Array를 사용하여 일반 코드를 수행 할 수 있습니다.

http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html


나는 제네릭 유형의 배열로 작업하지 않지만 직접적으로는 아니지만 제네릭 유형의 varargs입니다.
matt b
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.