Hamcrest 实际上遵循 JavaBeans 标准(允许任意访问器名称),所以我们可以使用hasProperty 来做到这一点。如果你想。不过,我不确定你是否这样做 - 这很麻烦。
如果我们遵循the source for HasPropertyWithValue 的工作原理,我们会发现它通过在相关类的BeanInfo 中查找属性的PropertyDescriptor 来发现访问器方法的名称,通过java.beans.Introspector 检索。
Introspector 有一些非常有用的文档,说明如何解决给定类的 BeanInfo:
Introspector 类为工具提供了一种标准的学习方式
关于目标 Java 支持的属性、事件和方法
豆子。
对于这三种信息中的每一种,Introspector 将
分别分析bean的类和超类寻找
显式或隐式信息,并使用该信息
构建一个全面描述目标的 BeanInfo 对象
豆子。
对于每个“Foo”类,如果有
存在一个对应的“FooBeanInfo”类,它提供了一个非空
查询信息时的值。我们首先查找 BeanInfo
通过获取目标 bean 的完整包限定名称来进行类
类并附加“BeanInfo”以形成新的类名。如果这
失败,然后我们取该名称的最终类名组件,并且
在 BeanInfo 中指定的每个包中查找该类
包搜索路径。
因此,对于诸如“sun.xyz.OurButton”之类的类,我们首先要查找
名为“sun.xyz.OurButtonBeanInfo”的 BeanInfo 类,如果失败
我们将在 BeanInfo 搜索路径中的每个包中查找
我们的ButtonBeanInfo 类。使用默认搜索路径,这意味着
正在寻找“sun.beans.infos.OurButtonBeanInfo”。
如果一个类提供了关于它自己的显式 BeanInfo,那么我们将它添加到
我们通过分析任何派生得到的 BeanInfo 信息
类,但我们认为显式信息是确定性的
对于当前类及其基类,不进行任何
进一步提升超类链。
如果我们没有在一个类上找到显式的 BeanInfo,我们使用低级
反思学习课堂方法和应用标准设计
识别属性访问器、事件源或公共的模式
方法。然后我们继续分析该类的超类并添加
来自它的信息(可能在超类链上)。
您可能认为Introspector 可以在最后一步(“我们使用低级反射”)中挖掘记录并生成正确的BeanInfo,但似乎不是。如果你谷歌一下,你会在 JDK 开发列表上找到一些关于添加它的讨论,但似乎什么也没发生。可能是 JavaBeans 规范必须更新,我想这可能需要一些时间。
但是,要回答您的问题,我们所要做的就是为您拥有的每种记录类型提供一个BeanInfo。然而,手写它们并不是我们想做的事情——它甚至比使用 getter 和 setter(以及 equals 和 hashCode 等等)编写类的老式方式更糟糕。
我们可以在构建步骤中自动生成 bean 信息(或者在我们启动应用程序时动态生成)。一种更简单的方法(需要一些样板文件)是制作可用于所有记录类的通用BeanInfo。这是一种最小努力的方法。首先,假设我们有这个记录:
public record Point(int x, int y){}
还有一个将其视为 bean 的主类:
public class Main {
public static void main(String[] args) throws Exception {
var bi = java.beans.Introspector.getBeanInfo(Point.class);
var bean = new Point(4, 2);
for (var prop : args) {
Object value = Stream.of(bi.getPropertyDescriptors())
.filter(pd -> pd.getName().equals(prop))
.findAny()
.map(pd -> {
try {
return pd.getReadMethod().invoke(bean);
} catch (ReflectiveOperationException e) {
return "Error: " + e;
}
})
.orElse("(No property with that name)");
System.out.printf("Prop %s: %s%n", prop, value);
}
}
}
如果我们像java Main x y z 那样编译和运行,你会得到这样的输出:
Prop x: (No property with that name)
Prop y: (No property with that name)
Prop z: (No property with that name)
所以它没有像预期的那样找到记录组件。让我们创建一个通用的BeanInfo:
public abstract class RecordBeanInfo extends java.beans.SimpleBeanInfo {
private final PropertyDescriptor[] propertyDescriptors;
public RecordBeanInfo(Class<?> recordClass) throws IntrospectionException {
if (!recordClass.isRecord())
throw new IllegalArgumentException("Not a record: " + recordClass);
var components = recordClass.getRecordComponents();
propertyDescriptors = new PropertyDescriptor[components.length];
for (var i = 0; i < components.length; i++) {
var c = components[i];
propertyDescriptors[i] = new PropertyDescriptor(c.getName(), c.getAccessor(), null);
}
}
@Override
public PropertyDescriptor[] getPropertyDescriptors() {
return this.propertyDescriptors.clone();
}
}
在我们的工具箱中有这个类,我们所要做的就是用一个正确名称的类来扩展它。对于我们的示例,PointBeanInfo 与 Point 记录在同一个包中:
public class PointBeanInfo extends RecordBeanInfo {
public PointBeanInfo() throws IntrospectionException {
super(Point.class);
}
}
所有这些东西都准备好后,我们运行我们的主类并获得预期的输出:
$ java Main x y z
Prop x: 4
Prop y: 2
Prop z: (No property with that name)
结束语:如果您只是想使用属性使您的单元测试看起来更好,我建议使用其他答案中给出的解决方法之一,而不是我提出的过度设计的方法。