Java教程

从Java匿名内部类深入到JDK8的Lombda表达式入门

Java基础 小海豚博客管理员 2020-03-19 14:21:17.0 386 0条

Lombda表达式是用简洁的代码创建只有一个抽象方法的接口(函数式接口),在这之前我们都是用匿名内部类创建的。

命令模式

考虑这样一种场景:
某个方法需要完成某一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组元素时如何处理这些元素,需要在调用该方法时指定具体的处理行为。这个要求看起来有点奇怪:这个方法不仅需要普通数据可以变化,甚至还有方法执行体也需要变化,难道需要把“处理行为”作为一个参数传入该方法?

对于这样一个需求,必须把“处理行为”作为参数传入该方法,这个“处理行为”用编程来实现就是一段代码。那如何把这段代码传入该方法呢? 可以定义一个接口 让这个接口的方法来封装这些行为。如:

  1. public interface Command {
  2. void process(int[]target)
  3. }

这个方法没有方法体,因为现在还无法确定这个处理行为。下面是需要处理数组的处理类,在这个处理类中包含一个process方法,这个方法无法确定处理数组的处理行为,所以定义该方法时使用了一个Commamnd参数,这个Command参数负责对数组的处理行为。该类的程序代码如下:

  1. public class ProcessArray {
  2. public void process(int[]target,Command cmd){
  3. cmd.process(target)
  4. }
  5. }

通过这个Command接口 就可以让ProcessArray的处理行为和数据分离了,具体实现我们只要存入具体实现的接口就行了

  1. public class CommandTest {
  2. public static void main(String[]args){
  3. int [] target ={1,-1,3,6};
  4. ProcessArray pa=new ProcessArray()
  5. /**第一次处理数组,具体处理行为取决于Printcommand*/
  6. pa.process(target,new Printcommand());
  7. /**第二次处理数组,具体处理行为取决于AddCommand*/
  8. pa.process(targetnew AddCommand())
  9. }
  10. }

Printcommand的实现:

  1. public class PrintCommand implements Command {
  2. public void process(int[]target){
  3. for(int tmp: target){
  4. System.out.pringlntmp
  5. }
  6. }
  7. }

AddCommand的实现:

  1. public class AddCommand implements Command {
  2. public void process(int[]target){
  3. int sum =0;
  4. for(int tmp: target){
  5. sum +=tmp
  6. System.out.pringlnsum
  7. }
  8. }
  9. }

对于AddCommand和PrintCommand2个实现类,最有意义的就是process(int[]target),而这个方法里封装的就是我们要传入的处理行为。但是我们有时候这些行为只用一次,而然还要写几个实现类创建对象不是很麻烦吗?于是可以用匿名内部类。

匿名内部类

匿名内部类适合创建那种只需要一次使用的类例如前面介绍命令模式时所需要的Command对象。匿名内部类的语法有点奇怪,创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用。定义匿名内部类的格式如下:

  1. new 实现接口()|父类构造器(实参列表)
  2. {
  3. 类体部分
  4. }

从上面定义可以看出,匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类,或实现一个接口。

如上面的程序我们可以改成这样,我们就不用实现具体类

  1. public class CommandTest {
  2. public static void main(String[]args){
  3. int [] target ={1,-1,3,6};
  4. ProcessArray pa=new ProcessArray()
  5. /**第一次处理数组 */
  6. pa.process(target,new Command(){
  7. public void process(int[]target){
  8. for(int tmp: target){
  9. System.out.pringlntmp
  10. }
  11. }
  12. });
  13. /**第而次处理数组 */
  14. pa.process(target,new Command(){
  15. public void process(int[]target){
  16. int sum =0;
  17. for(int tmp: target){
  18. sum +=tmp
  19. System.out.pringlnsum
  20. }
  21. }
  22. });
  23. }
  24. }

当创建匿名类时,必须实现所有抽象方法,匿名内部类主要用在只要用一次的时候,其次他不能再次时抽象方法,要实现所有接口的方法,当然也可以实现接口的默认方法(JDK8) ,因为我们要创建对象,然后他不能自定义构造方法,因为没有类名,所有只能用默认的构造方法

注意:在JDK8之前,我们在使用匿名内部类方法局部变量时,我们设置为final,但是JDK8后会自动给我们加上。

Lombda表达式入门

Lombda表达式是用简洁的代码创建只有一个抽象方法的接口(函数式接口)实现类的对象,如:
上面的匿名程序可以改成这样

  1. public class CommandTest {
  2. public static void main(String[]args){
  3. int [] target ={1,-1,3,6};
  4. ProcessArray pa=new ProcessArray()
  5. /**第一次处理数组 */
  6. pa.process(target,x->{
  7. for(int tmp: x){
  8. System.out.pringlntmp
  9. }
  10. });
  11. /**第而次处理数组 */
  12. pa.process(target,x->{
  13. int sum =0;
  14. for(int tmp: x){
  15. sum +=tmp
  16. System.out.pringlnsum
  17. }
  18. })
  19. }
  20. }

从这段代码来看,使用Lombda来代替匿名匿名类创建对象时,将代替的是匿名内部类的方法体,所以Lombda只能代替一个方法体。于是我们可以得出lambda要的只有一个抽象方法的接口,不然实现不了其他方法,于是还是抽象类不能创建对象。

Lombda表达式的组成

  1. () 这个是形参的部分,由于getName方法不用参数。就是一个括号就行
  2. -> 必须要用英文的-和>组成
  3. {} 这个是方法要实现的代码快

Lombda表达式几种简写的方式

1.当只有一个参数时,我们可以去掉()

  1. interface Dog{
  2. void getName(String name);
  3. }
  4. public class Test{
  5. //用Dog作为参数
  6. public void testDog(Dog d){
  7. d.getColor();
  8. }
  9. public static void main(String [] args){
  10. Test t=new Test();
  11. String name="dog";
  12. t.testDog(name->{
  13. System.out.pringln("我是Dog");
  14. }
  15. });
  16. }
  17. }

2.当代码块只有一句时,可以去掉{}

  1. interface Dog{
  2. void getName(String name);
  3. }
  4. public class Test{
  5. //用Dog作为参数
  6. public void testDog(Dog d){
  7. d.getColor();
  8. }
  9. public static void main(String [] args){
  10. Test t=new Test();
  11. String name="dog";
  12. t.testDog(name->System.out.pringln("我是Dog");
  13. });
  14. }
  15. }

3.当代码块只有一句时,改代码的值作为返回值,不用 return;

  1. interface Dog{
  2. String getName(String name);
  3. }
  4. public class Test{
  5. //用Dog作为参数
  6. public void testDog(Dog d){
  7. d.getColor();
  8. }
  9. public static void main(String [] args){
  10. Test t=new Test();
  11. String name="dog";
  12. t.testDog(name->"我是"+name
  13. });
  14. }
  15. }

函数式接口
上面说到只有一个抽象方法的接口是函数式接口,可以用Lombda表达式,那么JDK8中有很多都是这种的,如:Runable等等,其实可以分这几类:
XxxFunction:这种接口通常有个apply抽象方法,主要是用来对参数的处理转换,而然处理逻辑使用lambda表达式实现。最后返回值
XxxConsumer:这种接口通常有个accept抽象方法,主要用来对参数的处理转换,但是不返回值
XxxPredicate:这种接口通常包含一个test抽象方法,对参数的某种判断,返回boolean值,逻辑用lambda表达式实现
XxxSupplier:这种接口有个getAsXxx抽象方法,该方法不用参数,按照某种算法返回值

有了这些基础可以接着看这边文章深入了解lambda表达式JDK8特性一之Lambda表达式

暗锚,解决锚点偏移

文章评论

嘿,来试试登录吧!