2010-02-08 11 views
12

Tôi đang cố gắng để viết một đặc điểm (trong Scala 2.8) có thể được trộn lẫn vào một trường hợp lớp, cho phép các lĩnh vực của nó được kiểm tra trong thời gian chạy, cho một mục đích gỡ lỗi cụ thể. Tôi muốn đưa chúng trở lại theo thứ tự mà chúng đã được khai báo trong tệp nguồn và tôi muốn bỏ qua bất kỳ trường nào khác bên trong lớp chữ. Ví dụ:Reflection trên một trường hợp Scala

trait CaseClassReflector extends Product { 

    def getFields: List[(String, Any)] = { 
    var fieldValueToName: Map[Any, String] = Map() 
    for (field <- getClass.getDeclaredFields) { 
     field.setAccessible(true) 
     fieldValueToName += (field.get(this) -> field.getName) 
    } 
    productIterator.toList map { value => fieldValueToName(value) -> value } 
    } 

} 

case class Colour(red: Int, green: Int, blue: Int) extends CaseClassReflector { 
    val other: Int = 42 
} 

scala> val c = Colour(234, 123, 23) 
c: Colour = Colour(234,123,23) 

scala> val fields = c.getFields  
fields: List[(String, Any)] = List((red,234), (green,123), (blue,23)) 

Việc thực hiện ở trên là thiếu sót rõ ràng bởi vì nó đoán mối quan hệ giữa vị trí của một lĩnh vực trong sản phẩm và tên của nó bằng cách bình đẳng của các giá trị vào những lĩnh vực, do đó những điều sau đây, nói rằng, sẽ không làm việc :

Colour(0, 0, 0).getFields 

Có cách nào để thực hiện điều này không?

+0

Có một lỗi trong mã của bạn. Các giá trị không phải là duy nhất và do đó bạn sẽ ghi đè các giá trị bằng (field.get (this) -> field.getName) khi di chuyển hơn một trường có một tên đã cho. Xem phiên bản viết lại của mã bên dưới. –

+0

@SagieDavidovich Thật vậy, như đã nói, "việc thực hiện ở trên rõ ràng là thiếu sót" –

Trả lời

7

Trong mọi ví dụ tôi đã thấy các trường theo thứ tự ngược lại: mục cuối cùng trong mảng getFields là mảng đầu tiên được liệt kê trong lớp chữ hoa. Nếu bạn sử dụng các lớp chữ "độc đáo", thì bạn chỉ có thể ánh xạ productElement(n) lên getDeclaredFields()(getDeclaredFields.length-n-1). Nhưng điều này là khá nguy hiểm, vì tôi không biết bất cứ điều gì trong spec đó khẳng định rằng nó phải như vậy, và nếu bạn ghi đè một val trong trường hợp lớp, nó thậm chí sẽ không xuất hiện trong getDeclaredFields (nó sẽ xuất hiện trong các trường của siêu lớp đó).

Bạn có thể thay đổi mã để giả sử mọi thứ theo cách này, nhưng hãy kiểm tra xem phương thức getter có tên đó và productIterator trả về cùng một giá trị và ném một ngoại lệ nếu chúng không (nghĩa là bạn không thực sự biết cái gì tương ứng với cái gì).

+1

Tôi đang đối mặt với cùng một vấn đề như Matt R đang phải đối mặt. Là một noob tương đối ở Scala, bạn có thể giải thích câu trả lời của bạn thêm một chút nữa không. Điều đó sẽ khá hữu ích. Cảm ơn! –

+2

@Core_Dumped - Trên thực tế, tôi nghĩ rằng bạn có nhiều khả năng gặp rắc rối hơn là đưa ra giải pháp cho vấn đề của mình trừ khi bạn có thể sử dụng các gợi ý mơ hồ ở trên để tạo giải pháp của riêng bạn. Như tôi đã nói, "điều này khá nguy hiểm".Đó là trách nhiệm của bạn để có thể dự đoán và tránh các vấn đề, mà có thể yêu cầu chuyển từ "tương đối noob" sang "không" ít nhất trong khía cạnh này. Chơi xung quanh với các trường hợp phản ánh và mẫu trường hợp trong REPL và xem bạn có thể hình dung ra nó không! –

+0

getClass.getDeclaredFields.map (_. GetName) .zip (productIterator.toList) .toMap –

10

Nhìn vào thân cây và bạn sẽ tìm thấy điều này. Nghe bình luận, điều này không được hỗ trợ: nhưng kể từ khi tôi cũng cần những cái tên đó ...

/** private[scala] so nobody gets the idea this is a supported interface. 
*/ 
private[scala] def caseParamNames(path: String): Option[List[String]] = { 
    val (outer, inner) = (path indexOf '$') match { 
    case -1 => (path, "") 
    case x => (path take x, path drop (x + 1)) 
    } 

    for { 
    clazz <- getSystemLoader.tryToLoadClass[AnyRef](outer) 
    ssig <- ScalaSigParser.parse(clazz) 
    } 
    yield { 
    val f: PartialFunction[Symbol, List[String]] = 
     if (inner.isEmpty) { 
     case x: MethodSymbol if x.isCaseAccessor && (x.name endsWith " ") => List(x.name dropRight 1) 
     } 
     else { 
     case x: ClassSymbol if x.name == inner => 
      val xs = x.children filter (child => child.isCaseAccessor && (child.name endsWith " ")) 
      xs.toList map (_.name dropRight 1) 
     } 

    (ssig.symbols partialMap f).flatten toList 
    } 
} 
4

Bạn cũng có thể sử dụng ProductCompletion từ gói dịch viên để có được thuộc tính tên và giá trị của trường hợp lớp:

import tools.nsc.interpreter.ProductCompletion 

// get attribute names 
new ProductCompletion(Colour(1, 2, 3)).caseNames 
// returns: List(red, green, blue) 

// get attribute values 
new ProductCompletion(Colour(1, 2, 3)).caseFields 

Edit: gợi ý bởi roland và virtualeyes

Nó là cần thiết để bao gồm cácThư việnlà một phần của số scala-lang collection.

Cảm ơn các gợi ý của bạn, roland và virtualeyes.

+1

Lưu ý rằng lệnh gọi 'caseNames' chỉ hoạt động nếu scalap (http://www.scala-lang.org/node/292) có thể được tìm thấy trên classpath. Nếu không, một danh sách trống sẽ được trả lại (khi sử dụng Scala 2.9.1). –

+1

+1 @roland, đó là sự thật những gì bạn nói. Cho rằng tải xuống scalap không chính xác nhảy ra trong một tìm kiếm google, ở đây nó là dành cho 2.9.1: https://oss.sonatype.org/content/groups/scala-tools/org/scala-lang/scalap/2.9. 1/ – virtualeyes

+0

lưu ý bắt 22 của cần một trường hợp lớp trường hợp trước khi có thể phản ánh trên nó. Hy vọng rằng 2,10 sẽ mang lại hàng hóa để nói về vấn đề này, tay được gắn trong 2.9, làm việc với các trường hợp hộp đen là một PITA, gió lên gõ ra mô hình miền, lập bản đồ ORM, và xác thực trong ba lần, wtf ... – virtualeyes

9

Dưới đây là một phiên bản ngắn và làm việc, dựa trên ví dụ trên

trait CaseClassReflector extends Product { 
    def getFields = getClass.getDeclaredFields.map(field => { 
     field setAccessible true 
     field.getName -> field.get(this) 
    }) 
    } 
+0

I đã cố gắng điều này và nó đã làm việc, ít nhất là cho một trường hợp đơn giản lớp. –