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

Ter*_* Li 10 java constructor static-methods design-patterns 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?");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

FTh*_*son 13

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

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

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

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


Joh*_*rak 8

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

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

class Point2d{
  static Point2d fromCartesian(double x, double y);
  static Point2d fromPolar(double abs, double arg);
}
Run Code Online (Sandbox Code Playgroud)

Point2d.fromCartesian(1, 2)并且Point2d.fromPolar(1, 2)都是完全可读和明确的(除了参数顺序之外......).

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

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

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

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


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

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

  • +1,但我不同意不应该抛出异常的构造函数.我几乎在我的答案中包含了关于异常抛出的相同论点,但后来我考虑了可以抛出`IOException`的`new FileInputStream(..)`的反问题.虽然我同意在构造函数中抛出异常是混乱的,但这并不是一种罕见的做法,特别是在`java.net`和`java.io`包中. (2认同)

rmu*_*ler 5

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


Phi*_*ler 5

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


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

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

class Pattern {
  compile(String regexp) { ... }
  compileFromJson(String json) { ... }
  compileFromXML(String xml) { ... }
}
Run Code Online (Sandbox Code Playgroud)

即使您在创建类时没有这样做,工厂方法也使您能够在以后添加此类方法而不会造成怪异。

例如,我看到类后来需要新的构造函数,并且必须将一个特殊的无意义的第二个参数添加到第二个构造函数以允许重载。显然,这是非常丑陋的:

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

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

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


另一个之前没有提到的优点是工厂方法与包私有构造函数相结合,你可以禁止其他人的子类,但你自己仍然使用子类。在的情况下Pattern,你可能希望有私人的子类,如CompiledPatternLazilyCompiledPatternInterpretedPattern,但仍禁止子类,以确保不变性。

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