Java的语言特点是什么(java语言的主要特点是什么)
933
2022-05-30
2014年3月18日,Oracle公司发布了Java SE 8。最近正好抽空整理了Java 8的特性如下:
接口的默认方法
Lambda 表达式
函数式接口
方法与构造函数引用
Lambda 作用域
访问局部变量
访问对象字段与静态变量
访问接口的默认方法
Date API
Annotation 注解
本文将会重点讲解Java 8中的Lambda表达式,其他特性将会在后续文章中讲解。lambda 表达式,又被成为“闭包”或“匿名方法”。
背景
Java 是一门面向对象编程语言。面向对象编程语言和函数式编程语言中的基本元素(Basic Values)都可以动态封装程序行为:面向对象编程语言使用带有方法的对象封装行为,函数式编程语言使用函数封装行为。但这个相同点并不明显,因为Java 对象往往比较“重量级”:实例化一个类型往往会涉及不同的类,并需要初始化类里的字段和方法。
不过有些 Java 对象只是对单个函数的封装。例如下面这个典型用例:Java API 中定义了一个接口(一般被称为回调接口),用户通过提供这个接口的实例来传入指定行为。
public interface ActionListener { void actionPerformed(ActionEvent e); }
这里并不需要专门定义一个类来实现 ActionListener,因为它只会在调用处被使用一次。用户一般会使用匿名类型把行为内联(inline):
button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ui.dazzle(e.getModifiers()); } });
很多库都依赖于上面的模式。对于并行 API 更是如此,因为我们需要把待执行的代码提供给并行 API,并行编程是一个非常值得研究的领域,因为在这里摩尔定律得到了重生:尽管我们没有更快的 CPU 核心(core),但是我们有更多的 CPU 核心。而串行 API 就只能使用有限的计算能力。
匿名内部类
随着回调模式和函数式编程风格的日益流行,我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as data)的方法。匿名内部类并不是一个好的 选择,因为:
语法过于冗余
匿名类中的 this 和变量名容易使人产生误解
类型载入和实例创建语义不够灵活
无法捕获非 final 的局部变量
无法对控制流进行抽象
函数式接口
尽管匿名内部类有着种种限制和问题,但是它有一个良好的特性,它和Java类型系统结合的十分紧密:每一个函数对象都对应一个接口类型。之所以说这个特性是良好的,是因为:
接口是 Java 类型系统的一部分
接口天然就拥有其运行时表示(Runtime representation)
接口可以通过 Javadoc 注释来表达一些非正式的协定(contract),例如,通过注释说明该操作应可交换(commutative)
接口只有一个方法,大多数回调接口都拥有这个特征:比如 Runnable 接口和 Comparator 接口。我们把这些只拥有一个方法的接口称为 函数式接口。(之前它们被称为 SAM类型,即 单抽象方法类型(Single Abstract Method))
实现函数式类型的另一种方式是引入一个全新的 结构化 函数类型,我们也称其为“箭头”类型。例如,一个接收 String 和 Object 并返回 int 的函数类型可以被表示为 (String, Object) -> int。我们仔细考虑了这个方式,但出于下面的原因,最终将其否定:
它会为Java类型系统引入额外的复杂度,并带来 结构类型(Structural Type) 和 指名类型(Nominal Type) 的混用。(Java 几乎全部使用指名类型)
它会导致类库风格的分歧——一些类库会继续使用回调接口,而另一些类库会使用结构化函数类型
它的语法会变得十分笨拙,尤其在包含受检异常(checked exception)之后
每个函数类型很难拥有其运行时表示,这意味着开发者会受到 类型擦除(erasure) 的困扰和局限。比如说,我们无法对方法 m(T->U) 和 m(X->Y) 进行重载(Overload)
所以我们选择了“使用已知类型”这条路——因为现有的类库大量使用了函数式接口,通过沿用这种模式,我们使得现有类库能够直接使用 lambda 表达式。例如下面是 Java SE 7 中已经存在的函数式接口:
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.beans.PropertyChangeListener
除此之外,Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:
Predicate
除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如 IntSupplier 和 LongBinaryOperator。(我们只为 int、long 和 double 提供了特化函数式接口,如果需要使用其它原始类型则需要进行类型转换)同样的我们也提供了一些针对多个参数的函数式接口,例如 BiFunction
lambda表达式
匿名类型最大的问题就在于其冗余的语法。有人戏称匿名类型导致了“高度问题”。lambda表达式是匿名方法,它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。
(int x, int y) -> x + y () -> 42 (String s) -> { System.out.println(s); }
第一个 lambda 表达式接收 x 和 y 这两个整形参数并返回它们的和;第二个 lambda 表达式不接收参数,返回整数 ‘42’;第三个 lambda 表达式接收一个字符串并把它打印到控制台,不返回值。
lambda 表达式的语法由参数列表、箭头符号 -> 和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:
表达式:表达式会被执行然后返回执行结果。
语句块:语句块中的语句会被依次执行,就像方法中的语句一样——
return 语句会把控制权交给匿名方法的调用者
break 和 continue 只能在循环中使用
如果函数体有返回值,那么函数体内部的每一条路径都必须返回值
表达式函数体适合小型 lambda 表达式,它消除了 return 关键字,使得语法更加简洁。
lambda 表达式也会经常出现在嵌套环境中,比如说作为方法的参数。为了使 lambda 表达式在这些场景下尽可能简洁,我们去除了不必要的分隔符。不过在某些情况下我们也可以把它分为多行,然后用括号包起来,就像其它普通表达式一样。
实战应用
Function
Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法:andThen和compose。
@Test public void testFun() { //Function 接口有一个参数并且返回一个结果 Function
compose和andThen中定义的Function应用顺序正好相反,首先应用compose中的方法,其次才会应用当前Function。
Supplier
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数。代码如下:
@Test public void testSupplier() { //Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数 Supplier sp = () -> "sp"; System.out.println(sp.get()); }
如上代码将会返回一个字符串“sp”,通过get方法获取到返回的值。
Predicate
Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
@Test public void testPredicate() { Predicate
如上代码判断了字符串是否为空,并应用了与、非操作。
Consumer
Consumer 接口表示执行在单个参数上的操作,主要的方法为andThen和accept。
@Test public void testConsumer() { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//设置日期格式 Consumer
accept表示接收指定的参数执行操作,andThen表示当前操作结束之后附加的操作。
Comparator
Comparator接口用于比较, Java 8在此之上添加了多种默认方法,如reversed和thenComparing等。
@Test public void testComparator() { Comparator
Optional
用来防止NullPointerException异常的辅助类型,现在看看这个接口能干什么:
Optional被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
@Test public void testOptional() { //用来防止NullPointerException异常的辅助类型 List
optional.orElse用来对异常情况返回预设的返回结果。
Stream
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。
@Test public void testSort() { List
Map
中间操作map会将元素根据指定的Function接口来依次将元素转成另外的对象,下面的示例展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
@Test public void testMap() { List
Match
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
@Test public void testMatch() { List
Reduce
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
@Test public void testReduce() { List
ParallelStream
串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
@Test public void testParallelStream() { int max = 1000000; List
如上所示为并行排序,排序这个Stream耗时明显低于串行。
Map方法
Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
@Test public void testMapFun() { Map
UnaryOperator
继承自Function接口,表示对单个操作数的操作,该操作生成与其操作数相同类型的结果。
@Test public void testUnaryOperator() { UnaryOperator
小结
本文主要介绍了Java8中的Lambda表达式,选择其中常用的方法进行了简单的应用讲解。Lambda表达式是Java SE 8中一个重要的新特性。Lambda表达式允许你通过表达式来代替功能接口。Lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。
Lambda表达式还增强了集合库,包括java.util.function 包以及java.util.stream包。Lambda表达式非常简洁,大大简化代码行数,使代码在一定程度上变的简洁干净,但是同样的,这可能也会是一个缺点,由于省略了太多东西,代码可读性有可能在一定程度上会降低,这个完全取决于你使用lambda表达式的位置所设计的API是否被你的代码的其他阅读者所熟悉。
参考文档
Java 8中一些常用的全新的函数式接口
深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
【奔跑吧!JAVA】有奖征文火热进行中:https://bbs.huaweicloud.com/blogs/265241
API Java 面向对象编程
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。