java8已经出了很久,项目中也在使用。其最重要的特性就是Lambda表达式和函数式编程,这让我们的代码可以大大简化,更加优雅。
读者通过这篇文章,可以入门java8,并且可以开始进行java8的程序编写了。讲的还算细致,但是限于篇幅原理讲的不是太深入,原理以后有时间再单独写博客。
parameter -> expression body
/**
* Created by liubenlong on 2016/11/7.
*/
public class LambdaTest {
/**
* lambda表达式测试
* @param args
*/
public static void main(String args[]){
LambdaTest tester = new LambdaTest();
//with type declaration
MathOperation addition = (int a, int b) -> a + b;
//with out type declaration
MathOperation subtraction = (a, b) -> a - b;
//with return statement along with curly braces
MathOperation multiplication = (int a, int b) -> { return a * b; };
//without return statement and without curly braces
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
//with parenthesis
GreetingService greetService1 = message -> System.out.println("Hello " + message);
//without parenthesis
GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
greetService1.sayMessage("Mahesh");
greetService2.sayMessage("Suresh");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
输出结果:
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Mahesh
Hello Suresh
以下是对使用上述例子认为是重要的观点:
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
为了更友好方便 使用Lambda表达式,JDK设计了函数式接口。
函数式接口就是一个具有一个方法的普通接口
函数式接口可以被隐式转换为lambda表达式。java.lang.Runnable
与java.util.concurrent.Callable
是函数式接口最典型的两个例子。在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface
(Java 8中所有类库的已有接口都添加了@FunctionalInterface注解)
JDK中Callable
的定义:
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Predicate;
/**
* Created by liubenlong on 2016/11/8.
* 函数式接口测试
*/
public class FunctionInterfaceTest {
@Test
public void test1(){
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
//测试Predicate
eval(list, abc -> true);
eval(list, abc -> false);
eval(list, a -> a % 2 == 0);
//测试BiFunction
BiFunction biFunction = (a, b) -> {return a * b;};
System.out.println(biFunction.apply(10, 2));
}
public static void eval(List list, Predicate predicate){
list.forEach(integer -> {if(predicate.test(integer)) System.out.println(integer);});
}
}
其中BiFunction
和Predicate
都是函数式接口。读者可以自行运行,查看结果。
java.util.Function
包下都是函数式接口,我们可以直接拿来使用:
BiConsumer:表示接收两个输入参数和不返回结果的操作。
BiFunction:表示接受两个参数,并产生一个结果的函数。
BinaryOperator:表示在相同类型的两个操作数的操作,生产相同类型的操作数的结果。
BiPredicate:代表两个参数谓词(布尔值函数)。
BooleanSupplier:代表布尔值结果的提供者。
Consumer:表示接受一个输入参数和不返回结果的操作。
DoubleBinaryOperator:代表在两个double值操作数的运算,并产生一个double值结果。
DoubleConsumer:表示接受一个double值参数,不返回结果的操作。
DoubleFunction:表示接受double值参数,并产生一个结果的函数。
DoublePredicate:代表一个double值参数谓词(布尔值函数)。
DoubleSupplier:表示double值结果的提供者。
DoubleToIntFunction:表示接受double值参数,并产生一个int值结果的函数。
DoubleToLongFunction:代表接受一个double值参数,并产生一个long值结果的函数。
DoubleUnaryOperator:表示上产生一个double值结果的单个double值操作数的操作。
Function:表示接受一个参数,并产生一个结果的函数。
IntBinaryOperator:表示对两个int值操作数的运算,并产生一个int值结果。
IntConsumer:表示接受单个int值的参数并没有返回结果的操作。
IntFunction:表示接受一个int值参数,并产生一个结果的函数。
IntPredicate:表示一个整数值参数谓词(布尔值函数)。
IntSupplier:代表整型值的结果的提供者。
IntToDoubleFunction:表示接受一个int值参数,并产生一个double值结果的功能。
IntToLongFunction:表示接受一个int值参数,并产生一个long值结果的函数。
IntUnaryOperator:表示产生一个int值结果的单个int值操作数的运算。
LongBinaryOperator:
表示在两个long值操作数的操作,并产生一个long值结果。
LongConsumer:
表示接受一个long值参数和不返回结果的操作。
LongFunction:
表示接受long值参数,并产生一个结果的函数。
LongPredicate:
代表一个long值参数谓词(布尔值函数)。
LongSupplier:
表示long值结果的提供者。
LongToDoubleFunction:表示接受double参数,并产生一个double值结果的函数。
LongToIntFunction:表示接受long值参数,并产生一个int值结果的函数。
LongUnaryOperator:表示上产生一个long值结果单一的long值操作数的操作。
ObjDoubleConsumer:表示接受对象值和double值参数,并且没有返回结果的操作。
ObjIntConsumer:表示接受对象值和整型值参数,并返回没有结果的操作。
ObjLongConsumer:表示接受对象的值和long值的说法,并没有返回结果的操作。
Predicate:代表一个参数谓词(布尔值函数)。
Supplier:表示一个提供者的结果。
ToDoubleBiFunction:表示接受两个参数,并产生一个double值结果的功能。
ToDoubleFunction:代表一个产生一个double值结果的功能。
ToIntBiFunction:表示接受两个参数,并产生一个int值结果的函数。
ToIntFunction:代表产生一个int值结果的功能。
ToLongBiFunction:表示接受两个参数,并产生long值结果的功能。
ToLongFunction:代表一个产生long值结果的功能。
UnaryOperator:表示上产生相同类型的操作数的结果的单个操作数的操作。
先来看看JDK中Predicate
类的声明(为了节省篇幅,我删掉了注释):
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate {
boolean test(T t);
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate negate() {
return (t) -> !test(t);
}
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
这是一个典型的函数式接口,里面有多个default方法,和一个static方法。
默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)
在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。这方面好的例子是大量的方法被添加到java.util.Collection接口中去:stream(),parallelStream(),forEach(),removeIf(),……
在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。更多详情请参考 官方文档
注意:
1. 静态方法不可以被集成。
2. 默认方法可以被继承
3. 函数式接口可以实现另一个函数式接口
4. 默认方法可以被重写
5. 默认方法重新声明,则变成了普通方法
下面来通过实例说明:
定义一个函数式接口:
@FunctionalInterface
public interface Hello {
void sayHello();
/**
* default方法可以有多个
*/
default void sayDefault(){
System.out.println("我是default方法");
}
default void sayDefault11(){
System.out.println("我是default11方法");
}
static void sayStatic(){
System.out.println("我是static方法");
}
}
Hello1继承了上面的Hello:
@FunctionalInterface
public interface Hello1 extends Hello {
}
测试类:
public class Hello1Impl implements Hello1{
@Override
public void sayHello() {
System.out.println("interface_demo.HelloImpl.sayHello");
}
@Test
public void test(){
Hello1Impl impl = new Hello1Impl();
impl.sayHello();
impl.sayDefault();
sayDefault();
sayDefault11();
Hello.sayStatic();
//静态方法不可被继承哦
// Hello1.sayStatic();
}
}
输出结果:
interface_demo.HelloImpl.sayHello
我是default方法
我是default方法
我是default11方法
我是static方法
定义Hello2继承hello,重写默认方法:
@FunctionalInterface
public interface Hello2 extends Hello {
@Override
default void sayDefault() {
System.out.println("我是重写的Hello的default方法。");
}
}
测试类:
public class Hello2Impl implements Hello2{
@Override
public void sayHello() {
System.out.println("interface_demo.Hello2Impl.sayHello");
}
@Test
public void test(){
Hello2Impl impl = new Hello2Impl();
impl.sayHello();
sayDefault11();
sayDefault();
}
}
结果:
interface_demo.Hello2Impl.sayHello
我是default11方法
我是重写的Hello的default方法。
Hello3继承hello,但是重写了默认方法:
public interface Hello3 extends Hello {
/**
* 重新声明,成了普通抽象方法
*/
@Override
void sayDefault();
}
测试类:
public class Hello3Impl implements Hello3{
@Override
public void sayHello() {
System.out.println("interface_demo.Hello3Impl.sayHello");
}
/**
* 这里必须重写,因为sayDefault已经不是一个默认方法
*/
@Override
public void sayDefault() { System.out.println("interface_demo.Hello3Impl.sayDefault");
}
@Test
public void test(){
sayDefault();
sayHello();
sayDefault11();
}
}
输出结果:
interface_demo.Hello3Impl.sayDefault
interface_demo.Hello3Impl.sayHello
我是default11方法
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
HashSet::new
.下面详细实例中会有。实例:
import org.junit.Test;
import java.util.*;
import java.util.function.Supplier;
/**
* Created by liubenlong on 2016/11/7.
* 方法引用测试
*/
public class MethodReferencesTest {
public void print(){
System.out.println("sdfs");
}
@Test
public void test1(){
List names = new ArrayList();
names.add("Mahesh");
names.add("Suresh");
names.add("Ramesh");
names.add("Naresh");
names.add("Kalpesh");
names.forEach(System.out::println);
}
@Test
public void test2(){
String[] array = {"gjyg", "ads", "jk"};
Arrays.sort(array, String::compareToIgnoreCase);
for (String s : array) {
System.out.println(s);
}
}
class ComparisonProvider{
public int compareByName(Person a,Person b){
return a.getName().compareTo(b.getName());
}
public int compareByAge(Person a,Person b){
return a.getAge() - b.getAge();
}
}
@Test
public void test3(){
Person [] persons = initPerson();
for (Person person : persons) person.printPerson();
System.out.println("*******以下是lambda表达式写法");
Arrays.sort(persons, (a, b) -> a.getAge() - b.getAge());
for (Person person : persons) person.printPerson();
System.out.println("*******以下是引用静态方法,比lambda表达式写法简单");
Arrays.sort(persons, Person::compareByAge);
for (Person person : persons) person.printPerson();
System.out.println("*******以下是引用实例方法");
ComparisonProvider provider = new ComparisonProvider();
Arrays.sort(persons, provider::compareByAge);
for (Person person : persons) person.printPerson();
System.out.println("*******使用lambda表达式-引用的是构造方法");
final List personList = Arrays.asList(persons);
Set personSet = transferElements(personList,()-> new HashSet<>());
personSet.forEach(Person::printPerson);
System.out.println("*******使用方法引用-引用的是构造方法");
Set personSet2 = transferElements(personList, HashSet::new);
personSet2.forEach(Person::printPerson);
}
public static , DEST extends Collection> DEST transferElements(SOURCE sourceColletions, Supplier colltionFactory) {
DEST result = colltionFactory.get();
sourceColletions.forEach(o -> result.add(o));
return result;
}
private Person [] initPerson(){
Person [] persons = new Person[3];
Person person = new Person();
person.setName("张三");
person.setAge(10);
persons[0] = person;
person = new Person();
person.setName("李四");
person.setAge(50);
persons[1] = person;
person = new Person();
person.setName("王五");
person.setAge(2);
persons[2] = person;
return persons;
}
}
比较简单,就不列出结果了,读者自行运行该程序。
java8以前,注解在同一位置只能声明一次,不能声明多次。在java8中,允许同一个位置声明多次注解。
重复注解机制本身必须用@Repeatable
注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。让我们看一个快速入门的例子:
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
public class RepeatingAnnotations {
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
public @interface Filters {
Filter[] value();
}
@Target( ElementType.TYPE )
@Retention( RetentionPolicy.RUNTIME )
@Repeatable( Filters.class )
public @interface Filter {
String value();
};
@Filter( "filter1" )
@Filter( "filter2" )
public interface Filterable {
}
public static void main(String[] args) {
for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
System.out.println( filter.value() );
}
}
}
输出结果:
filter1
filter2
正如我们看到的,这里有个使用@Repeatable( Filters.class )注解的注解类Filter,Filters仅仅是Filter注解的数组,但Java编译器并不想让程序员意识到Filters的存在。这样,接口Filterable就拥有了两次Filter(并没有提到Filter)注解。
同时,反射相关的API提供了新的函数getAnnotationsByType()来返回重复注解的类型(请注意Filterable.class.getAnnotation( Filters.class )经编译器处理后将会返回Filters的实例)。
Optional
的引入是为了解决臭名昭著的空指针异常
问题.
Optional实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测
other
NoSuchElementException
上面翻译自官方文档
测试代码如下:
import org.junit.Test;
import java.util.Optional;
/**
* Created by liubenlong on 2016/11/8.
*
*/
public class OptionalTest {
@Test
public void test1(){
OptionalTest java8Tester = new OptionalTest();
Integer value1 = null;
Integer value2 = new Integer(10);
//Optional.ofNullable - allows passed parameter to be null.
Optional a = Optional.ofNullable(value1);
//Optional.of - throws NullPointerException if passed parameter is null
Optional b = Optional.of(value2);
System.out.println(java8Tester.sum(a,b));
}
public Integer sum(Optional a, Optional b){
//Optional.isPresent[判断值是否存在] - checks the value is present or not
System.out.println("First parameter is present: " + a.isPresent());
System.out.println("Second parameter is present: " + b.isPresent());
//Optional.orElse - returns the value if present otherwise returns
//the default value passed.
Integer value1 = a.orElse(new Integer(0));
//Optional.get - gets the value, value should be present
Integer value2 = b.get();
return value1 + value2;
}
}
输出结果:
First parameter is present: false
Second parameter is present: true
10
Stream 是java8最重要的新特新之一,真正的函数式编程风格引入到Java中,它大大简化了我们的编码量,让程序员写出高效率、干净、简洁的代码。
由于stream过于强大,我们这里只将常用的做一下说明
了解大数据map-reduce的人对这个新特性会比较容易掌握,思想比较像。
@Test
public void test(){
List strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List list = strings.stream().filter(n -> !"".equals(n)).limit(3).collect(Collectors.toList());
list.forEach(n -> System.out.println(n));
}
结果:
abc
bc
efg
@Test
public void test2(){
List ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
ints.stream().map(n->n*n).distinct().filter(n->n!=81).sorted().collect(Collectors.toList()).forEach(n->System.out.println(n));
}
结果:
1
4
16
25
36
@Test
public void test3(){
List ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
System.out.println(ints.parallelStream().filter(n->n>2).distinct().count());
Integer sum = ints.parallelStream().map(n -> n * n).filter(n -> n != 81).reduce(Integer::sum).get();
System.out.println(sum);
}
输出结果:
4
132
mapToInt
将其转换为可以进行统计的数值型。类似的还有mapToLong
、mapToDouble
@Test
public void test5(){
List ints = Arrays.asList(1,5,9,6,5,4,2,5,9);
IntSummaryStatistics statistics = ints.stream().mapToInt(n -> n).summaryStatistics();
System.out.println(statistics.getAverage());
System.out.println(statistics.getCount());
System.out.println(statistics.getMax());
System.out.println(statistics.getMin());
}
结果:
5.111111111111111
9
9
1
在开始研究Java 8日期/时间API之前,让我们先来看一下为什么我们需要这样一个新的API。在Java中,现有的与日期和时间相关的类存在诸多问题,其中有:
在现有的日期和日历类中定义的方法还存在一些其他的问题,但以上问题已经很清晰地表明:Java需要一个健壮的日期/时间类。这也是为什么- Joda Time在Java日期/时间需求中扮演了高质量替换的重要角色。
Java 8日期/时间API是JSR-310的实现,它的实现目标是克服旧的日期时间实现中所有的缺陷,新的日期/时间API的一些设计原则是:
java.time
包:这是新的Java日期/时间API的基础包,所有的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。所有这些类都是不可变的和线程安全的,在绝大多数情况下,这些类能够有效地处理一些公共的需求。java.time.chrono
包:这个包为非ISO的日历系统定义了一些泛化的API,我们可以扩展AbstractChronology类来创建自己的日历系统。java.time.format
包:这个包包含能够格式化和解析日期时间对象的类,在绝大多数情况下,我们不应该直接使用它们,因为java.time包中相应的类已经提供了格式化和解析的方法。java.time.temporal
包:这个包包含一些时态对象,我们可以用其找出关于日期/时间对象的某个特定日期或时间,比如说,可以找到某月的第一天或最后一天。你可以非常容易地认出这些方法,因为它们都具有“withXXX”的格式。java.time.zone
包:这个包包含支持不同时区以及相关规则的类Clock类,它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。Clock可以替换System.currentTimeMillis()
与TimeZone.getDefault()
。
Clock clock = Clock.systemUTC();
System.out.println( clock.instant() );
System.out.println( clock.millis() );
输出:
2017-03-14T08:22:08.019Z
1489479728168
LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息
@Test
public void testLocalDate(){
// Get the current date and time
LocalDateTime currentTime = LocalDateTime.now();
System.out.println("Current DateTime: " + currentTime);
LocalDate date1 = currentTime.toLocalDate();
System.out.println("date1: " + date1);
Month month = currentTime.getMonth();
int day = currentTime.getDayOfMonth();
int seconds = currentTime.getSecond();
System.out.println("Month: " + month
+" day: " + day
+" seconds: " + seconds
);
//指定时间
LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
System.out.println("date2: " + date2);
//12 december 2014
LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
System.out.println("date3: " + date3);
//22 hour 15 minutes
LocalTime date4 = LocalTime.of(22, 15);
System.out.println("date4: " + date4);
//parse a string
LocalTime date5 = LocalTime.parse("20:15:30");
System.out.println("date5: " + date5);
}
输出:
Current DateTime: 2017-03-14T16:23:08.997
date1: 2017-03-14
Month: MARCH day: 14 seconds: 8
date2: 2012-03-10T16:23:08.997
date3: 2014-12-12
date4: 22:15
date5: 20:15:30
@Test
public void testZonedDateTime(){
// Get the current date and time
ZonedDateTime now = ZonedDateTime.now();
System.out.println("now: " + now);
ZonedDateTime date1 = ZonedDateTime.parse("2007-12-03T10:15:30+05:30[Asia/Karachi]");
System.out.println("date1: " + date1);
ZoneId id = ZoneId.of("Europe/Paris");
System.out.println("ZoneId: " + id);
ZoneId currentZone = ZoneId.systemDefault();
System.out.println("CurrentZone: " + currentZone);
}
输出:
now: 2017-03-15T09:33:10.108+08:00[Asia/Shanghai]
date1: 2007-12-03T10:15:30+05:00[Asia/Karachi]
ZoneId: Europe/Paris
CurrentZone: Asia/Shanghai
可以代替Calendar
的日期操作
@Test
public void testChromoUnits(){
//Get the current date
LocalDate today = LocalDate.now();
System.out.println("Current date: " + today);
//add 1 week to the current date
LocalDate nextWeek = today.plus(1, ChronoUnit.WEEKS);
System.out.println("Next week: " + nextWeek);
//add 1 month to the current date
LocalDate nextMonth = today.plus(1, ChronoUnit.MONTHS);
System.out.println("Next month: " + nextMonth);
//add 1 year to the current date
LocalDate nextYear = today.plus(1, ChronoUnit.YEARS);
System.out.println("Next year: " + nextYear);
//add 10 years to the current date
LocalDate nextDecade = today.plus(1, ChronoUnit.DECADES);
//add 20 years to the current date
LocalDate nextDecade20 = today.plus(2, ChronoUnit.DECADES);
System.out.println("Date after 20 year: " + nextDecade20);
}
输出:
Current date: 2017-03-15
Next week: 2017-03-22
Next month: 2017-04-15
Next year: 2018-03-15
Date after 20 year: 2037-03-15
使用Java8,两个专门类引入来处理时间差。
@Test
public void testPeriod(){
//Get the current date
LocalDate date1 = LocalDate.now();
System.out.println("Current date: " + date1);
//add 1 month to the current date
LocalDate date2 = date1.plus(3, ChronoUnit.DAYS);
System.out.println("Next month: " + date2);
Period period = Period.between(date1, date2);
System.out.println("Period: " + period);
System.out.println("Period.getYears: " + period.getYears());
System.out.println("Period.getMonths: " + period.getMonths());
System.out.println("Period.getDays: " + period.getDays());
}
@Test
public void testDuration(){
LocalTime time1 = LocalTime.now();
Duration twoHours = Duration.ofHours(2);
System.out.println("twoHours.getSeconds(): " + twoHours.getSeconds());
LocalTime time2 = time1.plus(twoHours);
System.out.println("time2: " + time2);
Duration duration = Duration.between(time1, time2);
System.out.println("Duration: " + duration);
System.out.println("Duration.getSeconds: " + duration.getSeconds());
}
比较简单,读者自行运行查看结果
TemporalAdjuster 是做日期数学计算。例如,要获得“本月第二个星期六”或“下周二”。
@Test
public void testAdjusters(){
//Get the current date
LocalDate date1 = LocalDate.now();
System.out.println("Current date: " + date1);
//get the next tuesday
LocalDate nextTuesday = date1.with(TemporalAdjusters.next(DayOfWeek.TUESDAY));
System.out.println("Next Tuesday on : " + nextTuesday);
// 获得当月的第二个周六
LocalDate firstInMonth = LocalDate.of(date1.getYear(),date1.getMonth(), 1);
LocalDate secondSaturday = firstInMonth.with(
TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY)).with(
TemporalAdjusters.next(DayOfWeek.SATURDAY));
System.out.println("Second saturday on : " + secondSaturday);
}
输出:
Current date: 2017-03-15
Next Tuesday on : 2017-03-21
Second saturday on : 2017-03-11
toInstant()方法被添加到可用于将它们转换到新的日期时间的API原始日期和日历对象。使用ofInstant(Insant,ZoneId)方法得到一个LocalDateTime或ZonedDateTime对象。
@Test
public void testBackwardCompatability(){
//Get the current date
Date currentDate = new Date();
System.out.println("Current date: " + currentDate);
//Get the instant of current date in terms of milliseconds
Instant now = currentDate.toInstant();
ZoneId currentZone = ZoneId.systemDefault();
LocalDateTime localDateTime = LocalDateTime.ofInstant(now, currentZone);
System.out.println("Local date: " + localDateTime);
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(now, currentZone);
System.out.println("Zoned date: " + zonedDateTime);
}
输出:
Current date: Wed Mar 15 09:51:17 CST 2017
Local date: 2017-03-15T09:51:17.745
Zoned date: 2017-03-15T09:51:17.745+08:00[Asia/Shanghai]
java.util.Date#from
方法可以将Instant
转化为旧版本的Date对象。这两个方法都是jdk8以后新加的。
DateTimeFormatter可以替代以前的dateformat,使用起来方便一些。
注意DateTimeFormatter是线程安全的,因为他是final的类
@Test
public void testDateTimeFormatter(){
LocalDateTime currentDate = LocalDateTime.now();
System.out.println("Current date: " + currentDate);
System.out.println(currentDate.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
System.out.println(DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(currentDate));
System.out.println(currentDate.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG)));
System.out.println(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.LONG).format(currentDate));
System.out.println(currentDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(currentDate));
}
输出:
Current date: 2017-03-15T11:22:25.026
2017-03-15T11:22:25.026
2017-03-15T11:22:25.026
2017年3月15日 上午11时22分25秒
2017年3月15日 上午11时22分25秒
2017-03-15 11:22:25
2017-03-15 11:22:25
在Java 8中,Base64编码已经成为Java类库的标准。它的使用十分简单
Java8,Base64终于得到了在Java整合。 Java8现在有内置编码器和解码器的Base64编码。在Java8中,我们可以使用三种类型的Base64编码。
@Test
public void test(){
try {
byte[] bytes = "liubenlong?java8".getBytes();
// Encode using basic encoder
String base64encodedString = Base64.getEncoder().encodeToString(bytes);
System.out.println("Base64 Encoded String (Basic) :" + base64encodedString);
// Decode
byte[] base64decodedBytes = Base64.getDecoder().decode(base64encodedString);
System.out.println("Original String: "+new String(base64decodedBytes, "utf-8"));
base64encodedString = Base64.getUrlEncoder().encodeToString(bytes);
System.out.println("Base64 Encoded String (URL) :" + base64encodedString);
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < 10; ++i) {
stringBuilder.append(UUID.randomUUID().toString());
}
byte[] mimeBytes = stringBuilder.toString().getBytes("utf-8");
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(mimeBytes);
System.out.println("Base64 Encoded String (MIME) :"+mimeEncodedString);
}catch(UnsupportedEncodingException e){
System.out.println("Error :"+e.getMessage());
}
}
结果:
Base64 Encoded String (Basic) :bGl1YmVubG9uZz9qYXZhOA==
Original String: liubenlong?java8
Base64 Encoded String (URL) :bGl1YmVubG9uZz9qYXZhOA==
Base64 Encoded String (MIME) :NzU0ZjJiYzgtYmJkYS00YTBlLWJjZmItODZlYTY2OTM1ZWM3YTQ5YWNjNDktZDNkYy00NjA4LTgw
NjctODIzYzgwN2U5N2ViZWZmMmYwMzgtMTEwYS00NGE3LWFlNmMtNGQyMDJlOTA5MTM0OTMxMWRh
ZmYtNmNjMC00MzI5LWI0MGYtMmI3ZWMzMzEzMGMxMzE5NTZhMjYtMDAyZi00ZmFhLWI2ZTEtNDY3
Y2E0ZDBiZjhiYzY3N2Y5NjQtMWRiYy00ZTJlLWFhOGMtYzdkNzlmYTM3MGQzYmJlYTk0ZmUtZWFl
My00MjliLTk4MjEtNmQwNmExNjI3NzNmYmU5Yzc5MWItZjAyNy00NjI1LTliYmEtMjhmNWQyZDk3
NWI2NTlkOGM5ZjAtYzYxNC00YTFmLWI2NmUtZjc0MWE3MzM5ODJiODU0ZmFlMmQtNjFiZi00NWJl
LTgzNjQtNDJiOGY3ZGJiNzNm
Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度
实例:并行初始化一个20000的数组,并且填充1000000内的随机数,然后并行排序
import java.time.Duration;
import java.time.LocalTime;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
public static void main( String[] args ) {
LocalTime begin = LocalTime.now();
long[] arrayOfLong = new long [ 20000 ];
Arrays.parallelSetAll(arrayOfLong, index -> ThreadLocalRandom.current().nextInt(1000000));
Arrays.stream(arrayOfLong).limit(10).forEach(i -> System.out.print(i + " "));
System.out.println();
Arrays.parallelSort( arrayOfLong );
Arrays.stream(arrayOfLong).limit(10).forEach(i -> System.out.print(i + " "));
System.out.println();
System.out.println(Duration.between(begin, LocalTime.now()));
}
}
结果:
256647 515472 636377 442743 107942 904354 253278 17119 131953 86974
21 30 89 156 173 269 297 405 420 469
PT0.05S
LongAddr是高性能的原子计数器,比atomicLong性能还要高。
关于atomicLong请参照java高并发:CAS无锁原理及广泛应用。
关于LongAddr的实现原理,简单来说就是解决了伪共享的问题。具体我会单独再写一篇文章讲解这个伪共享以及LongAddr的实现原理。
先忙将对多线程下普通加锁,atomicLong,atomicLong三种原子计数性能进行一下比较:
package LongAddr;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Created by liubenlong on 2017/1/22.
* LongAddr: 比AtomicLong性能更高的原子类
*
* 本例比较加锁,AtomicLong,LongAddr 三种无锁
*/
public class LongAddrTest {
static long maxValue = 10000000;
static CountDownLatch countDownLatch = new CountDownLatch(10);
public volatile long count = 0;
public synchronized void incr(){
if(getValue() < maxValue) count++;
}
public long getValue(){
return count;
}
public static void main(String[] args) throws InterruptedException {
LongAddrTest longAddrTest = new LongAddrTest();
long begin = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i = 0 ; i < 10 ; i ++){
executorService.execute(() -> {
while(longAddrTest.getValue() < maxValue){
longAddrTest.incr();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+longAddrTest.getValue());
}
}
package LongAddr;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
/**
* Created by liubenlong on 2017/1/22.
* LongAddr: 比AtomicLong性能更高的原子类
*
* 本例比较加锁,AtomicLong,LongAddr 三种无锁
*/
public class LongAddrTest1 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
AtomicLong atomicLong = new AtomicLong(0);
long maxValue = 10000000;
long begin = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i = 0 ; i < 10 ; i ++){
executorService.execute(() -> {
while(atomicLong.get() < maxValue){
atomicLong.getAndIncrement();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+atomicLong.get());
}
}
package LongAddr;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.LongAdder;
/**
* Created by liubenlong on 2017/1/22.
* LongAddr: 比AtomicLong性能更高的原子类
*
* 本例比较加锁,AtomicLong,LongAddr 三种无锁
*/
public class LongAddrTest2 {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
LongAdder longAdder = new LongAdder();
long maxValue = 10000000;
long begin = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(10);
for(int i = 0 ; i < 10 ; i ++){
executorService.execute(() -> {
while(longAdder.sum() < maxValue){
longAdder.increment();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("总共耗时:"+(System.currentTimeMillis() - begin)+"毫秒, count="+longAdder.sum());
}
}
上面三个类执行结果可以看出来,AtomicLong,LongAddr两个比加锁性能提高了很多,LongAddr又比AtomicLong性能高。所以我们以后在遇到线程安全的原子计数器的时候,首先考虑LongAddr。
多开发者都在其系统中见过“java.lang.OutOfMemoryError: PermGen space
”这一问题。这往往是由类加载器相关的内存泄漏以及新类加载器的创建导致的,通常出现于代码热部署以及代理使用较多的情形时。
JDK8 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)
在老的JVM中会有PermGen
持久区,在java8中去掉了这个区域,取而代之的是将原本要放在持久区的类元数据信息、字节码信息及static final
的常量,转移到了计算机本地内存中。这样理论上说就不会出现PermGen space
的异常,而且不需要关心元空间的大小,它只受限于就算几内存。
但是不要高兴太早,好好想一想,真的不需要关心这部分数据量的大小吗?显然不可以。
因为依旧可能会出现元数据过多,依旧会有类卸载的需求。
MaxMetaspaceSize
)用于限制本地内存分配给类元数据的大小。如果没有指定这个参数,元空间会在运行时根据需要动态调整。理论上说,现在你完全可以不关注这个元空间,因为JVM会在运行时自动调校为“合适的大小”;元空间提高Full GC的性能,在Full GC期间,Metadata到Metadata pointers之间不需要扫描了,别小看这几纳秒时间;元空间隐患就是如果程序存在内存泄露,像OOMTest那样,不停的扩展metaspace的空间,会导致机器的内存不足,所以还是要有必要的调试和监控。
Future是Java 5添加的类,用来描述一个异步计算的结果。你可以使用isDone方法检查计算是否完成,或者使用get阻塞住调用线程,直到计算完成返回结果,你也可以使用cancel方法停止任务的执行。
虽然Future以及相关使用方法提供了异步执行任务的能力,但是对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。阻塞的方式显然和我们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,而且也不能及时地得到计算结果,为什么不能用观察者设计模式当计算结果完成及时通知监听者呢?
在Java 8中, 新增加了一个包含50个方法左右的类: CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
这里只是简单讲解他的使用,可以用于入门。
先定义两个静态的方法供后续使用:
public static Integer square(Integer a){
try {//模拟耗时任务
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return a * a;
}
public static Integer division(Integer a, Integer b){
return a / b;
}
CompletableFuture completableFuture = CompletableFuture.supplyAsync(()->square(10));
System.out.println("任务已提交完成,等待结果。");
Integer integer = completableFuture.get();
System.out.println(integer);
可以看到结果会先输出“任务已提交完成,等待结果。”。等待几秒钟才输出结果100
.
通过get方法,可以获取到线程的执行结果。如果还没有执行完,则阻塞。
默认是在ForkJoinPool.commonPool()线程池中执行任务,该线程池中的线程都是daemon的,所以必须调用get()方法等待返回结果。
计算平方,然后转为字符串并且拼接上”!!”:
completableFuture
.supplyAsync(()->square(20))
.thenApply(a -> a + "")
.thenApply(str -> str + "!!")
.thenAccept(System.out::println)
.get();
输出结果为:400!!
下面这段代码会爆出除数为0异常,通过exceptionally
捕获异常进行处理。
completableFuture
.supplyAsync(() -> division(10, 0))
.exceptionally((e) -> {//捕获异常,处理完成后返回了0
e.printStackTrace();
return 0;
})
.thenAccept(System.out::println)
.get();
输出结果:
java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.ArithmeticException: / by zero
at future.CompletableFutureTest.division(CompletableFutureTest.java:23)
at future.CompletableFutureTest.lambda$main$4(CompletableFutureTest.java:49)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
... 5 more
0
//多个CompletableFuture组合使用 方式一
CompletableFuture.supplyAsync(()->square(10))
.thenCompose(returnVal -> CompletableFuture.supplyAsync(()->division(200, returnVal)))
.thenAccept(System.out::println).get();
//多个CompletableFuture组合使用 方式二
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->square(10));
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->division(200, 10));
completableFuture1.thenCombine(completableFuture2, (i ,j) -> i - j)
.thenAccept(System.out::println).get();
输出结果:
2
80