Java Pattern类没有公共构造函数,为什么?

时间:2012-12-07 07:28:05

标签: java design-patterns constructor static-methods factory-pattern

我一直在审查Java Regex库,感到惊讶的是Pattern类没有我多年来认为理所当然的公共构造函数。

我怀疑静态compile方法被用于支持构造函数的一个原因可能是构造函数总是返回一个新对象,而静态方法可能会返回一个先前创建的(和缓存的)对象,前提是该模式字符串是一样的。

但是,情况并非如下所示。

public class PatternCompiler {
    public static void main(String[] args) {
        Pattern first = Pattern.compile(".");
        Pattern second = Pattern.compile(".");
        if (first == second) {
            System.out.println("The same object has been reused!");
        } else {
            System.out.println("Why not just use constructor?");
        }
    }
}

使用静态方法而不是构造函数的任何其他强有力的理由?

修改:我在这里找到了related question。那里的答案都没有说服我。通过阅读所有答案,我感觉静态方法相对于公共构造函数在创建对象方面具有相当多的优势,而不是相反。真的吗?如果是这样,我将为我的每个类创建这样的静态方法,并安全地假设它更具可读性和灵活性。

6 个答案:

答案 0 :(得分:13)

通常,由于以下三个原因之一,类不具有公共构造函数:

  • 该类是一个实用程序类,没有理由对其进行实例化(例如,java.lang.Math)。
  • 实例化可能失败,构造函数无法返回null
  • 静态方法阐明了实例化过程中发生的意义。

Pattern类中,第三种情况适用 - 静态compile方法仅用于清晰。从解释的角度来看,通过new Pattern(..)构建模式没有意义,因为有一个复杂的过程继续创建一个新的Pattern。为了解释这个过程,静态方法名为compile,因为正则编译正则表达式创建模式。

简而言之,没有编程目的使Pattern只能通过静态方法构建。

答案 1 :(得分:8)

一个可能的原因是这样,以后可以将缓存添加到方法中。

另一个可能的原因是可读性。考虑这个(经常被引用的)对象:

class Point2d{
  static Point2d fromCartesian(double x, double y);
  static Point2d fromPolar(double abs, double arg);
}

Point2d.fromCartesian(1, 2)Point2d.fromPolar(1, 2)都具有完美的可读性和明确性(除了参数顺序之外......)。

现在,考虑new Point2d(1, 2)。参数是笛卡尔坐标还是极坐标?如果具有相似/兼容签名的构造函数具有完全不同的语义(例如,int, int是笛卡儿,double, double是极坐标的),情况会更糟。

这个基本原理适用于任何可以用多种不同方式构造的对象,这些方式只是在参数类型上没有区别。虽然Pattern目前只能来自正则表达式compile,但是模式的不同表示形式可能在将来出现(顺便说一句,compile是一个糟糕的方法名称)。< / p>

@Vulcan提到的另一个可能的原因是构造函数不应该失败。

如果Pattern.compile遇到无效模式,则会抛出PatternSyntaxException。有些人可能会认为从构造函数中抛出异常是一种不好的做法。不可否认,FileInputStream就是这么做的。同样,如果设计决策是从null方法返回compile,那么构造函数就无法做到这一点。


简而言之,如果出现以下情况,构造函数不是一个好的设计选择:

  • 可能会发生缓存,或
  • 构造函数在语义上不明确,或
  • 创作可能失败。

答案 2 :(得分:5)

这只是一个设计决定。在这种情况下,没有“真正的”优势。但是,此设计允许在不更改API的情况下进行优化(例如缓存)。见http://gbracha.blogspot.nl/2007/06/constructors-considered-harmful.html

答案 3 :(得分:4)

工厂方法有几个优点,其中一些已在其他答案中指定。考虑工厂方法而不是构造函数的建议甚至是Joshua Bloch的伟大着作“Effective Java”的第一章(每个Java程序员都必须阅读)。


一个优点是您可以拥有多个具有相同参数签名但名称不同的工厂方法。这是构造函数无法实现的。

例如,有人可能想要从几种输入格式创建Pattern,所有输入格式都只是String s:

class Pattern {
  compile(String regexp) { ... }
  compileFromJson(String json) { ... }
  compileFromXML(String xml) { ... }
}

即使您在创建类时没有这样做,工厂方法也可以让您在不引起怪异的情况下添加此类方法。

例如,我已经看到了稍后需要新构造函数的类,并且必须在第二个构造函数中添加一个特殊的无意义的第二个参数以允许重载。显然,这非常难看:

class Ugly {
  Ugly(String str) { ... }

  /* This constructor interpretes str in some other way.
   * The second parameter is ignored completely. */
  Ugly(String str, boolean ignored) { ... }
}

不幸的是,我不记得这样一个类的名称,但我认为它甚至在Java API中。


之前未提及的另一个优点是,将工厂方法与包私有构造函数结合使用,您可以禁止对其他人进行子类化,但仍然可以自己使用子类。对于Pattern,您可能希望拥有CompiledPatternLazilyCompiledPatternInterpretedPattern等私有子类,但仍禁止进行子类化以确保不变性。< / p>

使用公共构造函数,您可以禁止每个人进行子类化,或者根本不禁止。

答案 4 :(得分:2)

如果你真的想深入研究,请深入了解JSR 51的档案。

正则表达式已作为JSR 51的一部分引入,您仍可在其档案中找到设计决策http://jcp.org/en/jsr/detail?id=51

答案 5 :(得分:1)

它有一个私有构造函数。

 /**
     * This private constructor is used to create all Patterns. The pattern
     * string and match flags are all that is needed to completely describe
     * a Pattern. An empty pattern string results in an object tree with
     * only a Start node and a LastNode node.
     */
    private Pattern(String p, int f) {

compile方法调用它。

public static Pattern compile(String regex) {
        return new Pattern(regex, 0);
    }

由于您正在使用==比较,这是用于参考,它将无法正常工作

我能想到这种行为的唯一原因是匹配标志在compile方法中默认为零,该方法用作工厂方法。