Java速通5:匿名类、Lambda、::表达式

134 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

1.匿名类

  • 当接口、抽象类的实现类,在整个项目中只用过一次,建议使用匿名类。

抽象类的实现类举例:

// 抽象类的实现类 
public class Persion implements Runnable{
    
    public void run(){
        // xxx
    }
}

public class Test{
    
    public static void main(String[] args){
        
        //Persion p = new Persion();
        //p.run();
        
        // 用匿名类
        Runable persion = new Runable(){
            public void run(){
                // xxx
            }
        };
        
        persion.run();
    }
}

接口举例:

public interface Eatable{
    String name();
}

public class Persion{
    public void eat(Eatable e){
        print(e.name());
    }
}

Persion persion = new Persion();

调用Persioneating方法的两种方式:

  1. 方式一:

    persion.eating(new Eatable(){
        public String name(){
            return "匿名类";
        }
    });
    
  2. 方式二:

    Eatable e = new Eatable(){
        public String name(){
            return "匿名类";
        }
    };
    
    persion.eating(e);
    

1.1.匿名类的使用注意:

  1. 匿名类不能定义除编译时常量以外的任何static成员。
  2. 匿名类只能访问final 或 有效final的局部变量。
  3. 匿名类可以直接访问外部类中的所有成员(即使被声明为private
  4. 匿名类不能自定义构造方法,但可以有初始化块(无类名也就无法定义构造方法)

1.2.匿名类的常见用途:

  1. 代码传递
  2. 过滤器
  3. 回调
匿名类用用途1 - 代码传递

写个可统计下列代码执行时间的工具类

for(int i = 0; i < 1999; i++){
    println("aaaa");
}
//工具类
public class TimeUtils{
    
    public interface Block{
        void execute();
    }
    
    public static void testUseTime(Block block){
        
        long begin = System.currentTimeMillis(); //获取当前时间
        block.execute();
        long end = System.currentTimeMillis();
        
        print("耗时:" + (end - begin)/1000.0 + "秒");
    }
}


//调用
TimeUnits.testUseTime(new Block(){
    
    //把想测试的代码防止execute()里
    @Override
    public void execute(){
        for(int i=0; i<9999; i++){
            println("aaaa");
        }
    }
});
匿名类用用途2 - 回调(请求一个链接,请求成功/失败 会调用对应的方法)
public class NetworkUtils{
    
    public interface Block{
        void success(Object response); //请求成功,返回数据
        void failure(); //请求失败
    }
    
    public static void get(String url, Block callback){
        // 1.根据url发送一个异步请求(开启一条子线程)
        
        // 2.请求完毕之后
        boolean result = true; //自定义个请求结果
        if(result){
            Object response = null; //自定义个服务器返回的数据
            callback.success(response); 
        }else{
            callback.failure();
        }
    }
}

//调用
NetworkUtils.get("http://xxx", new NetworkUtils.Block(){
    
    // 请求成功 会调用此方法
    @Override
    public void success(Object response){
        print("请求成功!");
    }
    
    // 请求失败 会调用此方法
    @Override
    public void failure(){
        print("请求失败!");
    }
});
匿名类用用途3 - 过滤器(传个文件夹,返回此文件夹下的所有文件)
public class FileUtils{
    
    public interface Filter{
        boolean accept(String filename); //表示接受此文件
    }
    
    public static String[] getAllFileNames(String dir, Filter filter){
        // 1. 先获取dir 文件夹下的所有文件名
        String[] allFileNames = {}; //假设这里已经获取到了
        
        // 2.进行过滤
        for(String filename : allFileNames){
            if(filter.accept(filename)){
                //将这个文件名装到集合中
            }
        }
        
        // 3.返回集合
    }
}


// 调用:搜索F盘下的 java文件
FileUtils.getAllFileNames("F:/", new filter(){
    @Override
    public boolean accept(String filename){
        return filename.contains(".java");  // contains() 是否包含
    }
});
Arrays.sort() 源码也使用了匿名类

数组排序:java.util.Arrays

Integer[] arr = {22, 55, 11, 44, 33};

// 默认是升序
Arrays.sort(arr); 
System.out.println(Arrays.toString(arr));


// 降序 (Comparator: 比较器)
Arrays.sort(arr, new Comparator<Integer>(){
    
    /**
     * 返回值情况:
     * > 0 : o1 > o2
     * = 0 : o1 = o2
     * < 0 : o1 < o2
     * Arrays.sort() 默认是升序,小的在左边,大的在右边
     * 例:当前传值(22, 33) 即o1=22,o2=33 -> 返回值会大于0 -> 根据上图 编译器会认为o1比o2大
     * 根据大的放在右边 -> 结果为: 33, 22。实现了降序
     */
    @Override
    public int compara(Integer o1, Integer o2){
        return o2 - o1; // 降序(o1-o2: 升序)
    }
});

2.Lambda

  1. 函数式接口(Functional Interface): 只包含一个抽象方法的接口。 建议在接口上加 @FunctionalInterface 注解,表示此接口是函数式接口。

    @FunctionalInterface
    public interface Filter{
        boolean accept(String filename);
    }
    
  2. 当匿名类实现的是函数式接口时,可用Lambda表达式进行简化。 Lambda表达式写法

    (参数列表) -> { // 参数列表:函数式接口里面唯一的那个方法的参数列表
        // 函数式接口里面唯一的那个方法
        // 里面需要写的东西,都写在这里
        // return xxx; 若唯一的那个方法有返回值,这里要写return
        // 如果花括号中只有一个语句,可以省略花括号
    }
    

2.1.举例一

// 工具类
public class TimeUtils{
    
    @FunctionalInterface
    public interface Block{
        void execute();
    }
    
    public static void test(Block block){
        
        long begin = System.currentTimeMillis(); //获取当前时间
        block.execute();
        long end = System.currentTimeMillis();
        
        print("耗时:" + (end - begin)/1000.0 + "秒");
    }
}




// 调用: 传统方式
TimeUnits.test(new Block(){
    
    //把想测试的代码防止execute()里
    @Override
    public void execute(){
        for(int i=0; i<1000; i++){
            println("1234");
        }
    }
});

// 调用:Lambda表达式
TimeUnits.test(() -> {
    for(int i=0; i<1000; i++){
        println("1234");
    }
});

2.1.举例二

FileUtils.getAllFileNames("F:/", new filter(){
    @Override
    public boolean accept(String filename){
        return filename.contains(".java");  // contains() 是否包含
    }
});


FileUtils.getAllFileNames("F:/", (String filename) -> {
    return filename.contains(".java"); 
});

// 甚至参数类型也可不写
FileUtils.getAllFileNames("F:/", (filename) -> {
    return filename.contains(".java"); 
});

//当Lambda表达式里只有一条语句时,可省略大括号、分号、return
FileUtils.getAllFileNames("F:/", (filename) -> filename.contains(".java"));

3.lambda表达式推导:

// 1.定义一个接口
public interface ILike{
    void lambda();
}

// 2.实现类
public class Like implements ILike{

    @Override
    public void lambda() {
        System.out.println("My Lambda");
    }
}

// 方法1
public class TestLambda {
    
    public static void main(String[] args) {
        
        ILike like = new Like();
        like.lambda();
    }
}

// 方法2 -- 静态内部类
public class TestLambda {
    
    // 静态内部类
    static Like implements ILike{
        
        @Override
        public void lambda() {
            System.out.println("My Lambda");
        }
    }
    
    public static void main(String[] args) {
        
        ILike like = new Like();
        like.lambda();
    }
}

// 方法3 -- 局部内部类
public class TestLambda {
    
    public static void main(String[] args) {
        
        // 局部内部类
        static Like implements ILike{
            
            @Override
            public void lambda() {
                System.out.println("My Lambda");
            }
        }
        
        ILike like = new Like();
        like.lambda();
    }
}

// 方法4 -- 匿名内部类(没有类的名称,必须借助接口或者父类)
public class TestLambda {
    
    public static void main(String[] args) {
        
        // 局部内部类
        ILike like = new Like(){
            
            @Override
            public void lambda() {
                System.out.println("My Lambda");
            }
        };
        like.lambda();
    }
}

// 方法5 -- lambda
public class TestLambda {
    
    public static void main(String[] args) {
        
        
        ILike like = () -> {
            System.out.println("My Lambda");
        };
        
        like.lambda();
    };
}


// 再来看看函数式接口
// 1.定义一个接口
public interface ILike{
    void lambda();
}

// 2.实现类
public class Like implements ILike{  // 作用域、实现关系、方法名都可以不要

    @Override
    public void lambda() {  // 方法名不需要、返回值类型可以推断出来、
        System.out.println("My Lambda"); // 留下的只有形参和具体的执行逻辑
    }
}



// 例子2
public interface IBird{
    void fly(int a);
}

public class Test{
    public static void main(String[] args){
        
        IBird bird = (int a) -> {
            System.out.println("飞" + a + "次");
        };
        bird.fly(23);


        // 简化:参数类型也可去掉,即隐式声明参数(多个参数也可以,要去掉就都去掉)
        IBird bird1 = (a) -> {
            System.out.println("飞" + a + "次");
        };
        bird1.fly(23);

        // 简化:括号也可以去掉 (只有一个参数时)
        IBird bird2 = a -> {
            System.out.println("飞" + a + "次");
        };
        bird2.fly(23);

        // 简化:花括号也可以去掉(前提是只有一行代码)
        IBird bird3 = a -> System.out.println("飞" + a + "次");
        bird3.fly(23);
    }
}

4.方法引用

方法引用是调用特定方法的Lambda表达式的一种快捷写法。
可以让你重复使用现有的方法定义,就像lambda一样传递他们。

   Stu          ::         getStuName()
目标引用    双分号分隔符       方法名

4.1.指向 静态方法 的方法引用

(args) -> ClassName.staticMethod(args);
等价于
ClassName::staticMethod;


// 举例
(String number) -> Integer.parseInt(number);
等价于
Integer::parseInt;

4.2.指向 任意类型实例方法 的方法引用

(args) -> args.instanceMethod();
等价于
ClassName::instanceMethod;


// 举例
(String str) -> str.length();
等价于
String::length;

4.3.指向 现有对象的实例方法 的方法引用

(args) -> object.instanceMethod(args);
等价于
object::instanceMethod;


// 举例
StringBuilder stringBuilder = new StringBuilder();
(String str) -> stringBuilder.append(Str);
等价于
stringBuilder::append;

5.总结

从匿名类到lambda,再到双冒号(::)表达式。理解其中的演变过程才是本次学习的重点。在实际编码过程中能够看懂以及灵活运用即可。本节课的内容在Stream流中是非常常见的。

如果你学过js, 那么一定会非常熟悉函数式编程, 那你一定能快速理解java的函数式接口。

下一节讲学习枚举以及基本数据类型的包装类, 也是工作中比较常用到的基本知识。