2012-03-24 13 views
10

Java afirma ser orientada a objetos y segura en cuanto a tipos, y Scala aún más.Java/Scala obtiene una referencia de campo de manera segura

Los campos de la clase interna están representados por la clase llamada Campo, a la que puede obtener una referencia a través de Reflection API.

Mi pregunta: ¿estos idiomas proporcionan alguna forma de obtener esa referencia de campo en un tipo seguro manera? (Y si no, ¿por qué no? Parece una deficiencia evidente)

Sería extremadamente útil al asignar un objeto a una representación externa, por ejemplo, a campos html en una plantilla, o a nombres de columna en una base de datos , para mantener los nombres de referencia automáticamente sincronizados.

Idealmente me gustaría decir algo como:

&(SomeClass.someField).name() 

para obtener el nombre de la declaración de campo, similar a cómo las enumeraciones de Java le permiten decir:

MyEnum.SOME_INSTANCE.name() 

[actualización] después de leer los comentarios de que esta funcionalidad de alguna manera violaría la intención de Reflection API, acepto que Reflection está diseñado para cosas que no se conocen en tiempo de compilación, y es exactamente por eso que es tan absurdo tener que usarlo para aprender cosas que son conocido en tiempo de compilación, es decir, los campos de la misma clase que está compilando!

El compilador proporciona esto para enumeraciones, por lo que si el compilador puede acceder a la referencia del campo enum para permitir MyEnum.SOME_INSTANCE.name(), entonces no hay ninguna razón lógica por la que no debería ser capaz de proporcionar este mismo funcionalidad a las clases ordinarias.

¿Hay alguna razón tecnológica por la cual esta funcionalidad no podría estar disponible para las clases ordinarias? No veo por qué no, y no estoy de acuerdo con que esta funcionalidad pueda "complicar" las cosas ... por el contrario, simplificaría en gran medida las engorrosas técnicas actuales de Reflection API. ¿Por qué obligar a los desarrolladores a Reflection a descubrir algo que se conoce en tiempo de compilación?

[actualización n.º 2] en cuanto a la utilidad de esta función, ¿alguna vez ha intentado utilizar la API de criterios en JPA o Hibernate para construir dinámicamente una consulta? ¿Has visto las absurdas soluciones que la gente ha inventado para evitar tener que pasar una representación insegura de String del campo para consultar?

[actualización # 3] Finalmente, un nuevo lenguaje JVM llamado Ceylon ha atendido la llamada y hace que este trivial haga!

+0

Realmente no entiendo su punto. Si '& (SomeClass.someField) .name()' se puede detectar estáticamente, ¿por qué no simplemente escribir 'SomeClass.someField.name()'? Es 'SomeClass.someField' una cadena? En caso afirmativo, ¿cómo debe el compilador reconocer su valor (se puede completar en tiempo de ejecución)? – sschaef

+0

El punto no es obtener el valor de String, sino obtener el nombre que ha asignado a ese campo de String. Por ejemplo, si el campo se definió como: public String title = "president"; luego quiero llamar a & (SomeClass.title) .name() y obtener el "título" – Magnus

+0

Si conoce el nombre, ¿por qué no simplemente acceder a él directamente? Mencionaste algo con "nombres de columna en una base de datos", pero cuando cambias el campo también tienes que cambiar todas las referencias a él. Y esto se puede hacer con IDEs modernos sin usar la función que desea tener. – sschaef

Respuesta

6

Mi pregunta: ¿estos idiomas proporcionan alguna forma de obtener esa referencia de campo de forma segura?

Tiempo de compilación tipo seguro? No es que yo sepa, al menos en Java. El propósito normal de la reflexión en Java es que el código sea capaz de tratar con tipos de los que no tiene conocimiento previo; es raro (en mi experiencia) estar en una posición en la que desee para poder referirse a un campo en un tipo conocido. Es hace suceder, pero no es muy común.

(Y si no, ¿por qué no?Parece una flagrante deficiencia)

Cada característica necesita ser diseñada, implementada, probada, y tiene que cumplir el objetivo de proporcionar más valor que la complejidad añadida en el lenguaje.

Personalmente puedo pensar en las características que preferiría ver en Java que esto.

+2

Postularía que la única razón por la que es raro * querer * es porque las personas no * lo * vieron *. Creo que si esta característica estuviera disponible, surgiría toda una clase de patrones de programación para abordar en tiempo de compilación lo que actualmente solo se aborda tediosamente en el tiempo de ejecución. – Magnus

+2

@Magnus: Consideraría que "actualmente se está tratando tediosamente en el tiempo de ejecución" como "querer poder referirme a un campo de un tipo conocido". No estoy diciendo que * no * sea útil, y sé que el equipo de C# ha considerado algo similar (http://blogs.msdn.com/b/ericlippert/archive/2009/05/21/in-foof -we-trust-a-dialogue.aspx) pero todavía no creo que sea la revelación transformadora del mundo que estás implicando. –

+2

¡Gracias por el enlace, discusión muy interesante! Sin embargo, mi propuesta, al apuntar a los campos y no a los métodos, evita las trampas de la "ambigüedad de sobrecarga" descritas, y la pregunta de alcance parece no tener importancia: simplemente siga las reglas normales de alcance de campo como siempre. No obstante, una discusión esclarecedora. – Magnus

1

Por qué no es porque no conoce el tipo de campo en tiempo de compilación al usar la reflexión. Este es el punto de reflexión: para darle acceso a información de clase en tiempo de ejecución. Por supuesto, obtendrá un error de tiempo de ejecución si utiliza el tipo incorrecto, pero eso realmente no ayuda mucho.

Desafortunadamente, aunque es difícil mantener los nombres iguales, es incluso más complicado mantener los tipos iguales, por lo que probablemente no valga la pena para la aplicación que tiene en mente.

En este momento no hay forma de hacer lo que quiera con un esfuerzo razonable en Scala o Java. Scala podría agregar esta información a sus manifiestos (o en otro lugar), pero no lo hace ahora y no me queda claro que valga la pena el esfuerzo.

+3

Pero * conocemos * el tipo de campo en tiempo de compilación, ¡está ahí en la definición de clase! Si la reflexión me permite obtener la referencia de campo a través de su nombre de cadena, ¿por qué no me permite tomar directamente la referencia de la definición de clase? – Magnus

+0

Usted conoce el tipo pero _not vía reflection_. El compilador podría poner a disposición un nameOf (myClass).x) método (ya sea en Scala o Java), pero no podría hacer razonablemente un método getField (myClass, "x") que fue tipado sensiblemente sin características de lenguaje dramáticamente diferentes (es decir, '" x "' y '" date "' son ambos solo cadenas, pero sobre la base del _contento_ de la cadena que le gustaría al compilador para inferir dos tipos diferentes ... ¿qué pasaría si esa cadena fuera simplemente 's'?). Entonces, de todos modos, reflejando tipos en tiempo de compilación: no, no es fácil. Conversión estática del nombre de campo a cadena: posible. –

+4

Ese es exactamente mi punto: habría sido fácil para los diseñadores de idiomas proporcionar esta función, pero simplemente no lo hicieron. En otras palabras, es una omisión (o diría "defecto") en el idioma. – Magnus

2

En Scala puede usar macros para ello. Véase el siguiente:

Ejemplo:

class Car(val carName: String); 

object Main { 
    def main(args: Array[String]): Unit = { 
    println(FieldNameMacro.getFieldName[Car](_.carName)) 
    } 
} 

Así que esto imprime el nombre del campo "carName". Si cambia el nombre del campo "carName" por "cName", imprimirá "cName" en su lugar.

Macro:

En este caso, en realidad el árbol de expresión "_.carName" se pasa al controlador de macro, más bien un método ejecutable. En nuestra macro podemos ver este árbol de expresiones y encontrar el nombre del campo al que nos referimos.

import scala.reflect.macros.whitebox.Context 
import scala.language.experimental.macros 
import scala.reflect.runtime.universe._ 
import scala.reflect.ClassTag 

object FieldNameMacro { 
    def getFieldNameImpl[T](c: Context)(block: c.Expr[T => AnyRef]): c.Expr[String] = { 
    import c.universe._ 
    // here we look inside the block-expression and 
    // ... bind the TermName "carName" to the value name 
    val Expr(Function(_, Select(_, TermName(name: String)))) = block; 
    // return the name as a literal expression 
    c.Expr(Literal(Constant(name))); 
    // Uncomment this to get an idea of what is "inside" the block-expression 
    // c.Expr(Literal(Constant(showRaw(block)))); 
    } 

    def getFieldName[T](block: (T) => AnyRef): String = macro getFieldNameImpl[T] 
} 

Me tomó algo de inspiración de http://blogs.clariusconsulting.net/kzu/linq-beyond-queries-strong-typed-reflection/. La publicación trata sobre el mismo problema, pero con respecto a C#.


Deficiencias

Tenga en cuenta que la macro tiene que ser llamado exactamente como anteriormente. Por ejemplo, el siguiente uso dará lugar a una excepción de compilación (en realidad es una excepción de coincidencia de Scala dentro de la macro).

object Main { 
    def main(args: Array[String]): Unit = { 
    val block = (car : Car) => car.carName; 
    println(FieldNameMacro.getFieldName[Car](block)) 
    } 
} 

El problema es que se pasa un árbol de expresiones diferente al manejador de macro. Para obtener más detalles sobre este problema, consulte Scala Macro get value for term name

2

En Scala 2.11 podemos utilizar esto:

object R_ { 
    def apply[T](x: (T) => AnyRef): (Class[T], Method) = macro impl 
    def impl(c: whitebox.Context)(x: c.Tree) = { import c.universe._ 
    val q"((${_: TermName}:${a: Type}) => ${_: TermName}.${p: TermName})" = x 

    val typeDef = a.typeSymbol 
    val propertyDef = p.toString 

    q"(classOf[$typeDef], classOf[$typeDef].getMethod($propertyDef))" 
    } 
} 

Uso:

class User(val name: String) 

object Test extends App { 
    println(R_((a: User) => a.name)) 
} 

y el resultado será:

(mref.User clase, java.lang.String pública mref.User.name())

3

Es una pena que Java todavía pierda esta característica. Esta característica no agregaría complejidad adicional porque interferiría con otros aspectos del lenguaje. Además, ser una característica que se usaría raramente no es excusa. Cada idioma está lleno de características y la mayoría de los proyectos hacen uso de un pequeño subconjunto de ellos.

Realmente no entiendo por qué el lenguaje me permite hacer esto:

campo Campo = MyClass.class.getField ("myField"); // sintaxis verbosa, evaluar en tiempo de ejecución, no de tipo seguro, debe lidiar con excepciones operación reflectantes

Pero no me deja hacer (algo así como) esto:

campo Campo = MyClass :: myField; // sintaxis compacta, evaluada en tiempo de compilación, seguro de tipo, sin excepciones!

(el operador "::" es sólo una sugerencia, tomado de Java o C++ 8)

Cuestiones relacionadas