最新Java基础系列课程--Day13-高级特性(二)https://developer.aliyun.com/article/1423543
五、方法引用
各位小伙伴,接下来我们学习JDK8的另一个新特性,叫做方法引用。我们知道Lambda是用来简化匿名代码的书写格式的,而方法引用是用来进一步简化Lambda表达式的,它简化的更加过分。
给大家交代清楚了,学习方法引用可能存在的一些心理特点之后,接下来我们再正式学习方法引用的代码怎么编写。
5.1 静态方法引用
我们先学习静态方法的引用,还是用之前Arrays代码来做演示。现在准备好下面的代码
public class Test1 { public static void main(String[] args) { Student[] students = new Student[4]; students[0] = new Student("蜘蛛精", 169.5, 23); students[1] = new Student("紫霞", 163.8, 26); students[2] = new Student("紫霞", 163.8, 26); students[3] = new Student("至尊宝", 167.5, 24); // 原始写法:对数组中的学生对象,按照年龄升序排序 Arrays.sort(students, new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getAge() - o2.getAge(); // 按照年龄升序排序 } }); // 使用Lambda简化后的形式 Arrays.sort(students, (o1, o2) -> o1.getAge() - o2.getAge()); } }
现在,我想要把下图中Lambda表达式的方法体,用一个静态方法代替
准备另外一个类CompareByData类,用于封装Lambda表达式的方法体代码;
public class CompareByData { public static int compareByAge(Student o1, Student o2){ return o1.getAge() - o2.getAge(); // 升序排序的规则 } }
现在我们就可以把Lambda表达式的方法体代码,改为下面的样子
Arrays.sort(students, (o1, o2) -> CompareByData.compareByAge(o1, o2));
Java为了简化上面Lambda表达式的写法,利用方法引用可以改进为下面的样子。**实际上就是用类名调用方法,但是把参数给省略了。**这就是静态方法引用
//静态方法引用:类名::方法名 Arrays.sort(students, CompareByData::compareByAge);
5.2 实例方法引用
还是基于上面的案例,我们现在来学习一下实例方法的引用。现在,我想要把下图中Lambda表达式的方法体,用一个实例方法代替。
在CompareByData类中,再添加一个实例方法,用于封装Lambda表达式的方法体
接下来,我们把Lambda表达式的方法体,改用对象调用方法
CompareByData compare = new CompareByData(); Arrays.sort(students, (o1, o2) -> compare.compareByAgeDesc(o1, o2)); // 降序
最后,再将Lambda表达式的方法体,直接改成方法引用写法。实际上就是用类名调用方法,但是省略的参数。这就是实例方法引用
CompareByData compare = new CompareByData(); Arrays.sort(students, compare::compareByAgeDesc); // 降序
给小伙伴的寄语:一定要按照老师写的步骤,一步一步来做,你一定能学会的!!!
5.3 特定类型的方法引用
各位小伙伴,我们继续学习特定类型的方法引用。在学习之前还是需要给大家说明一下,这种特定类型的方法引用是没有什么道理的,只是语法的一种约定,遇到这种场景,就可以这样用。
Java约定: 如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数作为方法的主调, 后面的所有参数都是作为该实例方法的入参时,则就可以使用特定类型的方法引用。 格式: 类型::方法名
public class Test2 { public static void main(String[] args) { String[] names = {"boby", "angela", "Andy" ,"dlei", "caocao", "Babo", "jack", "Cici"}; // 要求忽略首字符大小写进行排序。 Arrays.sort(names, new Comparator<String>() { @Override public int compare(String o1, String o2) { // 制定比较规则。o1 = "Andy" o2 = "angela" return o1.compareToIgnoreCase(o2); } }); //lambda表达式写法 Arrays.sort(names, ( o1, o2) -> o1.compareToIgnoreCase(o2) ); //特定类型的方法引用! Arrays.sort(names, String::compareToIgnoreCase); System.out.println(Arrays.toString(names)); } }
5.4 构造器引用
各位小伙伴,我们学习最后一种方法引用的形式,叫做构造器引用。还是先说明一下,构造器引用在实际开发中应用的并不多,目前还没有找到构造器的应用场景。所以大家在学习的时候,也只是关注语法就可以了。
现在,我们准备一个JavaBean类,Car类
public class Car { private String name; private double price; public Car() { } public Car(String name, double price) { this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getPrice() { return price; } public void setPrice(double price) { this.price = price; } @Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
因为方法引用是基于Lamdba表达式简化的,所以也要按照Lamdba表达式的使用前提来用,需要一个函数式接口,接口中代码的返回值类型是Car类型
interface CreateCar{ Car create(String name, double price); }
最后,再准备一个测试类,在测试类中创建CreateCar接口的实现类对象,先用匿名内部类创建、再用Lambda表达式创建,最后改用方法引用创建。同学们只关注格式就可以,不要去想为什么(语法就是这么设计的)。
public class Test3 { public static void main(String[] args) { // 1、创建这个接口的匿名内部类对象。 CreateCar cc1 = new CreateCar(){ @Override public Car create(String name, double price) { return new Car(name, price); } }; //2、使用匿名内部类改进 CreateCar cc2 = (name, price) -> new Car(name, price); //3、使用方法引用改进:构造器引用 CreateCar cc3 = Car::new; //注意:以上是创建CreateCar接口实现类对象的几种形式而已,语法一步一步简化。 //4、对象调用方法 Car car = cc3.create("奔驰", 49.9); System.out.println(car); } }
六、动态代理
6.1 动态代理介绍、准备功能
各位同学,这节课我们学习一个Java的高级技术叫做动态代理。首先我们认识一下代理长什么样?我们以大明星“杨超越”例。
假设现在有一个大明星叫杨超越,它有唱歌和跳舞的本领,作为大明星是要用唱歌和跳舞来赚钱的,但是每次做节目,唱歌的时候要准备话筒、收钱,再唱歌;跳舞的时候也要准备场地、收钱、再唱歌。杨超越越觉得我擅长的做的事情是唱歌,和跳舞,但是每次唱歌和跳舞之前或者之后都要做一些繁琐的事情,有点烦。于是杨超越就找个一个经济公司,请了一个代理人,代理杨超越处理这些事情,如果有人想请杨超越演出,直接找代理人就可以了。如下图所示
我们说杨超越的代理是中介公司派的,那中介公司怎么知道,要派一个有唱歌和跳舞功能的代理呢?
解决这个问题,Java使用的是接口,杨超越想找代理,在Java中需要杨超越实现了一个接口,接口中规定要唱歌和跳舞的方法。Java就可以通过这个接口为杨超越生成一个代理对象,只要接口中有的方法代理对象也会有。
接下来我们就先把有唱歌和跳舞功能的接口,和实现接口的大明星类定义出来。
6.2 生成动态代理对象
下面我们写一个为BigStar生成动态代理对象的工具类。这里需要用Java为开发者提供的一个生成代理对象的类叫Proxy类。
通过Proxy类的newInstance(…)方法可以为实现了同一接口的类生成代理对象。 调用方法时需要传递三个参数,该方法的参数解释可以查阅API文档,如下。
public class ProxyUtil { public static Star createProxy(BigStar bigStar){ /* newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 参数1:用于指定一个类加载器 参数2:指定生成的代理长什么样子,也就是有哪些方法 参数3:用来指定生成的代理对象要干什么事情 */ // Star starProxy = ProxyUtil.createProxy(s); // starProxy.sing("好日子") starProxy.dance() Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(), new Class[]{Star.class}, new InvocationHandler() { @Override // 回调方法 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 代理对象要做的事情,会在这里写代码 if(method.getName().equals("sing")){ System.out.println("准备话筒,收钱20万"); }else if(method.getName().equals("dance")){ System.out.println("准备场地,收钱1000万"); } return method.invoke(bigStar, args); } }); return starProxy; } }
调用我们写好的ProxyUtil工具类,为BigStar对象生成代理对象
public class Test { public static void main(String[] args) { BigStar s = new BigStar("杨超越"); Star starProxy = ProxyUtil.createProxy(s); String rs = starProxy.sing("好日子"); System.out.println(rs); starProxy.dance(); } }
运行测试类,结果如下图所示
恭喜同学们,当你把上面的案例写出来,并且理解,那么动态代理的基本使用就学会了。
6.3 动态代理应用
学习完动态代理的基本使用之后,接下来我们再做一个应用案例。
现有如下代码
/** * 用户业务接口 */ public interface UserService { // 登录功能 void login(String loginName,String passWord) throws Exception; // 删除用户 void deleteUsers() throws Exception; // 查询用户,返回数组的形式。 String[] selectUsers() throws Exception; }
下面有一个UserService接口的实现类,下面每一个方法中都有计算方法运行时间的代码。
/** * 用户业务实现类(面向接口编程) */ public class UserServiceImpl implements UserService{ @Override public void login(String loginName, String passWord) throws Exception { long time1 = System.currentTimeMillis(); if("admin".equals(loginName) && "123456".equals(passWord)){ System.out.println("您登录成功,欢迎光临本系统~"); }else { System.out.println("您登录失败,用户名或密码错误~"); } Thread.sleep(1000); long time2 = System.currentTimeMillis(); System.out.println("login方法耗时:"+(time2-time1)); } @Override public void deleteUsers() throws Exception{ long time1 = System.currentTimeMillis(); System.out.println("成功删除了1万个用户~"); Thread.sleep(1500); long time2 = System.currentTimeMillis(); System.out.println("deleteUsers方法耗时:"+(time2-time1)); } @Override public String[] selectUsers() throws Exception{ long time1 = System.currentTimeMillis(); System.out.println("查询出了3个用户"); String[] names = {"张全蛋", "李二狗", "牛爱花"}; Thread.sleep(500); long time2 = System.currentTimeMillis(); System.out.println("selectUsers方法耗时:"+(time2-time1)); return names; } }
观察上面代码发现有什么问题吗?
我们会发现每一个方法中计算耗时的代码都是重复的,我们可是学习了动态代理的高级程序员,怎么能忍受在每个方法中写重复代码呢!况且这些重复的代码并不属于UserSerivce的主要业务代码。
所以接下来我们打算,把计算每一个方法的耗时操作,交给代理对象来做。
先在UserService类中把计算耗时的代码删除,代码如下
/** * 用户业务实现类(面向接口编程) */ public class UserServiceImpl implements UserService{ @Override public void login(String loginName, String passWord) throws Exception { if("admin".equals(loginName) && "123456".equals(passWord)){ System.out.println("您登录成功,欢迎光临本系统~"); }else { System.out.println("您登录失败,用户名或密码错误~"); } Thread.sleep(1000); } @Override public void deleteUsers() throws Exception{ System.out.println("成功删除了1万个用户~"); Thread.sleep(1500); } @Override public String[] selectUsers() throws Exception{ System.out.println("查询出了3个用户"); String[] names = {"张全蛋", "李二狗", "牛爱花"}; Thread.sleep(500); return names; } }
然后为UserService生成一个动态代理对象,在动态代理中调用目标方法,在调用目标方法之前和之后记录毫秒值,并计算方法运行的时间。代码如下
public class ProxyUtil { public static UserService createProxy(UserService userService){ UserService userServiceProxy = (UserService) Proxy.newProxyInstance( ProxyUtil.class.getClassLoader(), new Class[]{UserService.class}, new InvocationHandler() { @Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { if( method.getName().equals("login") || method.getName().equals("deleteUsers")|| method.getName().equals("selectUsers")){ //方法运行前记录毫秒值 long startTime = System.currentTimeMillis(); //执行方法 Object rs = method.invoke(userService, args); //执行方法后记录毫秒值 long endTime = System.currentTimeMillis(); System.out.println(method.getName() + "方法执行耗时:" + (endTime - startTime)/ 1000.0 + "s"); return rs; }else { Object rs = method.invoke(userService, args); return rs; } } }); //返回代理对象 return userServiceProxy; } }
在测试类中为UserService创建代理对象
/** * 目标:使用动态代理解决实际问题,并掌握使用代理的好处。 */ public class Test { public static void main(String[] args) throws Exception{ // 1、创建用户业务对象。 UserService userService = ProxyUtil.createProxy(new UserServiceImpl()); // 2、调用用户业务的功能。 userService.login("admin", "123456"); System.out.println("----------------------------------"); userService.deleteUsers(); System.out.println("----------------------------------"); String[] names = userService.selectUsers(); System.out.println("查询到的用户是:" + Arrays.toString(names)); System.out.println("----------------------------------"); } }
执行结果如下图所示
动态代理对象的执行流程如下图所示,每次用代理对象调用方法时,都会执行InvocationHandler中的invoke方法。