内部类、lambda表达式、函数式接口、方法引用

一、内部类

1.1 概念

内部类是定义在另一个类中的类。

例:

public class OuterClass {

    private String name = "外部类";

    /*
        内部类访问特点:
            1. 内部类可以直接访问外部类的成员,包括私有
            2. 外部类要访问内部类的成员,必须创建对象
     */
    public class InnerClass {

        public void display() {
            System.out.println("name:" + name);
        }

    }

    public void show() {
        InnerClass i = new InnerClass();
        i.display();
    }
    
}

1.2 为什么要用内部类

  • 内部类可以对同一个包中的其他类隐藏;
  • 内部类方法可以访问定义这个类的作用域中的数据,包括原本私有的数据。

1.3 分类

  • 成员内部类
  • 局部内部类
    • 匿名内部类(重要)
  • 静态内部类

​ 成员内部类,在类的成员位置定义,与其他成员变量一样,作为类的一个属性,可以有访问修饰符。

​ 局部内部类,在类的局部位置定义(方法或代码块),声明局部内部类时不能有访问修饰符,即(public或private)。局部内部类的作用域被限定在声明这个局部类的块中。

1.4 详解

(1)成员内部类

同包下其他类如何访问一个类的内部类,需要通过外部类对象来创建内部类对象。

public class OuterTest {

    public static void main(String[] args) {

//        InnerClass i = new InnerClass();  //不能直接创建

        OuterClass o = new OuterClass();
        OuterClass.InnerClass oi = o.new InnerClass();	//通过外部类对象去创建
        oi.display();

    }
    
}

输出:
name:外部类
(2)局部内部类

例:在一个类的方法内创建内部类

public class OuterClass2 {

    private int num = 10;

    public void method() {
        int num2 = 20;

        class InnerClass2 {
            public void show() {
                System.out.println(num);
            }
        }

        InnerClass2 i = new InnerClass2();
        i.show();
    }
}


/*
    测试类
 */
public class OuterTest {

    public static void main(String[] args) {
        OuterClass2 o = new OuterClass2();
        o.method();
    }

}

输出:
10
(3)匿名内部类

是局部内部类的一种形式,所以属于局部内部类。

格式:

new 类名或接口名() {
    
    重写方法...
        
};	//注意这里的分号

//例如
new AnonymousInnerClass() {
    public void show() {
  		...      
    }
};

从上述定义来看,匿名内部类的本质其实就是一个对象 这里理解的不对,应该是说通过匿名内部类可以方便地创建对象,这个对象所属的类继承了Inter,或实现了Inter这个接口。再结合多态:

AnonymousInnerClass i = new AnonymousInnerClass() {
    public void show() {
  		...      
    }
};
i.show();

具体实例:

//接口
public interface Inner {
    void show();
}
public class OuterClass3 {

    public void method() {
        Inner i = new Inner() {
            @Override
            public void show() {
                System.out.println("匿名内部类");
            }
        };
        i.show();
    }

}
/*
    测试类
 */
public class OuterTest {

    public static void main(String[] args) {
        OuterClass3 o = new OuterClass3();
        o.method();
    }

}

输出:
匿名内部类

TODO:补充匿名内部类访问外部属性的限制,好像是只能是final的

(4)静态内部类

TODO

二、lambda表达式

2.1 Comparable接口

Arrays类中的sort方法承诺可以对对象数组进行排序,但要求满足下面这个条件:对象所属的类必须实现Comparable接口

public interface Comparable
{
    int comparaTo(Object other);
}

在JAVA 5中,Comparable接口已经提升为一个泛型类型。

public interface Comparable<T>
{
    int comparaTo(T other);
}

现在,假设希望使用Arrays类的sort方法对Employee对象数组进行排序,Employee类就必须实现Comparable接口。

public class Employee implements Comparable<Employee>{

    String name;
    double salary;
    
    //省略getter和setter和构造方法

//    public int compareTo(Object o) {
//        Employee other = (Employee) o;
//        return Double.compare(salary, other.salary);
//    }

	//使用泛型接口后
    public int compareTo(Employee o) {
        return Double.compare(salary, o.salary);
    }
}

为什么不能直接在Employee类提供一个compareTo方法,而必须实现Comparable接口呢?主要原因在于Java程序设计语言是一种强类型(strongly typed)语言。在调用方法的时候,编译器要能检查这个方法确实存在。

测试排序1

@Test
public void test() {
    Employee[] employees = new Employee[3];
    employees[0] = new Employee("a", 200);
    employees[1] = new Employee("b", 100);
    employees[2] = new Employee("c", 300);

    Arrays.sort(employees);

    for (Employee employee : employees) {
        System.out.println(employee.getName());
    }
    //结果:b a c
}

2.2 Comparator接口

在前面,我们已经了解了如何对一个对象数组进行排序,前提是这些对象是实现了Comparable接口的类的实例。例如,可以对一个字符串数组进行排序,因为String类实现了Comparable,而且String.compareTo方法可以按字典顺序比较字符串。

但是现在我们希望按长度递增的顺序对字符串进行排序,肯定不能让String类用两种不同的方式实现compareTo方法,而且String类也不应由我们来修改。

要处理这种情况,Arrays.sort方法还有第二个版本,有一个数组和一个比较器(Comparator)作为参数,比较器是实现了Comparator接口的类的实例。

public interface Comparator<T>
{
    int compare(T first, T second);
}

现在创建一个实现Comparator接口的类

public class LengthComparator implements Comparator<String> {

    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
}

测试排序2

@Test
public void test() {
    String[] friends = {"Peter", "Paul", "Mary12312321"};
    LengthComparator comp = new LengthComparator();
    //为sort方法传入一个LengthComparator对象
    Arrays.sort(friends, comp);
    
    for (String friend : friends) {
        System.out.println(friend);
    }
    //结果 Paul Peter Mary12312321
}

2.3 匿名内部类

紧接上面学到的,这里就可以用上匿名内部类了。

回顾一下匿名内部类的定义:匿名内部类的本质其实就是一个对象 通过匿名内部类可以方便地创建一个对象,这个对象所属的类必须继承某个类或实现了某个接口。

从上面的例子2来看,比较只需要用到一次LengthComparator类,而sort方法又只需要“实现了Comparator接口的类的对象"即可,那么就适合用匿名内部类了。

public class Test {

    public static void main(String[] args) {
        String[] friends = {"Peter", "Paul", "Mary12312321"};

        //为sort方法传入一个实现了Comparator接口的类的对象
        Arrays.sort(friends, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();
            }
        });

        for (String friend : friends) {
            System.out.println(friend);
        }
        //结果 Paul Peter Mary12312321
    }

}

2.4 lambda表达式

从上面的例子2来看,可以向sort方法传入一个"实现了Comparator接口的类的对象",而这个接口”只有一个抽象方法“,所以属于”函数式接口“。

public class LengthComparator implements Comparator<String> {

    public int compare(String o1, String o2) {
        return o1.length() - o2.length();
    }
}

···
    
Arrays.sort(strings, new LengthComparator());

这是将一个代码块传递到某个对象,这块代码来检查一个字符串是否比另一个字符串短。这里计算:

first.length - second.length();

first和second是什么?它们都是字符串。Java是一种强类型语言,所以我们还要指定它们的类型:

(String first, String second) -> first.length() - second.length();

这就是第一个lambda表达式。lambda表达式就是一个代码块,以及必须传入代码的变量规范。

  • Arrays.sort方法需要两个参数,一个是比较的对象数组,另一个是“实现了Comparator接口的对象“

  • lambda表达式的说明:对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式

  • 所以sort的第二个参数,正好符合上述定义

所以测试2写成lambda表达式:

@Test
public void test() {
    String[] friends = {"Peter", "Paul", "Mary12312321"};
   
    Arrays.sort(friends, (first, second) -> first.length() - second.length());
    
    for (String friend : friends) {
        System.out.println(friend);
    }
    //结果 Paul Peter Mary12312321
}

三、函数式接口

函数式接口:只有一个抽象方法的接口,如上述的Comparator接口,只有一个抽象方法compare(T o1, T o2)

Java API在java.util.function包中定义了很多非常通用的函数式接口:

  • BiFunction<T, U, R>描述了参数类型为T和U,返回类型为R的函数;

  • Predicate{boolean test(T t); }

  • Supplier{T get(); }供应者用于实现懒计算

四、方法引用

有时,lambda表达式仅仅涉及一个方法,例如,假设希望只要出现一个定时器事件就打印这个事件对象:

var timer = new Timer(1000, event -> System.out.println(event));

但是,如果直接把println方法传递到Timer构造器就更好了。具体做法如下:

var timer = new Timer(100, System.out::println);

表达式System.out::println是一个方法引用(method reference),它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。在这个例子中,会生成一个ActionListener,它的actionPerformed(ActionEvent e)方法要调用System.out.println(e)。

我的理解是:

在new Timer(1000, event -> System.out.println(event));这方法中,第二个参数本来是要接收一个实现了ActionListener接口的对象,这个对象重写了接口的actionPerformed(ActionEvent e)方法。由于这个接口是函数式接口,所以可以用lambda表达式代替。

然后,由于这个重写的方法里面,只调用一个方法而不做其他操作,所以可以把lambda表达式重写为方法引用。var timer = new Timer(100, System.out::println);

方法引用要使用::运算符分隔对象或类名与方法名。主要有3种情况:

  1. object::instanceMethod
  2. Class::instanceMethod
  3. Class::staticMethod

在第1种情况下,方法引用等价于向方法传递参数的lambda表达式。对于System.out::println,对象是System.out,所以方法表达式等价于x -> System.out.println(x);

对于第2种情况,第1个参数会成为方法的隐式参数,例如,String::compareToIgnoreCase等价于(x, y) -> x.compareToIgnoreCase(y);

在第3种情况下,所有参数都传递到静态方法,Math::pow等价于(x, y) -> Math.pow(x, y);

类型语法对应的lambda表达式
静态方法引用类名::staticMethod(args) -> 类名.staticMethod(args)
实例方法引用inst::instMethod(args) -> inst.instMethod(args)
对象方法引用类名::instMethod(inst,args) -> inst.instMethod(args)
构建方法引用类名::new(args) -> new 类名(args)

TODO:构造器引用

五、变量作用域

TODO

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值