如何通过var关键字在try-with-resources中简化流对象的声明,实现长尾疑问?
- 内容介绍
- 相关推荐
本文共计652个文字,预计阅读时间需要3分钟。
关键点在于:
哪些写法是合法的?
以下写法在 Java 10+ 编译通过:
-
try (var fis = new FileInputStream("a.txt")) { ... } ✅ —— 类型可推断为 FileInputStream
-
try (var reader = Files.newBufferedReader(Paths.get("b.txt"))) { ... } ✅ —— 推断为 BufferedReader
-
try (var out = new BufferedOutputStream(new FileOutputStream("c.bin"))) { ... } ✅ —— 推断为 BufferedOutputStream
try (var fis = new FileInputStream("a.txt")) { ... } ✅ —— 类型可推断为 FileInputStream
try (var reader = Files.newBufferedReader(Paths.get("b.txt"))) { ... } ✅ —— 推断为 BufferedReader
try (var out = new BufferedOutputStream(new FileOutputStream("c.bin"))) { ... } ✅ —— 推断为 BufferedOutputStream
但这些写法会失败:
-
var fis; try (fis = new FileInputStream("a.txt")) { ... }❌ ——var必须在声明时初始化 -
try (var stream = getStream()) { ... }❌(如果getStream()返回AutoCloseable但签名是AutoCloseable或Object)—— 类型推断失败,编译报错cannot infer type for var
为什么有时候推断出的类型不是你想要的?
var 推断的是表达式**右侧的静态类型**,不是运行时类型。例如:
try (var stream = new ByteArrayInputStream(new byte[0])) { ... }
这里推断出的类型是 ByteArrayInputStream,没问题;但如果你写:
try (var stream = (InputStream) new ByteArrayInputStream(new byte[0])) { ... }
推断结果就变成 InputStream,而 InputStream 本身也实现了 AutoCloseable,语法合法,但后续如果调用 stream.reset() 就会编译失败 —— 因为 InputStream 接口中没有这个方法。
所以要注意:强制转型或泛型擦除后的返回值,会让 var 推断出更宽泛的类型,可能丢失子类特有 API。
和显式类型声明比,有什么实际影响?
- 代码更短,尤其对嵌套构造(如
new BufferedReader(new InputStreamReader(...)))可显著减少重复类型名
- 重构友好:改了底层实现类(比如从
FileReader 换成 Files.newBufferedReader),只要返回类型仍兼容,var 声明无需改动
- 但调试时 IDE 可能不直观显示类型(需悬停查看),团队若有人不熟悉
var 容易误读
- 性能无差异 ——
var 是纯编译期特性,字节码和显式声明完全一致
new BufferedReader(new InputStreamReader(...)))可显著减少重复类型名FileReader 换成 Files.newBufferedReader),只要返回类型仍兼容,var 声明无需改动var 容易误读var 是纯编译期特性,字节码和显式声明完全一致真正容易被忽略的是:资源变量的作用域仍严格限制在 try 块内,哪怕用了 var,也不能在 catch 或 finally 中访问 —— 这和显式声明行为完全一致,但新手有时会下意识以为「简写了就能往外用」。
本文共计652个文字,预计阅读时间需要3分钟。
关键点在于:
哪些写法是合法的?
以下写法在 Java 10+ 编译通过:
-
try (var fis = new FileInputStream("a.txt")) { ... } ✅ —— 类型可推断为 FileInputStream
-
try (var reader = Files.newBufferedReader(Paths.get("b.txt"))) { ... } ✅ —— 推断为 BufferedReader
-
try (var out = new BufferedOutputStream(new FileOutputStream("c.bin"))) { ... } ✅ —— 推断为 BufferedOutputStream
try (var fis = new FileInputStream("a.txt")) { ... } ✅ —— 类型可推断为 FileInputStream
try (var reader = Files.newBufferedReader(Paths.get("b.txt"))) { ... } ✅ —— 推断为 BufferedReader
try (var out = new BufferedOutputStream(new FileOutputStream("c.bin"))) { ... } ✅ —— 推断为 BufferedOutputStream
但这些写法会失败:
-
var fis; try (fis = new FileInputStream("a.txt")) { ... }❌ ——var必须在声明时初始化 -
try (var stream = getStream()) { ... }❌(如果getStream()返回AutoCloseable但签名是AutoCloseable或Object)—— 类型推断失败,编译报错cannot infer type for var
为什么有时候推断出的类型不是你想要的?
var 推断的是表达式**右侧的静态类型**,不是运行时类型。例如:
try (var stream = new ByteArrayInputStream(new byte[0])) { ... }
这里推断出的类型是 ByteArrayInputStream,没问题;但如果你写:
try (var stream = (InputStream) new ByteArrayInputStream(new byte[0])) { ... }
推断结果就变成 InputStream,而 InputStream 本身也实现了 AutoCloseable,语法合法,但后续如果调用 stream.reset() 就会编译失败 —— 因为 InputStream 接口中没有这个方法。
所以要注意:强制转型或泛型擦除后的返回值,会让 var 推断出更宽泛的类型,可能丢失子类特有 API。
和显式类型声明比,有什么实际影响?
- 代码更短,尤其对嵌套构造(如
new BufferedReader(new InputStreamReader(...)))可显著减少重复类型名
- 重构友好:改了底层实现类(比如从
FileReader 换成 Files.newBufferedReader),只要返回类型仍兼容,var 声明无需改动
- 但调试时 IDE 可能不直观显示类型(需悬停查看),团队若有人不熟悉
var 容易误读
- 性能无差异 ——
var 是纯编译期特性,字节码和显式声明完全一致
new BufferedReader(new InputStreamReader(...)))可显著减少重复类型名FileReader 换成 Files.newBufferedReader),只要返回类型仍兼容,var 声明无需改动var 容易误读var 是纯编译期特性,字节码和显式声明完全一致真正容易被忽略的是:资源变量的作用域仍严格限制在 try 块内,哪怕用了 var,也不能在 catch 或 finally 中访问 —— 这和显式声明行为完全一致,但新手有时会下意识以为「简写了就能往外用」。

