前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你知道吗,Java中的受查和非受查异常,其实并不存在区别……

你知道吗,Java中的受查和非受查异常,其实并不存在区别……

作者头像
HikariLan贺兰星辰
发布于 2024-04-04 03:04:02
发布于 2024-04-04 03:04:02
14400
代码可运行
举报
文章被收录于专栏:HikariLan's BlogHikariLan's Blog
运行总次数:0
代码可运行

博主在文章中提到Java中的受查异常和非受查异常之间的区别在JVM的世界中实际上并不存在。传统理念认为继承自java.lang.RuntimeException的异常是非受查异常,其他异常则是受查异常。然而,通过比较JVM字节码层面的代码表示,发现无论是受查异常还是非受查异常,在JVM字节码中并没有实质差别。此外,文章也讨论了Kotlin语言对于受查异常的规避,通过Kotlin编写的类似Java代码可以正常运行且不需要显式捕获异常。最后,文章提到Java中的受查异常机制存在争议,而Kotlin作为一种新的JVM语言,避免了这一问题。

你知道吗,Java中的受查和非受查异常,其实并不存在区别......

相信写过 Java 的人都会知道,在 Java 的异常系统中,存在“受查(checked)”异常和“非受查(unchecked)”两座大山,两者虽然均为异常,但是却有着微妙的区别。但是你知道吗,实际上在 JVM 的世界里,这种区别根本不存在......

“受查”和“非受查”

为什么有时候调用某些方法的时候需要强制 try-catch 它们,亦或者在调用方法上加入 throws 关键字声明抛出,而有的方法虽然会抛出异常,但是并不会要求你这么做...... 如果有一位 Java 新手带着这样的疑惑问你,你一定会轻车熟路的告诉他:所有继承自 java.lang.RuntimeException 的异常,他们都是非受查异常,这些异常允许你不必强制在方法体上声明他们,亦或者强制通过 try-catch 捕获;而除此之外的异常,则都是受查异常,你必须按照上述的方法声明和捕获他们。

举个例子:以下代码是无法正常编译的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.IOException;

public class Main {

    public static void main(String[] args){
        throw new IOException("Goodbye, World!");
    }

}

因为 java.io.IOException 没有继承自 java.lang.RuntimeException,因此是一个非受查异常,而我们并没有通过 try-catch 捕获异常或是在调用函数上声明抛出该异常。因此我们会得到如下编译错误:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Main.java:6: error: unreported exception IOException; must be caught or declared to be thrown
        throw new IOException("Goodbye, World!");
        ^

改进措施也很简单,在 main 方法上声明抛出异常即可正常编译:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.IOException;

public class Main {

    public static void main(String[] args) throws IOException {
        throw new IOException("Goodbye, World!");
    }

}

亦或者,我们也可以通过 try-catch 来捕获这个异常:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.IOException;

public class Main {

    public static void main(String[] args) {
        try {
            throw new IOException("Goodbye, World!");
        } catch (IOException e){
            throw new RuntimeException("Caught!");
        }
    }

}

我们需要更深入点

而如果你是一个善于提出问题的人,你可能会接着问下去:既然 Java 代码最终会编译为 JVM 字节码,那么在 JVM 字节码层面,这些代码是如何表示的呢?

通过 javap 实用工具,我们得以有机会一窥上述代码的真面孔:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #14                         // Main
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // java/io/IOException
   #8 = Utf8               java/io/IOException
   #9 = String             #10            // Goodbye, World!
  #10 = Utf8               Goodbye, World!
  #11 = Methodref          #7.#12         // java/io/IOException."<init>":(Ljava/lang/String;)V
  #12 = NameAndType        #5:#13         // "<init>":(Ljava/lang/String;)V
  #13 = Utf8               (Ljava/lang/String;)V
  #14 = Class              #15            // Main
  #15 = Utf8               Main
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               main
  #19 = Utf8               ([Ljava/lang/String;)V
  #20 = Utf8               Exceptions
  #21 = Utf8               SourceFile
  #22 = Utf8               Main.java
{
  public Main();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]) throws java.io.IOException;
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: new           #7                  // class java/io/IOException
         3: dup
         4: ldc           #9                  // String Goodbye, World!
         6: invokespecial #11                 // Method java/io/IOException."<init>":(Ljava/lang/String;)V
         9: athrow
      LineNumberTable:
        line 6: 0
    Exceptions:
      throws java.io.IOException
}

眼尖的你可能已经注意到最下面两行已经展示出了我们想要的东西:我们在方法声明中填写的异常抛出声明,会作为 JVM 字节码方法表中的 Exception 属性表的一部分提供给 JVM 虚拟机

而当我们通过 try-catch 来显式捕获异常的时候,它看起来是这样的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Main
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #19                         // Main
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // java/io/IOException
   #8 = Utf8               java/io/IOException
   #9 = String             #10            // Goodbye, World!
  #10 = Utf8               Goodbye, World!
  #11 = Methodref          #7.#12         // java/io/IOException."<init>":(Ljava/lang/String;)V
  #12 = NameAndType        #5:#13         // "<init>":(Ljava/lang/String;)V
  #13 = Utf8               (Ljava/lang/String;)V
  #14 = Class              #15            // java/lang/RuntimeException
  #15 = Utf8               java/lang/RuntimeException
  #16 = String             #17            // Caught!
  #17 = Utf8               Caught!
  #18 = Methodref          #14.#12        // java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
  #19 = Class              #20            // Main
  #20 = Utf8               Main
  #21 = Utf8               Code
  #22 = Utf8               LineNumberTable
  #23 = Utf8               main
  #24 = Utf8               ([Ljava/lang/String;)V
  #25 = Utf8               StackMapTable
  #26 = Utf8               SourceFile
  #27 = Utf8               Main.java
{
  public Main();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: new           #7                  // class java/io/IOException
         3: dup
         4: ldc           #9                  // String Goodbye, World!
         6: invokespecial #11                 // Method java/io/IOException."<init>":(Ljava/lang/String;)V
         9: athrow
        10: astore_1
        11: new           #14                 // class java/lang/RuntimeException
        14: dup
        15: ldc           #16                 // String Caught!
        17: invokespecial #18                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
        20: athrow
      Exception table:
         from    to  target type
             0    10    10   Class java/io/IOException
      LineNumberTable:
        line 7: 0
        line 8: 10
        line 9: 11
      StackMapTable: number_of_entries = 1
        frame_type = 74 /* same_locals_1_stack_item */
          stack = [ class java/io/IOException ]
}

try-catch 会被转换为 JVM 字节码的异常表(Exception table),异常表会负责捕获指定范围内(from 和 to)的指定类型异常(type),当异常抛出时,将代码跳转到指定的 JVM 代码行中(target)。

看到这里你可能就会开始提问:那么受查异常和非受查异常的差别呢,如何体现在 JVM 字节码里呢?

而答案是:完全没有区别。

编译器诡计:所见不一定所得

其实 Java 中并不缺乏这种“编译器诡计”的例子,从泛型到自动拆装箱,从字符串连接再到 lambda 表达式...... Java 的语言设计者赋予 Java 编译器巨大的魔力,在不变动中间表示代码(这里是 JVM 字节码)的情况下提供更多的语法特性或者语义限制。而受查异常和非受查异常显然就是其中的一部分 —— 在 JVM 字节码的层面,它们不能说是一模一样,只能说是毫无区别。

Kotlin: 规则破坏者

其实 Java 的受查异常是一个饱受诟病的语法特性,就和 Java 的泛型一样远近闻名:这些异常声明可能会随着调用链的增加越来越长,而有时也许你根本不想捕获这些异常,你只想简单的抛出他们。Java 社区中著名的 Lombok 项目甚至专门提供了一个 @SneakyThrows 注解来替你生成这些冗长的模板代码。那么是否有一个 JVM 语言抛弃了这个设定?答案是肯定的,那就是大名鼎鼎的 Kotlin。

Kotlin does not have checked exceptions. There are many reasons for this, but we will provide a simple example that illustrates why it is the case. Kotlin 没有受查异常。造成这种情况的原因有很多,但我们将提供一个简单的示例来说明为什么会出现这种情况。 The following is an example interface from the JDK implemented by the StringBuilder class: 以下是 JDK 中由 StringBuilder 类实现的示例接口: Appendable append(CharSequence csq) throws IOException; This signature says that every time I append a string to something (a StringBuilder, some kind of a log, a console, etc.), I have to catch the IOExceptions. Why? Because the implementation might be performing IO operations (Writer also implements Appendable). The result is code like this all over the place: 这个签名表明,每次我将字符串附加到某些东西( StringBuilder 、某种日志、控制台等)时,我都必须捕获 IOExceptions 。为什么?因为该实现可能正在执行 IO 操作( Writer 也实现 Appendable )。结果到处都是这样的代码: try { log.append(message) } catch (IOException e) { // Must be safe } And that's not good. Just take a look at Effective Java, 3rd Edition, Item 77: Don't ignore exceptions. 这很不好。只需看一下《Effective Java》,第 3 版,第 77 条:不要忽略异常。 Bruce Eckel says this about checked exceptions: Bruce Eckel 对于受查异常是这样说的: Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result – decreased productivity and little or no increase in code quality. 对小型程序的检查得出的结论是,要求异常规范既可以提高开发人员的生产力,又可以提高代码质量,但大型软件项目的经验表明了不同的结果——生产力下降,代码质量几乎没有提高。 ...... —— https://kotlinlang.org/docs/exceptions.html#checked-exceptions

那么对于和上述代码类似的 Kotlin 代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.IOException;

fun main(){
     throw IOException("Goodbye, World!");
}

可以正常通过编译并运行。那么 Kotlin 是做了什么魔法呢?依然用 javap 来看看:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final class MainKt
  minor version: 0
  major version: 52
  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
  this_class: #2                          // MainKt
  super_class: #4                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 2
Constant pool:
   #1 = Utf8               MainKt
   #2 = Class              #1             // MainKt
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               main
   #6 = Utf8               ()V
   #7 = Utf8               java/io/IOException
   #8 = Class              #7             // java/io/IOException
   #9 = Utf8               Goodbye, World!
  #10 = String             #9             // Goodbye, World!
  #11 = Utf8               <init>
  #12 = Utf8               (Ljava/lang/String;)V
  #13 = NameAndType        #11:#12        // "<init>":(Ljava/lang/String;)V
  #14 = Methodref          #8.#13         // java/io/IOException."<init>":(Ljava/lang/String;)V
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = NameAndType        #5:#6          // main:()V
  #17 = Methodref          #2.#16         // MainKt.main:()V
  #18 = Utf8               args
  #19 = Utf8               [Ljava/lang/String;
  #20 = Utf8               Lkotlin/Metadata;
  #21 = Utf8               mv
  #22 = Integer            1
  #23 = Integer            9
  #24 = Integer            0
  #25 = Utf8               k
  #26 = Integer            2
  #27 = Utf8               xi
  #28 = Integer            48
  #29 = Utf8               d1
  #30 = Utf8               \u0000\u0006\n\u0000\n\u0002\u0010\u0002\u001a\u0006\u0010\u0000\u001a\u00020\u0001
  #31 = Utf8               d2
  #32 = Utf8
  #33 = Utf8               Main.kt
  #34 = Utf8               Code
  #35 = Utf8               LineNumberTable
  #36 = Utf8               LocalVariableTable
  #37 = Utf8               SourceFile
  #38 = Utf8               RuntimeVisibleAnnotations
{
  public static final void main();
    descriptor: ()V
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=3, locals=0, args_size=0
         0: new           #8                  // class java/io/IOException
         3: dup
         4: ldc           #10                 // String Goodbye, World!
         6: invokespecial #14                 // Method java/io/IOException."<init>":(Ljava/lang/String;)V
         9: athrow
      LineNumberTable:
        line 4: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x1009) ACC_PUBLIC, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=0, locals=1, args_size=1
         0: invokestatic  #17                 // Method main:()V
         3: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  args   [Ljava/lang/String;
}

作为受查异常的 IOException 依然通过 athrow 指令照常抛出,但是却没有任何的处理措施 —— 无论是异常表还是 Exception 属性表。万里长城今犹在,不见当年秦始皇。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-4-03 2,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
(译) Understanding Elixir Macros, Part 1 Basics
这是讨论宏 (Macros) 微系列文章的第一篇. 我原本计划在我即将出版的《Elixir in Action》一书中讨论这个主题, 但最终决定不这么做, 因为这个主题不符合这本书的主题, 这本书更关注底层 VM 和 OTP 的关键部分.
Cloud-Cloudys
2023/10/21
2300
(译) Understanding Elixir Macros, Part 1 Basics
(译) Understanding Elixir Macros, Part 5 - Reshaping the AST
上次我介绍了一个基本版本的可追溯宏 deftraceable, 它允许我们编写可跟踪的函数. 这个宏的最终版本还有一些遗留的问题, 今天我们将解决其中一个 — 参数模式匹配.
Cloud-Cloudys
2023/10/21
1710
(译) Understanding Elixir Macros, Part 4 - Diving Deeper
在前一篇文章中, 我向你展示了分析输入 AST 并对其进行处理的一些基本方法. 今天我们将研究一些更复杂的 AST 转换. 这将重提已经解释过的技术. 这样做的目的是为了表明深入研究 AST 并不是很难的, 尽管最终的结果代码很容易变得相当复杂, 而且有点黑科技(hacky).
Cloud-Cloudys
2023/10/21
1430
(译) Understanding Elixir Macros, Part 2 - Micro Theory
这是 Elixir 中的宏系列的第二篇. 上一次我们讨论了编译过程和 Elixir AST, 最后讲了一个基本的宏的例子 trace. 今天, 我们会更详细地讲解宏的机制.
Cloud-Cloudys
2023/10/21
2210
(译) Understanding Elixir Macros, Part 3 - Getting into the AST
是时候继续探索 Elixir 的宏了. 上次我介绍了一些关于宏的基本原理, 今天, 我将进入一个较少谈及的领域, 并讨论Elixir AST 的一些细节.
Cloud-Cloudys
2023/10/21
2010
深入浅出 Babel 下篇:既生 Plugin 何生 Macros
我想我们对宏并不陌生,因为很多程序员第一门语言就是 C/C++; 一些 Lisp 方言也支持宏(如 Clojure、Scheme), 听说它们的宏写起来很优雅;一些现代的编程语言对宏也有一定的支持,如 Rust、Nim、Julia、Elixir,它们是如何解决技术问题, 实现类Lisp的宏系统的?宏在这些语言中扮演着什么角色...
Nealyang
2019/10/18
1.6K0
来来来,咱们元编程入个门
前一篇文章竟然被很多人批「干货太少」 —— 一看你们就没有看过 Rich 他老人家的 Hammock Driven Development(我很久前推荐过滴),这世界不缺代码,缺的是思想。你们要干货。好,咱们来点干货。正好之前有个读者在留言中诉苦,说看了之前的文章 谈谈抽象 不解馋,虽然学了 clojure 却总也厘不清 macro 的使用,跟着书上的例子可以写下去,脱离了例子却步履维艰,总觉得自己对于 metapgrogramming 介于入门和没入门之间。那么本文就干一些,尝试用粗浅的语言对 metap
tyrchen
2018/03/28
9720
来来来,咱们元编程入个门
the-solution-of-elixir-continuous-runtime-system-code-coverage-collection
Code coverage is an effective means to assist software engineers in verifying code quality. The runtime environment’s ability to collect code coverage fully combines black and white box testing capabilities and greatly increases engineers’ confidence in software quality. This article introduces a solution for code coverage collection in the Elixir runtime environment, and provides an in-depth insight into its internal principles.
Cloud-Cloudys
2023/10/21
2030
the-solution-of-elixir-continuous-runtime-system-code-coverage-collection
Elixir 连续运行时代码覆盖率采集方案
作为 SET 和 SWE, 我们经常需要编写单元测试或集成测试用例来验证系统/应用的正确性, 但同时我们也常会质疑我们的测试是否充分了. 这时测试覆盖率是可以辅助用来衡量我们测试充分程度的一种手段, 增强发布成功率与信心, 同时给了我们更多可思考的视角. 值的注意的是代码覆盖率高不能说明代码质量高, 但是反过来看, 代码覆盖率低, 代码质量不会高到哪里去.
Cloud-Cloudys
2023/10/21
4190
Elixir 连续运行时代码覆盖率采集方案
Clojure 运行原理之编译器剖析
这里的 runtime 指的是 JVM,JVM 之初是为运行 Java 语言而设计,而现在已经发展成一重量级平台,除了 Clojure 之外,很多动态语言也都选择基于 JVM 去实现。 为了更加具体描述 Clojure 运行原理,会分两篇文章来介绍。 本文为第一篇,涉及到的主要内容有:编译器工作流程、Lisp 的宏机制。 第二篇将主要分析 Clojure 程序编译成的 bytecode 如何保证动态语言的特性以及如何加速 Clojure 程序执行速度,这会涉及到 JVM 的类加载机制、反射机制。
飞驰的西瓜
2022/07/26
1.1K0
Clojure 运行原理之编译器剖析
Scala Macros - 元编程 Metaprogramming with Def Macros
    Scala Macros对scala函数库编程人员来说是一项不可或缺的编程工具,可以通过它来解决一些用普通编程或者类层次编程(type level programming)都无法解决的问题,这
用户1150956
2018/01/05
3.2K0
Policy Engine 的前世今生
作为一个 video streaming service,TubiTV 很重要的一项功能是保证影视剧按照合约上的要求在规定的时间(窗口期),规定的平台,以及规定的国家发布。比如 Terminator,合约上规定 7/1 ~ 10/30(我瞎编的窗口),在美国可以上线,只允许 appletv,iphone,roku,web 访问,那么,如果我们不能正确处理,让加拿大的观众通过正常渠道访问到,或者过了窗口期,美国的观众也能访问,那么就是违约行为,可能导致严重的后果。这是 video stream service
tyrchen
2018/03/29
1.5K0
Policy Engine 的前世今生
听GPT 讲Rust源代码--src/tools(15)
在Rust源代码中,rust/src/tools/rust-analyzer/crates/mbe/src/token_map.rs文件的作用是实现了一个能够将输入的文本映射为标记的结构。具体来说,它定义和实现了几个结构体(struct)和枚举(enum),包括TokenMap和TokenTextRange。
fliter
2023/12/26
2380
听GPT 讲Rust源代码--src/tools(15)
当我做 hackathon 时我在做什么 (1)
从上周四开始的周末(1/7-1/10),是 Tubi 一年一度的 OSS-a-thon。所谓 OSS-a-thon,是我们为了回馈开源社区举办的 hackathon,参与者需要做和开源项目有关的项目 — 可以是对已有的开源项目进行改进,提交 PR,或者做新的项目,但需要开源。
tyrchen
2021/01/29
1.2K0
当我做 hackathon 时我在做什么 (1)
听GPT 讲Rust源代码--compiler(37)
在Rust编译器的源代码中,rust/compiler/rustc_expand/src/errors.rs文件的作用是定义了各种错误类型和帮助信息,这些错误和帮助信息用于扩展宏时的错误处理和用户提示。
fliter
2024/04/15
1770
听GPT 讲Rust源代码--compiler(37)
听GPT 讲Rust源代码--compiler(47)
在Rust源代码中,rust/compiler/rustc_builtin_macros/src/format_foreign.rs这个文件的作用是处理外部格式化宏的实现。这些宏是Rust语言用来格式化输出的宏,它们在编译时被翻译成具体的代码实现。
fliter
2024/04/26
1530
听GPT 讲Rust源代码--compiler(47)
模板引擎Velocity 基础
详细介绍大家可以看官网,传送门放这里了:The Apache Velocity Project
叫我阿杰好了
2022/11/07
5.7K0
模板引擎Velocity 基础
Vue项目框架搭建(不定时更新)
第二步:在 state.js action.js motation.js中分别编写对应的代码:
CherishTheYouth
2020/10/28
9350
Jinja2用法总结
渲染模版时有两种传递参数的方式:用 var='value' 传递一个参数;使用字典组织多个参数,并且加两个*号转换成关键字参数传入。
步履不停凡
2019/09/11
2.2K0
Postgresql源码(43)psql交互式词法解析流程分析
psql交互式词法解析流程分析 交互式词法解析的经典代码框架,需要自己写个交互式小工具可以用psql当模板,快速上手lex 0 总结 psqlscan_emit函数是psqlscan.l中的ECHO宏,负责匹配词法后,把数据整理到PsqlScanState->output_buf中。 基本所有的语法匹配完了都会调psqlscan_emit,所以调试语法树挂这个函数。 语法解析时不太好调试的是当前状态字的转换,可以在函数入参中增加YY_START宏查看当前状态: * <xb> bit string
mingjie
2022/07/14
4900
相关推荐
(译) Understanding Elixir Macros, Part 1 Basics
更多 >
LV.1
阿里巴巴集团
目录
  • 你知道吗,Java中的受查和非受查异常,其实并不存在区别......
    • “受查”和“非受查”
    • 我们需要更深入点
    • 编译器诡计:所见不一定所得
    • Kotlin: 规则破坏者
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档