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

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

介绍

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

var name = “Mohamed Taman”;

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

因为编译器须要经过检查赋值等式右侧(RHS)来推断var的实际类型,所以在某些状况下,这个特性具备局限性。我会在稍后提到这个问题。如今,让咱们来看一些简单的例子吧。express

在开始演示代码以前,你须要一个IDE来体验这些新特性。如今有不少可选择的IDE,因此你能够在它们当中选择你喜欢的可以支持Java SE 10的IDE,好比Apache NetBeans 九、IntelliJ IDEA 2018或最新版本的Eclipse。编程

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

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

除了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,能够从 Packt 或 Udemy 访问这些教程。

如今,让咱们经过一些简单的示例(使用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}

在此我向你们推荐一个架构学习交流群。交流学习群号:821169538  里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多。
如下也不起做用:

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也没有为它们提供任何特殊的指令。

相关文章
相关标签/搜索