4、装饰者设计模式
(1)什么是装饰者设计模式?
装饰:在已有的功能上增加额外的功能,并不影响原来的功能。
好比生活中的装饰,服饰的装饰,家里的装饰
(2)代码中如何实现?
JDK的核心类库中,已经有一套完整的装饰者设计模式的样例。IO流体系。
我们要在读取一个文件的时候,在读取过程中,
我想要增加缓冲功能,增加解码功能等,我们会用到哪些IO流?
依次会用到文件IO流,Buffered缓冲IO流,InputStreamReader的转换流等
四个角色:
A:抽象构件(Component)角色:接口
规定了构件等需要提供什么样的方法
例如:所有的读的IO流都有read方法
B:具体构件(Concrete Component)角色:实现类
实现具体的功能
例如:文件IO流实现具体如何读取文件
C:抽象装饰(Decorator)角色:抽象类
例如:四个基类
D:具体装饰(ConcreteDecorator)角色
例如:缓冲流,对象流,数据流
例子:卖饮料的小店,做一个收费的系统
卖:奶茶、豆浆等
用户对奶茶的需求:
原味奶茶 水 + 茶 + 牛奶
珍珠奶茶 水 + 茶 + 牛奶 + 珍珠
芒果布丁 水 + 茶 + 牛奶 + 芒果肉
抹茶红豆 水 + 茶 + 牛奶 + 抹茶 + 红豆
。。。
用户对豆浆的需求:
原味黄豆浆 水 + 黄豆浆粉
花生黄豆浆 水 + 黄豆浆粉 + 花生沫
黑豆+黄豆豆浆 水 + 黄豆浆粉 + 黑豆浆粉
咸味豆浆 水 + 黄豆浆粉 + 酱油
…
如果说是每一种用户需求的饮料都设计一个类的话,那么类就会爆炸。
我们换一种思路:
(1)抽象构件(Component)角色:接口
不管是基础材料还是装饰材料都要有两个行为:
获取价格
显示名称
(2)基础材料
(3)抽象装饰(Decorator)角色
(4)具体装饰(ConcreteDecorator)角色
例如:牛奶,红茶,芒果肉,珍珠,红豆,抹茶....
public class TestDecorator {
@Test
public void test01(){
//孙兵,需要白开水
Water water = new Water();
System.out.println("饮料:" + water.display());
System.out.println("价格:" + water.price());
}
@Test
public void test02(){
//柴老师,需要芒果奶茶
Water water = new Water();
Decorator milk = new Milk(water);//在water的基础上加牛奶
Decorator mango = new Mango(milk);//在加了牛奶的基础上加芒果肉
Decorator tea = new RedTea(mango);//在加了牛奶,芒果的基础上再加红茶
System.out.println("饮料:" + tea.display());
System.out.println("价格:" + tea.price());
}
@Test
public void test03(){
//小贾,红豆奶茶
Water water = new Water();
Decorator milk = new Milk(water);//在water的基础上加牛奶
Decorator hongDou = new AzukiBean(milk);//在加了牛奶的基础上加红豆
Decorator tea = new RedTea(hongDou);//在加了牛奶,红豆的基础上再加红茶
System.out.println("饮料:" + tea.display());
System.out.println("价格:" + tea.price());
}
}
//抽象构件(Component)角色
//Component:饮料中的某种成分,材料,构件
interface Component{
double price();
String display();
}
//基础材料
class Water implements Component{
@Override
public double price() {
return 1;
}
@Override
public String display() {
return "水";
}
}
//抽象装饰(Decorator)角色
//和基础材料不同的是,
// 在基础材料或在已经加了其他装饰材料的对象基础之上再增加新的功能
abstract class Decorator implements Component{
//表示基础材料或已经加了其他装饰材料的对象
private Component component;
//这个类只有一个有参构造,没有无参构造
public Decorator(Component component) {
this.component = component;
}
@Override
public double price() {
return component.price();
}
@Override
public String display() {
return component.display();
}
}
//具体装饰(ConcreteDecorator)角色
class Milk extends Decorator{
//因为父类没有无参构造,那么必须手动调用父类的有参构造
public Milk(Component component) {
super(component);
}
@Override
public double price() {
return super.price() + 3;
//这里的3是牛奶的价格
//这里super.price()是原来基础材料的价格,
//例如:在加牛奶之前,除了基础材料,什么都没加,那么这里super.price()就是水的价格
// 或已经加了其他装饰材料的总价格(基础材料+其他装饰材料的总价格)
//例如在加了牛奶之前,已经有水 + 红豆,这里super.price()就是水 + 红豆的价格
}
@Override
public String display() {
return super.display() + " + 牛奶";
//这里""中的+,表示一会显示结果中需要写 "水 + 牛奶"
}
}
class RedTea extends Decorator{
public RedTea(Component component) {
super(component);
}
@Override
public double price() {
return super.price() + 1;//这里的1是表示红茶的价格
}
@Override
public String display() {
return super.display() + " + 红茶";
}
}
class Mango extends Decorator{
public Mango(Component component) {
super(component);
}
@Override
public double price() {
return super.price() + 4;//这里的4表示芒果肉的价格
}
@Override
public String display() {
return super.display() +" + 芒果肉";
}
}
class AzukiBean extends Decorator{
public AzukiBean(Component component) {
super(component);
}
@Override
public double price() {
return super.price() + 1;//1表示红豆的价格
}
@Override
public String display() {
return super.display() + " + 红豆";
}
}
5、模板设计模式
(1)什么是模板设计模式?或什么情况下使用它?
生活中:
例如:简历模板,请假条模板,PPT模板。。。。
IDEA中的代码模板
模板:
主体的框架是确定的,然后一部分内容是由使用者来填写的。
优点:快,方便
当解决某个问题,或者完成某个功能时,主体的算法结构(步骤)是确定的,
只是其中的一个或者几个小的步骤不确定,或者某些步骤的实现和具体的环境有关,
要有使用者来确定时,就可以使用模板设计模式。
例1:
我们现在要编写一个功能:可以求xx代码的运行时间。
主体的步骤是什么?
(1)获取开始时间 start
(2)运行xx代码
(3)获取结束时间 end
(4)求时间差 (end-start)
(2)如何实现模板设计模式
A:模板类:抽象的
第一个:模板方法
解决问题的主体的算法结构,不应该让子类重写。
可以在模板方法中调用基本方法
第二个:基本方法
a:抽象方法:必须有,子类必须重写
b:具体方法(可选),如果有,有具体的实现的,子类可以重写,可以不重写
c:钩子方法(可选),如果有,一般实现为:空实现,或者有逻辑判断的实现(即根据条件不同的实现)
子类可以重写,可以不重写
B:子类,继承模板类
例2:电脑的程序
见TestTemplate2类
public class TestTemplate {
@Test
public void test01(){
//复制文件的运行时间
CopyFileTime c = new CopyFileTime();
System.out.println("耗时:" + c.getTime());
}
}
//模板类
abstract class CalTimeTemplate{
public final long getTime(){
//(1)获取开始时间 start
long start = System.currentTimeMillis();
// (2)运行xx代码:可能是求1-100的和,可能是复制文件,可能是上传文件,可能是访问数据库....
//由使用者来决定
//这里使用一个抽象方法表示
doWork();
//当我们getTime()方法被调用的时候,不是CalTimeTemplate的对象,
//而是CalTimeTemplate的子类对象调用它,这里的this是代表子类对象
//(3)获取结束时间 end
long end = System.currentTimeMillis();
//(4)求时间差 (end-start)
return end - start;
}
protected abstract void doWork();//由使用者来实现它,通过继承这个类,重写这个方法来实现它
}
//具体的使用者类
class CopyFileTime extends CalTimeTemplate{
//必须重写父类的抽象方法
@Override
protected void doWork() {
try {
//例如:求复制一个文件的时间
//复制d:\尚硅谷JavaSE每日单词.docx到d:/atguigu中
FileInputStream fis = new FileInputStream("d:\\尚硅谷JavaSE每日单词.docx");
FileOutputStream fos = new FileOutputStream("d:/atguigu\\words.docx");
byte[] data = new byte[10];
int len;
while((len = fis.read(data))!=-1){
fos.write(data,0,len);
}
fos.close();
fis.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
例2:电脑的开机流程的程序
A:模板类:抽象的
第一个:模板方法
解决问题的主体的算法结构,不应该让子类重写。
可以在模板方法中调用基本方法
第二个:基本方法
a:抽象方法:必须有,子类必须重写
b:具体方法(可选),如果有,有具体的实现的,子类可以重写,可以不重写
c:钩子方法(可选),如果有,一般实现为:空实现,或者有逻辑判断的实现(即根据条件不同的实现)
子类可以重写,可以不重写
B:子类,继承模板类
public class TestTemplate2 {
@Test
public void test01(){
//程序员电脑的开机流程
CoderComputer c = new CoderComputer();
c.startUp();
}
@Test
public void test02(){
//测试军事系统的电脑
MilitaryComputer m = new MilitaryComputer();
m.startUp();
}
}
//模板类,抽象类
abstract class AbstractComputer{
protected void powerOn() {//具体方法
System.out.println("开启电源");
}
protected void checkHardware() {//具体方法
System.out.println("检查硬件");
}
protected void loadOS() {//具体方法
System.out.println("加载操作系统");
}
protected abstract void login();//抽象方法
public void virusScan() {}//钩子方法,普通电脑不需要扫描病毒,特殊电脑可以视情况是否重写
//final表示子类不能重写
public final void startUp() {//模板方法
//第一步:开电源
powerOn();
//第二步:检查硬件
checkHardware();
//第三步:加载操作系统
loadOS();
//第四步:登录
login();
//第五步:杀毒
virusScan();
}
}
//子类:程序员电脑
class CoderComputer extends AbstractComputer{
@Override
protected void login() {
System.out.println("检查用户名和密码就可以了");
}
}
//子类:军事系统电脑
class MilitaryComputer extends AbstractComputer{
//重写具体方法
@Override
protected void checkHardware() {
super.checkHardware();//检查硬件本身
System.out.println("检查硬件防火墙");//额外增加了硬件防火墙的检查
}
//重写抽象方法
@Override
protected void login() {
System.out.println("进行指纹识别等复杂用户验证");
}
//重写钩子方法
@Override
public void virusScan() {
System.out.println("扫描病毒,并处理病毒");
}
}
6、迭代器设计模式
(1)什么情况下使用迭代器设计模式?
起源于对容器的元素的遍历。
(2)为什么要使用迭代器设计模式呢?
或者换句话说,为什么不把hasNext(),next()等迭代器的方法直接声明到集合中不是更好吗?
为什么要把它们抽象到迭代器Iterator类型中?
因为单一职责原则,接口的隔离原则等,解耦合。
我们所有的集合,数组,都具有迭代行为,虽然各自的内部实现不同,
有的是数组,有的是链表,有的是数组+链表,有的是二叉树,。。。。,
那么迭代的方式也不同,但是有迭代的需求,迭代的过程是一样的,
所以我们把他们的共同的行为特征,抽取出来,构成迭代器类型,Iterator类型。
A:boolean hasNext()
B:E next()
C:remove()
…
独立于集合接口Collection,List,Set,Map等。
在每一种集合的内部使用一个内部类来实现Iterator接口。
(1)有单独的迭代器接口java.util.Iterator接口
(2)集合内部有一个方法,可以获取迭代器对象
Iterator iterator();
(3)在集合内部有一个内部类,实现Iterator接口
外部在使用集合,并且对集合进行遍历的时候,可以不了解内部类是如何遍历集合的。
只要知道集合的 Iterator iterator(); 方法,和Iterator的几个遍历方法即可。
这样可以简化我们使用者的代码,也避免了暴露集合的内部实现的风险。
public class TestIterator {
public static void main(String[] args) {
MyArrayList<String> my = new MyArrayList<>();
my.add("hello");
my.add("java");
Iterator<String> iterator = my.iterator();
while(iterator.hasNext()){
String s = iterator.next();
System.out.println(s);
}
}
}
7、观察者设计模式
(1)什么情况下使用观察者设计模式?
一个对象的行为发生了改变,其他一个或多个对象的行为就需要自动跟着改变。
这种情况就可以使用观察者设计模式。
(2)如何实现?
角色:
A:抽象主题(Subject)角色
包含:
a:一个容器,可以装所有的观察者对象
b:提供一个方法,增加观察者
c:提供一个方法,减少观察者
d:提供一个方法,一旦变化,通知观察者
由具体的主题(被观察者)决定什么情况通知,是通知所有人,还是通知部分
B:具体主题(Concrete Subject)角色
例如:天气温度,股票,汇率,等
C:抽象观察者(Observer)角色
包含:
对被观察者的变化做出响应的方法
D:具体观察者(Concrete Observer)角色
例如:天气温度对应的具体观察者:天气预报员,普通名众,航空人员
股票价格对应的具体观察者: 股票,企业
汇率对应的具体观察者: 进出口公司等
重写响应方法,如何做出响应,每个人不同
具体的例子:
天气
public class TestObserver {
public static void main(String[] args) {
//被观察者
Temperature temperature = new Temperature();
//观察者
Weatherman weatherman = new Weatherman();
Person p1 = new Person("小贾",3);
Person p2 = new Person("小张",1);
//把观察者添加到被观察者的列表中
temperature.add(weatherman);
temperature.add(p1);
temperature.add(p2);
//被观察者发生变化了
temperature.change(10);
System.out.println("-------------------------");
temperature.change(10);
System.out.println("-------------------------");
temperature.change(10);
System.out.println("-------------------------");
temperature.change(-10);
System.out.println("$$$$$$$$$$$$$$$$$$$$$$");
temperature.remove(p2);
temperature.change(-30);
}
}
abstract class Weather{
//提供一个保存所有观察者的聚集类,例如:集合,数组等
protected List<Observer> all = new ArrayList<>();
//提供一个方法,可以添加观察者
public void add(Observer obj){
all.add(obj);
}
//提供一个方法,可以删除观察者
public void remove(Observer obj){
all.remove(obj);
}
//提供一个方法通知所有观察者
public abstract void change(int number);
}
//具体的被观察者,例如:温度,
//具体的被观察者,还可以有其他的,例如:风、颗粒物等
class Temperature extends Weather{
//温度变化时,要通知所有的观察者做出响应
@Override
public void change(int number) {
//当温度变化超过5度再通知大家
if(Math.abs(number)>5) {
for (Observer o : all) {
o.response(number);
}
}
}
}
//抽象的观察者角色
abstract class Observer{
//提供一个响应 被观察者行为变化的方法
public abstract void response(int number);
}
//具体的观察者
//例如:天气预报员
class Weatherman extends Observer{
@Override
public void response(int number) {
if(number > 0){
System.out.println("大家注意了,升温了,可以适当减少衣物,但是不要脱光");
}else if(number<0){
System.out.println("大家注意了,降温了,可以注意保暖,增加衣物,特别是秋裤");
}
}
}
//例如:普通名众
class Person extends Observer{
private String name;
private int count;
public Person(String name, int count) {
this.name = name;
this.count = count;
}
@Override
public void response(int number) {
if(number > 0){
if(count>1) {
count--;
System.out.println(name + "减少一件衣服,现在有" + count +"件衣服");
}else{
System.out.println(name + "不能再脱了,打开风扇或打开空调,或者到游泳池降降温");
}
}else if(number<0){
count++;
System.out.println(name + "增加一件衣服,现在有" + count +"件衣服");
}
}
}
一、lambda表达式
1、通过lambda表达式引入函数式编程思想
数学中的函数:y = f(x);
根据x的不同,y的值也不同。
代码中的函数,就是方法:【修饰符】 返回值类型 方法名(【形参列表】){…}
调用它,执行xx功能,有的时候,给形参赋不同的值,执行的代码也不同。
有的方法有返回值,得到的返回值也不同。
我们关注这个方法做什么,不关注如何做,用什么形式做。
例如:Math.sqrt(x),求平方根,传入x的值,就得到平方根,关注的是功能结果。
之前面向对象的编程思想,特别强调对象,用哪个对象完成,必须new对象。
函数式编程思想,只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程。
Java与时俱进,引入了函数式编程思想,通过lambda表达式的语法。
2、演示lambda表达式
发现:lambda表达式可以简化匿名内部类的代码
Lambda写的好可以极大的减少代码冗余,同时可读性也好过冗长的匿名内部类。
3、什么情况下才能使用lambda表达式?
是否是所有的情况都可以用匿名内部类? 不是
是不是所有的匿名内部类都可以使用lambda表达式? 不是
lambda表达式它只适用于给SAM接口的变量、形参赋值。
SAM接口:Single abstract Method,只有一个抽象方法的接口。
(非抽象方法的个数随意)
4、我们之前学过的接口中,哪些符合SAM接口的特征呢?
(1)java.util.Comparator:
int compare(T t1, T t2)
(2)java.lang.Comparable
int compareTo(T t1)
(3)java.lang.Iterable
Iterator iterator();
(4)java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)
(5)java.lang.Runnable:
void run()
(6)java.io.FileFilter:
boolean accept(File pathname)
但是,我们Java8说,建议我们只对标记了@FunctionalInterface注解的SAM接口使用Lambda表达式。
@FunctionalInterface: 函数式接口
上面的接口中标记了@FunctionalInterface的有
(1)java.util.Comparator:int compare(T t1, T t2)
(2)java.lang.Runnable:void run()
(3)java.io.FileFilter:boolean accept(File pathname)
没有标记@FunctionalInterface,一般不需要也不建议使用Lambda表达式。
5、我们也可以自己定义一个函数式接口
public class TestLambda {
@Test
public void test01(){
System.out.println(Math.sqrt(9));
Integer obj = new Integer(16);
System.out.println(Math.sqrt(obj));
}
@Test
public void test02(){
//启动一个线程,在这个线程中,打印"hello"
//这里我选择使用实现Runnable接口的方式
//这里用了匿名内部类
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
//启动线程
Thread thread = new Thread(runnable);
thread.start();
}
@Test
public void test03(){
//启动一个线程,在这个线程中,打印"hello"
//这里我选择使用实现Runnable接口的方式
//这里用了lambda表达式
Runnable runnable = () -> System.out.println("hello");
//启动线程
Thread thread = new Thread(runnable);
thread.start();
}
@Test
public void test04(){
//有一个字符串数组
String[] strings = {"hello","atguigu","Java","Chailinyan"};
//对它们进行排序,并且是不区分大小写的排序
// Arrays.sort(strings);//自然排序,使用String的compareTo方法,区分大小写的
// Arrays.sort(strings, Comparator 的实现类对象);//定制比较器
//这里使用匿名内部类方式
Arrays.sort(strings, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
System.out.println(Arrays.toString(strings));
}
@Test
public void test05() {
//有一个字符串数组
String[] strings = {"hello", "atguigu", "Java", "Chailinyan"};
//使用Lambda表达式实现
Arrays.sort(strings, (s1,s2)->s1.compareToIgnoreCase(s2));
System.out.println(Arrays.toString(strings));
}
@Test
public void test06() {
//对我们自己定义的MyInter接口,使用Lambda表达式
//匿名内部类:打印str
MyInter my = new MyInter(){
@Override
public void method(String str) {
System.out.println(str);
}
};
my.method("元宵节快乐!");
//使用Lambda表达式
MyInter my2 = str ->System.out.println(str);
my2.method("大家记得吃汤圆!");
}
}
@FunctionalInterface
interface MyInter{
void method(String str);
// void fun();
}
二、函数式接口
1、之前我们使用过的接口中,标记了 @FunctionalInterface注解的函数式接口有
(1)java.util.Comparator:int compare(T t1, T t2)
(2)java.lang.Runnable:void run()
(3)java.io.FileFilter: boolean accept(String pathName)
2、可以自定义函数式接口
@FunctionalInterface
【修饰符】 interface 接口名{
一个抽象方法
}
3、在Java8中新增了一批函数式接口
在java.util.function中提供了很多的函数式接口。
这个包中的函数式接口提供了lambda表达式和方法引用的目标类型。
分为四大类:
(1)消费型接口
这类接口的特征:
它们的抽象方法是有参无返回值的。
调用这个方法,需要提供实参,但是得不到返回值
例如:
Consumer
void accept(T t)
BiConsumer<T,U>
void accept(T t, U u)
(2)供给型接口
这类接口的特征:
它们的抽象方法是 无参有返回值
调用这个方法,不需要提供实参,却可以得到返回值。空手套白狼,白嫖
例如:
Supplier
T get()
IntSupplier
int getAsInt()
(3)判断型接口
这类接口的特征:
它们的抽象方法是 有参有返回值,并且返回值类型一定是boolean类型。
例如:
Predicate
boolean test(T t)
IntPredicate
boolean test(int value)
BiPredicate<T,U>
boolean test(T t, U u)
(4)功能型接口
这类接口的特征:
它们的抽象方法是 有参有返回值
例如:
Function<T,R>
R apply(T t)
BiFunction<T,U,R>
R apply(T t, U u)
BinaryOperator
T apply(T t, T u)
4、思考?
我再讲函数式接口的时候,一直让大家关注:
接口名、抽象方法(形参和返回值类型),不需要记抽象方法的名称。
因为我们的lambda表达式在写的时候,就需要关注这些:接口名、抽象方法(形参和返回值类型)
三、Lambda表达式的语法
1、语法
(形参列表) -> {Lambda体}
lambda表达式是用于给函数式接口的变量、形参赋值用的。
本质上,lambda表达式是在重写函数式接口的抽象方法。
因此:
lambda表达式的形参列表,必须与 函数式接口的形参列表一致。
lambda表达式的{Lambda体},就是重写这个抽象方法的方法体。
->:是lambda表达式的操作符,中间不能有空格,由-(减号)和>(大于号)组成,英文状态下输入。
如果抽象方法的返回值类型不是void,在{Lambda体}中必须返回一个对应类型的结果,否则编译不通过。
这就是为什么我们刚才让大家关注函数式接口的抽象方法的形参列表和返回值类型的原因。
2、如何编写lambda表达式?
要写好Lambda表达式,依次解决两个问题?
(1)接口的抽象方法是什么样的,主要关注(形参列表和返回值类型)
例如:Runnable接口的抽象方法 void run()
(2)实现抽象方法要完成什么事?
例如:这里在线程中打印"hello"
Runnable runnable = () -> {
System.out.println("hello");
};
3、可以优化lambda表达式?
(1)当{Lambda体}只有一个语句时,可以省略{},以及{}里面的;
如果一个语句是return语句,当省略{}和;之后,return也可以省略。
(2)注意,空()不能省略,它是表示抽象方法的空参列表
(3)当方法的形参列表的类型是确定的,或者可以推断(泛型自动推断),可以省略数据类型
此时,如果形参不止一个,()不能省略;
如果省略了数据类型,形参只有一个,()可以省略
public class TestLambdaGrammer {
@Test
public void test01(){
//用实现Runnable接口的方式,启动一个线程,在线程中打印"hello"
/*
要写好Lambda表达式,依次解决两个问题?
(1)接口的抽象方法是什么样的,主要关注(形参列表和返回值类型)
Runnable接口的抽象方法 void run()
(2)实现抽象方法要完成什么事?
这里在线程中打印"hello"
*/
//(形参列表) -> {Lambda体}
/*Runnable runnable = () -> {
System.out.println("hello");
};*/
//优化
// Runnable runnable = () -> System.out.println("hello");
// Thread thread = new Thread(runnable);
//可以在优化
// Thread thread = new Thread(() -> System.out.println("hello"));
// thread.start();
//继续省略
new Thread(() -> System.out.println("hello")).start();
}
@Test
public void test02() {
String[] strings = {"hello","atguigu","Java","Chailinyan"};
//使用Arrays.sort方法对strings数组进行排序,并且不区分字符串大小写字母的排序
/*
我们要调用的是java.util.Arrays工具类:
void sort(T[] a, Comparator<? super T> c)
我们现在要用lambda表达式给sort方法的形参Comparator<? super T> c赋值
要写好Lambda表达式,依次解决两个问题?
(1)接口的抽象方法是什么样的,主要关注(形参列表和返回值类型)
Comparator接口的抽象方法 int compare(T t1, T t2)
(2)实现抽象方法要完成什么事?
比较数组中两个字符串的大写,并且不区分大小写
这里的t1,t2就是代表数组的元素。
t1.compareToIgnoreCase(t2)
*/
// (形参列表) -> {Lambda体}
//int compare(T t1, T t2)
//(String t1, String t2) -> { return t1.compareToIgnoreCase(t2);};
// Arrays.sort(strings, (String t1, String t2) -> { return t1.compareToIgnoreCase(t2);});
//简化(1)当{lambda体}只有一个语句,可以省略{}和;,如果还有return,一并省略
// Arrays.sort(strings, (String t1, String t2) -> t1.compareToIgnoreCase(t2));
//再简化(2)当方法的形参列表的类型是确定的,或者可以推断(泛型自动推断),可以省略数据类型
// Arrays.sort(strings, (t1, t2) -> t1.compareToIgnoreCase(t2));
Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));//形参名不能省略,但是可以自己定,前面()中用什么,后面就用什么
System.out.println(Arrays.toString(strings));
}
@Test
public void test3(){
//数组工具类Arrays中有一个方法
//static <T> List<T> asList(T... a)
List<String> list = Arrays.asList("hello", "java", "world");
//现在我们要遍历这个集合
/*
List是继承Collection接口的,Collection是继承Iterable接口
Java8在java.lang.Iterable接口中增加了一个默认方法:
default void forEach(Consumer<? super T> action)
它的形参是Consumer接口,它是我们的函数式接口的代表之一,消费型接口。
这个方法的作用,就是遍历集合,对集合的元素做xx操作,这个操作由给形参Consumer<? super T> action赋值的lambda表达式指定。
例如:我这里要遍历集合,打印元素。
要用lambda表达式为forEach方法的形参Consumer<? super T> action赋值
(1)Consumer接口的抽象方法void accept(T t)
(2)要做什么呢?
打印元素
void accept(T t),这里的T就是集合的元素类型
(形参列表) -> {Lambda体}
*/
/* list.forEach((String element) -> {
System.out.println(element);
});*/
//简化
list.forEach(element -> System.out.println(element));
}
}
四大类函数式接口:
1、消费型接口:
抽象方法的特征: 有参无返回值
典型代表: Consumer<T> void accept(T t)
BiConsumer<T,U> void accept(T t, U u)
应用举例:
Collection系列的集合有一个forEach方法
Map系列的集合也有一个forEach方法
2、供给型接口
抽象方法的特征: 无参有返回值
典型代表:
Supplier<T> T get()
3、判断性接口
抽象方法的特征:有参有返回值,返回值的类型是boolean
典型代表:
Predicate<T> boolean test(T t)
4、功能型接口
抽象方法的特征:有参有返回值
典型代码:
Function<T,R> R apply(T t)
public class TestLambdaDemo {
@Test
public void test01(){
HashSet<String> set = new HashSet<>();
set.add("hello");
set.add("world");
//forEach方法的形参类型:Consumer
set.forEach(t-> System.out.println(t));
}
@Test
public void test02(){
/*
Java8在java.util.Map接口中,增加了一个默认方法:
default void forEach(BiConsumer<? super K,? super V> action)
*/
HashMap<String,String> map = new HashMap<>();
map.put("小贾","晓爽");
map.put("小张","沈飞飞");
//BiConsumer<T,U> 抽象方法 void accept(T t, U u)
//这里t,u在map中代表key,value;
map.forEach((key,value) -> System.out.println(key + "喜欢" + value));
}
@Test
public void test03(){
/*
在Java中有一个新的类型Stream,它是表示流,这个流不是IO流,它是数据加工流
java.util.stream.Stream类有一个方法:
static <T> Stream<T> generate(Supplier<T> s)
这个方法的作用是由为Supplier接口的形参s赋值的lambda表达式提供流中的数据
Supplier接口的抽象方法 T get()
*/
//由Math.random()的方法提供随机数给Stream流
// Stream.generate(()-> {return Math.random();});
//优化
Stream<Double> stream = Stream.generate(() -> Math.random());
//遍历流中 数据
/*
java.util.stream.Stream类有一个方法:
void forEach(Consumer<? super T> action)
*/
stream.forEach(t-> System.out.println(t));
}
@Test
public void test04(){
/*
之前讲集合的时候,集合中的元素要根据条件删除,必须用Iterator迭代器
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//删除其中的偶数
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer number = iterator.next();
if(number % 2 ==0 ){
iterator.remove();
}
}
System.out.println(list);
}
@Test
public void test05(){
/*
JDK1.8在集合中增加了新的默认方法
default boolean removeIf(Predicate<? super E> filter)
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
/*
用lambda表达式给removeIf方法的形参Predicate filter赋值。
Predicate接口的抽象方法 boolean test(T t)
*/
// list.removeIf(t -> {return t%2==0;});
list.removeIf(t -> t%2==0);
System.out.println(list);
}
@Test
public void test06(){
/*
Java8中的java.util.Map中增加了默认方法:
default void replaceAll(BiFunction<? super K,? super V,? extends V> function)
要使用lambda表达式为形参 BiFunction function赋值。
BiFunction接口是功能型接口,它的抽象方法是 R apply(T t, U u)
这里的t,u分别代表map中的key和vaule,返回值是替换原来的value的新值
*/
HashMap<String,String> map = new HashMap<>();
map.put("小贾","晓爽");
map.put("小张",null);
map.put("小乔",null);
//要替换原来map中value为null的(key,value)键值对的value为“如花”
// map.replaceAll((key,value) ->{ return value==null?"如花":value;});
map.replaceAll((key,value) -> value==null?"如花":value);
System.out.println(map);
}
}
/*
模拟Collection集合中的forEach方法
*/
public class TestLambdaWork1 {
public static void main(String[] args) {
MyArrayList<String> list = new MyArrayList<>();
list.add("hello");
list.add("world");
//public void forEach(Consumer<E> consumer)
//Consumer接口的抽象方法:
// void accept(T t)
list.forEach(t-> System.out.println(t));
//lambda表达式的lambda体:System.out.println(t)
//就是实现Consumer接口的抽象方法accept的方法体
}
}
class MyArrayList<E>{
private Object[] all = new Object[5];
private int size;
public void add(E e){
if(size>=all.length){
all = Arrays.copyOf(all,all.length*2);
}
all[size++] = e;
}
//其他方法省略
public void forEach(Consumer<E> consumer){
for (int i = 0; i < size; i++) {
//对集合中的每一个元素,执行consumer指定的操作
//这个操作就是外面的lambda表达式指定的
//例如:accept执行的就是 System.out.println(t)
//这里的all[i]就是上面的t
consumer.accept((E) all[i]);
}
}
}
/*
模拟集合的removeIf等方法
说明Predicate接口的工作情况
Predicate接口 boolean test(T t)
*/
public class TestLambdaWork2 {
public static void main(String[] args) {
EmployeeService es = new EmployeeService();
System.out.println("删除之前:");
es.forEach(t -> System.out.println(t));
//例如:删除年龄超过30岁
es.removeIf(t -> t.getAge()>30);
System.out.println("删除之后:");
es.forEach(t -> System.out.println(t));
System.out.println("--------------------");
//查询薪资高于10000
List<Employee> select = es.select(t -> t.getSalary() > 10000);
System.out.println(select);
System.out.println("--------------------");
//查询薪资高于20000
List<Employee> select2 = es.select(t -> t.getSalary() > 20000);
System.out.println(select2);
}
}
//员工管理类
class EmployeeService{
//这里用一个集合存储员工信息,模拟存储到数据库中的数据
private ArrayList<Employee> list = new ArrayList<>();
public EmployeeService(){
list.add(new Employee("小贾",25,12000));
list.add(new Employee("小张",35,13000));
list.add(new Employee("小高",26,20000));
}
//省略了其他方法
public void removeIf(Predicate<Employee> predicate){
//遍历list集合,删除满足条件的元素
Iterator<Employee> iterator = list.iterator();
while (iterator.hasNext()){
Employee employee = iterator.next();
if(predicate.test(employee)){
iterator.remove();
}
}
}
public void forEach(Consumer<Employee> consumer){
for (Employee employee : list) {
consumer.accept(employee);
}
}
public List<Employee> select(Predicate<Employee> predicate){
ArrayList<Employee> result = new ArrayList<>();
for (Employee employee : list) {
if(predicate.test(employee)){
result.add(employee);
}
}
return result;
}
}
class Employee{
private String name;
private int age;
private double salary;
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
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 double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
五、方法引用与构造器引用
1、方法引用和构造器引用的语法是用于对lambda表达式进行简化的
Lambda表达式是简化原来匿名内部类的写法。(有条件,只有函数式接口才可以)
方法引用和构造器引用是简化Lambda表达式的。(也有条件,只有满足下面的条件才可以)
2、什么情况下可以再次简化?
(1)Lambda体只有一个语句,并且这个语句是通过调用一个类或一个对象的现有的方法来完成的。
例如:
System.out.println(t)
Math.random()
s1.compareTo(s2)
以下不行:
employee.getAge > 30
num % 2 == 0
(2)并且lambda表达式的形参列表中的所有形参都被用上了,
可能是作为在lambda体中调用方法的对象,或者是所调用方法的实参。
t -> System.out.println(t)
(s1,s2) -> s1.compareTo(s2)
() -> Math.random()
(3)不能多出在lambda表达式的形参列表中没有出现的数据
例如:
以下不行
() -> System.out.println(“hello”);
3、演示
4、方法引用的语法格式
类名/对象 :: 方法名
例如:
System.out::println
String::compareToIgnoreCase‘
Math::random
5、构造器引用
构造器引用是一种特殊的方法引用。
当lambda体是一句语句,并且是一句new对象的语句。
lambda表达式的形参列表中的所有形参都被用于构造器的实参。
语法格式:
类名 :: new
public class TestReference {
@Test
public void test01(){
List<String> list = Arrays.asList("hello", "java", "atguigu");
// list.forEach(t -> System.out.println(t));
//使用方法引用再次简化
list.forEach(System.out::println);
//forEach遍历,遍历list集合,干什么呢,打印它
}
@Test
public void test02(){
String[] strings = {"hello","atguigu","Java","Chailinyan"};
// Arrays.sort(strings, (s1,s2) -> s1.compareToIgnoreCase(s2));
//使用方法引用再次简化
Arrays.sort(strings, String::compareToIgnoreCase);
//翻译,排序strings数组,使用String类的compareToIgnoreCase方法来比较
System.out.println(Arrays.toString(strings));
}
@Test
public void test03(){
// Stream.generate(() -> Math.random())
// .forEach(num -> System.out.println(num));
Stream.generate(Math::random).forEach(System.out::println);
}
@Test
public void test04(){
//演示一个Function接口的应用
ArrayList<String> list = new ArrayList<>();
list.add("小贾");
list.add("小张");
// Function R apply(T t)
// List<Student> students = change(list, s -> new Student(s));
//使用构造器引用简化
List<Student> students = change(list, Student::new);
System.out.println(students);
}
public List<Student> change(List<String> list, Function<String,Student> function){
ArrayList<Student> result = new ArrayList<>();
for (String s : list) {
result.add(function.apply(s));
}
return result;
}
}
class Student{
private String name;
public Student(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}