Java 8接口默认方法似乎没有声明属性

17
在我的应用程序中,当一个类的getter只有在接口中默认时(Java 8特性),就没有Java Beans属性。也就是说,对于普通方法调用,它的工作方式与标准方法相同,但对于通过“属性”访问时,它会突然表现出不同的行为...
以下是一个测试案例:
import java.beans.Introspector;
import java.util.Arrays;
import java.util.stream.Collectors;
import org.apache.commons.beanutils.PropertyUtils;

public class test
{
    public static void main (String[] arguments) throws Exception
    {
        // Normal language-level invocation, works fine.
        System.out.println (new Bean1 ().getFoo ());
        System.out.println (new Bean2 ().getFoo ());

        // Printing Java Beans properties; Bean2 doesn't have 'foo' property...
        System.out.println (Arrays.stream (Introspector.getBeanInfo (Bean1.class).getPropertyDescriptors ())
                            .map ((property) -> property.getName ())
                            .collect (Collectors.joining (", ")));
        System.out.println (Arrays.stream (Introspector.getBeanInfo (Bean2.class).getPropertyDescriptors ())
                            .map ((property) -> property.getName ())
                            .collect (Collectors.joining (", ")));

        // First call behaves as expected, second dies with exception.
        System.out.println (PropertyUtils.getProperty (new Bean1 (), "foo"));
        System.out.println (PropertyUtils.getProperty (new Bean2 (), "foo"));
    }

    public interface Foo
    {
        default String getFoo ()
        {
            return "default foo";
        }
    }

    public static class Bean1 implements Foo
    {
        @Override
        public String getFoo ()
        {
            return "special foo";
        }
    }

    public static class Bean2 implements Foo
    { }
}

结果:

special foo
default foo
class, foo
class
special foo
Exception in thread "main" java.lang.NoSuchMethodException: Unknown property 'foo' on class 'class test$Bean2'
        at org.apache.commons.beanutils.PropertyUtilsBean.getSimpleProperty(PropertyUtilsBean.java:1257)
        at org.apache.commons.beanutils.PropertyUtilsBean.getNestedProperty(PropertyUtilsBean.java:808)
        at org.apache.commons.beanutils.PropertyUtilsBean.getProperty(PropertyUtilsBean.java:884)
        at org.apache.commons.beanutils.PropertyUtils.getProperty(PropertyUtils.java:464)
        at test.main(test.java:21)

问题: 我是做错了什么还是Java里有个bug?除了永远不使用默认方法(用于getter/setter)以防在以后某个时刻需要将它们作为“属性”访问之外,是否还有其他解决方法?

我一直讨厌Java中的“约定属性”,因为它们往往会因为你打喷嚏而破坏。


6
看起来这个问题已经在JDK-8071693中被涉及到了,但是还没有在任何JDK版本中得到修复。 - Stuart Marks
是的,确实如此。希望他们能够修复它。 - user319799
我也被这个 bug 打败了。OpenJDK bug 计划在 Java 9 中修复,这意味着我们将不得不等到至少 2016 年 9 月才能解决它。同时,我会在需要此属性的类中创建一个委托方法。 - Henno Vermeulen
@HennoVermeulen:是的,问题在于我经常使用属性与EL和脚本语言进行交互。而且这个bug基本上让我对默认方法心存戒备,因为Java中不受编译器检查的属性可能会在没有通知的情况下损坏。尤其是在将某些标准实现移动到默认接口方法时特别烦人:这种看似安全的操作可能会搞砸某些东西,而事先没有好的方法可以告诉你。 - user319799
1
@StuartMarks:有没有什么办法可以解决JDK的这个bug?我的代码因为这个问题被强制加上了@Override public Foo getFoo () { return MyInterface.super.getFoo (); }... - user319799
JDK-8071693 现已作为 Java 21 的一部分得到修复。 - undefined
3个回答

3

这似乎是Beans Introspector中的一个错误遗漏。以下是一个解决方法,而不是不使用default方法:

public static void main (String[] arguments) throws Exception {
    testBean(new Bean1());
    System.out.println();
    testBean(new Bean2());
}
static void testBean(Object bean) throws Exception {
    PropertyDescriptor[] pd
        = Introspector.getBeanInfo(bean.getClass()).getPropertyDescriptors();
    System.out.println(Arrays.stream(pd)
        .map(PropertyDescriptor::getName).collect(Collectors.joining(", ")));
    for(PropertyDescriptor p: pd)
        System.out.println(p.getDisplayName()+": "+p.getReadMethod().invoke(bean));
}
public interface Foo {
    default String getFoo() {
        return "default foo";
    }
}
public static class Bean1 implements Foo {
    @Override
    public String getFoo() {
        return "special foo";
    }
}
public static class Bean2BeanInfo extends SimpleBeanInfo {
    private final BeanInfo ifBeanInfo;
    public Bean2BeanInfo() throws IntrospectionException {
        ifBeanInfo=Introspector.getBeanInfo(Foo.class);
    }
    @Override
    public BeanInfo[] getAdditionalBeanInfo() {
        return new BeanInfo[]{ifBeanInfo};
    }
}
public static class Bean2 implements Foo { }

class, foo
class: class helper.PropTest$Bean1
foo: special foo
class, foo
class: class helper.PropTest$Bean2
foo: default foo

3
老实说,这比从一开始就不使用默认方法更加不方便。 - user319799
1
嗯,你说你讨厌“约定属性”,那么使用显式的beaninfo有什么问题呢?但无论如何,Java 8允许您创建基于函数的类似属性的对象,只需一行代码即可完成,因此我不认为需要反射工具来实现这一点。尽管如此,唯一的选择是等待Oracle修复这个问题... - Holger
Holger: 我想要明确的属性,但如果Java有一个合理的语法来支持它们,而不是在现有机制的基础上进行一些修改。你的方法的一个巨大问题是所有内容都分散在不同的地方,所以你需要在一个地方获取/设置getter/setter,在另一个地方获取bean信息等等。 - user319799
顺便问一下,你所说的“基于函数的类似属性的对象”是什么意思? - user319799
1
我并没有看到有什么东西分散在不同的地方。Bean2BeanInfo就在描述它的Bean2类中,它只是说明Bean2应该包含Foo的信息,这与其下方七行处的Bean2 implements Foo所表达的是完全相同的意思。bean类和bean信息类在一个地方形成一对。而default方法在其他地方的位置是default方法本身的属性。 - Holger
1
“基于函数的属性对象” 意味着拥有像 class Property<B,V> { String name; Function<B,V> getter; BiConsumer<B, V> setter; } 这样的东西,再加上一些常规操作,比如说工厂方法等。根据不同的使用情况,它甚至不需要名称。然后,可以像这样声明一个属性 readOnly(Bean1::getFoo),假设有一个合适的工厂方法,那么你就会得到一个 Property<Bean1,String>。正如所说的,声明类的所有属性可能只需要一行代码,从而产生一个属性数组或者一个 Map<String,Property<Bean,?>>,等等... - Holger

1

我不确定我的回答是否有帮助,但我通过使用Spring中的BeanUtils.getPropertyDescriptors(clazz)解决了类似的问题。它可以理解默认方法。


我认为这是不正确的。我猜测你正在使用Apache的BeanUtils库,其中包含一个方法 PropertyUtils.getPropertyDescriptors(Class)。该方法使用与所讨论的java.beans.Introspector相同的Introspector(来源)。 - Jeff Brower
正如我所说,我的解决方案是使用Spring的BeanUtils,而不是Apache Commons中的一个。 - Anton Rybochkin
这不是关于我自己访问属性的问题,而是让我无法控制的代码(EL脚本实现)看到有一个属性。 - user319799

0
一个快速的解决方法:
try {
    return PropertyUtils.getProperty(bean, property);
}
catch (NoSuchMethodException e) {
    String getMethod = "get" + property.substring(0, 1).toUpperCase() + property.substring(1);
    return MethodUtils.invokeMethod(bean, getMethod, new Object[]{});
}

这对我的情况没有帮助,因为我需要属性可以从Java EL中可见。也就是说,我无法控制它们被访问的位置。 - user319799

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接