【问题标题】:Initializing empty instance variables in constructor在构造函数中初始化空实例变量
【发布时间】:2020-01-16 19:40:13
【问题描述】:

我有一个 LogAnalyzer 类,它查看 Web 服务器日志、创建 LogEntry 对象并将这些对象放入 HashMap 中进行分析。

我的 LogAnalyzer 类有以下字段:

private int totalVisits;
private int uniqueVisits;
private ArrayList<LogEntry> records;
private HashMap<String, ArrayList<LogEntry>> uniqueIPs; //<address, log entries>
private HashMap<String, ArrayList<LogEntry>> dailyRecords; // <date, log entries>

我的构造函数如下所示:

public LogAnalyzer() {

    records = new ArrayList<>();
    dailyRecords = new HashMap<>();
    uniqueIPs  = new HashMap<>();

}

然后我就有了这个方法:

public void initializeRecords(String path){
    readFile(path); //reads the web log file and fills out the records and dailyRecords fields
    getUniqueIPs(); //fills out the uniqueIPs HashMap field.
    uniqueVisits = uniqueIPs.size(); //fills out the uniqueVisits field
    totalVisits = records.size(); //fills out the totalVisits field
}

所以我的问题:

我已经读过(但不太明白)在构造函数中调用方法是“不好的”。然而,这里的构造函数似乎毫无意义,因为它实际上是 initializeRecords 完成了“创建”对象的所有有意义的工作。

我没有 Java 或编程背景,无法理解迄今为止找到的解释。有很多关于压倒一切的讨论,我认为这就是我不清楚的地方。我想知道为什么我要把我的构造函数和这个方法分开,用初学者可以理解的简单术语。

**编辑:** 这是 readFile() 的代码:

public void readFile(String filename) {
    FileResource fr = new FileResource(filename);
    for (String line : fr.lines()){
        LogEntry le = WebLogParser.parseEntry(line);
        String date = le.getAccessTime().toString().substring(4, 10);
        if (dailyRecords.keySet().contains(date)){
            dailyRecords.get(date).add(le);
        }
        else{
            ArrayList<LogEntry> entries = new ArrayList<>();
            entries.add(le);
            dailyRecords.put(date, entries);
        }
        records.add(le); 
    }

【问题讨论】:

  • 你从哪里读到在构造函数中调用方法不好?
  • 请提供此结论的参考。
  • 能否提供readFile方法的代码?
  • @mapeters stackoverflow.com/questions/5230565/…, stackoverflow.com/questions/51592673/…, stackoverflow.com/questions/18348797/… 是几个例子......显然这个问题之前已经问过,但我不明白答案。
  • 您并没有调用任何实例方法,而是在初始化实例变量,其中之一稍后会被覆盖。你提到的最后一个问题是最好的,回复:可覆盖的方法和泄漏this——这最终是你要问的。 稍后初始化你使用的变量是否有意义是一个观点和使用的问题。

标签: java oop


【解决方案1】:

保留这两种方法可以让您更灵活地使用代码。您可以在不知道日志路径的情况下实例化 LogAnalyzer。我会将initializeRecords 重命名为processRecords,哪个 IMO 更能描述您在那里所做的事情。

我们应该先创建对象,然后调用它的方法。例如,如果您的 readFile 方法因为无法读取文件而引发异常。当我构造对象时,我会发现遇到这个异常很奇怪。构造函数的重点是提供一个可以用来做某事的对象。

【讨论】:

  • 当你说“构造函数的目的是提供一个可以用来做某事的对象”。我认为你击中了我问题的核心。我的代码设置方式,似乎我提供了一个无用的对象,因为它只包含空变量。直到我打电话给processRecords(),它才变得有用。但是,您似乎在说我仍然应该将这两件事分开?还是我误解了你的答案?
  • 你基本上明白了。另一件事是不要将数据视为对象的“有用”部分。 OOP 的重点是提供有用的抽象,使事物易于理解。
  • 感谢您的澄清,特别是关于“OOP 的要点”。这也是我问题的核心。很久以前,我在大学上过一些 OOP 课程,但自从我参军以来,我一直没有找到编程方面的工作。现在,我不得不重新学习所有内容,而没有老师帮助我回答我所有的问题。
【解决方案2】:

从构造函数中调用方法不是一个好习惯,因为 Java 总是调用最派生的方法,这意味着我们可以在半初始化对象上调用方法。

回答你的问题,

public LogAnalyzer() {

    records = new ArrayList<>();
    dailyRecords = new HashMap<>();
    uniqueIPs  = new HashMap<>();

}

上述部分的具体作用是,它给变量、记录、dailyRecods 和 uniqueIPs 一个内存堆栈中的物理地址。

当我们在类中写private ArrayList&lt;LogEntry&gt; records;之类的东西时,此时只会产生一个引用,但真正的初始化只有在records = new ArrayList&lt;&gt;();这行被执行时才会发生。

希望这能澄清你的疑问!!!

【讨论】:

  • "我们可以在一个半初始化对象上调用方法" 需要详细说明吗?
  • @PM77-1 请看看这些文章:https://www.javaspecialists.eu/archive/Issue086.htmlhttps://www.javaspecialists.eu/archive/Issue086b.html
【解决方案3】:

正如您在readFile() 中看到的,它使用以下指令

dailyRecords.keySet().contains(date)

没有在它之前初始化dailyRecords。因此,如果您在声明 dailyRecords 时或在构造函数中未初始化它,您将面临 NullPointerException

在您的情况下,您可以使用这样的声明部分,而不是使用构造函数进行初始化

private HashMap<String, ArrayList<LogEntry>> dailyRecords = new HashMap<>();    

【讨论】:

    猜你喜欢
    • 2015-04-28
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 2015-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多