9.3 案例三
使用Lambda表达式给Supplier接口的变量赋值,实现产生1个100以内的整数功能。
代码演示如下:
@Test public void test10(){ //使用Lambda表达式给Supplier接口的变量赋值,实现产生1个100以内的整数功能。 Supplier<Integer> s= ()-> new Random().nextInt(100); System.out.println(s);//打印s是得不到这个随机数值的 System.out.println(s.get());//通过调用s.get()得到随机数值 }
9.4 案例四
声明一个Employee员工类型,包含属性编号、姓名、薪资,属性私有化,提供有参构造,get/set,重写toString.
添加n个员工对象到一个HashMap<lnteger,Employee>集合中,其中员工编号为key,员工对象为value。
调用Map的forEach遍历集合
调用Map的replaceAll方法,将其中薪资低于10000元的,薪资设置为10000。
再次调用Map的forEach遍历集合查看结果
代码演示如下:
public class Employee { private int id; private String name; private int salary; @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", salary=" + salary + '}'; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } public Employee(int id, String name, int salary) { this.id = id; this.name = name; this.salary = salary; } }
@Test public void test11(){ HashMap<Integer,Employee> map=new HashMap<>(); Employee e1=new Employee(1,"张三",6700); Employee e2=new Employee(2,"李四",9700); Employee e3=new Employee(3,"王五",16000); Employee e4=new Employee(4,"赵六",27000); map.put(e1.getId(),e1); map.put(e2.getId(),e2); map.put(e3.getId(),e3); map.put(e4.getId(),e4); map.forEach((key,value)-> System.out.println(map.get(key))); System.out.println("--------------------------------------"); map.replaceAll((key,value)-> { if (map.get(key).getSalary()<10000){ map.get(key).setSalary(10000); } return value; }); map.forEach((key,value)-> System.out.println(map.get(key))); }
十. 方法引用与构造器引用
10.1 方法引用
10.1.1 什么是方法引用?
它是指一种新的语法格式:
类名 :: 方法名
对象名 :: 方法名
10.1.2 方法引用用在哪里?
作用: 方法引用是用于简化Lambda表达式的语法。
10.1.3 方法引用可使用的情况
注意:
不是所有的Lambda表达式都可以用方法引用进行简化的,只有满足以下情况时,才能用它进行简化:
1)Lambda体只有一个语句,并且这个语句是通过调用一个现有的类或对象的方法来完成的。
并且在调用方法时,所使用的实参正好时Lambda表达式的形参,整个使用过程中,没有额外的数据出现。
代码演示如下:
@Test public void test12(){ ArrayList<String> list=new ArrayList<>(); list.add("jack"); list.add("hello"); list.add("rose"); list.add("tom"); list.forEach(t -> System.out.println(t));//使用lambda进行遍历,可进行方法引用以简化 list.forEach(t -> System.out.println(t+"\t"));//不能根据这个lambda进行方法引用 System.out.println("--------------------------"); list.forEach(System.out:: println );//使用方法引用 }
(2) Lambda体只有一个语句,并且这个语句是通过调用一个现有的对象的方法来完成的。
此时调用方法的对象是Lambda表达式的第一个形参,并且Lambda表达式的剩下的形参,正好作为该方法的实参,
整个使用过程中,没有额外的数据出现。
代码演示如下:
@Test public void test13(){ String[] arr = {"hello","Bob","Rose","java","chai"}; Arrays.sort(arr, (o1,o2) -> o1.compareToIgnoreCase(o2) ); for (String s : arr) { System.out.println(s); } System.out.println("-----------------------"); String[] arr1 = {"hello","Bob","Rose","java","chai"}; Arrays.sort(arr1, String::compareToIgnoreCase ); for (String s : arr) { System.out.println(s); } }
10.2 构造器引用
当Lambda表达式是一个创建对象的表达式,即Lambda体是一个new表达式,而且 所有构造器的实参,都是Lambda表达式的形参,那么此时就可以使用构造器引用。
语法格式:
类名 :: new
代码演示如下:
@Test public void test14(){ /* 功能型接口Function<T,R>,有一个抽象方法 R apply(T t) 希望它通过某个字符串,作为Person对象的name值,创建一个Person对象*/ // Function<String,Person> fun= s -> new Person(s); Function<String,Person> fun= Person::new; String[] names={"jack","rose","tom"}; //根据names的字符串,创建一个Person数组 Person[] people=new Person[names.length]; for (int i = 0; i < names.length ; i++) { people[i]=fun.apply(names[i]); } for (Person person : people) { System.out.println(person); } }
10.3 数组引用
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参正好是给创建这个数组对象的长度,就可以数组构造引用
语法格式:
数组类型名: :new
代码演示如下:
@Test public void test15(){ /* 如果使用Lambda表达式给一个Function接口的变量赋值 需求: 给定一个长度,你返回一个对应长度的String[]数组给我* 功能型接口Function<T,R> 有一个抽象方法 R apply(T t) 形参的类型: Integer 返回值类型: String[]*/ // Function<Integer,String[]> fun=l -> new String[l]; Function<Integer,String[]> fun= String[]::new; String[] str=fun.apply(5); for (String s : str) { System.out.print(s+"\t"); } }
十一. Stream API
11.1 stream是什么?
stream这个单词,在之前IO流的文章就提到过。在在IO流中是代表一个字节输入流或一个字节输出流,它是用于数据的传输的今天讲的这个stream也是代表一个数据流,但是这个数据流和之前的IO流是有区别的;
今天说的stream它是指用于数据“加工”的一套流程。例如:数据的过滤、数据的统计、数据的迭代、数据的修改、删除、查询筛选等
stream本身是不存储数据的,存储数据的是数组、集合这样的容器。
11.2 特点
(1) stream本身是不存储数据的
(2) stream每一次加工处理都会产生一个新的stream对象
代码演示如下:
@Test public void test16(){ ArrayList<String> list=new ArrayList<>(); list.add("jack"); list.add("option"); list.add("java"); list.add("world"); list.add("h5"); //1)创建stream //通过集合创建Stream Stream<String> stream = list.stream(); /* stream.filter(new Predicate<String>() { //Predicate接口为判断式,可使用lambda @Override public boolean test(String s) { return s.contains("o"); } });*/ //2)中间加工处理 //筛选含”o“的单词 //lambda表达式 Stream<String> o = stream.filter(s -> s.contains("o"));//原先得Steream流stream本身不会存储任何数据且每一次加工处理都会产生一个新的Stream流对象 //3)终结 //遍历流中剩余的元素 // o.forEach(s -> System.out.println(s)); o.forEach(System.out::println); System.out.println("list:"+list);//list:[jack, option, java, world, h5] 不会改变源数据 }
(3) stream的中间处理/加工操作会被延迟,一直要到最后取结果的“终结作”才会执行。
不加”终结操作“前后对比:
没加代码演示如下:
@Test public void test17(){ ArrayList<Student> list=new ArrayList<>(); list.add(new Student("KFC")); list.add(new Student("dragon")); //1)创建流对象 Stream<Student> stream = list.stream(); //2)加工处理 //peek():peek(Consumer<? super T> action) //返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作。 Stream<Student> peek = stream.peek(System.out::println); //3)终结操作 //3)没写得时间,前面得代码都没执行 /* long nums=peek.count();//统计流中的元素的个数 //peek.count()有返回值,不是stream,所以它是一个终结操作 System.out.println(nums); */ }
加了 代码演示如下:
@Test public void test17(){ ArrayList<Student> list=new ArrayList<>(); list.add(new Student("KFC")); list.add(new Student("dragon")); //1)创建流对象 Stream<Student> stream = list.stream(); //2)加工处理 //peek():peek(Consumer<? super T> action) //返回由该流的元素组成的流,另外在从生成的流中消耗元素时对每个元素执行提供的操作。 Stream<Student> peek = stream.peek(System.out::println); //3)终结操作 //3)没写得时间,前面得代码都没执行 long nums=peek.count();//统计流中的元素的个数 //peek.count()有返回值,不是stream,所以它是一个终结操作 System.out.println(nums); }
(4) stream的加工处理不会改变的数据源(集合和数组的元素,个数等)
11.3 如何使用stream?
(1) 创建Stream
创建Stream的几种方法:
①通过集合对象.stream
JDK1.8在
ColLection系列集合中增加了方法:default Stream stream()
②通过数组工具类Arrays.stream方法
代码演示如下:
@Test public void test18(){ //通过Arrays。stream()创建 int[] a={1,2,4,5,10}; IntStream stream = Arrays.stream(a); }
③Stream接口中有这样的一些静态方法,可以创建
Streamof(T… value) : 有限流
static Stream generate(Supplier s) : 无限流
static Stream iterate(T seed, UnaryOperator f):无限流
代码演示如下:
@Test public void test19(){ Stream<String> jack = Stream.of("jack", "hello", "world"); Stream<Integer> integerStream = Stream.of(1, 3, 5, 2); }
@Test public void test20(){ //static <T> Stream<T> generate(Supplier<T> s) // 返回无限顺序无序流,其中每个元素由提供的Supplier 。 这适合于产生恒定流,随机元素流 Stream<Double> generate = Stream.generate(Math::random); generate.forEach(s -> System.out.println(s)); }
@Test public void test21(){ /* static <T> Stream<T> iterate(T seed, UnaryOperator<T> f) 返回有序无限连续Stream由函数的迭代应用产生f至初始元素seed ,产生Stream包括seed , f(seed) , f(f(seed)) ,等 第一元件(位置0在) Stream将是提供seed 。 对于n > 0 ,位置n的元素将是将函数f应用于位置n - 1的元素的n - 1 。 参数类型 T - 流元素的类型 参数 seed - 初始元素 f - 要应用于前一个元素以生成新元素的函数 结果 一个新的顺序 Stream*/ /* Function<T,R>接口抽象方法 R apply(T t);参数的类型和返回值的类型可以不同 UnaryOperator<T> 接口抽象方法 T apply(T t)参数的类型和返回值的是一样的 */ //需求:对每一个元素,进行迭代处理,从第一个元素开始,每次迭代都+2,然后一直在上一次的基础上,不断迭代。 Stream<Integer> iterate = Stream.iterate(1, t -> t + 2); iterate=iterate.peek(t->{ //让流中的数据每隔100ms产生一个 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }); iterate.forEach(t-> System.out.println(t)); }
(2) 中间加工处理
处理方法如下:
代码演示如下:
@Test public void test22(){ //过滤所有的偶数 Stream.of(1,2,3,4,5) //创建流 .filter(t -> t%2==0) //中间加工处理 .forEach(t -> System.out.println(t)); //终结操作 }
@Test public void test23(){ //去掉所有的重复数字 Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流 .distinct() //中间加工处理 .forEach(t-> System.out.println(t)); //终结操作 } @Test public void test23(){ //去掉所有的重复数字 Stream.of(1,2,3,4,5,2,4,6,5,3,1) //创建流 .distinct() //中间加工处理 .forEach(t-> System.out.print(t+"\t")); //终结操作 }