java基本语句表达式



重要要点

  • Java的switch语句的当前设计紧密遵循C ++之类的语言,并且默认情况下支持直通语义。
  • 此控制流对于编写低级代码很有用。 但是,随着在更高级别的上下文中越来越多地使用switch,其容易出错的性质开始超过其灵活性。
  • 随着Java构建者也开始支持Java语言中的模式匹配,现有switch语句的不规则性成为了障碍。
  • 在Java 12中,对switch语句进行了新的增强,为它添加了新功能。 通过扩展旧的switch语句并将其用作常规的增强型switch语句或“ switch表达式”,可以潜在地简化代码。
  • 本文探讨了新的Java 12 switch语句,并提供了基于JShell的正确和不正确用法的示例。



没有免费的代码库可以使用switch语句。 即使是性能调整,我们也倾向于在if/else语句中使用它。 自Java诞生以来,盛大的Java switch语句就已经存在了,我们都已经习惯了它-尤其是它的怪癖。

Java的switch语句的当前设计紧密遵循C ++之类的语言,并且默认情况下支持直通语义。 此控制流对于编写低级代码很有用。 但是,由于在较高级别的上下文中使用了该开关,因此容易出错的性质开始超过了其灵活性。

随着Java构建者开始支持Java语言中的模式匹配 ,现有switch语句的不规则性已成为一个障碍。 问题包括开关块的默认控制流行为; 切换块的默认作用域,其中该块被视为一个单一作用域; 并仅以声明的方式进行切换。




在Java 12中,对switch进行了新的增强,为其添加了新功能,它通过扩展旧的switch语句以用作常规增强型switch语句或“ switch表达式”来简化编码。

这可以实现“ 传统的 ”或“ 简化的 ”作用域的两种形式,并控制流的行为。 这些更改将简化日常编码,并为将来通过switch语句/表达式使用模式匹配的方式做准备。

新功能火车

从JDK 9开始,引入了新的更快的发布计划。 通过新的6个月发布节奏,目标是使Java变得更有生产力和创新性,并且功能将更快,更频繁地可用。




自从JDK 9、10、11以及即将到来的JDK 12(将于2019年3月19日上市)以来,我们最近交付的每个JDK都已经看到了这一点,它总是为我们带来新的Java语言功能以及许多API增强功能。 )以及后续发行版本。

新项目

为了实现此类增强功能和新功能,社区需要关注该语言某个方面的重点项目,这使一组Java语言工程师可以处理这些新增强功能,而不是在庞大的JDK森林中包含和开发所有功能。 。

因此,已经创建了许多项目,并且它们的工作集中在语言的各个方面。 您可以在OpenJDK.net网站上的项目菜单部分下找到它们 。 仅举几个例子,我们有巴拿马,瓦尔哈拉,琥珀,拼图,织布机等项目。 我们在这里对Amber项目感兴趣。

琥珀计划

Project Amber由Compiler Group赞助,从赞助商的名字我们可以推测出项目目标。 是的,您是正确的开发人员。此项目的目的是探索和孵化较小的,面向生产力的Java语言功能,这些功能已被OpenJDK JEP流程接受为候选JEP。

正如我们在下面看到的所有JEP及其分配的JDK(如果有)的列表所示,项目Amber当前正在孵化或已经交付它们:

目前正在进行的JEP:

已交付的JEP:

  • JEP 286局部变量类型推断(var)(JDK 10)
  • 用于Lambda参数的JEP 323局部变量语法(JDK 11)

通过模式匹配增强Java

模式匹配是一种技术,已适应于1960年代的多种不同风格的编程语言,包括诸如SNOBOL4AWK的面向文本的语言,诸如HaskellML的 功能语言 ,以及最近扩展到诸如Scala 的面向对象的语言。 (以及最近的Microsoft C#语言)。

Project Amber添加了Java语言所包含的模式匹配。 模式匹配允许程序中的通用逻辑有条件地从对象中提取组件,并使其更简洁,更安全地表示,这将在许多结构中增强语言。 例如,现在它通过JEP 305增强了instanceof运算符( 尚未分配给任何JDK版本 ),并通过JEP 325增强了switch表达式( 针对JDK 12 )。

使用新的switch语句。

自Java SE 9发行以来,在交互式环境工具(REPL) JShell中尝试任何新的Java语言或API功能已成为一种传统! 这不需要IDE或任何其他软件来安装-仅JDK就足够了。

由于此功能随每个JDK一起提供,因此我们可以轻松尝试新的Java SE 12语言功能,例如,新的扩展switch语句和新switch表达式。 我们需要做的就是下载并安装JDK 12抢先体验版,可以从此链接安装。

成功下载后,将二进制文件解压缩到您选择的任何位置,并使JDK Binfolder可以从系统中的任何位置访问。

为什么选择JShell?

我总是喜欢使用交互式编程环境工具,我们在大多数现代语言中都使用这种工具,以便快速学习Java语言语法,探索新的Java API及其功能,甚至用于原型复杂代码。

这不再是繁琐的编辑,编译和执行代码的过程,该过程通常涉及以下过程:

  1. 编写一个完整的程序。
  2. 编译并修复所有错误。
  3. 运行程序。
  4. 找出问题所在。
  5. 编辑它。
  6. 重复该过程。

什么是JShell?

现在,Java有与JShell工具丰富的REPL(R ead-évaluate- P rint- 大号 OOP)实现,称为一个Java壳牌,作为一个交互式编程环境。 那么,这里的魔力是什么? 实际上很简单。 JShell提供了一个快速友好的环境,使您可以快速探索,发现和试验Java语言功能及其广泛的库。

使用JShell,您可以一次输入一个程序元素,立即查看结果,并根据需要进行调整。 在此REPL中,您不是编写完整的程序,而是编写JShell命令和Java代码段。

这对于JShell来说已经足够了, InfoQ最近发布了对该工具的详尽介绍。 为了深入学习并了解有关JShell的所有功能的更多信息,我已经录制了有关此主题的完整视频培训,标题为“ 使用JShell进行Java 10编程实践[视频] ”,这将有助于您精通该主题,或者可以来自Packt网站

开始一个JShell会话。

开始与该工具进行交互的第一件事是启动一个新的JShell会话,如下所示:

  1. 在Microsoft Windows上,只需打开命令提示符,然后键入jshell并按Enter
  2. 在Linux上,打开一个shell窗口,然后键入jshell并按Enter
  3. 如果您使用的是macOS(以前称为OS X),请打开“终端”窗口,然后键入“ jshell ”命令,最后按Enter

塔拉阿! 此命令执行一个新的JShell会话,并在jshell>提示符下显示此消息:

mohamed_taman:~$ jshell --enable-preview
|  Welcome to JShell -- Version 12-ea
|  For an introduction type: /help intro
 
jshell>

在上面的第一行中,“ Version 12-ea ”表示您正在使用Java SE JDK 12早期访问。 JShell在信息性消息之前带有竖线(|),现在您可以输入任何Java代码或JShell命令和代码片段。

我认为您有足够敏锐的视线来捕捉--enable-preview选项! 这个选项有什么用? 亲爱的,您看,该选项允许您解锁当前处于“预览”状态的任何新语言功能,而该功能尚未正式纳入JDK。 这些功能默认处于禁用状态,因为它们仍处于实验和反馈阶段。

使用此标志,您作为开发人员可以尝试预览功能,并提供反馈以获得更多改进,甚至提供您不喜欢它或其中任何部分的反馈。 在本文中,我们正在探索新的开关表达式功能,这就是为什么在启动JShell会话时需要包括此选项的原因。

注意 :由于这是预览功能,因此您仍有时间将反馈提供给Amber邮件列表

到目前为止,这已经足够了,并且让我们尝试一些新的switch语句/表达式片段,以弄清概念并学习如何使用这一强大的新功能。

入侵开关语句/表达式

首先,我们需要确保我们了解我们的现在。 以及当前版本的switch语句,甚至我们以前在日常编码任务中所做的古怪之处,我们今天所处的位置。

让我们考虑一个简单的方法,该方法将Day作为变量。 该方法切换包含普通日( Sat, Sun, Mon...等)的Day枚举,并根据该枚举返回“ 周末 ”或“ 工作日 ”。

在我们之前已经开始的Jshell会话中,让我们将Day enum定义如下:

jshell> enum Day{
   ...> NONE,
   ...> SAT,
   ...> SUN,
   ...> MON,
   ...> TUS,
   ...> WED,
   ...> THU,
   ...> FRI;
   ...> }
|  created enum Day

现在让我们定义我们的方法String whatIsToday (Day day)来切换(我们使用普通的切换) day值,并return 工作日或周末,如下所示:

jshell> String whatIsToday(Day day){
   ...>   var today = "";
   ...>   switch(day){
   ...>	case SAT:
   ...>	case SUN: today = "Weekend day";
   ...>          	break;
   ...>	case MON:
   ...>	case TUS:
   ...>	case WED:
   ...>	case THU:
   ...>	case FRI: today = "Working day";
   ...>          	break;
   ...>	default:  today = "N/A";
   ...>  }
   ...>  return today;
   ...> }
|  created method whatIsToday(Day)
 
jshell> whatIsToday(Day.SUN)
$3 ==> "Weekend day"

尽管此功能可以完美运行,并且如果我们在每种情况下都使用带有多个break语句的原始版本,则会引入视觉噪声,这通常使调试错误变得很困难。 这也使代码不必要地冗长,其中错误地丢失了break语句意味着意外的失败

在上面的示例中,我们甚至通过对具有相同值的普通日使用掉线机制来减少休息时间的使用,但是仍然很长。

传统的switch语句增强

感谢JEP 325,它提出了一种新的“箭头”(“带开关标记的规则”)形式,该形式写为“ case L-> ”,以表示如果该标签仅执行标签右侧的代码匹配,可以与switch语句或switch表达式一起使用。

同样,传统的“ 冒号 ”语法(“ 标记为语句组 ”的开关 )也可以在两种情况下使用。

尽管这两个结肠和新的箭头语法可以用这两种情况下使用,因此在结肠的存在(:) 并不一定意味着switch语句和的“箭头”的存在( - >) 并不一定意味着一个开关表达。

现在让我们将先前的理论陈述转化为行动,例如,先前的方法String whatIsToday(Day day)现在可以重写为以下简化版本:

jshell> /edit whatIsToday

通过运行前面的JShell /edit命令,它将打开JShell编辑板 ,以便于诸如此类方法之类的大型代码片段的修改,因此在JShell编辑板上可对方法进行如下修改:

String whatIsToday(Day day){
  var today = "";
  switch(day){
   case SAT, SUN -> today = "Weekend day";
   case MON, TUS, WED, THU, FRI -> today = "Working day";
   default -> throw new IllegalArgumentException("Invalid day: " + day.name());
  }
  return today;
}

完成修改后,单击Exit按钮以接受修改并返回到当前的JShell>会话,以使用一些值再次尝试该方法:

jshell> /edit whatIsToday
|  modified method whatIsToday(Day)
 
jshell> whatIsToday(Day.SUN)
$5 ==> "Weekend day"
 
jshell> whatIsToday(Day.MON)
$6 ==> "Working day"
 
jshell> whatIsToday(Day.NONE)
|  Exception java.lang.IllegalArgumentException: Invalid day: NONE
|    	at whatIsToday (#11:6)
|    	at (#12:1)
 
jshell>

如果仔细检查以前的新switch语句结构,您应该在这里注意到许多变化, 首先 ,它更加清晰,简洁和“无懈可击”,不是吗?

其次 ,你注意到电源开关语句采用新的“ 箭头 ”语法(“标签规则”)形式的优势来完成其开关没有明确的规范break ,它让我们可以从经常可怕切换“ 落空 ”的情况下。

值得注意的是,“ case L -> ”开关标签右侧的代码仅限于表达式,块或(为方便起见) throw语句,就像我们在前面方法中所做的那样。通过抛出IllegalArgumentException来标识无效日期的switch语句的default情况。

单个开关盒中有多个逗号分隔的标签

传统上,在旧的switch语句中,当我们需要确定应该执行同一组语句的多个案例时,我们会使用穿透机制。

但是现在这里的第三个通知是,我们已经使用了新的“ 在单个switch case中使用多个逗号分隔的标签 ”结构,只是使用逗号分隔了应执行同一语句的常见情况。

切换带标签的语句组

根据JEP 325,也可以正常使用传统的“ 冒号 ”语法(“ 标记为语句组 ”的开关 ),并且我们可以使用以下冒号形式再次重写先前的方法,如下所示:

String whatIsToday(Day day){
  var today = "";
  switch(day){
   case SAT, SUN: today = "Weekend day"; break;
   case MON, TUS, WED, THU, FRI: today = "Working day"; break;
   default: throw new IllegalArgumentException("Invalid day: " + day.name());
  }
  return today;
}

我将把它作为一个小练习留给您。 您可以自己运行JShell命令/edit whatIsToday并用箭头标记语法替换当前方法,并用冒号/ break语法替换之前的方法,并尝试使用一些值,它应该像魅力一样工作。

新开关表达式

在深入探讨这一点之前,您可能想知道在开始用作表达式之前, switch是什么样子? 在Java SE 12之前,切换始终是一条语句。 这是指导控制流的命令性构造,永远不能成为目的地。 另一方面, 表达式始终会被评估为精确值。 因为任何计算的最终目标都是结果,所以可以将值分配给目标。

现在,我们已经探讨了语句和表达式之间的区别,让我们看看新的switch表达式如何起作用。 实际上,我们日常代码甚至上面的代码中使用的许多现有switch语句本质上都是对switch表达式的模拟,其中每个分支要么分配给一个公共目标变量,要么返回一个值。

现在,我们可以使用新的switch表达式直接返回要分配给目标变量的值,让我们修改StringwhatIsToday(Day day)方法:

String whatIsToday(Day day){
  var today = switch(day){
	case SAT, SUN -> "Weekend day";
	case MON, TUS, WED, THU, FRI -> "Working day";
	default -> throw new IllegalArgumentException("Invalid day: " + day.name());
  };
 
  return today;
}

深入研究修改后的方法,我们可以注意到我们将switch语句编写为表达式:首先,将开关写在等号后; 第二,因为它是语句的一部分,所以必须以分号结尾,而经典的switch语句则不需要。 第三,箭头标记后面是返回值。

如前所述,我们可以在新的switch表达式中使用传统的“冒号”语法“ case L: ”,因此我们可以使用break with value语句来重写以前的方法,如下所示:

String whatIsToday(Day day){
  var today = switch(day){
	case SAT, SUN: break "Weekend day";
	case MON, TUS, WED, THU, FRI: break "Working day";
	default: throw new IllegalArgumentException("Invalid day: " + day.name());
  };
 
  return today;
}

break的两种形式(有值和无值)类似于方法中的两种return形式。

语句块

当我们在日常工作中使用新的switch语句/表达式时,使用“ 冒号 ”或“ 箭头 ”语法; 在大多数情况下,我们经常会在两种形式的右手使用单个表达式。

但是,在某些情况下,我们需要同时评估多个语句。 而且我们可以轻松地使用{{}块来做到这一点,这也非常方便,我们可以在单个switch语句/表达式中多次创建和使用相同的变量名,如下所示:

String whatIsToday(Day day){
  var today = switch(day){
	case SAT, SUN: break "Weekend day";
	case MON, TUS, WED, THU, FRI:{
   	 var kind = "Working day";
    	break kind;
	}
	default: {
    	var kind = day.name();
   	 System.out.println(kind);
   	 throw new IllegalArgumentException("Invalid day: " + kind);
	}
  };
 
  return today;
}

这是一个多表达

switch表达式是“ poly表达式”; 如果目标类型已知,则将该类型推入每个案例分支; 如果不是,则通过组合每个案例分支的类型来计算独立类型。

考虑以下开关表达式分配:

jshell> var day = Day.SUN
day ==> SUN
 
jshell> var today = switch(day){
   ...> 	case SAT, SUN -> "Weekend day";
   ...> 	case MON, TUS, WED, THU, FRI -> "Working day";
   ...> 	default -> {
   ...>          	var len = day.name().length();
   ...>          	break len;
   ...> 	}
   ...>   };
today ==> "Weekend day"
 
jshell> var day = Day.NONE
day ==> NONE
 
jshell> var today = switch(day){
   ...> 	case SAT, SUN -> "Weekend day";
   ...> 	case MON, TUS, WED, THU, FRI -> "Working day";
   ...> 	default -> {
   ...>          	var len = day.name().length();
   ...>          	break len;
   ...> 	}
   ...>   };
4
today ==> 4

如果我们更进一步并分析上面的switch表达式,可以发现我们有两个分支返回一个String ,而默认分支返回一个int变量len 。 两者都可以正常工作,因为目标目的地是var

现在,让我们明确声明目标分配为int类型而不是var类型,这将使编译器抱怨,要满足此条件“ 如果目标类型已知,则将该类型推入每个case臂”

jshell> int today = switch(day){
   ...> 	case SAT, SUN -> "Weekend day";
   ...> 	case MON, TUS, WED, THU, FRI -> "Working day";
   ...> 	default -> {
   ...>          	var len = day.name().length();
   ...>          	System.out.println(len);
   ...>          	break len;
   ...> 	}
   ...>   };
|  Error:
|  incompatible types: bad type in switch expression
|      java.lang.String cannot be converted to int
|  	case SAT, SUN -> "Weekend day";
|                       ^-----------^
|  Error:
|  incompatible types: bad type in switch expression
|      java.lang.String cannot be converted to int
|  	case MON, TUS, WED, THU, FRI -> "Working day";
|                                      ^-----------^

新的开关功能无法做什么

现在,让我们探索如何使编译器生气并抱怨我们编写的switch语句/表达式代码,并相应地学习如何避免这种情况使编译器满意:)。

break不能在switch语句中返回值

如果您尝试使与switch语句关联的break返回一个值,则会导致编译时错误:

jshell> String whatIsToday(Day day){
   ...>   var today = "";
   ...>   switch(day){
   ...>    case SAT, SUN: break "Weekend day";
   ...>    case MON, TUS, WED, THU, FRI: break "Working day";
   ...>    default: throw new IllegalArgumentException("Invalid day: " + day.name());
   ...>   }
   ...>   return today;
   ...> }
|  Error:
|  unexpected value break
| 	case SAT, SUN: break "Weekend day";
|    	            ^------------------^
|  Error:
|  unexpected value break
| 	case MON, TUS, WED, THU, FRI: break "Working day";
|                                   ^------------------^
|  Error:
|  unreachable statement
|	return today;
|	^-----------^
 
jshell>

箭头语法仅指向switch语句中的一条语句

同样,如果尝试将箭头语法与switch语句一起使用以返回值,则这是编译时错误。 在这种情况下,箭头语法应仅指向语句, 而不返回值:

jshell> String whatIsToday(Day day){
   ...>   var today = "";
   ...>   switch(day){
   ...>    case SAT, SUN -> "Weekend day";
   ...>    case MON, TUS, WED, THU, FRI -> "Working day";
   ...>    default -> throw new IllegalArgumentException("Invalid day: " + day.name());
   ...>   }
   ...>   return today;
   ...> }
|  Error:
|  not a statement
| 	case SAT, SUN -> "Weekend day";
|                  	^-----------^
|  Error:
|  not a statement
| 	case MON, TUS, WED, THU, FRI -> "Working day";

不允许将箭头和冒号/ break语法混合使用

如果尝试在“ arrow ”和传统的冒号/ break语法之间混合使用,编译器将非常生气并抱怨:

jshell> String whatIsToday(Day day){
   ...>   var today = "";
   ...>   switch(day){
   ...>    case SAT, SUN -> today = "Weekend day";
   ...>    case MON, TUS, WED, THU, FRI: today = "Working day";break;
   ...>    default -> throw new IllegalArgumentException("Invalid day: " + day.name());
   ...>   }
   ...>   return today;
   ...> }
|  Error:
|  different case kinds used in the switch
| 	case MON, TUS, WED, THU, FRI: today = "Working day";break;
|     ^--------------------------------------------------------^

所有匹配项都应包含在switch表达式中

最后,如果特定测试变量的switch表达式未涵盖所有开关情况,则再次是编译时错误。 如果您未涵盖全部内容,则会遇到以下错误:

jshell> String whatIsToday(Day day){
   ...>   var today = switch(day){
   ...>    case SAT, SUN -> "Weekend day";
   ...>    case MON, TUS, WED, THU, FRI -> "Working day";
   ...>   };
   ...>   return today;
   ...> }
|  Error:
|  the switch expression does not cover all possible input values
|	var today = switch(day){
|         	   ^-----------...

覆盖switch表达式中的所有情况都不是一件容易的事,并且肯定会令人烦恼,这里的修复很琐碎而且很小-只需添加default大小写,它就会像魅力一样进行编译。 我将把它留给您,以使上面的代码成功编译。

开关的未来

与旧的switch ,新的增强的switch语句switch表达式都可以与枚举一起正常工作。 那么,其他类型呢? 新的增强的“传统”和“表达式”形式都是相似的,它们还可以切换String, int, short, byte, char及其包装类型。 到目前为止,这里没有任何改变。

例如,在将来的Java版本中扩展切换是一个目标,以允许切换float, doublelong (及其框类型),这是以前不允许的。

结论

在本文中,您了解了Project Amber,模式匹配以及JEP 325如何扩展switch语句,以便可以将其用作语句表达式 ,并且两种形式都可以使用“传统”或“简化的作用域和控制流行为。

这些更改还将简化日常编码,并且可以使您避免因“直通”问题而导致的错误,而这些错误导致您忘记添加相关的break语句。 我还列出了开发人员在开始使用新的switch语句/表达式时可能犯的最常见错误。

此外,这种语言更改为在开关语句/表达式中使用模式匹配(JEP 305)铺平了道路,并且还扩展了当前开关以支持切换更多基本类型(例如float,double和long)以及它们的包装器类型。

我们已经到了文章的结尾,很高兴写这篇文章,希望您喜欢阅读! 如果您的答案是肯定的,那么请单击“赞”按钮,并通过您的朋友和社交媒体进行宣传!