2009-06-24 3 views
27

이것은 복싱과 언 박싱의 질문이 아닙니다. 오히려 입니다.은 Java와 C#과 같은 언어를 사용해야하는 이유는 무엇입니까?왜 일부 언어는 복싱과 언 박싱이 필요합니까?

저는 C++, STL 및 Boost에 매우 익숙합니다. 나는 아주 쉽게 이런 식으로 뭔가를 쓸 수 C에서

++,

std::vector<double> dummy; 

, 나는 자바 경험이 있지만, 나는 이런 식으로 뭔가를 작성했기 때문에 난 정말 놀랐습니다

ArrayList<Double> dummy = new ArrayList<Double>(); 

내 질문에, 왜 그것이 객체가되어야 하는가, 제네릭에 관해 말할 때 원시 타입을 기술적으로 포함하기가 어렵다면?

+0

C++에서 Java에 이르기까지 나는이 사실을 발견했을 때 완전히 충격을 받았습니다. – GuLearn

답변

46

Generics에 관해 이야기 할 때 기술적으로 기술적으로 원시 타입을 포함하는 것은 무엇입니까?

Java의 경우 제네릭 작동 방식 때문입니다. Java에서 제네릭은 Image 개체를 ArrayList<String>에 넣지 못하게하는 컴파일 타임 트릭입니다. 그러나 Java의 제네릭은 유형 지우기로 구현됩니다. 런타임 중에 일반 유형 정보가 손실됩니다. 이것은 제네릭이 자바의 일생에서 상당히 늦게 추가 되었기 때문에 호환성의 이유로 사용되었습니다. 즉, 실행 시간이 ArrayList<String> 인 경우 값을 검색 할 때 String으로 자동 변환되는 ArrayList<Object> (또는 더 나은 점은 모든 메서드에서 을 반환하고 반환하는 방금 ArrayList)이라는 의미입니다.

그러나 int 이후

은 (런타임에) 기대 ArrayList에에서 Object을 넣어 수 없습니다 Object에서 파생하지 않으며, 당신은 어느 쪽 Object int에 캐스팅 수 없습니다. 즉, int 프리미티브는 Integer과 같이 Object에서 상속되는 형식으로 래핑되어야합니다.

예를 들어, C#은 다르게 작동합니다. C#의 Generics는 런타임에 적용되며 복싱은 List<int>과 함께 필요하지 않습니다. C#의 복싱은 object과 같은 참조 유형 변수에 int과 같은 값 유형을 저장하려고 할 때만 발생합니다. C#에서 은 C#에서 Object을 상속받습니다. 따라서 object obj = 2은 완벽하게 유효하지만 컴파일러에 의해 자동으로 수행되는 int로 boxed됩니다 (Integer 참조 유형이 사용자에게 공개되지 않습니다).

+0

그냥 내 질문에 주목할 것입니다 희망을 물어 감히 : 왜 자바는 autoboxing을 통해 일반에 대한 프리미티브를 구현하지 않았습니까? 내 말은 목록 의 경우 자동으로 Integer로 컴파일 될 것입니다. – Dedyshka

11

복싱과 언 박싱은 언어 (C# 및 Java와 같은)가 메모리 할당 전략을 구현하는 방식에서 태어났다.

특정 유형은 스택에 할당되고 다른 유형은 힙에 할당됩니다. 스택 할당 유형을 힙 할당 유형으로 처리하려면 스택 할당 유형을 힙으로 이동하려면 복싱이 필요합니다. 언 박싱은 역 과정입니다. C# 1 스택 할당 종류

는 치형 (예컨대 System.Int32System.DateTime)라고하고 힙 할당 유형 참조 형식 (예컨대 System.StreamSystem.String)라고한다.

어떤 경우에는 참조 유형 (반사가 하나의 예)과 같은 값 유형을 처리하는 것이 유리하지만 대부분의 경우 복싱 및 언 박싱은 피해야합니다.

+4

값 유형과 스택 기반 할당에주의해야합니다. 에릭 리 퍼트 (Eric Lippert)는이 주제에 대한 두 가지 훌륭한 블로그 게시물을 보았습니다. http://blogs.msdn.com/ericlippert/archive/2009/04/27/the-stack-is-an-implementation-detail.aspx 및 http : // blogs .msdn.com/ericlippert/archive/2009/05/04/the-stack-is-an-implementation-detail-part-two.aspx – Joey

+2

AFAIK, C#은 사용자가 뭔가를 지정하지 않으면 일반 컨테이너에서 자동 복싱을 사용하지 않습니다. 을 나열하십시오. C#의 제네릭은 값 유형에 대해 C++과 유사한 방식으로 작동합니다. http://msdn.microsoft.com/en-us/library/ms379564(VS.80).aspx –

2

왜냐하면 프리미티브가 Object에서 상속받지 않기 때문입니다. 매개 변수로 무엇이든 허용 할 수있는 메소드가 있다고 가정 해 보겠습니다.

class Printer { 
    public void print(Object o) { 
     ... 
    } 
} 

당신처럼, 그 방법에 간단한 원시 값을 전달해야 할 수 있습니다

printer.print(5); 
당신은 할 수있을 것

그 권투/언 박싱없이, 5는 원시적이며 아니기 때문에 목적. 이러한 기능을 사용하려면 각 기본 유형에 대한 인쇄 메소드를 오버로드 할 수 있지만 고통 스럽습니다.

1

Java 및 C#에서는 C++과 달리 모든 것이 Object를 확장하므로 ArrayList와 같은 컬렉션 클래스는 Object 또는 그 자손 (기본적으로 아무 것도)을 포함 할 수 있습니다.

그러나 성능상의 이유로 Java의 기본 요소 또는 C#의 값 형식에는 특별한 상태가 부여되었습니다. 그들은 대상이 아닙니다. 다음과 같이 할 수 없습니다. (Java에서) :

7.toString() 

toString이 Object의 메소드 인 경우에도 마찬가지입니다. 이 끄덕임을 성능에 연결하기 위해 동일한 객체가 만들어졌습니다. AutoBoxing은 래퍼 클래스에 프리미티브를 넣고 다시 가져와 코드를 읽기 쉽도록 만드는 상용구 코드를 제거합니다.

C#의 값 형식과 개체의 차이가 더 회색입니다.서로 다른 점은 here을 참조하십시오.

+0

C#에서 기본 요소는 구조체로 구현됩니다 (예 : int는 System 네임 스페이스의 Int32에 정의되어 있음). 7.ToString()은 정상적으로 작동합니다. C#의 모든 것은 Object에서 파생되지만 Java의 경우는 아닙니다. – JulianR

+3

@JulianR :보다 구체적으로 모든 CLR 값 형식은 System.Object에서 상속하는 System.ValueType에서 상속됩니다. System.ValueType은 많은 System.Object의 가상 메서드를 재정 의하여 이러한 메서드가 호출 될 때 복싱을 방지합니다. 값 형식에 대해 boxing을 발생시키는 유일한 메서드는 Object.GetType 및 Object.MemberwiseClone입니다. –

2

제네릭에서 primitve 유형을 지원하지 않는 이유는 Java에 대해서만 말할 수 있습니다.

처음에는 자바가 기본 유형을 가져야하는 경우에도 매번이 질문을 지원하는 것이 문제였습니다. 물론 실제 질문에 대한 논의가 방해가되었습니다.

두 번째 이유는 이진 하위 호환성을 원했기 때문에 제네릭을 인식하지 못하는 VM에서 수정되지 않은 상태로 실행되기를 원했기 때문입니다. 이 이전 버전과의 호환성/마이그레이션 호환성 이유로 인해 Collections API가 제네릭을 지원하고 동일한 상태를 유지했으며 제네릭을 도입 한 C#에서와 같이 전혀 새로운 Generic Collection API 모음을 제공하지 않았습니다.

호환성은 ersure (컴파일시 일반 유형 매개 변수 정보 제거)를 사용하여 수행되었으므로 Java에서 너무 많은 캐스팅 경고가 너무 많이 발생합니다.

아직 제네릭을 추가 할 수는 있지만 그렇게 쉬운 것은 아닙니다. 형식 정보 추가 런타임을 제거하는 대신에 추가하면 소스 & 바이너리 호환성을 잃게됩니다 (원시 형식을 계속 사용할 수없고 해당 메서드가 없기 때문에 기존 컴파일 된 코드를 호출 할 수 없습니다)). 너무 많은 비용을 오토 박싱 때문에이 사용 사례에 대해 지원되지

그리고 자동화 된 오토 박싱/언 박싱 위 참조 :

다른 방법은 하나의 C#을 선택한 것입니다.

Java theory and practice: Generics gotchas

1

힙에 저장된 모든 어레이가 아닌 문자열이 아닌 개체는 개체의 공개 및 내용 다음에 8 비트 또는 16 바이트의 헤더 (64분의 32 비트 시스템의 크기)가 포함 비공개 필드. 배열과 문자열에는 위의 헤더와 배열의 길이와 각 요소의 크기 (그리고 아마도 치수의 수, 각 여분의 치수의 길이 등)를 정의하는 몇 바이트가 더해지며 첫 번째 요소의 모든 필드가 뒤 따른다 엘리먼트, 두 번째 엘리먼트의 모든 필드 등을 포함 할 수있다. 객체에 대한 참조가 주어지면, 시스템은 헤더를 검사하고 그것이 어떤 타입인지를 쉽게 판별 할 수있다.

참조 유형 저장 위치는 힙에 저장된 오브젝트를 고유하게 식별하는 4 또는 8 바이트 값을 보유합니다. 현재 구현에서이 값은 포인터이지만 "객체 ID"로 생각하는 것이 더 쉽습니다 (그리고 의미 상 동등).

값 형식 저장 위치는 값 형식의 필드 내용을 포함하지만 연결된 머리글은 없습니다. 코드가 Int32 유형의 변수를 선언하면 그 내용을 나타내는 Int32과 함께 정보를 저장할 필요가 없습니다. 해당 위치에 Int32이 있다는 사실은 프로그램의 일부로 효과적으로 저장되므로 위치 자체에 저장할 필요가 없습니다. 예를 들어, 하나의 필드에 각각 Int32 유형의 필드가있는 백만 개의 개체가있는 경우이 값은 큰 비용 절감 효과를 나타냅니다. Int32이있는 각 오브젝트에는이를 조작 할 수있는 클래스를 식별하는 헤더가 있습니다. 그 클래스 코드의 사본 하나가 백만 인스턴스 중 아무 곳에 나 작동 할 수 있기 때문에 필드가 Int32이라는 것이 코드의 일부가되는 것은 해당 필드의 모든 저장소에 대해 저장소 정보를 포함하는 것보다 훨씬 효율적입니다 .

값 유형 저장 위치의 내용을 특정 값 유형을 예상하지 못하는 코드로 전달하라는 요청이있을 때 복싱이 필요합니다. 알 수없는 유형의 객체를 예상하는 코드는 힙에 저장된 객체에 대한 참조를 허용 할 수 있습니다. 힙에 저장된 모든 객체에는 어떤 유형의 객체인지 식별하는 헤더가 있기 때문에 코드는 객체 유형을 알아야하는 방식으로 객체를 사용해야 할 때마다 해당 헤더를 사용할 수 있습니다.

.net에서는 generic 클래스 및 메서드라고하는 것을 선언 할 수 있습니다. 이러한 각 선언은 자동으로 클래스 나 메소드의 패밀리를 생성합니다. Int32을 루틴 DoSomething<T>(T param)에 전달하면 T 유형의 모든 인스턴스가 Int32으로 효과적으로 대체되는 루틴 버전이 자동으로 생성됩니다. 이 버전의 루틴은 T 유형으로 선언 된 모든 저장 위치가 Int32을 보유하고 있음을 알 수 있으므로 Int32 저장 위치를 ​​사용하도록 루틴을 하드 코딩 한 경우와 마찬가지로 유형 정보를 저장하지 않아도됩니다 위치 자체.