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

我一直在审查Java Regex Library,感到惊讶的是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?"); } } } 

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

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

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

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

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

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

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

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

 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是极性) double, double会更糟。

此基本原理适用于可以以多种不同方式构造的任何对象,这些方式仅在参数类型上不同。 虽然Pattern目前只能从正则表达式compile d,但是Pattern不同表示可能会在将来出现(顺便说一下, compile是一个糟糕的方法名称)。

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

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


简而言之,如果构造函数不是一个好的设计选择:

  • 缓存可能发生,或
  • 构造函数在语义上是模糊的,或者
  • 创作可能会失败。

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

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


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

例如,人们可能想要从几种输入格式创建一个Pattern ,所有这些格式都只是String

 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 ,但仍禁止子类以确保不变性。

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

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

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

它有一个私有构造函数。

  /** * 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方法中,匹配标志将默认为零。