序言
Java起源于20世纪90年代,从1991年开始,Java语言持续发展,并主要应用于Web端的开发。JDK(Java Development Kit)版本也在不断更新。作为一种高级语言,为了让开发者能够写出可读性更强,且更安全简洁的代码,它在一个个版本里增加了新的特性。在阅读一些老代码时,确实也发现了各种各样不同的问题,把这些新特性用上可以让代码更简洁更清晰。下面就来从JDK 8开始总结一些新特性。
JDK 8
首先,介绍Java开发工具 8 提供的新特性。
接口的默认方法(Default Methods For Interfaces)
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表达式(Lambda Expressions)
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);
}
}
函数式接口(Functional Interfaces)
为了让现有的函数友好地支持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());
}
}
方法和构造函数引用(Method And Constructor References)
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 Scopes)
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就会报错,错误信息如下,
内置函数式接口(Built-In Functional Interface)
Java 1.8 API包含许多内置函数式接口,如Comparator或Runnable,有一些接口来自Google Guava库里。下面看一下这些接口如何扩展到Lambda上使用的。
Predicates
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
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
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
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
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
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
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); //并行输出
}
}
Filter
过滤通过一个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
}
}
Sorted
排序也是一个中间操作,返回排序好后的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
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);
}
}
Match
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
}
}
Count
计数也是一个最终操作,返回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
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
}
}
Parallel Stream
前面提到过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));
}
}
Sequential Sort/Parallel Sort
串行排序即上面Stream流的排序,并行排序则是将Collection接口中的非抽象方法stream()改为parallelStream()。
Map
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);
}
}
Data API
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
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);
}
}
Annotation
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);
}
}
Nashorn
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
内置了 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);
}
}
}
总结
JDK 8的特性除了上述所述还有其他特性。上面包含大部分的特性,具体其他特性可以参看官网。
JDK 8之后的特性也可以在官网上找到。
参考链接:
1、Java 8 特性 – 终极手册 | 并发编程网 – ifeve.com
2、Java 8 特性 – 终极手册(一)-阿里云开发者社区
3、JDK