关于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