【问题标题】:Java Pattern class doesn't have a public constructor, why?Java Pattern 类没有公共构造函数,为什么?
【发布时间】:2012-12-07 07:28:05
【问题描述】:

我一直在审查 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。那里的答案也没有让我信服。通读所有答案,我觉得静态方法在创建对象方面比公共构造函数有很多优势,但反之则不然。真的吗?如果是这样,我将为我的每个类创建这样的静态方法,并安全地假设它更具可读性和灵活性。

【问题讨论】:

  • 您是否尝试过深入研究源代码?
  • 或许是为了保证可读性。
  • @JanDvorak new Pattern(".") 是可读的,如果不是更多的话,不是吗:)
  • 如果两个重载具有相同的签名,则不会。
  • 添加构造函数会得到什么?这是一个诚实的问题。

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


【解决方案1】:

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

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

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

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

【讨论】:

  • 第二个在这里也适用吗?
  • @Sanandrea 不,Pattern.compile 不会返回 null。实例化仍然可能失败,但它只是抛出一个异常。
  • @Sanandrea 你清楚吗?我觉得我没有把我的观点传达给你。第二点有什么不清楚的地方?
  • 如果你看source code of the pattern class,你可以看到Pattern.compile不能返回null,因为它只是返回调用构造函数所做的事情,而构造函数不能返回null,所以第二点在这里不适用。我想不出第二点的例子,但这是我以前见过的。
  • 在pattern的情况下如何应用?源代码显然不能返回 null,所以我不确定你为什么这么认为。您的示例创建了一个用例,其中 null 是模式编译失败的结果,但这并不意味着 Pattern.compile 本身返回 null(因为它没有!)。
【解决方案2】:

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

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

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 目前只能是来自正则表达式的compiled,但未来可能会出现不同的模式表示(不可否认,compile 是一个糟糕的方法名称)。

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

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


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

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

【讨论】:

  • +1,但我不同意构造函数不应该抛出异常。我几乎在答案中包含了关于抛出异常的相同论点,但后来我考虑了new FileInputStream(..) 的反点,它可以抛出IOException。虽然我同意在构造函数中抛出异常很麻烦,但这并不罕见,尤其是在 java.netjava.io 包中。
  • 另外,compile 方法也经常从静态初始化块(static final Pattern p = Pattern.compile(".");)中调用,所以这里没有区别。
  • Pattern.compile 只不过是一个命名的构造函数。如果可以的话,它可以而且将在任何地方有人会说new Pattern 的地方被调用——包括静态初始化块——并且会导致所有相同的问题。所以任何关于在构造函数中避免异常的建议也应该适用于它。规则不会因为名字而改变。
  • @JanDvorak:你有链接吗?我听说它是​​在 C++ 中说的,但是那里有一些在 Java 中不太适用的原因。
  • @JanDvorak:就个人而言,我愿意。但那是因为我不同意它,所以我已经有点偏见了。 :) 如果可以备份,请保留它。
【解决方案3】:

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

【讨论】:

  • 另一个原因是稍后在构造函数中添加优化将是一种不好的做法(在构造函数中传递它)。在静态方法中做同样的事情是可以的。
【解决方案4】:

工厂方法有几个优点,其中一些已经在其他答案中指定。考虑工厂方法而不是构造函数的建议甚至是 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,您可能希望拥有CompiledPatternLazilyCompiledPatternInterpretedPattern 等私有子类,但仍禁止子类化以确保不变性。

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

【讨论】:

    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

      它有一个私有构造函数。

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

      【讨论】:

      • 好吧,我没说它没有构造函数。我只是说它没有 PUBLIC 构造函数:)
      • 问题是为什么会这样,尤其是当compile方法这个简单的时候?
      • compile 将表现为创建 Pattern 实例的工厂方法。我能想到的唯一原因是在编译方法中将匹配标志默认为零。
      • @AjayGeorge 当然,但Pattern 不需要 工厂;-)
      • 默认行为在构造函数中也很容易发生。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-22
      • 1970-01-01
      • 2013-07-10
      • 2012-03-12
      • 2011-11-18
      • 2011-10-23
      相关资源
      最近更新 更多