Java8新特性的学习

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.函数式接口的定义:

接口中有且只有唯一一个抽象方法,那么他就是一个函数式接口。

  1. 接口中有且只有唯一一个抽象方法

    public interface MyRunnable {
      public abstract void run();
    }
    
  2. 可以用@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().

  3. 对于函数式接口,我们可以通过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

2.Java 接口 | 菜鸟教程 (runoob.com)

3.浅谈JAVA8引入的接口默认方法_泪鱼的博客-CSDN博客_java接口默认方法

4.Java 多态 | 菜鸟教程 (runoob.com)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容