11

我一直在审查 JavaRegex库,惊讶于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?");
        }
    }
}

在构造函数上使用静态方法背后还有其他强有力的理由吗?

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

4

6 回答 6

15

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

  • 该类是一个实用程序类,没有理由实例化它(例如,java.lang.Math)。
  • 实例化可能会失败,并且构造函数不能返回null
  • 静态方法阐明了实例化过程中发生的事情背后的含义。

在类中Pattern,第三种情况是适用的——静态compile方法只是为了清楚起见。从解释的角度来看,通过构建模式new Pattern(..)没有意义,因为有一个复杂的过程会继续创建一个新的Pattern. 为了解释这个过程,静态方法被命名为compile,因为正则表达式本质上是为了创建模式而编译的。

简而言之,Pattern仅通过静态方法可构造并没有编程目的。

于 2012-12-07T07:50:29.270 回答
9

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

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

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是极性的),那就更糟了。

这个基本原理适用于任何可以以多种不同方式构造的对象,这些方式仅在参数类型上没有区别。虽然dPattern目前只能compile来自正则表达式,但未来可能会出现不同的 Pattern 表示(不可否认,compile这是一个糟糕的方法名称)。

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

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


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

  • 可能会发生缓存,或者
  • 构造函数在语义上是模棱两可的,或者
  • 创建可能会失败。
于 2012-12-07T08:03:50.267 回答
6

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

于 2012-12-07T07:46:53.807 回答
5

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


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

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

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,您可能希望拥有私有子类CompiledPattern,如 、LazilyCompiledPatternInterpretedPattern,但仍禁止子类化以确保不变性。

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

于 2012-12-07T07:53:49.047 回答
2

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

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

于 2012-12-07T07:51:03.093 回答
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我能想到这种行为的唯一原因是,在充当工厂方法的方法中,匹配标志将默认为零。

于 2012-12-07T07:31:14.143 回答