前一阵子面试的时候突然被问到java1.8版本的新特性。虽然新特性可能不是很常用,但是了解还是很有必要的。如果你想写含有新特性的程序,那么首先最好更新一下你的IDE,10.9之前的MyEclipse已经不是很好用了,这里有MyEclipse2017破解版可供下载。
default 关键字
Java 8支持为接口添加非抽象的方法实现,只要使用 default关键字即可。很经典的一个问题就是:java接口中能不能有非抽象方法?,这个问题主要考你的基本功,一般笔试时要答:不能。但是面试的时候说出:Java 8新添加了default关键字支持非抽象方法,肯定会为你加分。
package CharactJava8;
public interface FunctionInter {
default public void syso(){
System.out.println("Hello World!");
}
}
package CharactJava8;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
FunctionInter fi = new FunctionInter() {};
fi.syso();
}
}
main方法中输出:Hello World! 。 default修饰的方法可以在实现类中被重写或者直接使用。需要注意的是:**接口不能用default方法提供对Object类的任何方法的默认实现。**特别是,这意味着从接口里不能提供对equals,hashCode或toString的默认实现。有这种限定的一个原因是:如果在接口中对Object类方法默认实现,会使得编辑器更难以判断什么时候该调用默认的方法什么时候调用Object中的方法。
静态方法与默认方法
与此同时,在java1.8中也可以定义静态方法。在之前的JDK版本中是不支持的,正如Collection类的所有静态方法都存放在Collections中一样,我们习惯使用类名+s来表示静态方法类。以后对于这些静态方法我们可以处理的更简单。
Lambda 表达式
Lambda是第十一个希腊字母λ的发音,在c语言中早就有了这种用来简化代码的表达式,在JDK1.8中java也推出了Lambda表达式用来简化匿名内部类的书写。要记住Lambda表达式只是简化了创建匿名内部类的书写,他本身的作用只是创建而没有进行任何方法调用。
package CharactJava8;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
//1
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
//2
Collections.sort(names, (String a,String b) ->{
return b.compareTo(a);
});
//3
Collections.sort(names, (String a,String b) -> b.compareTo(a));
//4
Collections.sort(names, (a,b) -> b.compareTo(a));
}
}
上面是百度的一个例子,用这个例子我们简化出三种Lambda表达式的情况。names需要排序,按照Comparator指定的方法,(这里的names是一个String类,实际上String已经实现了Comparable,对于自己定义的数据类型才有实现Comparator的必要)对names进行排序。
三类Lambda表达式
1是平常的实现方法。
2是经过Lambda表达式去掉了不必要的部分。只需要该类中方法的参数后跟一个箭头(->)后加大括号,大括号中正常地写出方法中代码。这种情况适用于:方法中代码有很多行,不能仅用一个return返回的情况
3是在2的基础上,去除了大括号和return关键字。这种情况适用于:方法中代码仅有一行return,3比2的好处在于简单。
4是在3的基础上,省略了参数类型。这种情况适用于:方法能辨别传入参数的类型,泛型情况慎用
再看几个例子:
(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }
第一行:参数是x和y类型是int,方法体中是{return x+y;} 第二行:无参数,返回42 第三行:参数是String类型s,方法体中将这句话输出,不返回
Lambda表达式适用场合
刚才我们使用Lambda表达式替换了匿名内部Comparator类,疑问随之诞生,Lambda表达式能够替代哪些类?这些类都有什么相似之处?在这里先给出定义:我们把只有一个抽象方法的接口叫做函数式接口,Lambda类型可以替代函数式接口,Lambda表达式中并不包含他所替代的类/接口的名字,它所替代的名字是由上下文推断得出。
上下文有哪些呢?因为Lambda表达式作为匿名内部类的一种简写方式,它的效果和一个新对象是相似的:变量声明、赋值、返回语句、数组初始化、方法参数、lambda表达式函数体、条件表达式(? :)、转型(Cast)表达式中都可以使用Lambda表达式。
Lambda表达式作用域问题
Lambda表达式的作用域和匿名内部类的作用域很类似,但也有些不同,对于外部的成员变量可直接访问,对于不修改的局部变量的才能访问。
匿名内部类只能访问final修饰的局部变量的原因:如果匿名内部类中的方法在调用之中进行了多次调用,为了防止外部的局部变量生命周期结束被回收而导致错误,匿名内部类要先将局部变量做备份保存起来,但如果局部变量不是final修饰,则有可能在这个外部方法继续进行的过程中被修改,最后导致外部的局部变量和匿名内部类中存放的局部变量值不同而出错。
我们知道了为什么匿名内部类只能访问final修饰的局部变量,但是在Lambda中更加灵活,我们可以访问没有被final修饰的局部变量,只要你能保证它在之后的代码中没被修改,也就是”隐式的final“。如果在之后的代码或者Lambda中该局部变量被修改则会报错。
函数式接口 Functional Interface
我们把只有一个抽象方法的接口叫做函数式接口,注意这里是一个抽象方法,所以说在此接口中存在多个default或static修饰的方法是不会有影响的。我们可以用注解——@FunctionalInterface来标记该接口,在标记之后,多于一个抽象方法会使得该接口报错。
package CharactJava8;
@FunctionalInterface
public interface FunctionInter {
default public void syso(){
System.out.println("Hello World!");
}
public int sum(int a,int b);
}
上文中的接口,我们进行改编后使之符合函数式接口的定义。
(int x, int y) -> x + y
Lambda表达式不能单独使用,在某个语境中我们可以这样书写来表示使用了匿名FunctionInter接口。
方法与构造器引用——关键字 ::
java8中还提供了对方法和构造函数的简单引用方法的关键字—— :: 。
List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);
可能有些难以理解,需要逐个方法进行分析。List中有sort方法,这个方法接受一个类型为Comparator的参数,所以,我们最开始的写法应该是:
List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return a - b;
}
});
使用一个Comparator的匿名内部类进行操作,我们可以使用Lambda表达式,像这样来写:
List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort((a,b) -> a-b);
但是我们这时发现,这个return a-b;的情况恰好是Integer类中compare方法的实现,这个时候我们想用这个方法来替换Lambda表达式。compare方法有两个参数,我们此时使用关键字”::“,可以让匿名内部类Comparator中方法的两个参数和Integer类中compare方法的两个参数自动对应起来。
List<Integer> ints = Arrays.asList(1, 2, 3);
ints.sort(Integer::compare);
最后形成了这种写法,正确无误。 再举一个构造器的例子:
class Person {
String firstName;
String lastName;
Person() {}
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
这个类有两个构造方法,我们再定义一个工厂方法:
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
定义了类和工厂方法之后,我们再使用Lambda表达式将其关联。
String firstName = "Quinn";
String lastName = "Norris";
PersonFactory<Person> p = (a, b) -> new Person(a, b);
p.create(firstName, lastName);
这种写法没有任何问题的创建了p,p是通过调用两个参数构造器初始化的Person类。但是现在,我们使用 :: 有更简单的调用方法。
PersonFactory<Person> p = Person::new;
p.create(firstName, lastName);
通过在 :: 后使用new关键字表示调用的Person类的方法是构造器方法,他会根据我们的参数个数自动的选择构造器。
其他更新
java1.8中除了上面的几个重要更新,还有类似1.在java.time下包含了一组全新的时间日期API;2.支持多重注解。等等微小的细节,这些事情我们知道就好,还没有普及,相信不久的将来,就像Date类中的方法一样渐渐被禁用之时这些新的方法和类将会焕发他们应有的生机。