关于java的总结一篇内容有点写不下,索性就新开一篇。目标是完成上节内容未完成的部分

java 8的新特性

之前在学习霍格沃茨的课程中看到老师用了很多我看不懂的语法,后来才知道那是java 8中的新特性,所以我觉得作为搞技术的我有必要学习 java 8的特性。java 8的新特性让写代码进一步变得简洁了。如果从代码可读性上来看,python代码真的完爆java,但是由于现在市场的原因,还是比较推荐大家优先把java搞通,学习python就很轻松。
学习java 8的新特性,给大家推荐一本书:《Java 8实战》。本章节的内容主要是对书里面内容的总结和梳理,大家也可以从自己的角度来进行自己的梳理和总结。

两个关键点

关于java 8的新特性,我们重点需要知道它增加了两方面的内容:

  • 第一个方面:行为参数化。支持把函数作为参数传递给方法。为了支持这一行为,java 8中提供了lambda表达式这一个新的语法,并且提出了函数式接口这一概念。行为参数化在某些场景下,帮我们使得代码更加简洁明了。

  • 第二方面:提供了新的流式操作。可以把这一个改动看作:对java中的集合类提供遍历数据集合的更加高级的迭代器,同时这种遍历还能支持并行处理,从而进一步利用CPU资源提高数据的处理能力。
    所以我们要学习java 8的新特性的话就要学习这两个方面的内容。对这部分内容,《Java 8实战》中包含了很多细节,然而我们在面试的时候应该不会会考察太过细节的内容,所以大家重点掌握相关的概念和基本实现demo,在脑海里有一个印象即可。

    lambda 表达式

    要想进一步明白lambda 表达式的作用,我们需要知道一些应用场景。java 8 中对lambda 表达式对支持帮助我们实现了函数式编程,允许我们把函数做为参数传递给方法,从而实现当我们想要这个方法具有不同的表现行为时,只需要改变传递给方法的函数参数即可。我们可以看一下这个排序的例子来真实的看lambda是如何运作的:

    一个简单的demo

    public class lambdaSort {
    
      static class Student2 implements Comparable<Student2>{
    
          int age;
          int height;
    
          Student2(int age, int height){
              this.age = age;
              this.height = height;
          }
    
          public int getHeight(){
              return  this.height;
          }
    
          public int getAge(){
              return this.age;
          }
    
          @Override
          public int compareTo(Student2 o) {
              if(this.age > o.age) //这样会从小到大进行排序
                  return 1;
              if(this.age == o.age)
                  return 0;
              else
                  return -1;
          }
      }
    
      public static void main(String[] args) {
    
          List<Student2> students = new ArrayList<>();
          for(int i=1; i<=5; i++){
              Student2 student = new Student2(i, (5-i));
              students.add(student);
          }
    
          //第一种排序方式,我们使用一个实现了Comparator接口的匿名类
          students.sort(new Comparator<Student2>() {
              @Override
              public int compare(Student2 o1, Student2 o2) {
                  if(o1.age > o2.age) //这样会从小到大进行排序
                      return 1;
                  if(o1.age == o2.age)
                      return 0;
                  else
                      return -1;
              }
          });
          //这个list的foreah方法感觉非常好用,forEach也接受lambda表达式
          students.forEach(s-> System.out.println("age: "+s.age + " height "+s.height));
    
          //第二种排序方式,这个是students这个类实现了Comparable接口
          Collections.sort(students);
          students.forEach(s-> System.out.println("age: "+s.age + " height "+s.height));
    
          //使用lambda表达式,直接定义Comparator中的compare 实现函数
          students.sort((s1, s2)->{ //lambda允许我们在接口定义明确的前提下不用写参数类型
              if(s1.age < s2.age) //这样会从小到大进行排序
                  return 1;
              if(s1.age == s2.age)
                  return 0;
              else
                  return -1;
          });
          students.forEach(s-> System.out.println("age: "+s.age + " height "+s.height));
    
          //使用lambda还是不够简洁,我们需要更简洁的方式。按照height从低到高排序。java新的api实现允许我们用方法引用来代替lambda表达式。Comparator.comparing是一个有用的用于排序的函数。表明我们按照height从高到底进行排序
          students.sort(Comparator.comparing(s->s.height));
          students.forEach(s-> System.out.println("age: "+s.age + " height "+s.height));
    
          //上面这个只是简单的比较,我们可以使用链式比较使得比较大方法更加丰富和完善。
          //使用reverse到时候一定要是Object类型,因此必须使用这个方式,要定义getHeight方法。
          students.sort(Comparator.comparing(Student2::getHeight).reversed());
    
          //先按照身高再按照年纪students.sort(Comparator.comparing(Student2::getHeight).reversed().thenComparing(Student2::getAge));
    
      }
    }

    这个例子让我们看到了传统中的方法传递方法:我们首先要定义一个上层的接口实现,然后去实现这个接口,这样在使用该接口的地方就能够使用我们新定义的实现了。这也是我其实一直强调了接口在java中的地位,在java中接口的实现是和类有这种同等重要作用的一个概念。

    大家把上述代码自己写出来,然后感受一下lambda表达式的魅力就会更加记忆深刻,有了lambda表达式,会让java代码变得更加简洁和可读,这也是我为什么喜欢java这两个新的特性的原因(我之前一直觉得java 相比python来说实在是太过啰嗦)

java api中的函数式接口

前面之所以可以针对Comparator这个接口用lambda表达式值作为参数进行取代,是因为Comparator是一个函数式接口(只有一个抽象方法的接口,忽略dea***t修饰的接口方法),这也是使用lambda表达式的一个重要的前提,在java api中也帮我们内置一些函数式接口,如:

  • Runnable 中的run方法,无入参,无返回值
  • Callable 中的call方法,返回一个反型,无入参
    还有一些我直接截取参考书中的内容:

lambda表达式的构成部分

关于lambda我认为大家了解到这里,在日常中看到别人的代码可以理解,并能够进行简单使用就可以了,不用太过于纠结其中的细节。

stream 流式操作

关于java 新的api中的流,我们可以把它理解成遍历数据集的高级迭代器。它也能帮助我们并行的进行流数据处理,不需要我们写任何多线程的代码。同样的我们直接看一个demo来了解stream到底是什么:

一个简单的demo

public class StreamUsing {


    static class Student implements Comparable<Student>{

        int age;
        int height;

        public String getName() {
            return name;
        }

        String name;

        Student(String name,int age, int height){
            this.age = age;
            this.height = height;
            this.name = name;
        }

        public int getHeight(){
            return  this.height;
        }

        public int getAge(){
            return this.age;
        }

        @Override
        public int compareTo(Student o) {
            if(this.age > o.age) //这样会从小到大进行排序
                return 1;
            if(this.age == o.age)
                return 0;
            else
                return -1;
        }
    }

    public static void main(String[] args) {

        List<Student> students = new ArrayList<>();

        for(int i=1; i<=5; i++){//初始化数据
            Student student = new Student("age:"+i +" height:"+(5-i), i, (5-i));
            students.add(student);

        }

        for(int i=1; i<=5; i++){//初始化数据

            Student student = new Student("age2:"+i +" height2:"+(5-i), i, (5-i));
            students.add(student);

        }

        List<String> filterStudents = students.stream().filter(s -> s.age < 3)
                .sorted(Comparator.comparing(Student::getHeight))
                .map(Student::getName)
                .collect(toList());//先用stream转化为流式操作,在用filter过来age小于3的student,然后再使用身高进行排序,然后再进行映射,取对应的名称,最后把这些数据归约到一个list中

        List<String> filterStudentsParrel = students.parallelStream().filter(s -> s.age < 3)
                .sorted(Comparator.comparing(Student::getHeight))
                .map(Student::getName)
                .collect(toList());//这里使用parallelStream来使用流的并行操作的技能


        filterStudents.forEach(System.out::println);//这种书写的方式简直不要太舒服
        filterStudentsParrel.forEach(System.out::println);

        List<String> title = Arrays.asList("java 8", "In", "action");
        List<Integer> lists = Arrays.asList(1,2,3,4);

        Stream<String> s = title.stream();
        s.forEach(System.out::println);
        lists.forEach(System.out::println);

        System.out.println("==========");

        try(Stream<String> lines = Files.lines(Paths.get("file1"),Charset.defaultCharset())){

            long word = lines.flatMap(line->Arrays.stream(line.split(" "))).distinct().count();//flatMap扁平操作,把所有流集合在一起
            System.out.println(word);
        }catch (Exception e){

        }

        System.out.println("==========");
        Map<Integer, List<Student>> maps = students.stream().collect(Collectors.groupingBy(Student::getAge));//groupingBy是另一种归约方法,Collectors中还有其他更多实用的规约方法
        maps.forEach((Integer key, List<Student> value)->
                System.out.println("key: "+ key + " value " + value));

    }
}

关于流,我们掌握一些知识点即可:

  • 使用Steram API,可以帮助我们写出【更简洁,更易读】、【更灵活】、【性能更好】的代码,其中更易读是我比较喜欢的特性
  • 流只能被消费一次
  • 流是“从支持数据处理操作的源生成的一系列元素”
  • 流利用的是内部迭代,迭代通过filter、map、sorted等操作被抽象掉了。
  • 流操作包含了两类:中间操作和终端操作,比如filter和map为中间操作,其返回值仍然是流,可以链接在一起;foreach和count等为终端操作返回的是非流类型的数据,会处理流水线返回的结果。
  • 流中的元素按需计算,一旦达到计算结果就会停止。

    常用的流操作

这些流操作灵活使用可以帮助我们减少很多复杂的代码,对上表中的的操作可以做如下归类:

  • 我们可以用filter、distinct、skip、limit对流做筛选和切片
  • 可以使用map和flatMap提取和转换流中的元素
  • 可以使用findFirst,finAny方法查找流中的元素。也可以使用allMatch,noneMatch,anyMatch方法来匹配给定的谓词。
  • 以上三类都利用了短路:找到结果会立即停止计算,没有必要处理整个流。
  • 我们可以利用reduce来将所有元素迭代合并成一个结果,例如求解和和最大值
  • 流不仅可以从集合中创建,也可以从值、数组,文件以及iterare、generate等特定方法中创建

collect方法中的归约

在之前我们看到了.collect这一个方法,需要把它作为一类特殊的终端操作:归约。它会把流操作的所有结果都汇集到一个数据结构中,通常collect方法中的入参可以从Collectors这个类中来寻找我们想要使用的已经被预定义的收集器。当然满足一定条件的前提下,我们也可以定义自己的收集器,但是估计目前还用不到那么高级的技巧,可以暂时忽略。
那么预定义收集器主要分为如下三类:

针对这些分类在参考书中也做了相应的分类汇总,我就不一一列表,大家知道有这些收集器,用到的时候查找一下相应的API即可。

java中的IO

之所以要特别把这块拎出来写是因为在刷题的时候一直被java中的从控制台上的输入给制约,不明白为什么语法是那样的,以至于觉得甚是烦恼,每次都记不到要如何写第一行代码。我不知道你们是否有同样都苦恼,但是我之前在这个里面绕了很久:因为java中提供的api太多了,以至于总是弄糊涂。但是实际上如果熟练了我们就不会有如此多的烦恼了,下面就给大家分享我在这个方面上的一些心得和感悟。

分类和枚举

java里面有很多处理io的类,我们可以把其从一下三个角度进行划分:

  • 处理流的用法:使用处理流来包装节点流,程序通过处理流来执行输入和输出功能,让节点流与底层的IO设备、文件交互。
  • 所有的节点流都是直接以物理的IO节点作为构造参数的,比如文件、数组、字符串。
  • 在使用处理流包装了底层节点流之后,关闭输入和输出资源时,主要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭被处理流包装的节点流。
  • 用字符流来出来文本文件、用字节流来处理二进制文件。

具体的类可以参考下面这个表格:

在一般我们进行数据读取的时候会直接和文件打交道,或者和网络数据打交道,那么首先数据会是一个节点流,我们需要把节点流转换为我们可读的字符流来处理,所以我们用的比较多的往往是:InputStreamReader、和OutputStreamWriter两个转化流,参考以下网络数据的读取过程:

    @Test
    public void testHttp() throws Exception{

        URI uri = new URI("http://localhost:8080/");
        URL url = uri.toURL();

        //在java的输入输出操作对象中
        InputStream response = url.openStream();

        BufferedReader buffer = new BufferedReader(new InputStreamReader(response));

        System.out.println(buffer.readLine());

    }

从控制台上获取输入

通过上面的梳理,我们应该能理解以下几种实现方式中为什么我们要如此使用了:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Scanner;
public class consoleOutInUsing {
    /*关于控制台的输入主要介绍三种方法:
    第一种方法使用BufferedReader获得控制台输入的数据,此方法是传统的输入方法,使用起来比较繁琐,输入效率也不高,
    其次是使用System.in作为输入,感觉此方法不是很灵活。
    最后,使用Scanner作为输入,这种方法使用起来非常的方便好用,并且功能强大。
     */
    //System.in是集成了InputStream类型的一个节点流,为了获取可读的字符,我们需要转化为字符流。
    //使用BufferedReader能让我们一行一行的进行读取。
    //返回输入的一行数据
    public static String readLine1() throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        return br.readLine();
    }
    //1.2 直接使用System.in输入
    //获得输入的一数据块(字节块)
    public static String readLine2() throws IOException
    {
        byte buf[] = new byte[1024];
        int i = System.in.read(buf);
        return new String(buf, 0, i-2);        //有两个结束符,\r\n,所以要减2
    }

  重点要学习scanner的用法
    //1.3使用Scanner输入,这种方法最好用。最好用的方法必须要快速地熟悉起来呀
    /**
     * 可以使用s.next()输入一个不含空格的字符串,
     * s.nextInt():输入一个整数
     * s.nextDouble():输入一个double
     * s.nextByte():输入一个字符
     **/
    public static String readLine3()
    {
        Scanner s = new Scanner(System.in);
        return s.nextLine();            //s.nextInt();
        //判断输入是否终止的方式就是查看是否hasNext成立
        Scanner s = new Scanner(System.in);
        while (s.hasNext())
            System.out.println(s.nextLine());
        return s.nextLine();            //s.nextInt();
    }
}

那为什么要转化为一层BufferReader?主要是InputStream非常不好用,如下我们可以看到是一个字符一个字符读取的,不利用解析数据。

那么比如我们要获取以下输入应该如何写呢?
1 2
3 4
5 6

用以下的代码就能够帮助我们解决输入的问题:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Scanner;

/**
 * @项目: basic.demo
 * @描述: 输入输出
 * @作者: 一叶浮尘
 * @创建日期: 2020-08-16
 **/

public class IOinput {

    public static void main(String[] args)  throws Exception{

        /*
        第一种方式
         */
//        Scanner scanner = new Scanner(System