代码之家  ›  专栏  ›  技术社区  ›  jthg

Java中类型安全方法的思考

  •  5
  • jthg  · 技术社区  · 16 年前

    是否有任何实际的方法以类型安全的方式引用类上的方法?一个基本的例子是,如果我想创建如下实用程序函数:

    public Result validateField(Object data, String fieldName, 
                                ValidationOptions options) { ... }
    

    要叫它,我必须:

    validateField(data, "phoneNumber", options);
    

    这迫使我要么使用一个魔法字符串,要么用这个字符串声明一个常量。

    我敢肯定,没有办法绕过Java Java语言,但是有没有某种(生产级)预编译器或替代编译器可以提供工作?(类似于AspectJ如何扩展Java语言)最好做如下的事情,而不是:

    public Result validateField(Object data, Method method, 
                                ValidationOptions options) { ... }
    

    并用以下方式称呼它:

    validateField(data, Person.phoneNumber.getter, options);
    
    8 回复  |  直到 7 年前
        1
  •  4
  •   PSpeed    16 年前

    正如其他人所说,没有真正的方法可以做到这一点……我还没有见过支持它的预编译程序。至少可以说,语法会很有趣。即使在您的示例中,它也只能覆盖一小部分潜在的反射可能性,用户可能希望这样做,因为它不会处理接受参数的非标准访问器或方法等。

    即使在编译时无法检查,如果希望坏代码尽快失败,那么一种方法是在类初始化时解析引用的方法对象。

    假设您有一个实用程序方法来查找可能引发错误或运行时异常的方法对象:

    public static Method lookupMethod( Class c, String name, Class... args ) {
        // do the lookup or throw an unchecked exception of some kind with a really
        // good error message
    }
    

    然后在类中,使用常量预解析要使用的方法:

    public class MyClass {
        private static final Method GET_PHONE_NUM = MyUtils.lookupMethod( PhoneNumber.class, "getPhoneNumber" );
    
        ....
    
        public void someMethod() {
            validateField(data, GET_PHONE_NUM, options);
        }
    }
    

    至少在第一次加载MyClass时它会失败。

    我经常使用反射,尤其是bean属性反射,并且我刚刚习惯了在运行时延迟异常。但是,由于其他各种原因,这种风格的bean代码往往很晚才出错,而且非常动态。对于介于两者之间的某件事,上述内容会有所帮助。

        2
  •  3
  •   Jon Skeet    16 年前

    语言里没有任何东西 然而 但是,我相信Java 7的闭包提议的一部分包括方法文本。

    恐怕我没有别的建议。

        3
  •  2
  •   Stijn Van Bael    12 年前

    Java错过了语法糖做一些不错的 Person.phoneNumber.getter . 但如果person是一个接口,则可以使用动态代理记录getter方法。您也可以使用cglib在非final类上记录方法,就像mockito那样。

    MethodSelector<Person> selector = new MethodSelector<Person>(Person.class);
    selector.select().getPhoneNumber();
    validateField(data, selector.getMethod(), options);
    

    方法选择器的代码: https://gist.github.com/stijnvanbael/5965609

        4
  •  2
  •   Dmitry Minkovsky    10 年前

    退房 http://jodd.org/doc/methref.html . 它使用jodd代理库(proxetta)来代理您的类型。不确定它的性能特征,但它确实提供了类型安全性。

    例如:假设 Str.class 有方法 .boo() ,您希望将其名称作为字符串 "boo" :

    Methref<Str> m = Methref.on(Str.class);
    
    // `.to()` returns a proxied instance of `Str` upon which you
    // can call `.boo()` Methods on this proxy are empty except when
    // you call them, the proxy stores the method's name. So doing this
    // gets the proxy to store the name `"boo"`.
    
    m.to().boo();
    
    // You can bget the name of the method you called by using `.ref()`:
    
    m.ref();   // returns "boo"                                 
    

    API比上面的例子更多: http://jodd.org/api/jodd/methref/Methref.html

        5
  •  1
  •   Stephen C    11 年前

    是否有任何实际的方法以类型安全的方式引用类上的方法?

    首先,反思 类型安全。它只是动态类型化,而不是静态类型化。

    所以,假设你想要一个 静态类型 相当于反射,理论上的答案是不可能。考虑一下:

    Method m;
    if (arbitraryFunction(obj)) {
        obj.getClass().getDeclaredMethod("foo", ...);
    } else {
        obj.getClass().getDeclaredMethod("bar", ...);
    }
    

    我们可以这样做以避免运行时类型异常发生吗?一般来说不是,因为这需要证明 arbitraryFunction(obj) 终止。(这相当于一般证明是不可解决的停顿问题,并且使用最先进的定理证明技术难以解决)。哎呀!

    我认为,这个路径块适用于任何方法,您可以将任意Java代码注入到用于从对象的类中反射性地选择方法的逻辑中。

    在我看来,目前唯一实用的方法是用生成和编译Java源代码的东西替换反射代码。如果这个过程发生在您“运行”应用程序之前,那么您已经满足了静态类型安全的要求。


    我更关心的是反思,在反思中,结果总是一样的。即。 Person.class.getMethod("getPhoneNumber", null) 将始终返回相同的方法,并且完全可以在编译时解析它。

    如果在编译包含此代码的类之后, 改变 Person 去掉 getPhoneNumber 方法?

    你唯一能确定你能解决的方法 获得电话号码 反省的是如果你能 防止 从被改变。但是在Java中你不能这么做。类的运行时绑定是语言的基本部分。

    (记录下,如果对一个非反射调用的方法执行此操作,您将得到一个 IncompatibleClassChangeError 当两个类被加载时……)

        6
  •  0
  •   Robert Jack Will    14 年前

    在模拟框架的启发下,我们可以想出以下语法:

    validator.validateField(data, options).getPhoneNumber();
    Result validationResult = validator.getResult();
    

    诀窍是通用声明:

    class Validator {
        public <T> T validateField(T data, options) {...}
    }
    

    现在,方法的返回类型与数据对象的类型相同,您可以使用代码完成(和静态检查)来访问所有方法,包括getter方法。

    作为一个缺点,代码读起来不是很直观,因为对getter的调用实际上没有得到任何东西,而是指示验证器验证字段。

    另一个可能的选项是注释数据类中的字段:

    class FooData {
        @Validate(new ValidationOptions(...))
        private PhoneNumber phoneNumber;
    }
    

    然后打电话给:

    FooData data;
    validator.validate(data);
    

    根据注释选项验证所有字段。

        7
  •  0
  •   CoronA    10 年前

    框架 picklock 允许您执行以下操作:

    class Data {
      private PhoneNumber phoneNumber;
    }
    
    interface OpenData {
      PhoneNumber getPhoneNumber(); //is mapped to the field phoneNumber
    }
    
    Object data = new Data();
    PhoneNumber number = ObjectAccess
      .unlock(data)
      .features(OpenData.class)
      .getPhoneNumber();
    

    这与setter和private方法的工作方式类似。当然,这只是一个反射包装器,但异常不会在解锁时发生,而不会在调用时发生。如果您在构建时需要它,您可以编写一个单元测试:

     assertThat(Data.class, providesFeaturesOf(OpenData.class));
    
        8
  •  0
  •   Scott    7 年前

    使用歧管 @Jailbreak 编译时 类型安全 访问私有字段、方法等。

    @Jailbreak Foo foo = new Foo();
    foo.privateMethod();
    foo.privateMethod("hey");
    foo._privateField = 88;
    

    public class Foo {
      private final int _privateField;
    
      public Foo(int value) {
        _privateField = value;
      }
    
      private String privateMethod() {
        return "hi";
      }
    
      private String privateMethod(String param) {
        return param;
      }
    }
    

    了解更多信息: type-safe-reflection