一、函数式接口
Java8中引入了一个核心概念函数式接口(Function Interfaces)。通过在接口中添加一个抽象的方法,这些方法可以直接从接口中运行。如果一个接口定义了唯一一个抽象方法,那么这个接口就成为函数式接口。同时引入了一个新的注解:@FunctionInterface可以把它放在一个接口前面,表示这个接口是一个函数式接口,这个接口是非必须的。只要接口是只包含一个方法的接口,虚拟机会自动判断,不过最好加上这个注解进行声明。在接口中添加了@FunctionInterface注解,那么这个接口只允许有一个抽象方法,否则编译器会报错。
@FunctionalInterface
public interface Runnable {
public void run();
}
二、Lambda表达式
函数式接口的重要属性是:我们能够使用Lambda实例化他们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。Lambda好处:在 Java 8 之前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性很差,Lambda 表达式的应用则使代码变得更加紧凑,可读性增强;Lambda 表达式使并行操作大集合变得很方便,可以充分发挥多核 CPU 的优势,更易于为多核处理器编写代码。
Lambda 表达式由三个部分组成:
- 第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;
- 第二部分为一个箭头符号:->
- 第三部分为方法体,可以是表达式和代码块。
1、方法体为表达式,该表达式的值作为返回值
(parameter)-> expression
2、方法体为代码块,必须用{}括起来,且需要一个 return 返回值,但若函数式接口中的返回值是oid,则无需返回值。
(parameter)-> {statements;}
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("haha");
}
}).run();
//使用 Lamdba 表达式之后
new Thread(()-> System.out.println("heihei")).run();
}
}
//使用内部类
Function<Integer,String> f1 = new Function<Integer, String>() {
@Override
public String apply(Integer t) {
System.out.println(t);
return null;
}
};
//使用Lambda表达式
Function<Integer,String> f2 = (t)->String.valueOf(t);
//使用方法引用的方式
Function<Integer,String> f3 = String::valueOf;
要使用 Lambda 表达式需要定义一个函数式接口,这样往往会使程序中充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减少这样的情况,Java 8 在 java.util.function 中增加了不少新的函数式通用接口。如:
Function<T, R>:将 T 作为输入,将 R 作为输出,它还包含了和其他函数组合的默认方法。
Predicate<T>:将 T 作为输入,返回一个布尔值作为输出。该接口包含多种默认的方法来将 Predicate 组合成其他复杂的逻辑(与、或、非)
Consumer<T>:将 T 作为输入,不返回任何内容,表示在单个参数上操作。
//People 类中有一个方法 getMaleList 需要获取男性的列表,这里需要定义一个函数式接口 PersonInterface:
interface PersonInterface{
public boolean test(Person person);
}
public class People {
private List<Person> persons1 = new ArrayList<>();
public List<Person> getMaleList1(PersonInterface filter){
List<Person> res = new ArrayList<>();
persons1.forEach(
(person)->{
if (filter.test(person)){//调用 PersonInterface 的方法
res.add(person);
};
}
);
return res;
}
//为了除去 PersonInterface 这个接口,可以调用通过函数式接口 Predicate 替代:
private List<Person> persons2 = new ArrayList<>();
public List<Person> getMaleList2(Predicate<Person> predicate){
List<Person> res = new ArrayList<>();
persons2.forEach(
(person)->{
if (predicate.test(person)){//调用 Predicate 的抽象方法test
res.add(person);
};
}
);
return res;
}
}
三、接口增强
Java 8 对接口做了进一步增强,在接口中可以添加使用 default 关键字修饰的非抽象方法。还可以在接口中定义静态方法。如今,接口看上去和抽象类的功能越来越类似了。
1、默认方法
Java 8 中允许我们给接口添加一个非抽象的方法实现,只需要使用 default 关键字即可,这个特征又叫做扩展方法。在实现该接口时,该默认扩展方法在子类上可以直接使用,使用方式类似于抽象类中非抽象成员方法。但是扩展方法不能重载 Object 中的方法。如:toString、equals、hashCode 不能在接口中被重载。
例如:下面接口中定义了一个默认方法 count(),该方法可以在子类中直接使用。
interface DefaultInterface{
//定义默认方法 count
default int count(){
return 1;
}
}
public class SubDefaultClass implements DefaultInterface {
public static void main(String[] args) {
//实例化一个子类对象,该子类对象可以直接调用父类中的默认方法
int s = new SubDefaultClass().count();
System.out.println(s);
}
}
2、静态方法
在接口中,还允许定义静态方法。接口中的静态方法可以直接用接口来调用。
例如:下面接口中定义了一个静态方法 find,该方法可以直接用 StaticFunInterface .find() 来调用。
interface DefaultInterface{
//定义默认方法 count
default int count(){
return 1;
}
public static int find(){
return 2;
}
}
public class SubDefaultClass implements DefaultInterface {
public static void main(String[] args) {
//实例化一个子类对象,该子类对象可以直接调用父类中的默认方法
int s = new SubDefaultClass().count();
System.out.println(s);
//接口中定义了静态方法 find 直接被调用
System.out.println(DefaultInterface.find());
}
}
四、集合的流式操作
Java 8 引入了流(Stream),该操作可以实现对集合(Collection)的并行处理和函数操作。根据操作返回的结果不同,流式操作分为中间操作和最终操作两种。最终操作返回一特定类型结果,而中间操作返回流本身,这样可以将多个操作依次串联起来。根据流的并发性,流又可以分为串行和并行两种。流式操作实现了集合的过滤、排序、映射等功能。
Stream 和 Collection 区别:Collection 是一种静态的内存数据结构,Stream 是有关计算的。前者主要面向内存,存储在内存中,后者主要是面向CPU,通过CPU实现计算。
1、串行和并行的流
流有串行和并行两种,串行流上的操作是在一个线程中依次完成的,而并行则是在多个线程上同时执行。并行与串行的流可以相互切换:通过 stream.sequential( ) 返回串行的流,通过 stream.parallel( ) 返回并行的流。相比较串行的流,并行的流可以很大程度上提高程序的执行效率。
List list = new ArrayList();
for (int i = 0; i < 1000000; i++) {
double d = Math.random()*1000;
list.add(d);
}
//串行
long start = System.nanoTime();
int count = (int)((Stream)list.stream().sequential()).sorted().count();
long end = System.nanoTime();
long ms = TimeUnit.NANOSECONDS.toMillis(end-start);
System.out.println("串行:"+ms+":"+count);
//并行
long start1 = System.nanoTime();
int count1 = (int)((Stream)list.stream().parallel()).sorted().count();
long end1 = System.nanoTime();
long ms1 = TimeUnit.NANOSECONDS.toMillis(end1-start1);
System.out.println("并行:"+ms1+":"+count1);
2、中间操作
该操作会保持 Stream 处于中间状态,允许做进一步的操作。返回的还是 Stream ,允许更多的链式操作。常见的中间操作有:
filter( ):对元素进行过滤;
sorted( ):对原始进行排序
map( ):元素的映射
distinct( ):去除重复元素
subStream( ):获取子 stream 等
3、终止操作
forEach( ):对每个元素做处理
toArray( ):把元素导出到数组
findFirst( ):返回第一个匹配的元素
anyMatch( ):是否有匹配的元素等
list.stream() //获取列表的 stream 操作对象
.filter((s) -> s.startsWith("s"))//对这个流做过滤操作
.forEach(System.out::println);
五、注解更新
对于注解Java 8改进有两点:类型注解和重复注解
Java 8的类型注解扩展了注解使用的范围。在该版本之前,注解只能是在声明的地方使用。现在几乎可以为任何东西添加注解:局部变量、类与接口,就连方法的异常都可以添加注解。新增的两个注解的程序元素类型 ElementType.TYPE_USE 和 ElementType.TYPE_PARAMETER 用来描述注解的新场合。ElementType.TYPE_PARAMETER 表明该注解能够写在类型变量的声明语句中。ElementType.TYPE_USE 而表示该注解能够写在使用类型的任何语句中。
对类型注解的支持,增强了通过静态分析工具发现错误的能力。原先只能在运行时发现的问题可以提前在编译时被排查出来。Java 8 本身虽然没有自带类型检测的框架,但可以通过使用 Checker Framework 这样的第三方工具自动检测和确认软件的缺陷,提高生产效率。
如:下面的代码可以通过编译,但是运行的时候会报 NullPointerException 异常
public class TestAnno {
public static void main(String[] args) {
Object obj = null;
obj.toString();
}
}
为了能够在编译期间就能够自动检测出这类异常可以通过类型注解结合 Checker Framework 提前排查出来
import org.checkerframework.checker.nullness.qual.NonNull;
public class TestAnno {
public static void main(String[] args) {
@NonNull Object obj = null;
obj.toString();
}
}
另外,在该版本之前,使用注解的一个限制就是相同的注解在同一位置只能声明一次,不能声明多次。Java 8 引入了重复注解机制,这样相同的注解可以在同一地方声明多次。重复注解机制本身必须用 @Repeatable 注解
如:
@Retention(RetentionPolicy.RUNTIME) \\该注解存在于类文件中并在运行时可以通过反射获取
@interface Annots {
Annot[] value();
}
@Retention(RetentionPolicy.RUNTIME) \\该注解存在于类文件中并在运行时可以通过反射获取
@Repeatable(Annots.class)
@interface Annot {
String value();
}
@Annot("a1")@Annot("a2")
public class Test {
public static void main(String[] args) {
Annots annots1 = Test.class.getAnnotation(Annots.class);
System.out.println(annots1.value()[0]+","+annots1.value()[1]);
// 输出: @Annot(value=a1),@Annot(value=a2)
Annot[] annots2 = Test.class.getAnnotationsByType(Annot.class);
System.out.println(annots2[0]+","+annots2[1]);
// 输出: @Annot(value=a1),@Annot(value=a2)
}
}
注释 Annot 被 @Repeatable( Annots.class ) 注解。Annots 只是一个容器,它包含 Annot 数组, 编译器尽力向程序员隐藏它的存在。通过这样的方式,Test 类可以被 Annot 注解两次。重复注释的类型可以通过 getAnnotationsByType() 方法来返回。
六、全球化功能
Java 8 完善了全球化功能:支持新的 Unicode6.2.0标准,新增了日历和本地化的API,改进了日期时间的管理等。
Java 的日期和时间 API 问题由来已久,Java 8 之前的版本中关于时间、日期及其他时间日期格式化类由于线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸收了 Joda-Time 的精华,以一个新的开始为Java创建优秀的API。新的 java.time 中包含了所有关于时钟(Clock)、本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的Date类新增了 toInstant()方法,用于把Date转化为新的表示形式。这些新增的本地化时间日期API大大简化了日期时间和本地化管理。
如:
//LocalDate
LocalDate localDate = LocalDate.now();//获取本地日期
localDate = LocalDate.ofYearDay(2014,200);//获得 2014 年的第 200 天
System.out.println(localDate.toString());
localDate = LocalDate.of(2014,11,20);
System.out.println(localDate.toString());
//LocalTime
LocalTime localTime = LocalTime.now();//获取当前时间
System.out.println(localTime.toString());
localTime = LocalTime.of(10,20,50);//获得 10:20:50 的时间点
System.out.println(localTime.toString());
//Clock
Clock clock = Clock.systemDefaultZone();//获取系统默认时区 (当前瞬时时间 )
long mills = clock.millis();
转载:https://www.ibm.com/developerworks/cn/java/j-lo-jdk8newfeature/index.html