详解Java 10 var关键字和示例教程

关键要点

  • Java 10引入了一个闪亮的新功能:局部变量类型推断。对于局部变量,现在可以使用特殊的保留类型名称“var”代替实际类型。
  • 提供这个特性是为了增强Java语言,并将类型推断扩展到局部变量的声明上。这样可以减少板代码,同时仍然保留Java的编译时类型检查。
  • 由于编译器需要通过检查赋值等式右侧(RHS)来推断var的实际类型,因此在某些情况下,这个特性具有局限性,例如在初始化Array和Stream的时候。
  • 如何使用新的“var”来减少样板代码。

在本文中,我将通过示例介绍新的Java SE 10特性——“var”类型。你将学习如何在代码中正确使用它,以及在什么情况下不能使用它。

介绍

Java 10引入了一个闪亮的新功能:局部变量类型推断。对于局部变量,现在可以使用特殊的保留类型名称“var”代替实际类型,如下所示:

var name = “Mohamed Taman”;

提供这个特性是为了增强Java语言,并将类型推断扩展到局部变量的声明上。这样可以减少板代码,同时仍然保留Java的编译时类型检查。

由于编译器需要通过检查赋值等式右侧(RHS)来推断var的实际类型,因此在某些情况下,这个特性具有局限性。我会在稍后提到这个问题。现在,让我们来看一些简单的例子吧。

在开始演示代码之前,你需要一个IDE来体验这些新特性。现在有很多可选择的IDE,所以你可以在它们当中选择你喜欢的能够支持Java SE 10的IDE,比如Apache NetBeans 9、IntelliJ IDEA 2018或最新版本的Eclipse。

就个人而言,我更喜欢使用交互式的编程工具,可以快速学习Java语言语法,了解新的Java API及其特性,甚至用来进行复杂代码的原型设计。这与枯燥的编辑、编译和执行代码的繁琐过程不太一样:

  • 写一个完整的程序;
  • 编译并修复错误;
  • 运行程序;
  • 弄清楚它有什么问题;
  • 修改;
  • 重复这个过程。

除了IDE之外,现在还可以使用从Java SE 9以就随ava SE JDK一起发布的JShell。

什么是JShell

现在,Java有了自己的REPL(Read-Evaluate-Print-Loop)实现JShell(Java Shell),作为交互式的编程环境。那么,它有什么神奇的地方?JShell提供了一个快速友好的环境,让你能够快速探索、发现和试验Java语言特性及其丰富的库。

在JShell中,你可以一次输入一个程序元素,并可以立即看到结果,然后根据需要对代码做出调整。因此,JShell用它的Read-Evaluate-Print循环取代了编辑、编译和执行的繁琐过程。在JShell中,你不需要编写完整的程序,只需要编写JShell命令和Java代码片段即可。

当你输入代码段时,JShell会立即读取、执行并打印结果,然后准备好执行下一个代码片段。因此,JShell的即时反馈可以让你保持注意力,提高你的效率,并加快学习和软件开发过程。

对JShell的介绍就到此为止(InfoQ最近对这个工具进行过全面介绍)。为了深入了解JShell的功能,我录制了一套视频教程“Hands-on Java 10 Programming with JShell”,可以帮助你掌握JShell,可以从PacktUdemy访问这些教程。

现在,让我们通过一些简单的示例(使用JShell)来了解这个新的var类型能做些什么。

必备软件

为了能用上JShell,我假设你安装了Java SE或JDK 10+,并且JDK的bin目录已经加入到系统路径中。如果还没有安装,可以在这里下载JDK 10+ 最新版本

启动JShell会话

  • 在Windows上,打开命令提示符,输入jshell并按回车键。
  • 在Linux上,打开一个shell窗口,输入jshell并按回车键。
  • 在macOS(以前称为OS X)上,打开终端窗口,输入“jshell”并按回车键。

这个命令会启动一个新的JShell会话,并显示这个消息:

| Welcome to JShell -- Version 10.0.1
| For an introduction type: /help intro
jshell>

使用“var”类型

现在你已经安装了JDK 10,现在让我们开始玩JShell。我们直接跳到终端,通过示例来了解var类型。只需在jshell提示符下输入我接下来要介绍的每个代码片段,我会把结果留给你作为练习。如果你稍微有瞄过一两眼在代码,你会注意到它们看起来好像是错的,因为当中没有分号。你可以试试看,看看能不能运行。

简单的类型推理

这是var类型的基本用法,在下面的示例中,编译器可以将RHS推断为String字面量:

var name = "Mohamed Taman"
var lastName = str.substring(8)
System.out.println("Value: "+lastName +" ,and type is: "+ lastName.getClass().getTypeName())

这里不需要分号,因为JShell是一个交互式环境。只有当同一行代码有多个语句或一个类型声明或方法声明中有多个语句时才需要分号,你将在后面的示例中看到。

var类型和继承

在使用var时,多态仍然有效。在继承的世界中,var类型的子类型可以像平常一样赋值给超类型的var类型,如下所示:

import javax.swing.*
var password = new JPasswordField("Password text")
String.valueOf(password.getPassword()) // // 将密码的字符数组转换成字符串
var textField = new JTextField("Hello text")
textField = password
textField.getText()

但不能将超类型var赋值给子类型var,如下所示:

password = textField

这是因为JPasswordField是JTextField的子类。

var和编译时安全性

如果出现错误的赋值操作会怎样?不兼容的变量类型不能相互赋值。一旦编译器推断出实际类型的var,就不能将错误的值赋值给它,如下所示:

var number = 10
number = "InfoQ"

这里发生了什么?编译器将“var number = 10”替换为“int number = 10”,所以仍然可以保证安全性。

var与集合和泛型

现在让我们来看看var与集合和泛型一起使用时如何进行类型推断。我们先从集合开始。在下面的情况中,编译器可以推断出集合元素的类型是什么:

var list = List.of(10);

这里没有必要进行类型转换,因为编译器已经推断出正确的元素类型为int。

int i = list.get(0); //等效于: var i = list.get(0);

下面的情况就不一样了,编译器只会将其作为对象集合(而不是整数),因为在使用菱形运算符时,Java需要LHS(左侧)的类型来推断RHS的类型:

var list2 = new ArrayList<>(); list2.add(10); list2
int i = list2.get(0) //编译错误
int i = (int) list2.get(0) //需要进行转换,获得int

对于泛型,最好在RHS使用特定类型(而不是菱形运算符),如下所示:

var list3 = new ArrayList<Integer>(); list3.add(10); System.out.println(list3)
int i = list3.get(0)

for循环中的var类型

让我们先来看看基于索引的For循环:

for (var x = 1; x <= 5; x++) {
  var m = x * 2; //等效于: int m = x * 2;
  System.out.println(m);
}

下面是在For Each循环中:

var list = Arrays.asList(1,2,3,4,5,6,7,8,9,10)
 for (var item : list) {
  var m = item + 2;
  System.out.println(m);
}

现在我有一个问题,var是否适用于Java 8 Stream?让我们看看下面的例子:

var list = List.of(1, 2, 3, 4, 5, 6, 7)
var stream = list.stream()
stream.filter(x -> x % 2 == 0).forEach(System.out::println)

var类型和三元运算符

那么三元运算符呢?

var x = 1 > 0 ? 10 : -10
int i = x

现在,如果在三元运算符的RHS中使用不同类型的操作数会怎样?让我们来看看:

var x = 1 > 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) //Integer
var x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass()) // String

这两个例子是否可以说明var的类型是在运行时决定的?绝对不是!让我们以旧方式实现同样的逻辑:

Serializable x = 1 < 0 ? 10 : "Less than zero"; System.out.println(x.getClass())

Serializable是其中两个操作数最具兼容性和最专的有类型(最不专有的类型是java.lang.Object)。

String和Integer都实现了Serializable。Integer从int自动装箱。换句话说,Serializable是两个操作数的LUB(最小上限)。所以,这表明往前数第三个例子中的var类型也是Serializable。

让我们转到另一个主题:将var类型传给方法。

var类型与方法

我们先声明一个名为squareOf的方法,这个方法的参数为BigDecimal类型,并返回参数的平方,如下所示:

BigDecimal squareOf(BigDecimal number) {
 var result= number.multiply(number);
 return result;
 }

var number = new BigDecimal("2.5")
number = squareOf(number)

现在让我们看看它如何与泛型一起使用。我们声明一个名为toIntgerList的方法,参数类型为List<T>(泛型类型),并使用Streams API返回一个整数列表,如下所示:

<T extends Number> List<Integer> toIntgerList(List<T> numbers) {
  var integers = numbers.stream()
     .map(Number::intValue)
     .collect(Collectors.toList());
  return integers;
}

var numbers = List.of(1.1, 2.2, 3.3, 4.4, 5.5)
var integers = toIntgerList(numbers)

var类型与匿名类

最后,让我们看一下var和匿名类。我们通过实现Runnable接口来使用线程,如下所示:

<T extends Number> List<Integer> toIntgerList(List<T> numbers) {
  var integers = numbers.stream()
     .map(Number::intValue)
     .collect(Collectors.toList());
  return integers;
}

var numbers = List.of(1.1, 2.2, 3.3, 4.4, 5.5)
var integers = toIntgerList(numbers)

到目前为止,我已经介绍了Java 10的新特性——“var”类型,它减少了样板编码,同时保持了Java的编译时类型检查。我还通过实例说明了可以用它做些什么。接下来,你将了解var类型的局限性以及不能将它用在哪些地方。

var message = "running..." //effectively final
  var runner = new Runnable(){
   @Override
   public void run() {
    System.out.println(message);
   }}

runner.run()

“var”的局限性

接下来,你将看一些示例,以便了解var类型功能无法做到的事情。

jshell提示符将会告诉你代码出了什么问题,你可以利用这些交互式的即时反馈。

应该要进行初始化

第一个也是最简单的原则就是不允许没有初始值的变量。

var name;

你将得到一个编译错误,因为编译器无法推断这个局部变量x的类型。

不允许复合声明

尝试运行这行代码:

var x = 1, y = 3, z = 4

你将得到一个错误消息:复合声明中不允许使用'var'。

不支持确定性赋值(Definite Assignment)

尝试创建一个名为testVar的方法,如下所示,将下面的代码复制并粘贴到JShell中:

void testVar(boolean b)
{
var x; if (b)
{
 x = 1;
 }
else
{
 x = 2;
}
System.out.println(x);
}

方法不会被创建,而是会抛出编译错误。因为没有设置初始值,所以不能使用'var'。

null赋值

不允许进行null赋值,如下所示:

var name = null;

这将抛出异常“variable initializer is 'null'”。因为null不是一个类型。

与Lambda一起使用

另一个例子,没有Lambda初始化器。这与菱形操作符那个示例一样,RHS需要依赖LHS的类型推断。

var runnable = () ->
 {
}

将抛出异常:“lambda expression needs an explicit target-type”。

var和方法引用

没有方法引用初始值,类似于Lambda和菱形运算符示例:

var abs = BigDecimal::abs

将抛出异常:“method reference needs an explicit target-type”。

var和数组初始化

并非所有数组初始化都有效,让我们看看什么时候var与[]不起作用:

var numbers[] = new int[]
{
2, 4, 6
}

以下也不起作用:

var numbers =
{
2, 4, 6
}

抛出的错误是: “array initializer needs an explicit target-type”。

就像上一个例子一样,var和[]不能同时用在LHS一边:

var numbers[] =
{
2, 4, 6
}

错误: 'var' is not allowed as an element type of an array。

只有以下数组初始化是有效的:

var numbers = new int[]
{
2, 4, 6
}
var number = numbers[1]number = number + 3

不允许使用var字段

class Clazz
{
private var name;
}

不允许使用var方法参数

void doAwesomeStuffHere(var salary)
{
}

不能将var作为方法返回类型

var getAwesomeStuff()
{
 return salary;
}

catch子句中不能使用var

try
{
Files.readAllBytes(Paths.get("c:\temp\temp.txt"));
}
catch (var e)
{
}

在编译时var类型究竟发生了什么?

“var”实际上只是一个语法糖,并且它不会在编译的字节码中引入任何新的结构,在运行期间,JVM也没有为它们提供任何特殊的指令。

结论

在这篇文章中,我介绍了“var”类型是什么以及它如何减少样板编码,同时保持Java的编译时类型检查。

然后,你了解了新的JShell工具,即Java的REPL实现,它可以帮助你快速学习Java语言,并探索新的Java API及其功能。你还可以使用JShell对复杂代码进行原型设计,而不是重复编辑、编译和执行的传统繁琐流程。

最后,你了解了所有var类型的功能和限制,例如什么时候可以和不可以使用var。写这篇文章很有意思,所以我希望你喜欢它并能给你带来帮助。

其他资源

  1. JDK 10 Documentation
  2. Hands-on Java 10 Programming with JShell .
  3. Getting Started with Clean Code Java SE 9 .
  4. Overview of JDK 10 and JRE 10 Installation .
  5. JEP 286: Local-Variable Type Inference .
  6. Definite Assignment

总结

以上所述是小编给大家介绍的详解Java 10 var关键字和示例教程,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

推荐:

感兴趣的朋友可以关注小编的微信公众号【码农那点事儿】,更多网页制作特效源码及学习干货哦!!!

(0)

相关推荐

  • 谈谈Java类型中ParameterizedType,GenericArrayType,TypeVariabl,WildcardType

    (1). 和反射+泛型有关的接口类型 java.lang.reflect.Type:java语言中所有类型的公共父接口 java.lang.reflect.ParameterizedType java.lang.reflect.GenericArrayType java.lang.reflect.WildcardType 1. Type直接子接口 ParameterizedType,GenericArrayType,TypeVariable和WildcardType四种类型的接口 Paramet

  • Javarscript中模块(module)、加载(load)与捆绑(bundle)详解

    JS模块简介 js模块化,简单说就是将系统或者功能分隔成单独的.互不影响的代码片段,经过严格定义接口,使各模块间互不影响,且可以为其他所用. 常见的模块化有,C中的include (.h)文件.java中的import等. 为什么JS需要模块 很显然,没有模块我们也可以实现同样的功能,为什么我们还要使用模块来写js代码呢?下面几点是模块化给我们带来的一些变化: 抽象代码:我们在使用模块来调用一个api时,可以不用知道内部是如何实现的,避免去理解其中复杂的代码: 封装代码:在不需要再次修改代码的前

  • Java源码解析之TypeVariable详解

    TypeVariable,类型变量,描述类型,表示泛指任意或相关一类类型,也可以说狭义上的泛型(泛指某一类类型),一般用大写字母作为变量,比如K.V.E等. 源码 public interface TypeVariable<D extends GenericDeclaration> extends Type { //获得泛型的上限,若未明确声明上边界则默认为Object Type[] getBounds(); //获取声明该类型变量实体(即获得类.方法或构造器名) D getGenericDe

  • java10下编译lombok注解代码分享

    序 本文主要研究下在带有lombok(1.16.20版本)注解的代码在java10下的编译问题. 问题 Fatal error compiling at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:216) at org.apache.maven.lifecycle.internal.MojoExecutor.execute(MojoExecutor.java:153) at org.apa

  • 详解Java 10 var关键字和示例教程

    关键要点 Java 10引入了一个闪亮的新功能:局部变量类型推断.对于局部变量,现在可以使用特殊的保留类型名称"var"代替实际类型. 提供这个特性是为了增强Java语言,并将类型推断扩展到局部变量的声明上.这样可以减少板代码,同时仍然保留Java的编译时类型检查. 由于编译器需要通过检查赋值等式右侧(RHS)来推断var的实际类型,因此在某些情况下,这个特性具有局限性,例如在初始化Array和Stream的时候. 如何使用新的"var"来减少样板代码. 在本文中,

  • 详解Java中static关键字和内部类的使用

    目录 一. static 关键字 1. static修饰成员变量 2. static修饰成员方法 3. static成员变量的初始化 二. 内部类 1. 实例内部类 2. 静态内部类 3. 局部内部类 4. 匿名内部类 一. static 关键字 在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的. 1. static修饰成员变量 static修饰的成员变量,称为静态成员变量 [静态成员变量特性]: 不属于某个具体的对象,是类的属

  • 详解Java中static关键字的使用和原理

    目录 概述 定义和使用格式 类变量 静态方法 调用格式 静态原理图解 静态代码块 概述 关于 static 关键字的使用,它可以用来修饰的成员变量和成员方法,被修饰的成员是属于类的,而不是单单是属 于某个对象的.也就是说,既然属于类,就可以不靠创建对象来调用了. 定义和使用格式 类变量 当 static 修饰成员变量时,该变量称为类变量.该类的每个对象都共享同一个类变量的值.任何对象都可以更改 该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作. 类变量:使用 static关键字修

  • 详解Java单元测试之Junit框架使用教程

    目录 单元测试 Junit单元测试框架 单元测试快速入门 单元测试 单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,单元测试就是针对Java方法的测试,进而检查方法的正确性 目前测试方法是怎么进行的,存在什么问题? 1.只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响 2.无法得到测试的结果报告,需要程序员自己去观察测试是否成功 3.无法实现自动化测试 Junit单元测试框架 1.Junit是使用Java语言实现的单元测试框架,它是开源的

  • 详解Java目录操作与文件操作教程

    目录 目录操作 创建目录 判断这个文件或目录是否存在 判断是否是目录 读取目录 删除目录 文件操作 创建文件 删除文件 File对象常用函数 目录操作 创建目录 File类中有两个方法可以用来创建文件夹: mkdir( )方法创建一个文件夹,成功则返回true,失败则返回false.失败表明File对象指定的路径已经存在,或者由于整个路径还不存在,该文件夹不能被创建. mkdirs()方法创建一个文件夹和它的所有父文件夹. 创建目录AAA路径为D:AAA public class Mk { pu

  • 详解Java中synchronized关键字的死锁和内存占用问题

    先看一段synchronized 的详解: synchronized 是 java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以

  • 详解Java中native关键字

    一. 什么是Native Method    简单地讲,一个Native Method就是一个java调用非java代码的接口.一个Native Method是这样一个java的方法:该方法的实现由非java语言实现,比如C.这个特征并非java所特有,很多其它的编程语言都有这一机制,比如在C++中,你可以用extern "C"告知C++编译器去调用一个C的函数.    "A native method is a Java method whose implementatio

  • 详解JAVA调用WCF服务的示例代码

    这一篇将要解决java中调用WCF的问题,使用的依旧是上一篇中托管在IIS中的WCF服务,本来我是打算用axis来写这篇文章的,可就在我开始之前,无意中发现了在java包中自带的wsimport工具,用起来是极为爽快,而且也节省了配置axis的时间.所以,就它吧 其实在有了wsimport,在java调用wcf的时候是极为简单的,当然这是建立在使用不太复杂的服务的情况下,如果还要考虑安全验证.发布订阅等问题,还是相对复杂的,但是这三篇文章没准备写那么多,只是想能把跨平台这三个字真的应用在实践中

  • 详解Java面试官最爱问的volatile关键字

    本文向大家分享的主要内容是Java面试中一个常见的知识点:volatile关键字.本文详细介绍了volatile关键字的方方面面,希望大家在阅读过本文之后,能完美解决volatile关键字的相关问题.  在Java相关的岗位面试中,很多面试官都喜欢考察面试者对Java并发的了解程度,而以volatile关键字作为一个小的切入点,往往可以一问到底,把Java内存模型(JMM),Java并发编程的一些特性都牵扯出来,深入地话还可以考察JVM底层实现以及操作系统的相关知识. 下面我们以一次假想的面试过

  • 详解java 拼音首字母搜索内容功能的示例

    序 一款成熟的产品,首页的搜索功能除了正常的关键词匹配以外:还要考虑到用户忘记输入汉字或者用户想通过关键字首字母来进行搜索的操作. 这不,阿淼公司最近在做游戏盒子,其中包含很多游戏,有个需求就是要用户可以根据游戏名称首字母搜索游戏,如搜索 zwdzjs 可以搜索出来植物大战僵尸等:输入 hzw 可以搜索出来海贼王等. 功能如何实现?接下来阿淼就直接带大家实操. 1.导入依赖包 <dependency> <groupId>com.belerweb</groupId> &l

随机推荐