java基础之接口基础
一、lambda函数式表达
首先理解一个概念:
- 函数式编程思想
-
在数学中,函数是有输入量,输出量的一套计算方案。就是“拿数据做操作”。
-
在面向对象的思想中强调“必须通过对象的形式来做事情”
-
函数式思想则是尽量忽略面向对象的复杂语法:“强调的是做什么,而不是以什么形式去做”
而我们学的Lambda就是体现这种函数式思想的。
注意:千万记住这几句话,真的很重要。下面所有操作全都围绕这几句话展开的。
举个例子:
要求:需要启动一个线程,输出一句话,多线程程序启动了
-
(常规类):按照正常的步骤,我们需要先建一个类,这个类是Runnable的实现类并重写其run()方法,然后是在测试类里面去调用该类对象,把其作为参数构建Thread对象,然后使用Theard的start() 方法启动线程。`
public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println("多线程已启动"); } } ------------------------ //测试类 public static void main(String[] args) { //常规类方法 RunnableImpl runnable = new RunnableImpl(); new Thread(runnable).start(); } }
-
(匿名内部类)在创建Thread对象的时候,在其参数位置直接new一个匿名内部类,然后实现在测试类直接重写run()方法,并调用Thread对象的strat() 方法,实现启动多线程。
//匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程已启动");
}
}).start();
-
(Lambda表达式)在匿名内部类的基础上,我们使用函数式思想,只在乎做的是什么,发现是创建的这个内部类进行了一句:输出一句字符串 ;
所以我们把这个操作简化一下,
参数:所以用()括起来,有多个参数可以用 , 隔开。
执行了什么:我们用 ->表示。
执行的东西:利用{ } 括起来。
总结一下:Lambda三要素:形参(),箭头 ->,代码块 { } .
//lambda表达式
/* new Thread(()->{
System.out.println("多线程已启动");
}).start();*/
/*简化,简化前提:
1.参数:有参数时,可把参数类型省略,只留下参数名;但是省略的时候要全部参数的类型名都要省略
2.如果参数只有一个时,可以把()也省略掉。
2.代码块:当代码块只有一句话的时候,可以把代码块的{}和;省略。
3.当代码块种有return,且只有一句话,要把return也省略掉
*/
//省略后
new Thread(()-> System.out.println("多线程已启动")).start();
Lambda方式虽然使用起来十分的方便,但是这种方便的东西肯定是有一个前提的:
/*Lambda使用前提:
1.当使用到接口,且接口有且仅有一个抽象方法的时候。
2.有上下文环境,能理解其调用的是什么方法,调用的参数是什么。
根据局部变量的赋值得知Lambda对应的接口:
Runnable r = ()->System.out.println("Lambda表达式");
根据调用方法的参数得知Lambda对应的接口:
new Thread(()->System.out.println("Lambda表达式").start();
*/
省略return的代码:
public class AddDemo1 {
public static void main(String[] args) {
//具体类
AddInterface a1 = new AddImpliment();
a1.add(10, 20);
//当一个方法,要用到接口对象来完成一个操作,就可以利用匿名内部类
useadd(new AddInterface() {
@Override
public int add(int x, int y) {
return x+y;
}
});
//当该方法调用的接口是有且仅有一个抽象方法时,可以使用lambda函数化表达
useadd((int x,int y)->{
return x+y;
});
//形参可以省略类型名,方法体只有一条语句可以直接省略大括号和分号,
// 而且具有return的语句。return也可以省略
// 简化后:
useadd((x,y)->x+y);
//lambda是相当于代码简化的匿名内部类
//根据函数化思想只展现出谁干了什么。而不在乎是用什么形式去做的。
}
private static void useadd(AddInterface i ){
i.add( 10,20);
}
}
二、lambda函数式表达与匿名内部类的区别
1.用法区别
-
lambda表达作用对象:
- 接口类(有且仅有一个抽象方法)
-
匿名内部类作用对象:
-
可以是具体类(可以具有多种方法)
-
可以是接口类(可以具有多种方法)
-
可以是抽象类(可以具有多种方法)
-
2.底层原理
- lambda表达:
- 对应的字节码文件会在运行时动态生成。
- 不会在产生其他的字节码文件。
- 匿名内部类:
- 在运用匿名内部类的方法执行的程序,会在磁盘产生两个字节码文件
- 第一个文件是原来的文件的字节码文件;设原类的字节码文件为 LambadDemo.class
- 第二个文件则是匿名内部类的字节码文件;则为 LambdaDemo$1.class
- 在运用匿名内部类的方法执行的程序,会在磁盘产生两个字节码文件
3.接口的组成更新
- 接口的组成
- 常量
- Public static final
- 抽象方法
- public abstract
- 常量
jdk8以后,更新了接口的组成更新,加入了
- 默认方法
- pubilc default
- 静态方法
- pubilc static
jdk9以后,加入了
- 私有方法
- private
3.1接口的默认方法
默认方法的出现,是为了增强程序的扩展性。
举个例子:
若一个写好的接口类,我想扩展一个新的方法,那么我如果直接写一个抽象方法进去,所以以匿名内部类方法调用该接口的类,都需要重写新写入的方法。
要解决这个方法可以在一个新的接口种写入该方法,然后去继承这个要扩展的接口,但是这种方法会让代码维护量变得十分庞大。
所以java8提出了默认方法,只需要在原来的接口中,把想扩展的方法用关键字default修饰。就可解决该问题。
接口种默认方法的定义格式:
- 格式:pubilc default 返回值类型 方法名(参数列表){ }
- 范例 pubilc default void add( int x,int y){ }
- 注意事项:
- 默认方法不是抽象方法,可以有具体方法体
- 不强制被重写。但是可以重写,重写的时候把default关键字去掉
- 在接口内定义时,public可以省略,但是default不能省略
3.2接口的静态方法
看见静态方法一定要想起一句话:静态方法是类的方法,不是成员方法。
接口的静态方法其实和具体类的成员的静态方法是一样的格式的,而且也是有方法体的。
接口钟静态方法的定义格式:
- 格式:public static 返回值类型 方法名(参数列表){ }
- 范例: public static void show( ) { }
- 注意事项:
接口的静态方法是只允许接口名调用的,而不是给接口实现类对象名或者是接口的实现类名调用。
因为若一个实现类他继承自多个接口,则无法区别这个静态方法到底是来自谁的。
类似这种格式调用 Thread.start(); //伊呦
而不能是 runnable.start(); //哒咩
或者是Runnable r =new Runnble(); r.strat(); //哒咩呦
在接口中定义时,public是可以省略的,但是static是不能省略的。
3.3接口的私有方法
java9中,新增了带方法体的私有方法;
由于java8的默认方法和静态方法引进,导致一个问题:就是如果一个接口中具有两个默认方法或者静态方法,并且这两个方法具有一个共性方法的时候。
我们其实按习惯的编程思想,是会把这个共性方法封装到一个私有(封装为私有是为了不给外部利用)的成员方法中从而减少相同代码块带来的冗余的。
于是在java9就新增了这样一种只给内部使用的方法——私有方法。
私有方法分为普通的私有方法和静态的私有方法。
格式:
- 格式1:private 返回值类型 方法名 (参数列表){ }
- 范例 : private void show() { }
- 格式2:private static 返回值类型 方法名 (参数列表){ }
- 范例 : private static void show() { }
- 注意:
- 静态的私有方法能同时被默认和静态方法调用,
- 普通的私有方法是动态的,不能被静态方法调用。
4.方法引用
方法引用其实就是lambda函数式思想的延续,
lambda的原理就是:
在原有的情况下,为了突显**“什么东西干了什么”**,
lambda就在引用这个方法,并且省略了执行方式。
但是其实这个方法是具体存在的。
于是就有一个问题,既然是有方法,并且我们是面向对象的编程。
那我们在遇到相同的情况,直接把这个原有的方法拿过来就很快了鸭。于是就搞了个方法引用符。
引用运算符::
用这个鬼东西,表示哪个东西去引用了哪个方法的过程。
::
这个东西在的表达式就称为 方法引用。
在代码中举个例子,
lambda表达式:usePrintable(s->System.out.println(s));
lambda的处理是,把参数s传给System.out这个对象的println方法去处理。
方法引用:usePrintable(System.out::println)
所以方法引用这里,直接把这个参数s,传给了println这个方法去执行。而这个方法是system.out的。就把这个方法的对象拿过来,放在前面。
可以看出,方法引用其实是Lambda的孪生兄弟。
上面的只是一个例子推出两者间的关系,下面是两者具体还要什么关系可以互相转化表达。
Lambda表达式支持的方法引用:
常见的引用方式:
- 引用类方法
- 引用对象的实例方法
- 引用类的实例方法
- 引用构造器
4.1引用类方法
引用类方法,其实就是引用类的静态方法
-
格式:类名::静态方法
-
范例:
Integer::parseInt
-
注释:Interger类中有的一个parseInt方法
public static int parseInt(String s)将此String型转化为int型数据
代码展示:
-
//测试类
public class ConverterDemo {
public static void main(String[] args) {
//Lambda表达式
useConverter(s -> Integer.parseInt(s));
//方法引用
useConverter(Integer::parseInt);
//可以看出,Lambda表达式被方法引用代替时,是把参数全部传输到类的静态方法里。
}
private static void useConverter(converter c){
c.convert("666");
}
}
//接口类
public interface converter {
int convert(String s);
}
4.2引用对象的实例方法
引用对象的实例方法,其实就是引用类的成员方法
- 格式:对象::成员方法
- 范例:“HelloWorld”::toUpperCase
- 注释:String类中方法:public String toUpperCase () 将此String所有字符转化为大写。
练习:
代码:
//自定义类
public class PrintString {
public void printupper( String s){
String result = s.toUpperCase();
System.out.println(result);
}
}
//接口类
public interface Printer {
void printUpperCase(String s );
}
//测试类
public class PrinterDemo {
public static void main(String[] args) {
//
//lambda
usePrinter(s-> System.out.println(s.toUpperCase()));
//方法引用(printString对象的成员方法)
PrintString printString = new PrintString();//把对象引入
usePrinter(printString::printupper);
//lambda表达式被方法引用替代时,参数全部传给成员方法。
}
private static void usePrinter(Printer p ){
p.printUpperCase("HelloWorld");
}
}
4.3引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法
- 格式 :类名::成员方法
- 范例:String::toUpperCase
其实刚刚那个引用对象的实例方法,也可以用作引用类的实例方法的例子。
看代码:
package com.lys.heima.javaSEjichu.Lambda;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class PrinterDemo {
public static void main(String[] args) {
//
//lambda
usePrinter(s-> System.out.println(s.toUpperCase()));
//方法引用(String类的方法)引用类的实例方法
usePrinter(String::toUpperCase);
//方法引用(printString对象的成员方法)引用对象的实例方法
PrintString printString = new PrintString();//把对象引入
usePrinter(printString::printupper);
//lambda表达式被方法引用替代时,参数全部传给成员方法。
}
private static void usePrinter(Printer p ){
p.printUpperCase("HelloWorld");
}
}
4.3引用构造器
引用构造器,其实就是引用构造方法
- 格式:类名::new
- 范例:Student::new
代码:
//自定义学生类
public class Student {
private int age ;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
//接口类
public interface StudentBuilder {
Student build(String name,int age);
}
测试类
public class StudentDemo {
public static void main(String[] args) {
//lambda
/* useStudentBuilder((name,age)->{
return new Student("王五",15);
});*/
//简化
useStudentBuilder((name,age)-> new Student("王五",15));
//方法引用(引用构造器)
useStudentBuilder(Student::new);
//可以看出当lambda被方法引用代替,是把数据直接传输给student类的构造器的。
}
private static void useStudentBuilder(StudentBuilder S){
S.build("张三",18);
}
}
三、函数式接口
1.函数式接口概述
函数式接口:有且只有一个抽象方法。
java中的函数式编程体现的就是lambda表达式,所以函数式接口就是可以适用于lambda使用的接口,只要确保接口中有且仅有一个抽象方法,java中的lambda才能顺利进行推导。
如何检测一个接口是不是函数式接口呢?
- @FunctionalInterface
- 把这个注解定义在接口的定义上方:如果接口是函数式接口,编译通过,否则会报错
对于我们自己定义来说,这个@FunctionalInterface注解是可选的,起的是一个提醒功能,即使不写,符合函数式接口条件,它依然是函数式接口是不变的。但是,开发的时候还是建议写上。
//这个是API中的Runnable类
@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();
2.函数式接口作为方法的参数
如果方法的参数是一个函数式接口,可以使用lambda表达式作为参数传递
- StartThread(() ->System.out.println(Thread.currentThread().getName()+“线程启动了”));
- StartThread(Runnable r )这个方法就是参数为Runnable。
3.函数式接口作为方法的返回值
如果方法的返回值是一个函数式接口,可以使用Lambda表达式作为结果返回
-
private static Comparator getCompartor(){
return (s1,s2 )->s1.length - s2.length
}
- Comparator是集合里面的比较器,当初还是通过重写匿名内部类在测试类里定义集合元素排序的。
4.常用函数式接口
在java 8在java.util.function包下预定义了大量的函数式接口
常见的4个:
- Supplier接口
- Consumer接口
- Perdicate接口
- Function接口
4.1Supplier接口
Supplier:包含一个无参的方法
- T get0:获得结果
- 该访法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
- Supplier接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
package com.lys.heima.javaSEjichu.Lambda.Supplier;
import java.util.function.Supplier;
//Supplier测试类
public class SupplierDemo1 {
public static void main(String[] args) {
int x = getInteger(()->10 );
String s = getString(()->"String");
System.out.println(s+"\t"+x);
//返回最大值
int[] arr = {0,5,6,22,3,444,888};
int max1 = getMax(arr,()-> {
int max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (max <arr[i]){
max=arr[i];
}
}
return max;
}
);
System.out.println(max1);
}
//定义一个方法,返回一个整数数据
private static Integer getInteger(Supplier<Integer> sup){
return sup.get();
}
//定义一个方法,返回一个字符串数据
private static String getString(Supplier<String> sup){
return sup.get();
}
//定义一个方法,返回一个数组中的最大值
private static int getMax(int[] x ,Supplier<Integer> sup){
return sup.get();
}
}
/*结果:
*String 10
*888
* */
4.2Consumer接口
Consumer:包含两个方法
- void accept(T t):对给定的参数执行此操作
- default ConsumerandThen(Comsumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作。
- Consumer Consumer接口被称为消费型接口。它消费的数据的数据类型由泛型指定。
//消费型接口
import java.util.function.Consumer;
public class consumerDemo1 {
public static void main(String[] args) {
//函数式编程思想:什么东西干了什么;
consumerStr("陈奕迅",(s1)-> System.out.println(s1));
consumerTwoStr("陈奕迅", //第一个参数
(s2) -> System.out.println(s2), //第二个参数
(s2)-> System.out.println(s2+"我又来啦") //第三个参数
);
String[] Array = {"林青霞,30", "张曼玉,35","王祖贤,33"};
consumerPrint(Array,
(name)-> System.out.print(name.split(",")[0])
,
(age)-> System.out.println( Integer.parseInt(age.split(",")[1]))
);
}
//定义一个方法,消费一个字符串数据
private static void consumerStr(String s1, Consumer<String> C){
C.accept(s1);
}
//定义一个方法,用不同的方式消费同一个字符串数据两次
private static void consumerTwoStr(String s2,Consumer<String> c1,Consumer<String> c2){
/*
c1.accept(s2);
c2.accept(s2);
*/
c1.andThen(c2).accept(s2);//尽量使用通过方法封装的连用两个参数的方法。
}
//根据格式“姓名:xx,年龄:xx”的格式把信息从String[]中打印出来。
//● String[] Array = {"林青霞,30", "张曼玉,35","王祖贤,33"};
//要求:
//把打印姓名的动作作为第一个Consumer接口的Lambda实例
//把打印年龄的动作作为第: -个Consumer接口的L .ambda实例
//将两个Consurer接口按照顺序组合到一起使用
private static void consumerPrint(String[] s, Consumer<String> c1, Consumer<String> c2) {
for (String ss : s){
c1.andThen(c2).accept(ss);
}
}
}
/*
结果:
陈奕迅
陈奕迅
陈奕迅我又来啦
林青霞30
张曼玉35
王祖贤33
进程已结束,退出代码为 0
*/
4.3Predicate接口
Predicate:常用的四个方法
- boolean test(T t):对给定的参数进行判断(判断逻辑由L ambda表达式实现),返回一个布尔值
- default Predicate negate0:返回-个逻辑的否定,对应逻辑非
- default Predicate and(Predicate other):返回-一个组合判断,对应短路与
- default Predicate or(Predicate other):返回一个组合判断,对应短路或
- Predicate接口通常用于判断参数是否满足指定的条件
代码:
//
import java.util.ArrayList;
import java.util.function.Predicate;
public class PredicateDemo {
public static void main(String[] args) {
boolean a = CheckS1("我在学java", (s1) -> s1.length() > 8);
System.out.println(a);
System.out.println("------------------------------");
boolean b = checkS2("我在一边听歌,一边写这个代码",
(s2) -> s2.length() > 8
,
(s2) -> s2.length() == 8 ? true : false//顺便复习一下三元运算符
);
System.out.println(b);
System.out.println("------------------------------");
String[] strArray = {"林青霞,30", "柳岩,34", "张曼玉,35", "貂蝉,31", "王祖贤,33"};
ArrayList<String> alist = new ArrayList();
for (String str:strArray) {
boolean b2 =checkS3(str,
(name) -> name.split(",")[0].length() > 2
,
(age) -> Integer.parseInt(age.split(",")[1]) > 33
);
if (b2){
alist.add(str);
}
}
for (String s4 : alist){
System.out.println(s4);
}
}
//判断给定的字符串是否满足条件
private static boolean CheckS1(String s1, Predicate<String> p1) {
// return p1.test(s1); //原式 false
return p1.negate().test(s1);//非 ture
}
//同一个字符串给出两个不同的判断条件,最后把这两个结果进行逻辑与运算作为结果返回;
private static boolean checkS2(String s2, Predicate<String> p1, Predicate<String> p2) {
/* boolean b1 = p1.test(s2);
boolean b2 = p2.test(s2);
boolean b = b1&&b2;
return b;*/
//利用封装好的方法
return p1.and(p2).test(s2); //返回逻辑与
//对应的返回逻辑或
//return p1.or(p2).test(s2);
}
//StringOstrArray= {"林青霞,30", "柳岩,34", "张曼玉,35",“貂蝉,31", "王祖贤,33"};
//字符串数组中有多条信息,请通过Predicate接口的拼装将符合要 求的字符串筛选到集合ArrayList中,并遍历ArrayList集合
//同时满足如下要求:姓名长大于2;年龄大于33
private static boolean checkS3(String s3, Predicate<String> p1, Predicate<String> p2) {
return p1.and(p2).test(s3);
}
}
/*
结果:
true
------------------------------
false
------------------------------
张曼玉,35
进程已结束,退出代码为 0
* */
4.4Function接口
Function<T,R>:常用的两个方法
- R apply(Tt): 将此函数应用于给定的参数
- default Function andihen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
Function<T,R>接口通常用于对参数进行处理,转换处理逻辑由Lambda表达式实现),然后返回一个新的值
//Function<T,R>接口通常用于对参数进行处理,转换处理逻辑由Lambda表达式实现),然后返回一个新的值
import java.util.function.Function;
public class FuntionDemo1 {
public static void main(String[] args) {
convertInt("100", (s1)-> Integer.parseInt(s1));
System.out.println("------------------------------------");
convertAddStr(100, (s1)-> Integer.parseInt(s1));
System.out.println("------------------------------------");
StrAddStr("100",
// (s)->Integer.parseInt(s)
Integer::parseInt
,
(i)->String.valueOf(i+200)
);
System.out.println("------------------------------------");
function1("林青霞,30",
(s)->Integer.parseInt(s.split(",")[1])
,
(s)->s+70
);
}
//定义一个方法,把一个字符串转换int类型,在控制台输出
private static void convertInt(String s1, Function<String,Integer> f1){
/* System.out.println( f1.apply(s1)); */
Integer int1= f1.apply(s1);
System.out.println(int1+"我是int类型的");
}
//定义一个方法,把一个int类型的数据加上一个整数之后,转为字符串在控制台输出
private static void convertAddStr(int x ,Function<String,Integer> f2 ){
String value = String.valueOf(x + 100);
/*System.out.println(f2.apply(value));*/
Integer int2 = f2.apply(value);
System.out.println(int2+"我是int类型的");
}
//定义-个方法,把一个字符串转换int类型,把int类型的数据加上一个整数之后,转为字符串在控制台输出
private static void StrAddStr (String s , Function<String,Integer> f3,Function<Integer,String> f4 ){
/*System.out.println(f3.andThen(f4).apply(s));*/
String string = f3.andThen(f4).apply(s);
System.out.println(string+"我是字符串");
}
/* Strings = "林青霞,30";
请按照我指定的要求进行操作:
1:将字符串截取得到数字年龄部分
2:将上一步的年龄字符串转换成为int类型的数据
3:将上一步的int数据加70,得到一个int结果,在控制台输出
请通过Function接口来实现函数拼接*/
private static void function1(String s , Function<String,Integer> f5,Function<Integer,Integer> f6 ){
//可以拆分为三个部分,也可以一个fybction就完成,看你的实现怎么编写而已。
Integer int3 = f5.andThen(f6).apply(s);
System.out.println(int3+"我是int类型的");
}
}
/*
结果:
100我是int类型的
------------------------------------
200我是int类型的
------------------------------------
300我是字符串
------------------------------------
100我是int类型的
进程已结束,退出代码为 0
*/
四、Stream流
1.体验Stream
要求:
- 把姓“张”的提取出来到一个ArrayList集合。
- 再把这个ArrayList中长度大于等于3的元素提取出来。放到一个新的ArrayList集合。
- 遍历最后的集合。
//体验Stream流
import java.util.ArrayList;
import java.util.Arrays;
/*要求:
1. 把姓“张”的提取出来到一个ArrayList集合。
2. 再把这个ArrayList中长度大于等于3的元素提取出来。放到一个新的ArrayList集合。
3. 遍历最后的集合。*/
public class StreamDemo1 {
public static void main(String[] args) {
String[] str= {"张三","张三丰","张无忌","王五","李四"};
ArrayList<String> strarray1 = new ArrayList();
for (String s : str) {
if (s.startsWith("张")){
strarray1.add(s);
}
}
ArrayList<String> strarray2 = new ArrayList();
for (String s :strarray1){
if (s.length()>=3){
strarray2.add(s);
}
}
for (String s : strarray2){
System.out.println(s);
}
System.out.println("-----------------------------");
//可以发现要完成一次筛选就要3-5行的代码,显得十分的复杂麻烦。使用引出了一个stream流的概念
Arrays.stream(str).filter((s)->s.startsWith("张")).filter(s -> s.length()>=3).forEach( System.out::println);
} //forEach(s->System.out.println(s))
}
/*
结果:
张三丰
张无忌
-----------------------------
张三丰
张无忌
进程已结束,退出代码为 0
*/
2.stream流的生成方式
从刚刚给出的实例可以推导出:
Stream流的使用
- 生成流
- 通过数据源(集合,数组等)生成流
list.stream0
- 通过数据源(集合,数组等)生成流
- 中间操作
- 一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤映射,然后返回一个新的流,
交给下一个操作使用
filter0
- 一个流后面可以跟随零个或多个中间操作,其目的主要是打开流,做出某种程度的数据过滤映射,然后返回一个新的流,
- 终结操作
- 一个流只能有一个终结操作, 当这个操作执行后,流就被使用"光"了,无法再被操作。所以这必定是流的最后一个操作
forEach()
- 一个流只能有一个终结操作, 当这个操作执行后,流就被使用"光"了,无法再被操作。所以这必定是流的最后一个操作
3.Stream流的常见生成方式
Stream流的常见生成方式
- Collection体系的集合可以使用默认方法stream0生成流
defaultStream stream0- 范例:list.stream();
- Map体 系的集合间接的生成流
- 范例:
- ketSet().stream();//键集合的stream流
- values().stream();//值集合的stream流
- entrySet().stream();//键值对集合的stream流
- 范例:
- 数组可以通过Stream接口的静态方法f(T… values)生成流
- 范例:Stream().of(“a”,“b”,“c”)
package com.lys.heima.javaSEjichu.Lambda.Stream;
//Stream生成方式
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class StreamDemo2 {
public static void main(String[] args) {
//collection集合
ArrayList<String> list = new ArrayList();
Stream liststream = list.stream();
//map集合
Map<String,String> map = new HashMap<>();
Stream<String> key = map.keySet().stream();
Stream<String> values = map.values().stream();
Stream<Map.Entry<String, String>> entry = map.entrySet().stream();
//数组
String[] str ={"111","222","333"};
Stream<String> str1 = Stream.of(str);
//Stream<String> str1=Stream.of("111","222","333");
Stream<Integer> integerStream = Stream.of(11,22,33);
}
}
4.Stream流的常见中间操作方法
- Stream filter(Predicate predicate):用于对流中的数据进行过滤。
- 底层依赖于 Predicate接口中的方法 boolean test(T t) :对给定的参数进行判断,返回一个布尔值。
- Stream limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据。
- Stream skip(long n):跳过指定参数个数的数据,返回由该流的剩元素组成的流。
- static Stream concat(Stream a, Stream b):合并a和b两个流为一个流
- Stream distinct():返回由该流的不同元素(根据Objectequals(Object)组成的流
import java.util.ArrayList;
import java.util.Arrays;
import java.util.stream.Stream;
//Stream的中间操作方法1
public class StreamDemo3 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("张家辉");
list.add("张学友");
list.add("贱辉");
list.add("大口辉");
list.add("化骨龙");
list.add("狄龙·辉");
//前面推导Stream的时候用过的筛选方法
list.stream().filter((s)->s.startsWith("张")).filter(s -> s.length()>=3).forEach( System.out::println);
System.out.println("1-----------------------");
//1.取前3个数据在控制台输出
list.stream().limit(3).forEach(System.out::println);
System.out.println("2-----------------------");
//2.跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(System.out::println);
System.out.println("3-----------------------");
//3.跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);
//需求4:取前4个数据组成-一个流
Stream<String> limit = list.stream().limit(4);
//需求5:跳过2个数据组成-一个流
Stream<String> skip = list.stream().skip(2);
System.out.println("6-----------------------");
//需求6:合并需求1和需求2得到的流,并把结果在控制台输出
//Stream.concat(limit,skip).forEach(System.out::println);
System.out.println("7-----------------------");
//需求7:合并需求1和需求2得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(limit,skip).distinct().forEach(System.out::println);
}
}
/*
结果:
张家辉
张学友
1-----------------------
张家辉
张学友
贱辉
2-----------------------
大口辉
化骨龙
狄龙·辉
3-----------------------
贱辉
大口辉
6-----------------------
7-----------------------
张家辉
张学友
贱辉
大口辉
化骨龙
狄龙·辉
进程已结束,退出代码为 0
*/
以为完了?还有呢!!!
- Stream sorted0:返回由此流的元素组成的流,根据自然顺序排序
- Stream sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
- Stream map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流.
- 依赖于Function接口中的方法上 R apply(T t)
- IntStream mapTolnt(ToIntFunction mapper):返回一个ntStream其中包含将给定函数应用于此流的元素的结果
- IntStream: 表示原始int流
- 底层依赖于TolntFunction接口中的方法 int applyAsInt(T value)
Stream的中间操作方法2
import java.util.ArrayList;
import java.util.stream.IntStream;
public class StreamDemo4 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("hello");
list.add("java");
list.add("c++");
list.add("phyton");
list.add("c#");
list.add("php");
//需求1。按照字母顺序把数据在控制台输出
list.stream().sorted().forEach(System.out::println);
System.out.println("2----------------------");
//需求2:按照字符串长度把数据在控制台输出
list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);
System.out.println("3-----------------------");
ArrayList<String> intarr = new ArrayList();
intarr.add("10");
intarr.add("20");
intarr.add("30");
intarr.add("40");
intarr.add("50");
//需求3: 将集合中的字符串数据转换为整数之后在控制台输出
// 利用<R> Stream<R> map(Function mapper)方法
intarr.stream().map((s)->Integer.parseInt(s)).forEach(System.out::println);
System.out.println("4-----------------------");
//需求4: 将集合中的字符串数据转换为整数之后在控制台输出
//利用IntStream mapTolnt(ToIntFunction mapper)方法
intarr.stream().mapToInt(Integer::parseInt).forEach(System.out::println);
//利用IntStream流特有方法,sum()求和
int sum = intarr.stream().mapToInt(Integer::parseInt).sum();
System.out.println(sum);
//总结两者差异:
//map方法返回的是根据你在function接口操作后的数据类型进行判断类型的。没有具体的一个类型锁定。
//但是maptoInt方法是要求返回的流的类型必须是IntStream类型的。
}
}
4.Stream流的常见终结方法
Stream流的常见终结操作方法
-
void forEach(Consumer action):对此流的每个元执行操作
- 依赖于Consumer接口中的方法 void accept(T t):对给定的参数执行此操作
-
long count(): 返回此流中的元素数
package com.lys.heima.javaSEjichu.Lambda.Stream;
import java.util.ArrayList;
//常见Stream终结方式
public class StreamDemo5 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList();
list.add("张家辉");
list.add("张学友");
list.add("贱辉");
list.add("大口辉");
list.add("化骨龙");
list.add("狄龙·辉");
//需求1:把集合中的元素在控制台输出
list.stream().forEach(System.out::println);
//需求2:统计集合中有几个以张开头的元素,并把统计结果在控制台输出
System.out.println("2------------------------------");
long count = list.stream().filter(s -> s.startsWith("张")).count();
System.out.println("张姓人数有:"+count);
}
}
/*
结果:
张家辉
张学友
贱辉
大口辉
化骨龙
狄龙·辉
2------------------------------
张姓人数有:2
进程已结束,退出代码为 0
*/
5.Stream流的练习
现在有两个ArrayList集合,分别存储6名男演员名称和6名女演员名称,要求完成如下的操作
- 男演员只要名字为3个字的前三人
- 女演员只要姓林的, 粗不要第一个
- 把过滤后的男演员姓名和女演员姓名合并到起
- 把上一步操作后的元素作为构造方法的参 数创建演员对象遍历数据
- 演员类Actor已经提供,里面有一个成员变量, 一个带参构造方法,以吸成员变量对应的get/set方法
import java.util.ArrayList;
import java.util.stream.Stream;
/*Stream流的练习:
* 男演员只要名字为3个字的前三人
* 女演员只要姓林的, 且不要第一个
* 把过滤后的男演员姓名和女演员姓名合并到起-
* 把上一步操作后的元素作为构造方法的参 数创建演员对象遍历数据
* 演员类Actor已经提供,里面有一个成员变量, 一个带参构造方法,以吸成员变量对应的get/set方法*/
public class StreamDemo6 {
public static void main(String[] args) {
//创建集合
ArrayList<String> manList = new ArrayList<String>();
manList.add("周润发");
manList.add( "成龙");
manList.add("刘德华");
manList.add("吴京");
manList.add("周星驰");
manList.add("李连杰");
ArrayList<String> womanList = new ArrayList<String>();
womanList.add("林心如");
womanList.add("张曼玉");
womanList.add("林青霞");
womanList.add("柳岩");
womanList.add("林志玲");
womanList.add("王祖贤");
// * 男演员只要名字为3个字的前三人
Stream<String> man = manList.stream().filter(s -> s.length() == 3).limit(3);
// * 女演员只要姓林的, 且不要第一个
Stream<String> woman = womanList.stream().filter(s -> s.startsWith("林")).skip(1);
// * 把过滤后的男演员姓名和女演员姓名合并到一起
Stream<String> concat = Stream.concat(man, woman);
// * 把上一步操作后的元素作为构造方法的参 数创建演员对象遍历数据
// * 演员类Actor已经提供,里面有一个成员变量, 一个带参构造方法,以吸成员变量对应的get/set方法*/
/* Stream<Actor> actorStream = concat.map(Actor::new);
actorStream.forEach(s-> System.out.print(s.getName()+" "));*/
concat.map(Actor::new).forEach(s->System.out.println(s.getName()));
//
}
}
/*结果:
周润发
刘德华
周星驰
林青霞
林志玲
进程已结束,退出代码为 0
*/
6.Stream流的收集操作
对数据使用Stream流的方式操作完毕后,我想把流中的数据收集到集合中,该怎么办呢?
Stream流的收集方法
- R collect(Collector collector)
- 但是这个收集方法的参数是 -个Collector接口
Colletor是一个接口,无法提供方法,但是提供了它的实现工具类Collectors。
工具类Collectors提供了具体的收集方式
- public static Collector toList0:把元素收集到List集合中
- public static Collector toSet():把元素收集到Set集合中
- public static Collector toMap(Function keyMapper,FunctionvalueMapper):把元素收集到Map集合中
import javax.xml.transform.Source;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
//对Stream的结果收集
public class StreamDemo7 {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("张曼玉");
list.add("林青霞");
list.add("柳岩");
list.add("王祖贤");
//需求1:得到名字为3个字的流
Stream<String> name = list.stream().filter(s -> s.length() == 3);
//需求2:把使用Stream流操作完毕的数据收集到list集合中并遍历
List<String> newlist = name.collect(Collectors.toList());
newlist.forEach(System.out::println);
System.out.println("-----------------------------");
//set集合
Set<Integer> set = new HashSet<Integer>();
set.add(10);
set.add(20);
set.add(30);
set.add(33);
set.add(35);
//需求3:得到年龄大于25的流
Stream<Integer> age = set.stream().filter(s -> s > 25);
//需求4:把使用Stream流操作完毕的数据收集到Set集合并遍历
age.collect(Collectors.toSet()).forEach(System.out::println);
System.out.println("-----------------------------");
//定义一个字符串数组,每个字符串数据有姓名数据和年龄数据组合而成
String[] strings = {"林青霞,30", "张曼玉,35", "王祖贤,33","柳岩,25"};
//需求5:得到字符串中年龄数据大于28的流
Stream<String> agestream = Arrays.stream(strings).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);
//需求6:把使用Stream流操作完毕的数据收集到Map集合并遍历,字符串中的姓名做键,年龄做值
Map<String, String> map = agestream.collect(Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));
//三种遍历方式
//1.键值对流
map.entrySet().forEach(System.out::println);
System.out.println("-----------------------------");
//2.键流
map.keySet().forEach(key-> System.out.println(key+","+map.get(key)));
System.out.println("-----------------------------");
//普通foreach循环
Set<String> keySet = map.keySet();
for (String S:keySet){
String V=map.get(S);
System.out.println(S+","+V);
}
//懂得流的使用,比起普通方式做操作,实在简单太多了。同样遍历一个集合。用Stream方式只需要1句,常规操作要4句。
}
}
t.add(35);
//需求3:得到年龄大于25的流
Stream age = set.stream().filter(s -> s > 25);
//需求4:把使用Stream流操作完毕的数据收集到Set集合并遍历
age.collect(Collectors.toSet()).forEach(System.out::println);
System.out.println("-----------------------------");
//定义一个字符串数组,每个字符串数据有姓名数据和年龄数据组合而成
String[] strings = {"林青霞,30", "张曼玉,35", "王祖贤,33","柳岩,25"};
//需求5:得到字符串中年龄数据大于28的流
Stream<String> agestream = Arrays.stream(strings).filter(s -> Integer.parseInt(s.split(",")[1]) > 28);
//需求6:把使用Stream流操作完毕的数据收集到Map集合并遍历,字符串中的姓名做键,年龄做值
Map<String, String> map = agestream.collect(Collectors.toMap(s -> s.split(",")[0], s -> s.split(",")[1]));
//三种遍历方式
//1.键值对流
map.entrySet().forEach(System.out::println);
System.out.println("-----------------------------");
//2.键流
map.keySet().forEach(key-> System.out.println(key+","+map.get(key)));
System.out.println("-----------------------------");
//普通foreach循环
Set<String> keySet = map.keySet();
for (String S:keySet){
String V=map.get(S);
System.out.println(S+","+V);
}
//懂得流的使用,比起普通方式做操作,实在简单太多了。同样遍历一个集合。用Stream方式只需要1句,常规操作要4句。
}
}
/*
结果:
张曼玉
林青霞
王祖贤
-----------------------------
33
35
30
-----------------------------
林青霞=30
王祖贤=33
张曼玉=35
-----------------------------
林青霞,30
王祖贤,33
张曼玉,35
-----------------------------
林青霞,30
王祖贤,33
张曼玉,35
进程已结束,退出代码为 0
*/