2013-01-23 24 views
6

Tôi có một số mã Java di sản mà định nghĩa một generic payload biến đâu đó ngoài tầm kiểm soát của tôi (tức là tôi không thể thay đổi loại của nó):Trộn Scala và Java: Làm thế nào để có được tham số hàm tạo kiểu gõ chung phải không?

// Java code 
Wrapper<? extends SomeBaseType> payload = ... 

tôi nhận một giá trị như payload như một tham số phương pháp trong mã của tôi và muốn chuyển nó vào một Scala case class (để sử dụng như thông báo với một hệ thống diễn viên), nhưng không nhận được các định nghĩa đúng như vậy mà tôi không nhận được ít nhất một cảnh báo trình biên dịch.

// still Java code 
ScalaMessage msg = new ScalaMessage(payload); 

này đưa ra một cảnh báo trình biên dịch "Loại an toàn: contructor ... thuộc loại thô ..."

Các Scala case class được định nghĩa là:

// Scala code 
case class ScalaMessage[T <: SomeBaseType](payload: Wrapper[T]) 

Làm thế nào tôi có thể xác định các trường hợp lớp như vậy mà mã biên dịch sạch? (Buồn thay, thay đổi mã của lớp Java Wrapper hoặc kiểu của tham số payload không phải là một lựa chọn)

Cập nhật để làm rõ nguồn gốc của tải trọng tham số

Added Để so sánh, trong Java Tôi có thể xác định tham số giống như cách biến số payload được xác định:

// Java code 
void doSomethingWith(Wrapper<? extends SomeBaseType> payload) {} 

và gọi nó là acco rdingly

// Java code 
doSomethingWith(payload) 

Nhưng tôi không thể khởi tạo, ví dụ: một đối tượng Wrapper trực tiếp mà không nhận được cảnh báo "kiểu thô". Ở đây, tôi cần phải sử dụng một phương pháp static helper:

static <T> Wrapper<T> of(T value) { 
    return new Wrapper<T>(value); 
} 

và sử dụng helper tĩnh này để tạo một đối tượng Wrapper:

// Java code 
MyDerivedType value = ... // constructed elsewhere, actual type is not known! 
Wrapper<? extends SomeBaseType> payload = Wrapper.of(value); 

Giải pháp

tôi có thể thêm một phương pháp helper tương tự đối tượng đồng hành Scala:

// Scala code 
object ScalaMessageHelper { 
    def apply[T <: SomeBaseType](payload: Wrapper[T]) = 
     new ScalaMessage(payload) 
} 
object ScalaMessageHelper2 { 
    def apply[T <: SomeBaseType](payload: Wrapper[T]) = 
     ScalaMessage(payload) // uses implicit apply() method of case class 
} 

và sử dụng từ Java để nhanh chóng lớp ScalaMessage w/o vấn đề:

// Java code 
ScalaMessage msg = ScalaMessageHelper.apply(payload); 

Trừ khi ai đó đến với một giải pháp thanh lịch hơn, tôi sẽ trích xuất này như một câu trả lời ...

Cảm ơn bạn !

Trả lời

3

Tôi nghĩ rằng vấn đề là ở Java nếu bạn làm như sau:

ScalaMessage msg = new ScalaMessage(payload); 

Sau đó, bạn đang instantiating ScalaMessage sử dụng thô loại này. Hay nói cách khác, bạn sử dụng ScalaMessage như một loại không chung chung (khi Java giới thiệu generics, chúng giữ khả năng xử lý một lớp chung chung là một lớp không chung chung, chủ yếu là cho tương thích ngược).

Bạn chỉ nên xác định các thông số loại khi instantiating ScalaMessage:

// (here T = MyDerivedType, where MyDerivedType must extend SomeBaseType 
ScalaMessage<MyDerivedType> msg = new ScalaMessage<>(payload); 

CẬP NHẬT: Sau khi nhìn thấy comment của bạn, tôi thực sự đã thử nó trong một dự án giả, và tôi thực sự nhận được một lỗi:

[error] C:\Code\sandbox\src\main\java\bla\Test.java:8: cannot find symbol 
[error] symbol : constructor ScalaMessage(bla.Wrapper<capture#64 of ? extends bla.SomeBaseType>) 
[error] location: class test.ScalaMessage<bla.SomeBaseType> 
[error]  ScalaMessage<SomeBaseType> msg = new ScalaMessage<SomeBaseType>(payload); 

Dường như không khớp giữa các Generics java (chúng ta có thể mô phỏng thông qua exitsentials trong Scala) và Generics Scala. Bạn có thể khắc phục điều này bằng cách chỉ thả các tham số gõ vào ScalaMessage và sử dụng existentials thay vì:

case class ScalaMessage(payload: Wrapper[_ <: SomeBaseType]) 

và sau đó nhanh chóng nó trong java như thế này:

new ScalaMessage(payload) 

này hoạt động. Tuy nhiên, bây giờ ScalaMessage không phải là chung chung nữa, có thể là một vấn đề nếu bạn muốn sử dụng nó với các paylod tinh tế hơn (nói một Wrapper<? extends MyDerivedType>).

Để khắc phục điều này, chúng ta hãy làm thêm một thay đổi nhỏ cho ScalaMessage:

case class ScalaMessage[T<:SomeBaseType](payload: Wrapper[_ <: T]) 

Và sau đó trong java:

ScalaMessage<SomeBaseType> msg = new ScalaMessage<SomeBaseType>(payload); 

vấn đề giải quyết :)

+0

Sẽ không làm việc như tôi thực sự có một tham số phương pháp của loại 'Wrapper 'trong Java được truyền vào khối mã mà chúng ta đang thảo luận. Tôi sẽ cập nhật câu hỏi gốc cho phù hợp. –

+0

Tôi đã cập nhật, hãy kiểm tra. –

1

Những gì bạn đang trải qua là thực tế là Java Generics được triển khai kém. Bạn không thể thực hiện chính xác hiệp phương sai và đối nghịch trong Java và bạn phải sử dụng ký tự đại diện.

case class ScalaMessage[T <: SomeBaseType](payload: Wrapper[T]) 

Nếu bạn cung cấp một Wrapper[T], điều này sẽ làm việc một cách chính xác và bạn sẽ tạo một thể hiện của một ScalaMessage[T]

gì bạn muốn làm là để có thể tạo ra một ScalaMessage[T] từ một Wrapper[K] nơi K<:T là không biết. Tuy nhiên, điều này chỉ có thể nếu

Wrapper[K]<:Wrapper[T] for K<:T 

Đây chính xác là định nghĩa về phương sai. Kể từ khi Generics trong Java là bất biến, hoạt động là bất hợp pháp.Giải pháp duy nhất mà bạn có là thay đổi chữ ký của các nhà xây dựng

class ScalaMessage[T](wrapper:Wrapper[_<:T]) 

Tuy nhiên, nếu Wrapper được thực hiện một cách chính xác trong Scala sử dụng loại sai

class Wrapper[+T] 
class ScalaMessage[+T](wrapper:Wrapper[T]) 

object ScalaMessage { 
    class A 
    class B extends A 

    val myVal:Wrapper[_<:A] = new Wrapper[B]() 

    val message:ScalaMessage[A] = new ScalaMessage[A](myVal) 
} 

Tất cả mọi thứ sẽ suôn sẻ biên dịch và thanh lịch :)

+0

+1 để có giải thích chi tiết về các hệ thống kiểu Java/Scala –

Các vấn đề liên quan