Java起源于20世纪90年代,从1991年开始,Java语言持续发展,并主要应用于Web端的开发。JDK(Java Development Kit)版本也在不断更新。作为一种高级语言,为了让开发者能够写出可读性更强,且更安全简洁的代码,它在一个个版本里增加了新的特性。在阅读一些老代码时,确实也发现了各种各样不同的问题,把这些新特性用上可以让代码更简洁更清晰。下面就来从JDK 8开始总结一些新特性。
首先,介绍Java开发工具 8 提供的新特性。
Interface中定义的方法默认修饰符是public abstract表示它是一个抽象方法,通过实现类来进行具体的实现,Java 8中可以使用default关键字向接口添加非抽象方法实现(虚拟扩展方法),如下。
public interface Formula { double caculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
在测试类中看到可以直接使用接口中的非抽象方法,
public class FormulaTest { public static void main(String[] args) { Formula formula = new Formula() { @Override public double caculate(int a) { return sqrt(a * 100); } }; System.out.println(formula.caculate(100)); //100.0 System.out.println(formula.sqrt(100)); //10.0 } }
lambda表达式应该是我们使用的最多的了,下面直接给出一个简单例子,按照字典序进行排序,将创建的一个匿名的比较器对象传入sort方法,让代码更加简洁易懂。
public class LambdaExpressions { public static void main(String[] args) { List<String> names = Arrays.asList("john","peter","jerry","tom"); //按字典序进行排序,下面也可以使用Comparator.reverseOrder()静态方法 names.sort((o1, o2) -> o2.compareTo(o1)); names.stream().forEach(System.out::println); } }
为了让现有的函数友好地支持Lambda,增加了函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法的接口。像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口最典型的例子。Java 8还增加了特殊注解@FunctionalInterface进行声明函数式接口,编译器会进行静态代码检查。不过大部分的函数式接口都不需要我们写,java.util.function包里有现成的。
public class FunctionalInterfaceTest { @FunctionalInterface public interface Converter<F, T> { //仅包含一个抽象方法 T convert(F from); } public static void main(String[] args) { Converter<String, Integer> converter = (from -> Integer.valueOf(from)); Integer converted = converter.convert("123"); System.out.println(converted.getClass()); } }
Java 8 允许通过::关键字传递方法或构造函数的引用,lambda表达式中的demo也展示了list集合通过stream流的forEach方法进行打印输出,这里打印输出就使用的是::关键字进行方法引用。
下面看一个引用对象方法的例子,
public class MethodReferences { @FunctionalInterface public interface Converter<F, T> { T convert(F from); } String startWith(String s){ return String.valueOf(s.charAt(0)); } public static void main(String[] args) { MethodReferences references = new MethodReferences(); Converter<String,String> converter = references::startWith; String converted = converter.convert("Java"); System.out.println(converted); //J } }
再看构造函数的引用的例子,
创建一个含有无参构造函数(可以使用@NoArgsConstructor注解)和有参构造函数(可以使用@AllArgsConstructor注解)的Person对象,
public class Person { String firstName; String lastName; Person() { } public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
创建一个Person对象的对象工厂接口,
public interface PersonFactory<T extends Person> { T create(String firstName, String lastName); }
使用构造函数引用将它们关联起来,Person::new可以获取Person类构造函数的引用。
public class PersonCreateTest { public static void main(String[] args) { PersonFactory<Person> factory = Person::new; Person person = factory.create("John","Parker"); System.out.println(person.firstName); } }
Lambda表达式其作用域主要有,
可以直接在lambda表达式中访问外部的局部变量,但是外部局部变量必须是最终变量(不可被修改),
public class LambdaScopes { public static void main(String[] args) { final int num = 1; Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num); String res = stringConverter.convert(2); //3 System.out.println(res); } }
num不加final关键字可以,但是修改num就会报错,错误信息如下,
Java 1.8 API包含许多内置函数式接口,如Comparator或Runnable,有一些接口来自Google Guava库里。下面看一下这些接口如何扩展到Lambda上使用的。
Predicate接口是只有一个参数的返回布尔类型值的断言型接口。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(与、或、非)。
@FunctionalInterface public interface Predicate<T> { //通过参数评估断言 boolean test(T t); //默认方法(非抽象方法) 与关系型运算符&&类似 default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } //默认方法(非抽象方法) 与关系型运算符!类似 default Predicate<T> negate() { return (t) -> !test(t); } //默认方法(非抽象方法) 与关系型运算符||类似 default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } //比较第一个test的方法和第二个test方法是否相同 static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
Predicate使用示例如下,
public class PredicateTest { public static void main(String[] args) { Predicate<String> predicate = s -> s.length() > 0; System.out.println(predicate.test("foo")); //true System.out.println(predicate.negate().test("foo")); //false Predicate<Boolean> nonNull = Objects::nonNull; Predicate<Boolean> isNull = Objects::isNull; System.out.println(nonNull.test(false)); //true System.out.println(isNull.test(false)); //false Predicate<String> isEmpty = String::isEmpty; Predicate<String> isNotEmpty = isEmpty.negate(); System.out.println(isNotEmpty.test("predicate")); //true } }
Function接口接受一个参数并生成接口,默认方法可用于多个函数链接在一起(compose,andThen),java.util.function包下的接口源码大家可自行查阅,使用示例如下,
public class FunctionTest { public static void main(String[] args) { Function<String, Integer> toInteger = Integer::valueOf; Function<String, String> backToString = toInteger.andThen(String::valueOf); System.out.println(backToString.apply("111")); //111 } }
Supplier接口产生给定泛型类型的结果。与Function接口不同,Supplier接口不接受参数。
public class SupplierTest { public static void main(String[] args) { Supplier<Person> personSupplier = Person::new; Person person = personSupplier.get(); //new Person person.setFirstName("John"); System.out.println(person.firstName); //John } }
Consumer接口表示要对单个输入参数执行的操作。
public class ConsumerTest { public static void main(String[] args) { Consumer<Person> greeter = (p) -> System.out.println("Hello," + p.firstName); greeter.accept(new Person("John", "Peter")); //Hello,John } }
Comparator是Java中经典老接口,Java 8在上面添加了多种默认方法。
public class ComparatorTest { public static void main(String[] args) { //下面等价于Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName); Comparator<Person> comparator = Comparator.comparing(p -> p.firstName); Person p1 = new Person("John", "Peter"); Person p2 = new Person("Alice", "Bob"); System.out.println(comparator.compare(p1, p2)); //9 System.out.println(comparator.reversed().compare(p1, p2)); //-9 } }
Optional是为了解决空指针异常而产生的,虽然不能完全解决,但是非常有效的防止NullPointException的产生。它是一个简单的容器,其值可能是null或者不是null。在Java 8中方法的返回尽量使用Optional替代null。
public class OptionalTest { public static void main(String[] args) { Optional<Person> result = searchById(); Person person = result.orElseThrow(() -> new BusinessException("待查对象未找到!")); System.out.println(person.firstName); //John } private static Optional<Person> searchById() { // return Optional.of(new Person("John", "Peter")); return Optional.empty(); } }
Stream流应该是我们在Java 8中用的最多的了。Java.util.Collection的子类中List或Set都可以用Stream流进行操作,可以是并行执行或串行执行,默认是串行执行。Map不支持。
public class StreamTest { public static void main(String[] args) { Stream<Integer> stream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8).stream(); //设置为并行流 stream.parallel(); System.out.println(stream.isParallel()); //true stream.forEach(System.out::println); //并行输出 } }
过滤通过一个Predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来完成其他操作。还是用上面的例子,过滤出上面集合中3的倍数。
public class FilterTest { public static void main(String[] args) { Stream<Integer> stream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8).stream(); stream.filter(n -> n % 3 == 0).forEach(System.out::println); //3 6 } }
排序也是一个中间操作,返回排序好后的Stream。sorted()方法里可以指定自定义的Comparator。
下面的例子先按字典序进行排序,然后过滤出第三个字符为c开头的字符串。
public class SortedTest { public static void main(String[] args) { Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream(); //字符串先按字典序排序,再过滤出字符串第三个字符为c开头 stream.sorted().filter(s -> s.startsWith("c", 2)).forEach(System.out::println); //abc cac } }
map也是一个中间操作,这个我们用的非常多,比如对外暴露的接口的对象集合数据流映射成待请求的对象集合。下面举个简单的字符串映射输出例子,集合中字符串全大写输出,
public class MapTest { public static void main(String[] args) { Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream(); stream.sorted().map(String::toUpperCase).forEach(System.out::println); } }
Stream提供的匹配操作,允许检测制定的Predicate是否匹配整个Stream。所有匹配操作都是最终操作,并返回一个布尔类型的值。下面举的例子是集合中没有以m开头的字符串,除了noneMatch,还有anyMatch和allMatch,anyMatch表示有任意一个匹配,allMatch表示全部匹配。
public class MatchTest { public static void main(String[] args) { Stream<String> stream = Lists.newArrayList("cac", "bew", "weee", "zzz", "abc").stream(); boolean result = stream.noneMatch(s -> s.startsWith("m")); System.out.println(result); //true } }
计数也是一个最终操作,返回Stream中元素的个数,返回值类型为long。
public class CountTest { public static void main(String[] args) { Stream<String> stream = Lists.newArrayList("cac", "abc", "aaa", "awf").stream(); long count = stream.filter(s -> s.startsWith("a")).count(); System.out.println("集合元素个数为:" + count); //3 } }
Reduce规约也是一个最终操作,允许通过指定的函数来将Stream中的多个元素规约为一个元素,规约后的结果是通过Optional接口表示的。
public class ReduceTest { public static void main(String[] args) { Stream<String> stream = Lists.newArrayList("cac", "abc", "aaa", "awf").stream(); Optional<String> reduced = stream.reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); //cac#abc#aaa#awf Stream<Integer> integerStream = Sets.newSet(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).stream(); int sum = integerStream.reduce(Integer::sum).get(); //这里隐式进行了拆包 System.out.println(sum); //55 String concat = Stream.of("R", "e", "d", "u", "c", "e").reduce("", String::concat); System.out.println(concat); //Reduce } }
前面提到过Stream的串行流和并行流,默认是串行,可以通过Stream.parallel()方法让其成为一个并行流。通过默认的ForkJoinPool提高多线程任务的速度,ForkJoinPool线程池类似于ThreadPoolExecutor线程池都继承了抽象类AbstractExecutorService。ForkJoinPool的线程数量默认是CPU数,主要通过分治法(Divide-and-Conquer)来解决问题,它适用于计算密集型任务。
它用的好可以提高执行效率,用的不好适得其反。《Java编程规范》以及《Effective-Java》都提及请谨慎使用并行流。
public class ParallelStreamTest { private static long calculatePI(long n) { return LongStream.rangeClosed(2, n) //2到n的Long数值流 // .parallel() .mapToObj(BigInteger::valueOf) //转换为BigInteger .filter(i -> i.isProbablePrime(50)) //isProbablePrime:如果BigInteger可能是素数 .count(); } public static void main(String[] args) { long startTime = System.currentTimeMillis(); calculatePI(10000000L); System.out.println("计算Pi(n)耗时:" + (System.currentTimeMillis() - startTime)); } }
串行排序即上面Stream流的排序,并行排序则是将Collection接口中的非抽象方法stream()改为parallelStream()。
Map类型不支持Stream流,但可以在键值上创建专门的流,如map.keySet().stream()或者map.values().stream()以及map.entrySet().stream()。
public class MapStreamTest { public static void main(String[] args) { Map<Integer, String> map = new HashMap<>(); for (int i = 0; i < 3; i++) { map.putIfAbsent(i, "val" + i); //不存在则存入 } map.forEach((id, val) -> System.out.println(val)); String value = map.getOrDefault(5, "not found number"); System.out.println(value); } }
Java 8在java.time包下包含一个全新的日期和时间API。
public class DataAPITest { public static void main(String[] args) { ZoneId zoneId = ZoneId.systemDefault(); System.out.println("当前系统时区:" + zoneId); LocalTime localTime = LocalTime.now(zoneId); System.out.println("本地时间:" + localTime); LocalDate localDate = LocalDate.now(); System.out.println("本地日期:" + localDate); LocalDateTime currentTime = Instant.now() .atZone(ZoneId.of("Asia/Shanghai")) .toLocalDateTime(); System.out.println("本地日期时间:" + currentTime); DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒"); String date = currentTime.format(formatter); System.out.println("格式化后的时间:" + date); } }
Clock类提供了访问当前日期和时间的方法,它是时区敏感的。
public class ClockTest { public static void main(String[] args) { Clock clock = Clock.systemDefaultZone(); long mills = clock.millis(); System.out.println(mills); Instant instant = clock.instant(); System.out.println(instant); } }
Java 8中支持多重注解,如下,通过@Repeatable隐性定义好集合。
@interface Elements { Element[] value(); } @Repeatable(Elements.class) @interface Element { String value(); } //old @Elements({@Element("circle"), @Element("square")}) class Shape { } //new @Element("brush") @Element("cup") class Product { } public class MultipleAnnotation { public static void main(String[] args) { Element element = Product.class.getAnnotation(Element.class); System.out.println(element); Elements elements = Product.class.getAnnotation(Elements.class); System.out.println(elements.value().length); Element[] elementArray = Product.class.getAnnotationsByType(Element.class); System.out.println(elementArray.length); } }
javascript 引擎,从JDK1.8开始,Nashorn取代Rhino(JDK 1.6, JDK1.7)成为Java的嵌入式JavaScript引擎,性能提升了2到10倍。
public class NashornTest { public static void main(String[] args) { ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); ScriptEngine nashorn = scriptEngineManager.getEngineByName("nashorn"); String name = "Runoob"; Integer result = null; try { nashorn.eval("print('" + name + "')"); result = (Integer) nashorn.eval("10 + 2"); } catch (ScriptException e) { System.out.println("执行脚本错误: " + e.getMessage()); } System.out.println(result.toString()); } }
内置了 Base64 编码的编码器和解码器。
public class Base64Test { public static void main(String[] args) { try { // 编码 String base64encodedString = Base64.getEncoder().encodeToString("runoob?java8".getBytes("utf-8")); System.out.println("Base64 编码字符串 (基本) :" + base64encodedString); // 解码 byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString); System.out.println("原始字符串: " + new String(base64decodedBytes, "utf-8")); } catch (UnsupportedEncodingException e) { System.out.println("不支持编码解码异常,异常信息:" + e); } } }
Java 8的特性除了上述所述还有其他特性。上面包含大部分的特性,具体其他特性可以参看官网。