Java8新特性
刚进入工作,加了第一位前辈的微信以后,问我的第一个问题就是,会不会用java8的Stream流式编程。
基于学习与开发经验,作出自己对Java8新特性的一些理解与总结。
一、接口(interface)中支持普通方法
本节中的接口都代表interface修饰的java抽象类型
1.Java8 interface的变化
在jdk8之前,interface中定义成员时,变量必须时public、static、final的,方法必须是public、abstract的,这些修饰符都是默认的。
在jdk1.8开始,支持static和default修饰,可以写方法体,不强制子类重写。
public interface JDK8Interface {
/**
* 默认时为public abstract
*/
void add();
/**
* jdk8提供的默认方法
* 实现类中不强制要求重写
*/
default void defaultGet() {
System.out.println("defaultGet");
}
/**
* jdk8提供的静态方法
* 实现类中不强制要求重写
*/
static void staticGet() {
System.out.println("staticGet");
}
}
2.主要优势
1.提供一种拓展接口的方法,而不破坏现有代码
可以在一个已经投入使用的interface中方便地拓展一个新方法。在jdk8以前接口中定义的方法默认都是需要子类实现的,当我们需要在接口中加入一个新方法时,所有的存在子类都需要去实现新的方法,否则编译会出现异常。若子类的数量很多,维护起来会十分麻烦,并且并不是所有子类你都会有权限修改。
2.默认方法是可选的
子类可以根据不同的需求Override。并且如果90%的实现类都是以相同的逻辑处理数据时,则只需要那10%的子类去写自己的实现,使编写更简洁。(这点类似于抽象类中的抽象方法与普通方法,同样,也可以通过抽象类来实现这种效果)
3.个人建议:
不建议在接口中使用定义默认方法。在传统开发中,interface是用来定义一组规范方法,要求所有的实现类都应该按照制定的要求规范,实现所有的方法,可以理解成子类都用统一的“工作流程”。而默认方法破坏了这一规范性。日常开发中,基本也基本不会这样使用。
二、java的多态(这里多提一下多态,不属于Java8新特性)
多态是同一个行为具有多个不同表现形式或形态的能力。同一个接口,使用不同的实例而执行不同操作。
多态的定义格式:父类的引用指向子类实例
三、lambda表达式(重点)
1.lambda表达式定义
一个匿名函数,即没有函数名的函数。
2.回顾匿名内部类
public void myMethod() {
//MyInterface为一个接口类(interface)
new MyInterface() {
@Override
public String get() {
return "msg";
}
};
}
//用lambda简化
MyInterface myInterface = () -> {return "msg"};
MyInterface myInterface = () -> return "msg";
//方法体只有一条不用写return;
MyInterface myInterface = () -> "msg";
//一般排序
List<Boy> boyList = new ArrayList<>();
boyList.sort(new Comparator<Boy>() {
@Override
public int compare(Boy o1, Boy o2) {
return o2.getAge() - o1.getAge();
}
});
//利用lambda简化排序
boyList.sort((o1, o2) -> o2.getAge() - o1.getAge());
3.优缺点
1.简化开发、精简代码
简化匿名内部类的使用
2.不太好用编译工具调试
新版本的idea也在不断改进,基本上调试也不是大问题。
四、函数式接口
1.函数式接口的定义:
接口中有且只有唯一一个抽象方法,那么他就是一个函数式接口。
-
接口中有且只有唯一一个抽象方法。
public interface MyRunnable { public abstract void run(); }
-
可以用@FunctionalInterface标记接口为函数式接口
@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
注:用@FunctionalInterface标记接口为函数式接口中,允许定义default方法,允许重写Object的方法,例如toString().
-
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
public void test() { //箭头后面写方法实现,只有单个语句时可以省略大括号 MyRunnable myRunnable = () -> System.out.println("myRunnable running..."); }
五、方法/函数引用(类名称::方法名称)
1.方法引入的定义
方法引入需要配合lambda表达式一起使用,使得lambda表达式变得更加精简。
方法引入包含四种:
1.静态方法引入 类名::(静态)方法名称
2.对象方法引入 类名::实例方法名称
3.示例方法引入 new 对象 对象实例::实例方法
4.构造函数引入 类名::new
方法引用的要求:返回值,参数与函数接口保持一致。
//1.静态方法引入
@FunctionalInterface
public interface MyMethodReference {
String get(int a);
}
public void test2() {
MyMethodReference methodReference = Java8StudyApplicationTests::staticGet;
String string = methodReference.get(15);
System.out.println(string);
}
public static String staticGet(int a) {
return "get a : " + a;
}
//2.运用到lambda表达式中
//简化前
List<Boy> collect2 = boyList.stream()
.filter(i -> i.isFlag())
.collect(Collectors.toList());
//简化后
List<Boy> collect1 = boyList.stream()
.filter(Boy::isFlag)
.collect(Collectors.toList());
六、Stream流接口(重点)
根据我日常的使用情况总结了一些Stream接口一些方法的使用,具体底层原理,有兴趣的 大家可以去自主学习一下。
java8 Stream List操作
1.抽取属性,汇成list集合
List<String> nameList = personList.stream().map(Person::getName).collect(Collectors.toList());
2.按照大小分组成多个list
List<List<Integer>> lists = ListUtils.partition(intList, 3);
3.去重
// 对List<对象>获取某个属性并去重
List<String> entitySet=list.stream().map(PersonInfo::getDepartment).distinct().collect(Collectors.toList());
//按照属性去重
studentList = studentList.stream().collect(
collectingAndThen(
toCollection(() -> new TreeSet<>(Comparator.comparing(Student::getName))), ArrayList::new)
);
//根据多个属性去重
List<ClassEntity> distinctClass = classEntities.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(
() -> new TreeSet<>(
Comparator.comparing(
o -> o.getProfessionId() + ";" + o.getGrade()))), ArrayList::new));
4.字段求和
double collectionRatio2 = userList.stream().mapToDouble(OrderReceivablesDetail::getCollectionRatio).sum();
BigDecimal sumPrice = list.stream()
.map(Order::getOrderPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
//使用reduce聚合函数,初始值设置为0
5.if/else过滤逻辑
使用filter,我们可以把if/else的逻辑改写为两个filter:
List<Integer> ints = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Stream<Integer> evenIntegers = ints.stream()
.filter(i -> i.intValue() % 2 == 0);
Stream<Integer> oddIntegers = ints.stream()
.filter(i -> i.intValue() % 2 != 0);
有了这两个filter,再在filter过后的stream中使用for each去做其他业务逻辑:
evenIntegers.forEach(i -> System.out.println("i is even"));
oddIntegers.forEach(i -> System.out.println("i is old"));
6.分组,汇成Map
// 分组
Map<BigDecimal, List<Item>> groupByPriceMap =
items.stream().collect(Collectors.groupingBy(Item::getPrice));
Map<String, List<Student>> map2 = stuList.stream().collect(Collectors.groupingBy(d -> fetchGroupKey(d)));
注意:分组后,处理时用的是原list元素的引用。dao层保存直接用原list即可。
7.将String List以符号分隔,汇成String
String str = String.join(",", ArrrayList);
String str = users.stream().map(User::getName).collect(Collectors.joining(";"));
8.条件排序
8.1排序
List<Student> studentList1=studentList.stream().sorted().collect(Collectors.toList());//自然序列
List<Student> studentList2=studentList.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());//逆序
List<Student> studentList3=studentList.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());//根据年龄自然顺序
List<Student> studentList4=studentList.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());//根据年龄逆序
8.2排序后分组(不打乱顺序)
List<User> userList = new ArrayList<>();
User user1 = new User("1",1,"AAA");
User user2 = new User("2",2,"AAA");
User user3 = new User("3",3,"AAA");
User user4 = new User("4",4,"CCC");
User user5 = new User("5",5,"CCC");
User user6 = new User("6",6,"DDD");
User user7 = new User("7",7,"DDD");
User user8 = new User("8",8,"BBB");
userList.add(user4);
userList.add(user5);
userList.add(user6);
userList.add(user3);
userList.add(user1);
userList.add(user2);
userList.add(user8);
userList.add(user7);
System.out.println("原数组:"+JSON.toJSONString(userList));
LinkedHashMap<String, List<User>> collect = userList.stream()
.sorted(Comparator.comparing(User::getOrder))
.collect(Collectors.groupingBy(User::getRow, LinkedHashMap::new, Collectors.toList()));
System.out.println("分组排序后:"+JSON.toJSONString(collect));
Iterator<Map.Entry<String, List<User>>> iterator = collect.entrySet().iterator();
System.out.println("打印:");
while(iterator.hasNext()){
System.out.println(iterator.next());
}
9.取最值
public static void main(String[] args) throws ParseException {
List<Employee> empList = new ArrayList<Employee>();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
empList.add(new Employee("张2",sdf.parse("2022-02-01 13:32:40")));
empList.add(new Employee("张3",sdf.parse("2022-02-01 13:32:30")));
// empList.add(new Employee("张4",sdf.parse("2022-02-01 13:30:30")));
empList.add(new Employee("张4",null));
Employee empMax = empList.stream().filter(i -> i.getBirthday() != null).max(Comparator.comparingLong(i -> i.getBirthday().getTime())).orElse(null);
Employee empMin = empList.stream().filter(i -> i.getBirthday() != null).min(Comparator.comparingLong(i -> i.getBirthday().getTime())).orElse(null);
Date dateMax = empList.stream().map(Employee::getBirthday).filter(Objects::nonNull).max(Date::compareTo).get();
Date dateMin = empList.stream().map(Employee::getBirthday).filter(Objects::nonNull).min(Date::compareTo).get();
System.out.println("最大值:"+JSON.toJSONString(empMax)+"\n最小值:"+JSON.toJSONString(empMin));
System.out.println("直接取最大值:"+dateMax+"\n直接取最小值:"+dateMin);
}
10.List->Map
10.1取出属性生成Map
Map<String, Integer> collect = userList.stream().collect(Collectors.toMap(User::getName, User::getAge));
10.2key值为当前对象
Map<String, User> collect = userList.stream().collect(Collectors.toMap(User::getName, (user) -> user));
总结:
使用这些新特性,可以在一定程度上精简我们的代码,提高开发效率。
在某些方面也省去了 if代码块 和 for代码块 使得代码也更便于理解,不再冗长。
同时我们也需要根据项目上的规范以及客户要求来编写代码,不允许在interface中定义默认方法就不要定义。
当然,java8还有许多其他的新东西:Optional 类、新的日期时间 API、Base64等等。多学新知识,紧跟新技术不断提升自己。
参考:
1.Java8新特性lambda&stream&optional实现原理(余胜军通俗易懂版本)_哔哩哔哩_bilibili