控制流是命令式编程的“遗留物”,它已泄漏到其他各种编程范例中,包括Java的面向对象范例 。 除了有用的和无处不在的分支和循环结构外,还包括原语(例如GOTO)和非局部变量(例如异常)。 让我们仔细看看这些有争议的控制流技术。
去
goto
是Java语言中的保留字 。 goto
也是JVM字节码中的有效指令。 但是,在Java中,执行goto
操作并不容易。 可以从以下堆栈溢出问题中获取一个示例 :
向前跳
label: {// do stuffif (check) break label;// do more stuff
}
在字节码中:
2 iload_1 [check]3 ifeq 6 // Jumping forward6 ..
向后跳
label: do {// do stuffif (check) continue label;// do more stuffbreak label;
} while(true);
在字节码中:
2 iload_1 [check]3 ifeq 96 goto 2 // Jumping backward9 ..
当然,这些技巧仅在非常罕见的情况下才有用,即使那样,您可能仍要重新考虑。 因为我们都知道在代码中使用goto
会发生什么:
取自xkcd的图形: http : //xkcd.com/292/
打破控制流与例外
异常是在发生错误或故障时突破控制流结构的好工具。 但是也可以使用异常来定期向下跳转(没有错误或失败):
try {// Do stuffif (check) throw new Exception();// Do more stuff
}
catch (Exception notReallyAnException) {}
这就像前面提到的涉及标签的技巧一样让人感到困惑。
合法使用异常控制流:
但是,在其他一些非常罕见的情况下,异常是摆脱复杂的嵌套控制流(没有错误或失败)的好工具。 当您使用SAXParser
解析XML文档时,可能就是这种情况。 也许,您的逻辑将要测试至少三个<check/>
元素的出现,在这种情况下,您可能希望跳过对文档其余部分的分析。 这是实现上述内容的方法:
创建一个ControlFlowException
:
package com.example;public class ControlFlowException
extends SAXException {}
请注意,通常,您可能更喜欢使用RuntimeException
,但是SAX合同要求处理程序实现抛出SAXException
。
在SAX处理程序中使用该ControlFlowException
:
package com.example;import java.io.File;import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;import org.xml.sax.Attributes;
import org.xml.sax.helpers.DefaultHandler;public class Parse {public static void main(String[] args) throws Exception {SAXParser parser = SAXParserFactory.newInstance().newSAXParser();try {parser.parse(new File("test.xml"),new Handler());System.out.println("Less than 3 <check/> elements found.");} catch (ControlFlowException e) {System.out.println("3 or more <check/> elements found.");}}private static class Handler extends DefaultHandler {int count;@Overridepublic void startElement(String uri, String localName, String qName,Attributes attributes) {if ("check".equals(qName) && ++count >= 3)throw new ControlFlowException();}}
}
何时将异常用于控制流:
对于SAX,上述实践似乎是合理的,因为SAX合同期望发生此类异常,即使在这种情况下,它们不是异常而是常规控制流。 以下是在实际示例中何时使用上述做法的一些提示:
- 您想突破复杂的算法(而不是简单的块)。
- 您可以实现“处理程序”以将行为引入复杂的算法中。
- 这些“处理程序”明确允许在合同中抛出异常。
- 您的用例不会真正重构复杂算法。
真实示例:使用jOOQ进行批处理查询
在jOOQ中 ,可以“批量存储”记录的集合。 jOOQ不会为每个记录运行单个SQL语句,而是会收集所有SQL语句并执行JDBC批处理操作以一次存储所有这些SQL语句。
由于每条记录都以面向对象的方式封装了给定store()
调用的生成的SQL渲染和执行,因此以可重用的方式提取SQL渲染算法而不破坏(或暴露)太多东西将非常棘手。 相反,jOOQ的批处理操作实现了以下简单的伪算法:
// Pseudo-code attaching a "handler" that will
// prevent query execution and throw exceptions
// instead:
context.attachQueryCollector();// Collect the SQL for every store operation
for (int i = 0; i < records.length; i++) {try {records[i].store();}// The attached handler will result in this// exception being thrown rather than actually// storing records to the databasecatch (QueryCollectorException e) {// The exception is thrown after the rendered// SQL statement is availablequeries.add(e.query()); }
}
一个真实的例子:异常变化的行为
jOOQ的另一个示例显示了此技术如何可用于引入仅在极少数情况下适用的异常行为。 如问题#1520中所述 ,某些数据库在每个语句可能的绑定值数量方面存在限制。 这些是:
- SQLite:999
- 英格莱斯10.1.0:1024
- Sybase ASE 15.5:2000
- SQL Server 2008年:2100
为了规避此限制,一旦达到最大值,jOOQ必须内联所有绑定值。 由于jOOQ的查询模型通过应用复合模式大量封装了SQL呈现和变量绑定行为,因此在遍历查询模型树之前无法知道绑定值的数量。 有关jOOQ的查询模型架构的更多详细信息,请考虑以下先前的博客文章: http ://blog.jooq.org/2012/04/10/the-visitor-pattern-re-visited
因此,解决方案是呈现SQL语句并计算将要呈现的绑定值。 规范的实现将是以下伪代码:
String sql;query.renderWith(countRenderer);
if (countRenderer.bindValueCount() > maxBindValues) {sql = query.renderWithInlinedBindValues();
}
else {sql = query.render();
}
可以看出,规范的实现将需要两次渲染SQL语句。 第一个渲染仅用于计算绑定值的数量,而第二个渲染将生成真实的SQL语句。 这里的问题是,一旦异常事件(绑定值太多)发生,异常行为就应该被放置。 更好的解决方案是引入一个“处理程序”,在常规的“渲染尝试”中对绑定值进行计数,并为绑定值数量超过最大值的少数例外“尝试”抛出ControlFlowException
:
// Pseudo-code attaching a "handler" that will
// abort query rendering once the maximum number
// of bind values was exceeded:
context.attachBindValueCounter();
String sql;
try {// In most cases, this will succeed:sql = query.render();
}
catch (ReRenderWithInlinedVariables e) {sql = query.renderWithInlinedBindValues();
}
第二种解决方案更好,因为:
- 我们仅在例外情况下重新呈现查询。
- 我们还没有完成呈现查询以计算实际计数,而是提早中止以重新呈现。 即我们不在乎是否有2000、5000或100000绑定值。
结论
与所有特殊技术一样,请记住在正确的时机使用它们。 如有疑问,请三思。
翻译自: https://www.javacodegeeks.com/2013/05/rare-uses-of-a-controlflowexception.html