java8新特征,java8特征
java8新特征,java8特征
Java8新特征
简介
Java 8是自Java 5(2004年)发布以来Java语言最大的一次版本升级,Java 8带来了很多的新特性,比如编译器、类库、开发工具和JVM(Java虚拟机)。在这篇教程中我们将会学习这些新特性,并通过真实例子演示说明它们适用的场景。
- 为什么需要再次修改java
1996 年1 月,Java 1.0 发布,此后计算机编程领域发生了翻天覆地的变化。商业发展需要更复杂的应用,大多数程序都跑在功能强大的多核CPU 的机器上。带有高效运行时编译器的Java 虚拟机(JVM)的出现,使程序员将更多精力放在编写干净、易于维护的代码上,而不是思考如何将每一个CPU 时钟周期、每字节内存物尽其用。多核CPU 的兴起成为了不容回避的事实。涉及锁的编程算法不但容易出错,而且耗费时间。人们开发了java.util.concurrent 包和很多第三方类库,试图将并发抽象化,帮助程序员写出在多核CPU 上运行良好的程序。很可惜,到目前为止,我们的成果还远远不够。开发类库的程序员使用Java 时,发现抽象级别还不够。处理大型数据集合就是个很好的例子,面对大型数据集合,Java 还欠缺高效的并行操作。开发者能够使用Java 8 编写复杂的集合处理算法,只需要简单修改一个方法,就能让代码在多核CPU 上高效运行。为了编写这类处理批量数据的并行类库,需要在语言层面上修改现有的Java:增加Lambda 表达式。Lambda表达式和函数式接口
2. 什么是函数式编程
每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
函数式编程与面向对象编程的思想比较: oop, 就是让operation 围绕data, 这样的好处是,当你要添加新的data type的时候,好方便! 原来写的代码都不用改,functional programming采取的是另一种思路,data更多的围绕operation, 所以添加新的方法很容易。
- Lambda表达式的基本语法和格式
- 基本语法
java8中引入了新的操作符“->”,称为箭头操作符或者lambda操作符,箭头操作符将lambda拆分为两部分,左侧:lambda表达式的参数列表,右侧:lambda表达式中的所需要执行的功能(接口实现的功能代码)。
lambda表达式需要“函数式接口”的支持,接口中只有一个抽象方法的接口称为函数式接口,可以使用注解@FunctionalInterface检查接口是否是函数式接口。
lambda表达式的参数列表的数据类型可以省略不写,因为jvm编辑器可以通过上下文判断。 - 基本格式
- 无参数,无返回值。
() -> System.out.println(“hello”)
实现Runnable接口(无参,无返回)
Runnable r = () -> System.out.println("hello lambda"); - 一个参数(小括号可以省略不写,习惯上加上小括号),无返回值。
(x) -> System.out.println(x)
Consumer接口(一个参数,无返回值),之后会提到
第一种,小括号不省略
Consumer<String> c = (x) -> System.out.print(x);
c.accept("hello");
小括号省略
Consumer<String> c1 = x -> System.out.print(x);
c1.accept("hello") - 有多个参数,并且lambda有多条语句,则lambda语句必须用大括号括起来并有return返回(若有一条语句则可以省略大括号和return),有返回值。
(x,y) ->{System.out.println(“hello”);return Integer.compare(x, y);};
Comparator接口
//多条语句
Comparator<Integer> comparator = (x,y) ->{
System.out.println("hello");
return Integer.compare(x, y);
};
一条语句//
// Comparator<Integer> comparator2 = (x,y) -> Integer.compare(x, y);//
//System.out.println(comparator2.compare(23, 22));
- 无参数,无返回值。
- 基本语法
- 函数式接口是什么?
在前面也提到了函数接口,那么函数接口到底是什么呢?是个接口,只包含一个抽象方法,那么它就是函数式接口,我们可以在任意函数式接口上使用 @FunctionalInterface 检查它是否是一个函数式接口。函数式接口里是可以包含默认方法、静态方法,他们不是抽象方法;也可以包含Java.lang.Object里的public方法,因为任何一个类都继承Object类,包含了来自java.lang.Object里对这些抽象方法的实现,也不属于抽象方法;函数式接口里允许子接口继承多个父接口,但每个父接口中都只能存在一个抽象方法,且必须的相同的抽象方法。- 默认方法和静态方法
Java 8增加了两个新的概念在接口声明的时候:默认和静态方法。默认方法允许我们在接口里添加新的方法,而不会破坏实现这个接口的已有类的兼容性,也就是说不会强迫实现接口的类实现默认方法。
默认方法和抽象方法的区别是抽象方法必须要被实现,默认方法不是。作为替代方式,接口可以提供一个默认的方法实现,所有这个接口的实现类都会通过继承得倒这个方法(如果有需要也可以重写这个方法),让我们来看看下面的例子:
public interface Defaulable {
// Interfaces now allow default methods, the implementer may or
// may not implement (override) them.
default String notRequired() {
return "Default implementation";
}
}
public static class DefaultableImpl implements Defaulable {
}
public static class OverridableImpl implements Defaulable {
@Override
public String notRequired() {
return "Overridden implementation";
}
}
接口Defaulable使用default关键字声明了一个默认方法notRequired(),类DefaultableImpl实现了Defaulable接口,没有对默认方法做任何修改。另外一个类OverridableImpl重写类默认实现,提供了自己的实现方法。
Java 8 的另外一个有意思的新特性是接口里可以声明静态方法,并且可以实现。例子如下:
public interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面是把接口的静态方法和默认方法放在一起的示例(::new 是构造方法引用,后面会有详细描述):
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
控制台的输出如下:
Default implementation
Overridden implementation
JVM平台的接口的默认方法实现是很高效的,并且方法调用的字节码指令支持默认方法。默认方法使已经存在的接口可以修改而不会影响编译的过程。java.util.Collection中添加的额外方法就是最好的例子:stream(), parallelStream(), forEach(), removeIf()
虽然默认方法很强大,但是使用之前一定要仔细考虑是不是真的需要使用默认方法,因为在层级很复杂的情况下很容易引起模糊不清甚至变异错误。- 问题:为什么要有默认方法?
虽然Java 在持续演进,但它一直在保持着向后二进制兼容。具体来说,使用Java 1 到Java 7 编译的类库或应用,可以直接在Java 8 上运行。当然,错误也难免会时有发生,但和其他编程平台相比,二进制兼容性一直被视为Java 的关键优势所在。除非引入新的关键字,如enum,达成源代码向后兼容也不是没有可能实现。可以保证,只要是Java 1 到Java 7 写出的代码,在Java 8 中依然可以编译通过。事实上,修改了像集合类这样的核心类库之后,这一保证也很难实现。我们可以用具体的例子作为思考练习。Java 8 中为Collection 接口增加了stream 方法,这意味着所有实现了Collection 接口的类都必须增加这个新方法。对核心类库里的类来说,实现这个新方法(比如为ArrayList 增加新的stream 方法)就能就能使问题迎刃而解。缺憾在于,这个修改依然打破了二进制兼容性,在JDK 之外实现Collection 接口的类,例如MyCustomList,也仍然需要实现新增的stream 方法。这个MyCustomList 在Java 8 中无法通过编译,即使已有一个编译好的版本,在JVM 加载MyCustomList 类时,类加载器仍然会引发异常。这是所有使用第三方集合类库的梦魇,要避免这个糟糕情况,则需要在Java 8 中添加新的语言特性:默认方法.
- 问题:为什么要有默认方法?
- 默认方法和静态方法
- 方法引用
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
方法引用分为4类,常用的是前两种。方法引用也受到访问控制权限的限制,可以通过在引用位置是否能够调用被引用方法来判断。具体分类信息如下:- 引用静态方法
ContainingClass::staticMethodName
例子: String::valueOf,对应的Lambda:(s) -> String.valueOf(s)
比较容易理解,和静态方法调用相比,只是把.换为:: - 引用特定对象的实例方法
containingObject::instanceMethodName
例子: x::toString,对应的Lambda:() -> this.toString()
与引用静态方法相比,都换为实例的而已 - 引用特定类型的任意对象的实例方法
ContainingType::methodName
例子: String::toString,对应的Lambda:(s) -> s.toString()
太难以理解了。难以理解的东西,也难以维护。建议还是不要用该种方法引用。
实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。 - 引用构造函数
ClassName::new
例子: String::new,对应的Lambda:() -> new String()
构造函数本质上是静态方法,只是方法名字比较特殊。
- 引用静态方法
3. 流
Java 8 中新增的特性旨在帮助程序员写出更好的代码,其中对核心类库的改进是很关键的一部分。对核心类库的改进主要包括集合类的API 和新引入的流(Stream)。流使程序员得以站在更高的抽象层次上对集合进行操作。这部分将会介绍Stream 类中的一组方法,每个方法都对应集合上的一种操作。
- 从外部迭代到内部迭代
- 外部迭代:
Java 程序员在使用集合类时,一个通用的模式是在集合上进行迭代,然后处理返回的每一个元素。比如要计算从伦敦来的艺术家的人数,通常代码会写成例3-1 这样。
例3-1 使用for 循环计算来自伦敦的艺术家人数
int count = 0;
for (Artist artist : allArtists) {
if (artist.isFrom("London")) {
count++;
}
}
尽管这样的操作可行,但存在几个问题。每次迭代集合类时,都需要写很多样板代码。将for 循环改造成并行方式运行也很麻烦,需要修改每个for 循环才能实现。此外,上述代码无法流畅传达程序员的意图。for 循环的样板代码模糊了代码的本意,程序员必须阅读整个循环体才能理解。若是单一的for 循环,倒也问题不大,但面对一个满是循环(尤其是嵌套循环)的庞大代码库时,负担就重了。就其背后的原理来看,for 循环其实是一个封装了迭代的语法糖,我们在这里多花点时间,看看它的工作原理。首先调用iterator 方法,产生一个新的Iterator 对象,进而控制整个迭代过程,这就是外部迭代。迭代过程通过显式调用Iterator 对象的hasNext 和next方法完成迭代。展开后的代码如例3-2 所示,图3-1 展示了迭代过程中的方法调用。
例3-2 使用迭代器计算来自伦敦的艺术家人数
int count = 0;
Iterator<Artist> iterator = allArtists.iterator();
while(iterator.hasNext()) {
Artist artist = iterator.next();
if (artist.isFrom("London")) {
count++;
}
}
图3-1: - 内部迭代
外部迭代有很多缺点,首先,它很难抽象出复杂的一些操作;此外,它从本质上来讲是一种串行化操作。总体来看,使用for 循环会将行为和方法混为一谈。另一种方法就是内部迭代,如例3-3 所示。首先要注意stream() 方法的调用,它和例3-2中调用iterator() 的作用一样。该方法不是返回一个控制迭代的Iterator 对象,而是返回内部迭代中的相应接口:Stream。
例3-3 使用内部迭代计算来自伦敦的艺术家人数
long count = allArtists.stream().filter(artist -> artist.isFrom("London")).count();
图3-2内部迭代, 展示了使用类库后的方法调用流程,与图3-1 形成对比。
图3-2:- 实现机制
如示例3-2那种操作是否意味着需要两次循环?事实上,类库设计精妙,只需对艺术家列表迭代一次。allArtists.stream().filter(artist -> artist.isFrom("London"))这行代码并未做什么实际性的工作,filter 只刻画出了Stream,但没有产生新的集合。像filter 这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法;而像count 这样最终会从Stream 产生值的方法叫作及早求值方法。判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。
- 实现机制
- 外部迭代:
- 流的初始化与转换:
Java中的Stream的所有操作都是针对流的,所以,使用Stream必须要得到Stream对象:- 初始化一个流:
Stream stream = Stream.of("a", "b", "c"); - 数组转换为一个流:
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
或者
stream = Arrays.stream(strArray); - 集合对象转换为一个流(Collections):
List<String> list = Arrays.asList(strArray);
stream = list.stream();
- 初始化一个流:
- 流的操作:
流的操作可以归结为几种:- 遍历操作(map):
使用map操作可以遍历集合中的每个对象,并对其进行操作,map之后,用.collect(Collectors.toList())会得到操作后的集合。- 遍历转换为大写:
List<String> output = wordList.stream().map(String::toUpperCase).collect(Collectors.toList()); - 平方数:
List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = nums.stream().map(n -> n * n).collect(Collectors.toList());
- 遍历转换为大写:
- 过滤操作(filter):
使用filter可以对象Stream中进行过滤,通过测试的元素将会留下来生成一个新的Stream。- 得到其中不为空的String
List<String> filterLists = new ArrayList<>();
filterLists.add("");
filterLists.add("a");
filterLists.add("b");
List afterFilterLists = filterLists.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
- 得到其中不为空的String
- 循环操作(forEach):
如果只是想对流中的每个对象进行一些自定义的操作,可以使用forEach:
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.stream().forEach(s-> System.out.println(s)); - 返回特定的结果集合(limit/skip):
limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素:
List<String> forEachLists = new ArrayList<>();
forEachLists.add("a");
forEachLists.add("b");
forEachLists.add("c");
forEachLists.add("d");
forEachLists.add("e");
forEachLists.add("f");
List<String> limitLists = forEachLists.stream().skip(2).limit(3).collect(Collectors.toList());
注意skip与limit是有顺序关系的,比如使用skip(2)会跳过集合的前两个,返回的为c、d、e、f,然后调用limit(3)会返回前3个,所以最后返回的c,d,e - 排序(sort/min/max/distinct):
sort可以对集合中的所有元素进行排序。max,min可以寻找出流中最大或者最小的元素,而distinct可以寻找出不重复的元素:- 对一个集合进行排序:
List<Integer> sortLists = new ArrayList<>();
sortLists.add(1);
sortLists.add(4);
sortLists.add(6);
sortLists.add(3);
sortLists.add(2);
List<Integer> afterSortLists = sortLists.stream().sorted((In1,In2)-> In1-In2).collect(Collectors.toList()); - 得到其中长度最大的元素:
List<String> maxLists = new ArrayList<>();
maxLists.add("a");
maxLists.add("b");
maxLists.add("c");
maxLists.add("d");
maxLists.add("e");
maxLists.add("f");
maxLists.add("hahaha");
int maxLength = maxLists.stream().mapToInt(s->s.length()).max().getAsInt();
System.out.println("字符串长度最长的长度为"+maxLength); - 对一个集合进行查重:
List<String> distinctList = new ArrayList<>();
distinctList.add("a");
distinctList.add("a");
distinctList.add("c");
distinctList.add("d");
List<String> afterDistinctList = distinctList.stream().distinct().collect(Collectors.toList());
其中的distinct()方法能找出stream中元素equal(),即相同的元素,并将相同的去除,上述返回即为a,c,d。
- 对一个集合进行排序:
- 匹配(Match方法):
有的时候,我们只需要判断集合中是否全部满足条件,或者判断集合中是否有满足条件的元素,这时候就可以使用match方法:
allMatch:Stream 中全部元素符合传入的 predicate,返回 true
anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true- 判断集合中没有有为‘c’的元素:
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isExits = matchList.stream().anyMatch(s -> s.equals("c")); - 判断集合中是否全不为空:
List<String> matchList = new ArrayList<>();
matchList.add("a");
matchList.add("");
matchList.add("a");
matchList.add("c");
matchList.add("d");
boolean isNotEmpty = matchList.stream().noneMatch(s -> s.isEmpty());
则返回的为false
- 判断集合中没有有为‘c’的元素:
- 高级集合类和收集器
- 元素顺序
在一个有序集合中创建一个流时,流中的元素就按出现顺序排列,因此,例c-1的代码总是可以通过。
例: 顺序测试永远通过
List<Integer> numbers = asList(1, 2, 3, 4);
List<Integer> sameOrder = numbers.stream().collect(toList());
assertEquals(numbers, sameOrder);
如果集合本身就是无序的,由此生成的流也是无序的。HashSet 就是一种无序的集合,因此不能保证例c-2 所示的程序每次都通过。
例: 顺序测试不能保证每次通过
Set<Integer> numbers = new HashSet<>(asList(4, 3, 2, 1));
List<Integer> sameOrder = numbers.stream().collect(toList());
//该断言有时会失败
assertEquals(asList(4, 3, 2, 1), sameOrder);
一些操作在有序的流上开销更大,调用unordered 方法消除这种顺序就能解决该问题。大多数操作都是在有序流上效率更高,比如filter、map 和reduce 等。
使用并行流时,forEach 方法不能保证元素是按顺序处理的。如果需要保证按顺序处理,应该使用forEachOrdered 方法,它是你的朋友。 - 转化成其他集合
前面已经见过的toList,生成了java.util.List 类的实例。还有toSet ,toCollection和toMap分别生成Set,Collection和map 类的实例,
可能还会有这样的情况,你希望使用一个特定的集合收集值,而且你可以稍后指定该集合的类型。比如,你可能希望使用TreeSet,而不是由框架在背后自动为你指定一种类型的Set。此时就可以使用toCollection,它接受一个函数作为参数,来创建集合
例: 使用toCollection,用定制的集合收集元素
stream.collect(toCollection(TreeSet::new)); - 转换成值:maxBy、minBy
List<String> views = Lists.newArrayList("wsbs","xafaswzx","b8fw","ad");
Optional<String> res = views.stream().collect(minBy(Comparator.comparing(String::length);
System.out.println(res.get());
minBy的最终实现: (a, b) -> comparator.compare(a, b) <= 0 ? a : b
maxBy的最终实现: (a, b) -> comparator.compare(a, b) >= 0 ? a : b - 拼接字符串: joining
System.out.println(views.stream().map(s -> s).collect(joining("-"~)~)~); - 数据分组: groupingBy
List<String> views = Lists.newArrayList("wsbs","xafaswzx","b8fw","ad");
Map<Integer, List<String>> res = views.stream().collect(groupingBy(String::length)); - 数据分区: partitioningBy
分区函数必须返回一个boolean值,也就是说最后的分区组一个是true,一个是false。
List<String> views = Lists.newArrayList("wsbs","1232","b8fw","wsad");
Map<Boolean, List<String>> res =views.stream().collect(partitioningBy(str -> str.startsWith("ws");
{false=[1232, b8fw], true=[wsbs, wsad]}
- 元素顺序
- 延伸:组合收集器和重构和定制收集器
- 问题:流底层是如何实现的?
答案参考:http:www.cnblogs.com/CarpenterLee/p/6637118.html
- 遍历操作(map):
4. 数据并行化
过去我们可以指望CPU 时钟频率会变得越来越快。1979 年,英特尔公司推出的8086 处理器的时钟频率为5 MHz;到了1993 年,奔腾芯片的速度达到了60 MHz。在21 世纪早期,CPU 的处理速度一直以这种方式增长。然而在过去十年中,主流的芯片厂商转向了多核处理器。这种变化影响到了软件设计。我们不能再依赖提升CPU 的时钟频率来提高现有代码的计算能力,需要利用现代CPU 的架构,而这唯一的办法就是编写并行化的代码。
- 并行化操作
并行化操作流只需改变一个方法调用。如果已经有一个Stream 对象, 调用它的parallel 方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用parallelStream 就能立即获得一个拥有并行能力的流。
让我们先来看一个具体的例子,例7-1 计算了一组专辑的曲目总长度。它拿到每张专辑的曲目信息,然后得到曲目长度,最后相加得出曲目总长度。
例6-1 串行化计算专辑曲目长度
public int serialArraySum() {
return albums.stream(). flatMap(Album::getTracks).mapToInt(Track::getLength).sum();
}
调用parallelStream 方法即能并行处理,如例7-2 所示,剩余代码都是一样的,并行化就
是这么简单!
例6-2 并行化计算专辑曲目长度
public int parallelArraySum() {
return albums.parallelStream().flatMap(Album::getTracks).mapToInt(Track::getLength).sum();
}
读到这里,大家的第一反应可能是立即将手头代码中的stream 方法替换为parallelStream方法,因为这样做简直太简单了!先别忙,为了将硬件物尽其用,利用好并行化非常重要,但流类库提供的数据并行化只是其中的一种形式。我们先要问自己一个问题:并行化运行基于流的代码是否比串行化运行更快?这不是一个简单的问题。回到前面的例子,哪种方式花的时间更多取决于串行或并行化运行时的环境。以例7-1 和例7-2 中的代码为准,在一个四核电脑上,如果有10 张专辑,串行化代码的速度是并行化代码速度的8 倍;如果将专辑数量增至100 张,串行化和并行化速度相当;如果将专辑数量增值10 000 张,则并行化代码的速度是串行化代码速度的2.5 倍。输入流的大小并不是决定并行化是否会带来速度提升的唯一因素,性能还会受到编写代码的方式和核的数量的影响。 - 影响性能的因素
- 数据大小
输入数据的大小会影响并行化处理对性能的提升。将问题分解之后并行化处理,再将结果合并会带来额外的开销。因此只有数据足够大、每个数据处理管道花费的时间足够多
时,并行化处理才有意义。6.3 节讨论过。 - 源数据结构
每个管道的操作都基于一些初始数据源,通常是集合。将不同的数据源分割相对容易,这里的开销影响了在管道中并行处理数据时到底能带来多少性能上的提升。- 性能好
ArrayList、数组或IntStream.range,这些数据结构支持随机读取,也就是说它们能轻而易举地被任意分解。 - 性能一般
HashSet、TreeSet,这些数据结构不易公平地被分解,但是大多数时候分解是可能的。 - 性能差
有些数据结构难于分解,比如,可能要花O(N) 的时间复杂度来分解问题。其中包括LinkedList,对半分解太难了。还有Streams.iterate 和BufferedReader.lines,它们长度未知,因此很难预测该在哪里分解。
- 性能好
- 装箱
处理基本类型比处理装箱类型要快。 - 核的数量
极端情况下,只有一个核,因此完全没必要并行化。显然,拥有的核越多,获得潜在性能提升的幅度就越大。在实践中,核的数量不单指你的机器上有多少核,更是指运行时你的机器能使用多少核。这也就是说同时运行的其他进程,或者线程关联性(强制线程在某些核或CPU 上运行)会影响性能。 - 单元处理开销
比如数据大小,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。
- 数据大小
5. 新工具
- Nashorn引擎:jjs
jjs是个基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。例如,我们创建一个具有如下内容的func.js文件:
function f() {
return 1;
};
print( f() + 1 );
我们可以把这个文件作为参数传递给jjs使得这个文件可以在命令行中执行命令:jjs func,js,输出结果为2。
在Java代码中执行javascript代码代码,示例代码如下:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "javascript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );
执行结果:
jdk.nashorn.api.scripting.NashornScriptEngine
Result:2 - 类依赖分析工具:jdeps
Jdeps是一个功能强大的命令行工具,它可以帮我们显示出包层级或者类层级java类文件的依赖关系。它接受class文件、目录、jar文件作为输入,默认情况下,jdeps会输出到控制台。
这个命令输出内容很多,我们只看其中的一部分,这些依赖关系根据包来分组,如果依赖关系在classpath里找不到,就会显示not found.
示例:
6. JVM的新特性
JVM内存永久区已经被metaspace替换(JEP 122)。JVM参数 -XX:PermSize 和 -XX:MaxPermSize被-XX:MetaSpaceSize 和 -XX:MaxMetaspaceSize代替。
相关文章
- 暂无相关文章
用户点评