java - 将 toString 与对象创建相关联

标签 java object reflection

我有一个相当基本的 Java 类,其中包含一些类变量。我已经覆盖 toString() 为我提供了一个字符串输出(最终将输出到一个文本文件)。
我正在尝试优雅地创建一种方法,让我使用此字符串输出来重新创建具有以前设置的所有变量的对象。这个类看起来像这样:

public class Report {

    private String itemA;
    private String itemB;
    private String itemC;

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Items are::");
        sb.append("\nItem A is: ").append(itemA);
        sb.append("\nItem B is: ").append(itemB);
        sb.append("\nItem C is: ").append(itemC);
        return sb.toString();
    }
}
这就是我可以使用反射来解决它的方法:
public class Report {

    private String itemA;
    private String itemB;
    private String itemC;

    private final Map<String, String> MAPPING = new HashMap<>();

    public Report(String itemA, String itemB, String itemC) {
        this.itemA = itemA;
        this.itemB = itemB;
        this.itemC = itemC;

        MAPPING.put("Item A is: ", "itemA");
        MAPPING.put("Item B is: ", "itemB");
        MAPPING.put("Item C is: ", "itemC");
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("Items are::");

        MAPPING.entrySet().forEach(entry -> {
            sb.append("\n").append(entry.getKey()).append(BeanUtils.getProperty(this, entry.getValue()));
        });

        return sb.toString();
    }


    public Report createReportFromString(String reportString) {
        List<String> reportLines = Arrays.asList(reportString.split("\n"));
        HashMap<String, String> stringObjectRelationship = new HashMap<>();
        
        reportLines.forEach(reportLine -> {
            Optional<String> matchingKey = MAPPING.keySet().stream().filter(reportLine::contains).findFirst();
            matchingKey.ifPresent(key -> {stringObjectRelationship.put(MAPPING.get(key), reportLine.split(key)[1]);});
        });
        
        stringObjectRelationship.forEach((variableName, variableValue) -> BeanUtils.setProperty(this, variableName, variableValue));
        return this;
    }
}
我基本上想将报告中的键(“Item A 是:”)与相应变量的名称(“itemA”)相关联,并在 toString() 方法和 createReportFromString(String string) 方法中使用这种关系。现在,在执行此操作时,可能会抛出许多可能的异常,需要处理或抛出 - 然后它看起来不像我想要的那么优雅。
我不知道这是否可以在没有反射的情况下完成 - 或者我可以重新安排这个类(class)以使这成为可能?
我无法改变的是 toString() 中字符串输出的结构。

最佳答案

反射具有多种特征:

  • 在运行时自动发现程序的特性
  • 支持处理编译时未知的特性
  • 提供程序功能的抽象(例如方法或字段)

  • 您的方法表明您不想要自动发现,因为您明确指定了三个元素。这是一件好事,因为它使您的程序在 future 的更改方面更加健壮,因为处理自动发现的潜在未知程序元素将破坏编译器的任何帮助,因为它无法告诉您何时存在不匹配。
    您只需要第三点,即对报告元素的抽象。您可以自己创建这样的抽象,根据您的用例量身定制,无需反射,这将更健壮,甚至更高效:
    public class Report {
        static final class Element {
            final String header;
            final Function<Report,String> getter;
            final BiConsumer<Report,String> setter;
            final Pattern pattern;
            Element(String header,
                    Function<Report, String> getter, BiConsumer<Report, String> setter) {
                this.header = header;
                this.getter = getter;
                this.setter = setter;
                pattern = Pattern.compile("^\\Q"+header+"\\E(.*?)$", Pattern.MULTILINE);
            }
        }
        static final List<Element> ELEMENTS = List.of(
            new Element("Item A is: ", Report::getItemA, Report::setItemA),
            new Element("Item B is: ", Report::getItemB, Report::setItemB),
            new Element("Item C is: ", Report::getItemC, Report::setItemC));
    
        private String itemA, itemB, itemC;
    
        public Report(String itemA, String itemB, String itemC) {
            this.itemA = itemA;
            this.itemB = itemB;
            this.itemC = itemC;
        }
        @Override public String toString() {
            StringBuilder sb = new StringBuilder();
            sb.append("Items are:");
            ELEMENTS.forEach(e ->
                sb.append('\n').append(e.header).append(e.getter.apply(this)));
            return sb.toString();
        }
        public static Report createReportFromString(String reportString) {
            return new Report("", "", "").setValuesFromString(reportString);
        }
        public Report setValuesFromString(String reportString) {
            Matcher m = null;
            for(Element e: ELEMENTS) {
                if(m == null) m = e.pattern.matcher(reportString);
                else m.usePattern(e.pattern).reset();
    
                if(!m.find())
                    throw new IllegalArgumentException("missing \""+e.header+'"');
                e.setter.accept(this, m.group(1));
            }
            return this;
        }
        public String getItemA() {
            return itemA;
        }
        public void setItemA(String itemA) {
            this.itemA = itemA;
        }
        public String getItemB() {
            return itemB;
        }
        public void setItemB(String itemB) {
            this.itemB = itemB;
        }
        public String getItemC() {
            return itemC;
        }
        public void setItemC(String itemC) {
            this.itemC = itemC;
        }
    }
    
    这适用于 Java 的开箱即用功能,不需要另一个库来简化操作。
    请注意,我更改了代码模式,如 createReportFromString是修改已存在对象的方法的误导性名称。我使用工厂方法的名称来真正创建一个新对象,并添加了另一种设置对象值的方法(作为 toString 的直接对应部分)。
    如果您仍在使用 Java 8,您可以替换 List.of(…)Arrays.asList(…)或更好 Collections.unmodifiableList(Arrays.asList(…)) .

    您也可以删除 .reset()调用 setValuesFromString方法。删除它时,输入字符串中的元素必须与toString() 的顺序相同。方法产生。这使得它不太灵活,但如果您扩展代码以包含更多元素,则效率会更高。

    关于java - 将 toString 与对象创建相关联,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65080551/

    相关文章:

    java - 属性文件导致空 Spring Boot 项目出现异常

    ios - 在 View Controller 之间传递图像在呈现模态视图时不起作用

    javascript - 访问JSON信息: Objects in Objects

    c# - 克隆/复制获取访问器主体到新类型

    java - lucene 4.10.2中生成多个CFS文件

    java - 出错时重复 CXF 请求

    java - 安装java jre 5.0

    java - 如何将String解析为对象

    java - 如何使用 java 反射创建 protobuf 实例?

    java - 如何在java中通过反射获取主要类型的泛型字段?