IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 学习笔记(JavaSE入门)——第5章·接口、lambda表达式与内部类 -> 正文阅读

[Java知识库]学习笔记(JavaSE入门)——第5章·接口、lambda表达式与内部类

第5章 ?接口、lambda表达式与内部类

5.1 ?接口

5.1.1 接口的基础语法

1)接口我们可以看作是抽象类的一种特殊情况,在接口中只能定义抽象的方法和常量。

2)接口是一种“引用数据类型”,编译之后也是一个class字节码文件。

3)接口怎么定义:[修饰符列表] interface 接口名{}。

4)接口支持多继承,一个接口可以继承多个接口。接口之间可以继承,但接口之间不能实现。

5)接口中只有常量+抽象方法。其所有元素都是public修饰的。

6) 接口中的方法默认都是 public abstract 的,不能更改,但可以省略。

7) 接口中的变量默认都是 public static final 类型的,不能更改(可省略),所以必须显式的初始化。

8) 接口不能被实例化,接口中没有构造函数的概念,接口中方法不能有方法体(JDK8中可以有方法体)。

9) 接口中的方法只能通过类来实现,通过implements 关键字。

10) 如果一个类实现了接口,那么接口中所有的方法必须实现。

11) 一类可以实现多个接口,用逗号隔开。

12)extends和implements可以共存,extends在前,implements在后。

13)使用接口,写代码的时候,可以使用多态(父类型引用指向子类型对象)。

5.1.2 接口在开发中作用

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

public interface Compare<T>{
    int compareTo(T o);
}

这说明,任何实现Comparable接口的类都需要包含compareTo方法,这个方法有一个Object参数,并且返回一个整数。假设希望根据员工的薪水进行比较,以下是compareTo方法的实现:

class Employee implements Comparable<Employee>{
    public int compareTo(Employee other){
        return Double.compare(salary, other.salary);
    }
}

要让一个类使用排序服务必须让他实现compareTo方法。这是理所当然的,因为要向sort方法提供对象的比较方法。但是为什么不能再Employee类中直接提供一个compareTo方法,而必须实现Comparable接口呢?

主要原因在于Java程序设计语言是一种强类型语言。在调用方法的时候,编译器要能检查这个方法确实存在。

注意:Comparable接口的文档建议compareTo方法应该与equals方法兼容。也就是说,当x.equals(y)时x.compareTo(y)就应该等于0。JavaAPI中大多数实现Comparable接口的类都遵从了这个建议。不过又一个重要的例外,就是BigDecimal。

在Java中接口其实描述了类需要做的事情,类需要做的事情,类要遵循接口的定义来做事,使用接口到底有什么本质的好处?可以归纳为两点:

  • 采用接口明确的声明了它所能提供的服务
  • 解决了Java单继承的问题
  • 实现了可接插信(低耦合度)

5.1.3 接口和抽象类的区别

  1. 接口描述了方法的特征,不给出实现,一方面解决 java 的单继承问题,实现了强大的可接插性。
  2. 抽象类提供了部分实现,抽象类是不能实例化的,抽象类的存在主要是可以把公共的代码移植到抽象类中。
  3. 面向接口编程,而不要面向具体编程(面向抽象编程,而不要面向具体编程)。
  4. 优先选择接口(因为继承抽象类后,此类将无法再继承,所以会丧失此类的灵活性)。?
  5. 抽象类是半抽象的;接口是完全抽象的。
  6. 抽象类中有构造方法;接口中没有构造方法。
  7. 接口和接口之间支持多继承;类和类之间只能单继承。
  8. 一个类可以同时实现多个接口;一个抽象类只能继承一个类
  9. 接口中只允许出现常量和抽象方法。

5.1.4 静态和私有方法(了解)

在Java8中,允许在接口中增加静态方法。在标准库中,会看到成对出现的接口和实用工具类,如Collection/Collections。在Java9中,接口中的方法可以是private。private方法可以是静态方法或实例方法。由于私有方法只能在接口本身的方法中使用,所以它们的用法很有限,只能作为接口中其他方法的辅助方法。

5.1.5 接口的属性

如同使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instanceof检查一个对象是否实现了某个特定的接口。接口不是类。具体来说,不能使用new运算符实例化一个接口。不过,尽管不能构造接口的对象,却能声明接口的变量:

Comparable x;

接下来,如果使用instanceof检查一个对象是否属于某个特定类一样,也可以使用instanceof检查一个对象是否实现了某个特定的接口:

if(anObject instanceOf Comparable){...}

与建立类的继承层次一样,也可以扩展接口。这里允许有多条接口链,从通用性较高的接口扩展到专用性较高的接口。

5.1.7 Comparator接口

假设我们希望按长度递增的顺序对字符串进行排序,而不是按字典顺序进行排序。肯定不能让String类用两种不同的方式实现compareTo方法。要处理这种情况,Arrays.sort方法还有第二个版本,有一个数组和一个比较器(comparator)作为参数,比较器是实现了Comparator接口的类的实例。

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

要按长度比较字符串,可以如下定义一个实现Comparator<String>的类:

class LengthComparator implements Comparator<String>{
    public int compare(String first, String second){
        return first.length() - second.length();
    }
}

具体完成比较时,需要建立一个实例:

LengthComparator comp = new LengthComparator();
if(comp.compare(words[i], words[j]) > 0){...}

将这个调用与word[i].compareTo(words[j])进行比较。这个compare方法要在比较器对象上调用,而不是在字符串本身上调用。要对一个数组排序,需要为Arrays.sort方法传入一个LengthComparator对象:

String[] friends = {“Peter”, “Paul”, “Mary”};
Arrays.sort(friends, new LengthComparator());

现在这个数组可能是[“Paul”,”Mary”,”Peter”]或[“Mary”,”Paul”,”Peter”]。

5.2 ?lambda表达式

5.2.1 为什么引入lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。在Java中传递一个代码段并不容易,不能直接传递代码段。Java是一种面对对象语言,所以必须构造一个对象,这个对象的类需要又一个方法包含所需要的代码。就像下面的代码一样:

public class LambdaDemo {
    //函数定义
    public void printSomething(String something){
        System.out.println(something);
    }
    //通过创建对象调用函数
    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String something = "一般调用模式";
        demo.printSomething(something);
    }
}

这是经典OOP的实现样式。下面我们对上面的代码做一个修改,创建一个功能接口,并对该接口定义抽象方法。

public class LambdaDemo {
    //抽象功能接口
    interface Printer {
        void print(String val);
    }
    //通过参数传递功能接口
    public void printSomething(String something, Printer printer) {
        printer.print(something);
    }

    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String something = "传统的接口函数实现方式";
        //实现Printer接口
        Printer p = new Printer() {
            @Override
            public void print(String val) {System.out.println(val);}
        };
        demo.printSomething(something, p);    
    }    
}

而lambda它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,非常方便易用,能够大幅度的提高我们的编码效率。

至此我们都尚未使用lambda表达式。我们仅创建了Printer接口的具体实现,并将其传递给printSomething方法。

首先我们知道lambda表达式,表达的是接口函数,箭头左侧是函数的逗号分隔的形式参数列表,箭头右侧是函数体代码。现在,我们使用lambda表达式重构下之前的代码。

public class LambdaDemo {
    //抽象功能接口
    interface Printer {
        void print(String val);
    }
    //通过参数传递功能接口
    public void printSomething(String something, Printer printer) {
        printer.print(something);
    }

    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String something = "lambda表达式实现方式";
        //实现Printer接口(请关注下面这行lambda表达式代码)
        Printer printer = (String toPrint)->{System.out.println(toPrint);};
        //调用接口打印
        demo.printSomething(something, printer);
    }
}

lambda表达式使我们代码更简洁。对比传统java代码的实现方式,代码量减少了很多。但这仍然不是最简的实现方式,我们一步一步来。

public class LambdaDemo {
    //抽象功能接口
    interface Printer {
        void print(String val);
    }
    //通过参数传递功能接口
    public void printSomething(String something, Printer printer) {
        printer.print(something);
    }
    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String something = "lambda表达式实现方式";
        //实现Printer接口(请关注下面这行lambda表达式代码)
        Printer printer1 = (String val)->{System.out.println(val);};
        //调用接口打印
        demo.printSomething(something, printer1);

        //简化:去掉参数类型
        Printer printer2 = (b)->{System.out.println(b);};
        demo.printSomething(something, printer2);

        //简化:去掉参数括号
        Printer printer3 = c->{System.out.println(c);};
        demo.printSomething(something, printer3);

        //简化:去掉函数体花括号
        Printer printer4 = d->System.out.println(d);
        demo.printSomething(something, printer4);
    }
}

即使没有在箭头的左侧指定参数的类型,编译器也会从接口方法的形式参数中推断出其类型。那么,我们最终通过lambda表达式,简化完成的代码:

public class LambdaDemo {
    interface Printer {
        void print(String val);
    }
    public void printSomething(String something, Printer printer) {
        printer.print(something);
    }
    public static void main(String[] args) {
        LambdaDemo demo = new LambdaDemo();
        String str = "lambda表达式函数式接口";
        demo.printSomething(str, a -> System.out.println(a));
    }
}

lambda表达式表达的是接口函数,箭头左侧是函数参数,箭头右侧是函数体。函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。

(param1,param2,param3 ...,paramN)- > { //代码块; }

语法格式一:无参数,无返回值

() -> System.out.println(Hello Lambda!);

语法格式二:有一个参数,无返回值

(x) -> System.out.println(x);

语法格式三:若只有一个参数,小括号可以省略不写

x -> System.out.println(Hello Lambda!);

语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句

Comparator<Integer> com = (x,y) -> {

System.out.println(函数式接口);

return

}

语法格式五:若Lambda体中只有一条语句,return和大括号都可以省略不写

Comparator<Integer> com = (x,y) -> Integer.compare(x,y);

语法格式六:lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文推断出数据类型,即“类型推断”。

Comparator<Integer> com = (Integer x, Integer y) -> Integer.compare(x,y);

?5.2.2 内置的四大核心函数式接口

1.Consumer<T>消费型接口:void accept<T t>;

public void happy(double money, Consumer<Double> con){
        con.accept(money);
}
public void test1(){
    happy(10000.11, m -> System.out.println("消费了" + m + "元"));
}

2.Supplier<T>供给型接口:T get();

    public List<Integer> getNumList(int num, Supplier<Integer> sup){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++){
            Integer n = sup.get();
            list.add(n);
        }
        return list;
    }
    public void test2(){
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));
        for (Integer n : numList){
            System.out.println(n);
        }
    }

3.Function<T, R>函数型接口:R apply(T r);

    public String strHandler(String str, Function<String, String>fun){
        return fun.apply(str);
    }
    public void test3(){
        String newStr = strHandler("\t\t\tlambda表达式",(str) -> str.trim());
        System.out.println(newStr);    //lambda表达式
        String subStr = strHandler("傻逼lambda表达式", (str) -> str.substring(2,8));
        System.out.println(subStr);    //lambda
    }

4.Predicate<T>判断型接口:boolean test(T t);

    public List<String> filterStr(List<String> list, Predicate<String> pre){
        List<String> strList = new ArrayList<>();
        for (String str : list){
            if (pre.test(str)){
                strList.add(str);
            }
        }
        return strList;
    }
    public void test4(){
        List<String> list = Arrays.asList("Hello", "Lambda", "www", "OK");
        List<String> stringList = filterStr(list, (str) -> str.length() > 3);
        for (String s : stringList){
            System.out.println(s);
        }
    }

5.2.4 变量作用域

①在lambda表达式中,只能引用值不会改变的变量。如果在lambda表达式中更改变变量,并发执行多个动作时就会不安全。

②如果在lambda表达式中引用一个变量。而这个变量可能在外部改变,这也是不合法的。

③lambda表达式中捕获的变量必须实际上是事实最终变量(effectively final)。

④lambda表达式的体与嵌套块有相同的作用域。这里同样适用明明冲突和遮蔽的有关规则。在lambda表达式中声明一个局部变量同名的参数或者局部变量是不合法的。

⑤在一个lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数。

5.2.5 Comparator

Comparator接口包含很多方便的静态方法来创建比较器。这些方法可以用于lambda表达式或方法引用。

静态comparing方法提取一个“键提取器”函数,他将类型T映射为一个可比较的类型(如String)。对要比较的对象应用这个函数,然后对返回的键完成比较。例如,假设有一个Person对象数组,可以如下按名字对这些对象进行排序:

Arrays.sort(people, Comparator.comparing(Person::getName));

与手动实现一个Comparator相比,这当然要容易得多。另外,代码也更为清晰。可以把比较器与thenComparing方法串起来,来处理比较结果相同的情况。例如:

Arrays.sort(people,Comparator.comparing(Person::getLastName).thenComparing(Person::getFirstName));

如果两人的姓相同,就会使用第二个比较器。这些方法有很多变体形式,可以为comparing和thenComparing方法提取的键指定一个比较器。例如,可以如下根据人名长度完成排序:

Arrays.sort(people, Comparator.comparing(Person::getName, (s, t) -> Integer.compare(s.length() - t.length())));

5.3 ?内部类

在一个类的内部定义的类,称为内部类,主要分类:①实例内部类;②局部内部类;③静态内部类。

为什么需要使用内部类呢?主要有两个原因:

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

内部类原先对于简洁地实现回调非常重要,不过如今lambda表达式在这方面可以做得更好。但内部类对于构建代码还是很有用的。使用内部类编写的代码,可读性很差。能不用尽量不用。

5.3.1 实例内部类

  • 创建实例内部类,外部类的实例必须已经创建
  • 实例内部类会持有外部类的引用
  • 实例内部不能定义 static 成员,只能定义实例成员
public class InnerClassTest01 {
    private int a;
    private int b;
    InnerClassTest01(int a, int b) {
        this.a = a;
        this.b = b;
    }
    //内部类可以使用 private 和 protected 修饰
    private class Inner1 {
        int i1 = 0;
        int i4 = b;
    }
    public static void main(String[] args) {
        InnerClassTest01.Inner1 inner1 = new InnerClassTest01(100, 200).new Inner1();
        System.out.println(inner1.i1);  //0
        System.out.println(inner1.i4);  //200
    }
}

5.3.4 静态内部类

  • 静态内部类不会持有外部的类的引用,创建时可以不用创建外部类
  • 静态内部类可以访问外部的静态变量,如果访问外部类的成员变量必须通过外部类的实例访问
public class InnerClassTest02 {
    static int a = 200;
    int b = 300;
    static class Inner2 {
        //在静态内部类中可以定义实例变量
        int i1 = 10;
        //可以定义静态变量
        static int i3 = 100;
        //可以直接使用外部类的静态变量
        static int i4 = a;
        //不能直接引用外部类的实例变量
        //int i5 = b;
        //采用外部类的引用可以取得成员变量的值
        int i5 = new InnerClassTest02().b;
    }
    public static void main(String[] args) {
        InnerClassTest02.Inner2 inner = new InnerClassTest02.Inner2();
        System.out.println(inner.i5);    //300
    } 
}

5.3.5 局部内部类

局部内部类是在方法中定义的,它只能在当前方法中使用,和局部变量的作用一样。局部内部类和实例内部类一致,不能包含静态成员。

public void start(){
    class TimePrinter implements ActionListener{
        public void actionPerformed(ActionEvent event){
            System.out.println(“time is”  + Instant.ofEpochMilli(event.getWhen()));
        }
    }
    TimePrinter listener = new TimePrinter();
    Timer timer = new Timer(interval, listener);
    timer.start();
}

5.3.6 匿名内部类

使用局部内部类时,通常还可以再进一步。假设只想创建这个类的一个对象,甚至不需要为类指定名字。这样一个类被称为匿名内部类(anonymous inner class)。

public void start(){
    ActionListener listener = new ActionListener{
        public void actionPerformed(ActionEvent event){
            System.out.println(“time is”  + Instant.ofEpochMilli(event.getWhen()));
        }
    }
    Timer timer = new Timer(interval, listener);
    timer.start();
}

一般地,语法如下:

new SuperType(construction parameters){

inner class methods and data

}

其中,SuperType可以是接口,如ActionListener,如果是这样,内部类就要实现这个接口。SuperType也可以是一个类,如果是这样,内部类就要扩展这个类。

由于构造器的名字必须与类名相同,而匿名内部类没有类名,所以,匿名内部类不能有构造器。实际上,构造参数要传递给超类构造器。具体地,只要内部类实现一个接口,就不能有任何构造参数。不过,仍然要提供一组小括号。尽管匿名内部类不能有构造器,但可以提供一个对象初始化块。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-07-22 14:00:25  更:2021-07-22 14:03:13 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/18 18:00:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码