分类目录归档:java8

Java Time(一)

在讨论java8的time前,先回顾一下java8以前的日期,主要两个类java.util包下的Date、Calendar,其次是java.sql包下面对java.util.Date的扩展类Date、Time、Timestamp。
下面就来吐槽一下以前的日期API:
1,Date计算、格式化麻烦;
2,非线程安全
3,时区处理麻烦;
吐槽完了,来看下以前处理日期的一些实例:
1,获取指定的时间,比如获取 2015-08-23 22:00:00 的时间:

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date date = sdf.parse("2015-08-23 22:00:00");
        } catch (ParseException e) {
            e.printStackTrace();
        }

(这里使用SimpleDateFormat还有个问题,SimpleDateFormat也是非线程安全的,假如说运行过程中其他线程修改了SimpleDateFormat的值,那悲剧就发生了。)
或者:

 Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.YEAR,2015);
        calendar.set(Calendar.MONTH,7);
        calendar.set(Calendar.DAY_OF_MONTH,23);
        calendar.set(Calendar.HOUR_OF_DAY,22);
        calendar.set(Calendar.MINUTE,0);
        calendar.set(Calendar.SECOND,0);
        Date time = calendar.getTime();

.
这里还要注意月份是从0开始的;

2,所有日期类都可变的,非线程安全的,简单说下:
一般的bean类都会有个getter和setter方法,修改date用set方法,getter方法只用做读取,但是由于Date是可变的,getter方法也能用于修改:

bean.getDate().setTime(1440342793161L);

这显然违反了我们的初衷,也容易出问题。

3,关于时区问题:
java.util.Date是没有时区概念的,时区一般是由SimpleDateFormat这个类来控制的,

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            sdf.setTimeZone(TimeZone.getTimeZone("America/New_York"));
            Date date = sdf.parse("2015-08-23 11:00:00");//得到美国时间 date
            System.out.println(sdf.format(new Date()));//格式化为美国的当前时间

或者是Calendar:

        Calendar calendar = Calendar.getInstance();
        calendar.setTimeZone(TimeZone.getTimeZone("America/New_York"));
        System.out.println(calendar.get(Calendar.HOUR_OF_DAY));

所以,java以前的时间经常被吐槽,所以一般我们都会写一个DateUtil来处理,或者用第三方的库比如说apache commons的DateUtils;因此后来joda解决了以前java日期各种问题,再后来官方的java8也加入了datetime包解决旧版本时间的各种问题

Java 8 lambda表达式

jdk8最重要的特性-lambda表达式,意味着java可以函数式编程那样直接定义一个函数了,因此java8先从学习ambda开始。先看熟悉的例子,新建一个线程:

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("this is thread run method");
}
});

用lambda表达式的代码:


new Thread(() -> System.out.println("this is thread run method")).start()

上面“ () -> System.out.println(“this is thread run method”) ”这部分就是lambda表达式了。
这一行代码运行了一个新建的线程,并用lambda表达式传递了一个 System.out.println(“this is thread run method”)的run方法体,相比以前代码简洁了不少。

Java8加入lambda表达式后,意味着方法可以进行传递了,Java也能像python的filter,map,reduce这样进行玩耍了。

在介绍lambda表达式之前,我们先得知道java8的函数接口:

JSR335中对函数式接口的描述是:

functional interface is an interface that has just one abstract method (aside from the methods of Object), and thus represents a single function contract. (In some cases, this “single” method may take the form of multiple abstract methods with override-equivalent signatures inherited from superinterfaces; in this case, the inherited methods logically represent a single method.)

函数接口是声明有且只有一个抽象方法的接口,这个抽象方法也可以是继承其他接口的,函数接口也可以有java8新增的default方法。上面这段代码用lambda表达式替换了继承Runnable接口的匿名类,这里Runnable就是所谓的函数接口了。在jdk8新增了一个函数接口的注解“ @FunctionalInterface” ,加上这个注解后,该接口就必须符合函数接口的规范,否则无法编译。

申明了函数接口后,我们就可以通过lambda表达式来实现这个接口的方法及这个接口的调用了。

lambda的申明有两部分组成,分别为参数和方法体,由“->”隔开,
参数部分:当没有参数时,我们用”()”表示,有参数时,我们在括号内申明参数,如”(int arg1,String arg2)”;编译器可以对参数类型自动推导,因此我们还可以写成”(arg1,arg2)”,如果只有一个参数,甚至可以去掉”()”,直接写成”arg1″;

方法体部分:我们可以用”{}”来表示,当该函数接口有返回值的时候我们可以这么表示{return value;} ,当然我们也可以直接写成“value”,如果有函数接口的方法有返回值则会自动返回value。

例:

函数接口:


@FunctionalInterface
interface Addable{
 int add(int i);
}

调用接口方法:

public void testAdd(Addable fn,Integer num){
System.out.println(fn.add(num));
 }

因此我们可以这么调用testAdd方法:

testAdd((int i)->{return i+100;},88);

我们可以把类型定义去掉,方法体只有一句代码我们也可以把“{}”去掉,同时把return去掉,变成了下面的代码:

testAdd((i) -> i + 100,88);

由于参数只有一个,我们可以把参数部分的”()”都去掉,最简洁的代码是:

testAdd(i -> i + 100,88);

以上的运行结果当然都是188。

以后也可以这样引用一个lambda,代码:


Comparator<String> compareLength = (a,b)->a.length()-b.length();

lambda表达式我们可以理解为就是实现了一个函数接口的匿名类的实例。
但是实际上java8并不是仅仅只在编译器上做个编译转换处理完事了,我们把上面的代码编译的class类的字节码扒出来看一下就知道了。
java代码:

public class LambdaTest {
 public static void main(String[] args) {
 new Thread(() -> System.out.println("hello lambda")).start();
 }
}

在命令行切换到class目录,运行命令:

javap -verbose LambdaTest.class

找到如下的打印的信息:

public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
 Code:
 stack=3, locals=1, args_size=1
 0: new #2 // class java/lang/Thread
 3: dup
 4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
 9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
 12: invokevirtual #5 // Method java/lang/Thread.start:()V
 15: return

可以看到“4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;”,这里有一个invokedynamic的指令。而且没有生成“Method com/sed/LambdaTest$1.”这种带$符号的匿名类。所以这里只是定义了一个方法调用点,只有在执行的时候才能知道具体调用的是哪个方法,这就是所谓的延迟执行。

了解了lamdba表达式,我们可以像Python一样使用filter,map,reduce类似的方法了。

在集合中,java8在Collection接口新增了一个stream()的default方法,可通过返回的Stream对象进行filter和forEach操作,代码:


List<String> list = Arrays.asList("aaa","bba","ccc");
 list.stream().filter(s->s.contains("a"))
              .forEach(s-> System.out.println(s+"_"));

运行结果如下:


aaa_

bba_

怎么样,java8的lambda是不是很酷!!!