前言
这篇文章并不是讲解java8新特性的具体语法是什么的,而是记录我对为什么java8加入这些新特性的理解。
一、java代码写法改进过程
首先,我们来看一个很小的需求:有一个员工类,要求
(1)获取年龄大于30岁的员工列表
(2)获取工资大于3000元的员工列表
写法1:直接定义两个方法
这是最简单的写法,直接定义两个方法分别实现对应的功能。
1、员工类:
/**
* @author Caocs
* @date 2020/4/2
*/
public class Employee {
private String name;
private int age;
private double money;
// 有参构造函数
// 无参构造函数
// toString() 方法
// get()、set()方法
}
2、定义方法及测试
/**
* @author Caocs
* @date 2020/4/2
* 需求:
* (1)获取年龄大于30岁的员工列表
* (2)获取工资大于3000元的员工列表
*
* 通过定义两个方法分别获取
*/
public class MainClass {
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("1",23,2300),
new Employee("2",34,3400));
MainClass mainClass = new MainClass();
List<Employee> list1 = mainClass.filterByAge(employeeList);
for(Employee emp: list1){
System.out.println(emp.toString());
}
System.out.println("-----------------------------------");
List<Employee> list2 = mainClass.filterByMoney(employeeList);
for(Employee emp: list2){
System.out.println(emp.toString());
}
}
// (1)获取年龄大于30岁的员工列表
public List<Employee> filterByAge(List<Employee> employeeList){
List<Employee> employees = new ArrayList<>();
for(Employee emp: employeeList){
if(emp.getAge()>=30){
employees.add(emp);
}
}
return employees;
}
// (2)获取工资大于3000元的员工列表
public List<Employee> filterByMoney(List<Employee> employeeList){
List<Employee> employees = new ArrayList<>();
for(Employee emp: employeeList){
if(emp.getMoney()>=3000){
employees.add(emp);
}
}
return employees;
}
}
总结:
(1)这样写的话,我们每次有一个新的需求都需要添加一个方法。
(2)我们可以发现,这两个方法其实只有if语句里面的判断条件不同,其他的完全一致。
(3)有很多重复代码,所以我们就有了下面的写法
写法2:策略设计模式
根据上面的写法,我们发现两个方法其实只有一句判断条件不同。我们可以通过策略模式实现功能。
首先定义一个接口,用来提供一个判断条件的方法,作为不同的策略。
然后根据不同的需求分别实现该方法。
最后,对外提供一个统一的方法,将不同的策略传递进去就可以实现不同的需求了。
1、定义接口,提供方法用于不同策略去实现。
/**
* @author Caocs
* @date 2020/4/2
*
*/
public interface MyPredicate<T> {
public boolean predicate(T t);
}
2、根据不同需求(策略),分别实现上述接口。
public class FilterByMoney implements MyPredicate<Employee> {
@Override
public boolean predicate(Employee employee) {
return employee.getMoney()>=3000;
}
}
public class FilterByAge implements MyPredicate<Employee> {
@Override
public boolean predicate(Employee employee) {
return employee.getAge()>=30;
}
}
3、对外提供统一方法,并将策略传入。
/**
* @author Caocs
* @date 2020/4/2
*
* 策略设计模式
*/
public class MainClass2 {
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("1",23,2300),
new Employee("2",34,3400));
MainClass2 mainClass2 = new MainClass2();
List<Employee> list1 = mainClass2.filterEmployee(employeeList,new FilterByAge());
for(Employee emp: list1){
System.out.println(emp.toString());
}
System.out.println("-----------------------------------");
List<Employee> list2 = mainClass2.filterEmployee(employeeList,new FilterByMoney());
for(Employee emp: list2){
System.out.println(emp.toString());
}
}
// 只需要一个对外的接口,将策略传入即可
public List<Employee> filterEmployee(List<Employee> employeeList,MyPredicate<Employee> myPredicate){
List<Employee> employees = new ArrayList<>();
for(Employee emp: employeeList){
if(myPredicate.predicate(emp)){
employees.add(emp);
}
}
return employees;
}
}
总结:
优点:
(1)需要编写的重复代码变少
(2)只需要对外提供一个方法即可,减少对外暴露的方法个数。
缺点:
(1)其实,接口中只有一个方法需要被实现。
(2)我们发现,每一种策略的实现都需要去定义一个新的类并实现。如果策略很多的话就需要定义很多很多的类,并且类中只有一个方法。就会很臃肿。
写法3:匿名内部类
根据上面的写法,我们很容易发现定义太多的实现类。
我们可以使用匿名内部类的方式,在需要用到对应策略时再实现其方法。
匿名内部类写法:
/**
* @author Caocs
* @date 2020/4/2
* 匿名内部类
*/
public class MainClass3 {
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("1",23,2300),
new Employee("2",34,3400));
MainClass3 mainClass3 = new MainClass3();
// 匿名内部类
List<Employee> list1 = mainClass3.filterEmployee(employeeList,new MyPredicate<Employee>(){
@Override
public boolean predicate(Employee employee) {
return employee.getAge()>=30;
}
});
for(Employee emp: list1){
System.out.println(emp.toString());
}
System.out.println("-----------------------------------");
List<Employee> list2 = mainClass3.filterEmployee(employeeList,new MyPredicate<Employee>(){
@Override
public boolean predicate(Employee employee) {
return employee.getMoney()>=3000;
}
});
for(Employee emp: list2){
System.out.println(emp.toString());
}
}
public List<Employee> filterEmployee(List<Employee> employeeList,MyPredicate<Employee> myPredicate){
List<Employee> employees = new ArrayList<>();
for(Employee emp: employeeList){
if(myPredicate.predicate(emp)){
employees.add(emp);
}
}
return employees;
}
}
总结:
(1)这样写的确可以省定义很多实现类
(2)但是,我们发现匿名内部类里面,还是有很多冗余代码。
比如,new 对象,定义方法等等。
写法4:Lambda表达式
根据上面的写法,我们很容易发现代码还是有很多冗余。
在java8中,Lambda表达式就应运而生啦!!!
Lambda表达式写法:
/**
* @author Caocs
* @date 2020/4/2
* Lambda表达式
*/
public class MainClass4 {
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("1",23,2300),
new Employee("2",34,3400));
MainClass4 mainClass4 = new MainClass4();
List<Employee> list1 = mainClass4.filterEmployee(employeeList,(e)->e.getAge()>=30);
for(Employee emp: list1){
System.out.println(emp.toString());
}
System.out.println("-----------------------------------");
List<Employee> list2 = mainClass4.filterEmployee(employeeList,(e)->e.getMoney()>=3000);
for(Employee emp: list2){
System.out.println(emp.toString());
}
}
public List<Employee> filterEmployee(List<Employee> employeeList,MyPredicate<Employee> myPredicate){
List<Employee> employees = new ArrayList<>();
for(Employee emp: employeeList){
if(myPredicate.predicate(emp)){
employees.add(emp);
}
}
return employees;
}
}
总结:
(1)结果上述一系列改进,需求中的方法的写法已经很精炼啦
(2)但是,输出list列表的写法还是很繁琐。
写法5:Stream API
其实在项目中,我们操作最多的就是数组、列表对象。那么对数组、列表操作的简化更是真正惠及广大程序员啊!!!
/**
* @author Caocs
* @date 2020/4/2
* Stream API
*/
public class MainClass5 {
public static void main(String[] args) {
List<Employee> employeeList = Arrays.asList(
new Employee("1",23,2300),
new Employee("2",34,3400));
employeeList.stream()
.filter((e)->e.getAge()>=30)
.forEach(System.out::println);
System.out.println("-----------------------------------");
employeeList.stream()
.filter((e)->e.getMoney()>=3000)
.forEach(System.out::println);
}
}
总结:
这段代码中,我们使用了Lambda表达式,Stream流操作,方法引用
。已经经代码变得很简洁易懂。
二、java8的新特性
正如我的标题写的那样,java8的所有新特性都是为了简化!!!
那么我们来看一看到底是如何个简化法呢?
1、Lambda表达式
从上面的代码中改进的过程中,我们看到Lambda表达式是取代了匿名内部类的写法。
这样的写法的确省去很多不必要的方法声明的代码。
来分析一下Lambda表达式:() -> () 是一种(参数,箭头,方法实现)的形式。
那么,我们就要好好考虑一下什么时候才能用Lambda表达式了,
(1)首先,必须得有一个接口,来定义输入和输出吧。不然Lambda表达式怎么知道参数和返回值是什么。
(2)而且,接口里面只能有一个抽象方法吧。不然Lambda表达式怎么知道是哪个方法。
注意:
(1)访问局部变量
可以直接在lambda表达式中访问外层的局部变量,但是和匿名对象不同的是,这里的变量num可以不用声明为final,不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义)。该代码同样正确:
int num = 1;
Converter<Integer, String> stringConverter = (from) -> String.valueOf(from + num);
(2)访问对象字段与静态变量
和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:
class Lambda4 {
static int outerStaticNum;
int outerNum;
void testScopes() {
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23; //
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
}
总结:
提供了只有一个抽象方法的匿名内部类的更好的写法
2、函数式接口
在分析Lambda表达式的时候,我们说只有一个抽象方法的接口才能用Lambda表达式实现。
“函数式接口”就是指仅仅只包含一个抽象方法的接口
这时,我们为了标识这种只有一个抽象方法的接口,java8引入@FunctionalInterface
注解。添加该注解的接口中只要多于一个抽象方法的时候就会报错。
注意:
如果接口上没有@FunctionalInterface
注解,代码也是对的。也就是说@FunctionalInterface
注解仅仅起到校验是否是函数式接口的作用。
总结:
为了配合Lambda表达式更好得使用
3、内置的函数式接口
我们发现如果想用Lambda表达式,还要自定义函数式接口的话,也很麻烦。
实际上,在java8中提供了常用的内置函数式接口。
Consumer<T>
、Supplier<T>
、Function<T,R>
、Predicate<T>
等
总结:
内置函数式接口基本上满足常用的Lambda表达式需求。不用再自定义函数式接口。
4、接口的默认方法和静态方法
我们知道,在java8以前的版本中,接口中是没法写实现方法的,里面全部是由全局变量和公共的抽象方法组成。
(1)默认方法
Java 8允许我们给接口添加非抽象的方法实现,只需要使用 default
关键字即可,这个特征又叫做扩展方法,示例如下:
interface Formula {
double calculate(int a);
// 接口的默认方法
default double sqrt(int a) {
return Math.sqrt(a);
}
}
默认方法在子类上可以直接使用。默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,
(2)静态方法
Java 8允许我们给接口添加静态的方法实现,只需要使用 static
关键字即可,示例如下:
interface DefaulableFactory {
// 接口的静态方法
static int add( int a, int b ) {
return a+b;
}
}
// 使用
DefaulableFactory.add(1,2);
注意:
由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection
接口添加新方法,如stream()
、parallelStream()
、forEach()
和removeIf()
等等。
尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。
总结:
在接口中实现默认方法和静态方法,给java的单继承特性,提供更多的实现代码编写方式。
5、方法引用
Java 8 允许你使用 ::
关键字来传递方法或者构造函数引用。
(1)构造函数引用 类::new
(2)静态方法引用 类::静态方法名
(3)类实例方法引用 类::实例方法名
(4)对象实例方法引用 对象::实例方法名
(5)数组引用 Type::new
例如:
// Lambda表达式写法
list.stream().forEach(item->System.out.println(item));
// 方法引用写法
list.stream().forEach(System.out::println);
注意:
方法引用的参数和返回类型需要跟Lambda的参数和返回类型一致、
总结:
通过方法引用简写Lambda表达式中已经存在的方法。
6、Stream API
上面几个新特性,都可以看做是对方法写法的改进。
那么,Stream流就可以看做是对数组、列表操作的改进。
Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。元素流在管道中经过中间操作(intermediate operation
)的处理,最后由最终操作(terminal operation
)得到前面处理的结果。
总结:
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
想要了解更多关于Stream API的操作,请移步到我的另一篇文章:(啊啊啊,目前还没写。以后可能写了也忘更新此处的链接,感兴趣的可以去我博客里翻一翻有没有更新)
7、Date Time API
对时间日期操作的改进。
Java 8 在包java.time下包含了一组全新的时间日期API。提供很多对日期、时间还有时区的操作,使用起来特别方便。
Clock 时钟
Timezones 时区
LocalTime 本地时间
LocalDate 本地日期
LocalDateTime 本地日期时间
DateTimeFormatter 格式转换
想要了解更多关于java中对时间日期的改进,请移步到我的另一篇文章:Java中关于时间日期的操作总结
8、Optional
对空指针问题的解决方式改进。
根据我的经验,一般在链式调用
的时候,使用Optional才会更优雅。如果只需要对某个对象判断null的话,完全没必要为了使用而使用。不仅不会更优雅而且会让代码更难理解。
例如:
user.getName().getAge().getMoney()
想要了解更多关于Optional的用法,请移步到文章:JAVA8之妙用Optional解决NPE问题。我觉得作者写的特别棒。
9、重复注解
Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@Repeatable即可。
(我暂时还没用过这种用法,有很多隐藏技能点需要点啊)