【问题标题】:Java Pattern Variable ScopeJava 模式变量范围
【发布时间】:2022-10-17 12:58:41
【问题描述】:

我正在通过Oracle官方docs了解模式变量范围在 Java 17 中。在以下示例中,方法 testScope1 的工作原理与文档中的说明相同,但方法 testScope2 给出了编译错误。我无法弄清楚为什么 void 方法的返回类型会导致问题?

interface Vehicle{}
class Car implements Vehicle{}

class Pattern{
    public int testScope1(Vehicle v){
        if(!(v instanceof Car c)){
            return 1;
        }
        System.out.println(c.toString());  //WORKS FINE
        return 2; 
    }
    public void testScope2(Vehicle v){
        if(!(v instanceof Car c)){
                 
        }
        System.out.println(c.toString());  //COMPILE TIME ERROR: Cannot resolve symbol c
    }
}

【问题讨论】:

  • “模式变量的范围是程序只有在 instanceof 运算符为真时才能到达的地方:”
  • 显然返回类型不是原因——它是返回语句。
  • 我也对此感到困惑。没有调查,给出的答案对我来说都不是很满意。无论运行时的值如何,变量的范围对我来说都是模糊的。由于 c 在 if 表达式中被引入,我希望它的范围涵盖表达式以及 thenelse 代码块,但仅此而已。这样一来,我已经很惊讶地看到第一种方法很好。然而我读到“模式变量的范围可以超出引入它的语句”

标签: java instanceof java-17


【解决方案1】:

想想如果v不是Car 实例:

  • testScope1 中,return 1; 语句会导致方法退出。不执行该方法的后续语句。一切都很好。
  • testScope2中没有return,所以控制流到达c.toString()。但是v 不是Car,那么……c 是什么?!它不可能存在,因为它必须是 Car 类型,但这是反事实的。这就是您收到错误“无法解析符号c”的原因。

【讨论】:

    【解决方案2】:

    模式变量(模式中声明的绑定变量)使用流敏感范围.与位于连续区域范围内的普通本地变量不同,模式变量在范围内他们肯定会被他们的声明模式分配的地方.

    如果您有 if 声明:

    if (x instanceof Foo(var v)) { 
        A;
    }
    else {
        B;
    }
    

    那么vA 的范围内,但不在B 的范围内,因为我们不能保证在达到B 的情况下一定会为v 分配一个值。如果我们使用明显的重构来反转我们的测试:

    if (!(x instanceof Foo(var v))) { 
        B;
    }
    else {
        A;
    }
    

    也是如此; vA 范围内,但不在B 范围内。规则完全一样明确的任务当地人的规则——“如果我到达这一点,这个值是否保证已经被分配了一个值。”

    其他条件构造,例如短路&&||,也参与此范围界定。例如,以下是有效的:

    if (x instanceof Foo(var v) && v != null) { 
        A;
    }
    

    但以下不是:

    if (x instanceof Foo(var v) || v != null) { 
        A;
    }
    

    因为在后者中,当我们到达v != null 子句时,不能保证v 已被赋值。

    这些规则甚至包含非本地控制流,例如异常。例如,如果我们有:

    if (!(x instanceof Foo(var v)) { 
        System.out.println("Not a Foo");
    }
    B(v);
    

    这将是一个错误,因为当我们到达B(v) 时,不能保证为v 分配一个值,但是如果if 块突然完成:

    if (!(x instanceof Foo(var v)) { 
        throw new NotFooException();
    }
    B(v);
    

    那么vB(v) 的范围内,因为我们保证如果我们到达那个点,v 已经被分配了一个值。

    这可能看起来很复杂,但实际上非常简单:鉴于您对 ifthrow 等结构的流控制的了解,是否保证在给定点为模式变量分配了一个值?如果是这样,那么它在当时的范围内。

    【讨论】:

    • 您使用了一个术语flow sensitive scoping。你可以通过链接、书籍或文章来支持它吗?我第一次听说。还有将其链接到Pattern variable 的文章。那会很有帮助。因为实际上,if-elseinstanceof、变量声明, assignment` 和普通的旧 variable scope 概念没有什么不同。
    • @SyedAqeelAshiq 例如,参见 JLS 6.3.1,“表达式中模式变量的范围”。这些规则起初可能看起来不透明,但它们反映了明确分配的结构,这是一种基于流的分析。 openjdk.org/projects/amber/design-notes/patterns/… 中的“模式变量的作用域”下有一个可能更平易近人的解释。 (您似乎非常努力地说服自己这与您已经了解的其他事物“相同”,但现实更加微妙。)
    • OP 的问题是关于为什么他必须发表returnelse 声明。在这种情况下,知道这是编译器在幕后所做的非常有帮助。如果这是关于设计模式或语言架构的辩论,那么您肯定是正确的,但必须在 OP 的上下文和他们的困惑中看待它。
    • @SyedAqeelAshiq 这在第一句话中得到了回答:模式变量在肯定会被分配的范围内。因为无论模式是否匹配都可以达到c 的使用,所以不能保证在达到第二个println 语句时一定会分配c
    【解决方案3】:

    对此花了更多的思考后,我有了一个解释:只有当那段代码的 instanceof 明显为真时,才能访问该变量。

    在 testScope1 中,您无法在 if 语句中访问 c。 但是由于 if 语句之后的代码只有在 instanceof 为真时才会执行,所以c 是可以访问的。

    在 testScope1 中,您无法在 if 语句中访问 c。 但是由于 if 语句之后的代码无论 instanceof 是真还是假都会被执行,c 是不可访问的。 毕竟我们可能会遇到错误的情况,编译器会相应地采取行动。

    【讨论】:

      【解决方案4】:

      放一个 else 语句。只有当instanceof 运算符产生true 时,才会创建模式变量。否则范围内没有变量,因此编译器错误。

        public void testScope2(Vehicle v){
          if(!(v instanceof Car c)){
      
          } else {
            System.out.println(c.toString());
          }
        }
      

      编辑: testScope1 方法不需要 else 语句,因为 return 语句已经存在。如果v 不是Car 的实例,return 语句确保控制永远不会到达c.toString()

      模式变量只是java 14中添加的语法黑巧克力。生成字节码时,它与简单的instanceofif-else语句没有区别。例如,我使用 java 17 编译了您的 Pattern 类,并使用 java 8 进行了反编译。这里是 testScope1 方法的反编译版本。 Java 17 编译器自己放了一个else

      public int testScope1(Vehicle var1) {
        if (var1 instanceof Car) {
          Car var2 = (Car)var1;
          System.out.println(var2.toString());
          return 2;
        } else {
          return 1;
        }
      }
      

      所以,你可以看到,java 编译器已经修改了你的if 语句,所以只有当vinstanceofCar 时才会创建Car 变量。使用了简单的概念instanceofTypeCastingvariable scopesif-else,这些概念早已存在。

      【讨论】:

      • @Steve 因为它不需要它:如果由于returnv 不是Car,则永远无法到达使用c 的语句。
      • testScope2 需要 else,因为在前一个 if 块内没有 return - testScope1 有一个 return,它基本上可以作为后续语句的 else,所以不需要一个明确的else
      • @Steve(恕我直言,在testScope1 中没有else 并不是最佳做法。另请注意,这两种方法,不管返回声明不同,在逻辑上也是不同的——在testScope2 中,例如@987654361 @ 在if 之后总是会被执行)
      • @SyedAqeelAshiq 我认为您以这种方式看待它是在伤害自己。根据这个论点,每种编程语言中的所有内容都可以被描述为图灵机的“语法水果蛋糕”——但这对于理解事物的工作方式是不利的。模式匹配在语言中引入了许多新概念,其中大多数本质上不是句法(例如,新的范围规则、条件模式与无条件模式、模式组合等)。 "是反有益的。
      • @SyedAqeelAshiq 如果您想专注于 OP 的问题而不是讨论语言设计方面,那么不要引入诸如“语法糖”或“语法黑巧克力”之类的语言设计方面。甚至讨论字节码也无济于事。可以说,在这个具体的例子中,源代码与您发布的内容是等价的。
      猜你喜欢
      • 1970-01-01
      • 2011-06-01
      • 1970-01-01
      • 2021-07-15
      • 2014-03-02
      • 2017-02-17
      • 1970-01-01
      • 1970-01-01
      • 2017-02-13
      相关资源
      最近更新 更多