与对象创建相关联的字符串

z9zf31ra  于 2021-07-03  发布在  Java
关注(0)|答案(2)|浏览(319)

我有一个相当基本的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 is:”)与相应变量(“itema”)的名称相关联,并在tostring()方法和createreportfromstring(string string)方法中使用这种关系。现在,当这样做时,有很多可能的异常可以抛出,需要处理或抛出-然后它看起来没有我想要的那么优雅。
我不知道这是不是可以不用反思就可以做到——或者我可以重新安排这个类使之成为可能?
我不能改变的是tostring()中字符串输出的结构。

yruzcnhs

yruzcnhs1#

@jimbomchiggins假设我可以更改tostring输出,那么您如何将序列化和反序列化与一些公共Map联系起来呢?
我会保持tostring不变,并将序列化的责任转移到java.io.serializable。如果这不是一个可接受的方法,请纠正我。Map将由报表pojo的类字段定义。这还允许您更改tostring,而不破坏现有对象的反序列化。

import java.io.Serializable;

public class Report implements Serializable {
    private static final long serialVersionUID = 1L;

    private String itemA;
    private String itemB;
    private String 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::");
        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 Test1 {
    public static void main(String[] args) {
        Report report = new Report("W", "O", "W");
        System.out.println(report);

        String filename = "file.ser";

        // Serialization
        try
        {
            //Saving of report in a file
            FileOutputStream file = new FileOutputStream(filename);
            ObjectOutputStream out = new ObjectOutputStream(file);

            // Method for serialization of report
            out.writeObject(report);

            out.close();
            file.close();

            System.out.println("Report has been serialized");

        }

        catch(IOException ex)
        {
            System.out.println("IOException is caught");
        }

        Report report1 = null;

        // Deserialization
        try
        {
            // Reading the report from a file
            FileInputStream file = new FileInputStream(filename);
            ObjectInputStream in = new ObjectInputStream(file);

            // Method for deserialization of report
            report1 = (Report)in.readObject();

            in.close();
            file.close();

            System.out.println("Report has been deserialized ");
            System.out.println(report1);
        }

        catch(IOException ex)
        {
            System.out.println("IOException is caught");
        }

        catch(ClassNotFoundException ex)
        {
            System.out.println("ClassNotFoundException is caught");
        }
    }
}

输出

Items are::
Item A is: W
Item B is: O
Item C is: W
Report has been serialized
Report has been deserialized 
Items are::
Item A is: W
Item B is: O
Item C is: W
chhqkbe1

chhqkbe12#

反射具有多种特征:
在运行时自动发现程序的特性
支持处理编译时未知的特性
提供程序特性的抽象(例如方法或字段)
您的方法表明您不希望自动发现,因为您正在显式地指定这三个元素。这是一件好事,因为它使您的程序对于将来的更改更加健壮,因为处理自动发现的、潜在未知的程序元素会破坏编译器的任何帮助,因为它无法告诉您何时存在不匹配。
您只需要第三点,即对报表元素的抽象。您可以自己创建这样一个抽象,根据您的用例进行定制,而无需反射,这样会更加健壮,甚至更加高效:

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() 方法生成。这使得它不那么灵活,但是如果您扩展代码以拥有更多的元素,它的效率也会更高。

相关问题