第六章 接口、lambda表达式与内部类 | 学习笔记

Java核心技术 卷一

6.1 接口

  • 接口概念

在Java程序设计语言中,接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义。

例如:
    public interface Comparable
    {
        int compareTo(Object other);
    }

任何实现Comparable接口的类都需要包含compareTo方法,并且这个方法的参数必须是一个Object对象,返回一个整型数组。
接口中的所有方法自动属于public。因此,在接口中声明方法时,不必提供关键字public。
为了让类实现一个接口,需要下面两个步骤:
1.将类声明为实现给定的接口。
2.对接口中的所有方法进行定义。

例如:
    public int compareTo(Object otherObject)
    {
        Employee other = (Employee) otherObject;
        return Double ,compare(sal ary, other,sal ary);
    }
  • 接口的特性

接口不是类,尤其不能使用new运算符实例化一个接口,然而,尽管不能构造接口的对象,却能声明接口的变量。

Comparable x; // OK

接口变量必须引用实现了接口的类对象:

x = new Employee(. . .); // OK provided Employee implements Comparable

接口也可以拓展:

例如:假设有一个称为Moveable的接口
    public interface Moveable
    {
        void move(double x, double y);
    }
然而,可以以它为基础拓展一个叫做Powered的接口:
    public interface Powered extends Moveable
    {
        double milesPerCallon();
    }
虽然在接口中不能包含实例域或静态方法,但却可以包含常量。
    public interface Powered extends Moveable
    {
        double milesPerCallon();
        double SPEED. LIHIT = 95; // a public static final constant
    }

与接口的方法都自动地被设置为public一样,接口中地域将被自动设为public static final。

  • 接口与抽象类
  • 静态方法

目前为止,通常做法斗志将静态方法房子伴随类中。

例如:
    public interface Path//Path.get("jdk1.8.0", "jre", "bin")
    {
        public static Path get(String first, String... more) {
            return Fi1eSystems.getDefault().getPath(first, more);
        }
        ...
    }
  • 默认方法

可以为接口方法提供一个默认实现。必须使用default修饰符。

例如:
    public interface Comparable<T>
    {
        default int compareTo(T other) { return 0; }
        // B y default, all elements are the same
    }

有些时候默认方法很有用。例如,如果希望在发生鼠标点击时间时得到通知,就要实现包含5个接口:

  public interface MouseListener
    {
        void mousedieked(MouseEvent event);
        void mousePressed(MouseEvent event);
        void mouseReleased(MouseEvent event);
        void mouseEntered(MouseEvent event);
        void mouseExited(MouseEvent event);
    }
    可以把所有方法声明为默认方法,这些默认方法什么也不做:
    public interface MouseListener
    {
        default void mousedieked(MouseEvent event) {}
        default void mousePressed(MouseEvent event) {}
        default void mouseReleased(MouseEvent event) {}
        default void mouseEntered(MouseEvent event) {}
        default void mouseExited(MouseEvent event) {}
    }

默认方法可以调用任意其他方法。

例如,Collection接口可以定义一个便利方法:
    public interface Collection
    {
        int size(); // An abstract method
        default boolean isEmpty()
        {
            return size() == 0;
        } 
        ...
    }

这样实现Collection地程序员就不用操心实现isEmpty方法了。

  • 解决默认方法冲突

规则:
1.超类优先。如果超类提供了一个具体方法,同名而且有相同参数类型地默认方法回被忽略。
2.接口冲突。如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型相同地方法,必须覆盖这个方法来解决冲突。

6.2 接口示例

  • 接口与回调

回调是一种常见的程序设计模式。在模式中,可以指出某个特定事件发生时应该采取的动作。

  • Comparator接口
  • 对象克隆

Cloneable接口中提供了一个安全地clone方法。
在这里插入图片描述
对于每一个类,需要确定:
1. 默认的 clone 方法是否满足要求;
2.是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
3. 是否不该使用 clone
实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须:
1.实现 Cloneable 接口;
2 .重新定义 clone 方法,并指定 public 访问修饰符。

6.3 lambda表达式

  • 为什么引入lambda表达式

lambda表达式时一个可传递地代码块,可以在以后执行一次或多次。
了解了如何按指定时间间隔完成工作,将这个工作放在一个ActionListener 的 actionPerformed方法中:

   class Worker implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            // do some work
        }
    }

想要反复执行相和歌代码时,可以构造Worker类地一个实例。然后把这个实例提交给一个Timer对象。
如果想按长度而不是默认的字典顺序对字符串排序,可以向sort方法传入一个Comparator对象:

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

compare不是立即调用。实际上,在数组完成排序之前,sort方法会一直调用compare方法,只要元素地顺序不正确就会重新排列元素。

  • lambda表达式地语法
例如:
    (String first, String second)
        -> first.length() - second.length()

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

(String first, String second) ->
{
if (first.length()< second.length()) return -1;
else if (first.length() > second.length()) return 1;
else return 0;
}

即使lambda表达式没有参数,仍然要提供空括号,就像无参方法一样:

   () -> { for (int i = 100;i >= 0;i ) System.out.println(i); }
  • 函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。

Timer t = new Timer(1000, event ->
{System.out.println("At the tone,thetime is " + new Date());
Toolkit.getDefaultToolkit().beep();
};

BiFunction<String, String, Integer> comp
    = (first, second) -> first.length() - second.length();
  • 方法引用

例如,假设你希望 只要出现一个定时器时间就打印这个事件对象,可以调用:

Timer t = new Timer(1000, event -> System.out.println(event)):

将print方法传递到Timer构造器就更好了。

Timer t = new Timer(1000, Systey.out::println);

表达式Systey.out::println是一个方法引用。等价于lambda表达式x 一> System.out.println(x)
假设想对字符串排序,而不考虑字母的大小写。

  Arrays.sort(strings,String::conpareToIgnoreCase)

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

•object::instanceMethod
•Class::staticMethod
•Class::instanceMethod

可以在方法引用中使用this参数。例如,this::equals 等同于 x -> this.equals(x)。
使用super也是合法的:super::instanceMethod

例如:
    class Greeter
    {
        public void greet()
        {
            System.out.println("Hello, world!");
        }
        }
     class TimedCreeter extends Greeter
        {
            public void greet()
        {
        Timer t = new Timer(1000, super::greet);
        t.start();
        }
    }
  • 构造器引用

构造器引用与方法很类似,只不过方法名为new。例如,Person::new是Person构造器的一种引用。
假设你有一个字符串列表,可以把它转换为一个Person对象数组,为此要在各个字符串上调用构造器,调用如下:

  ArrayList<String> names = . . .;
        Stream<Person> stream = names.stream().map(Person::new);
        List<Person> people = stream.collect(Collectors.toList());

可以用数组类型建立构造器引用。例如,int[]::new 是一个构造器引用,它有一个参数:即数组的长度。这等价于 lambda 表达式 x -> new int[x]。
Java有一个限制,无法构建泛型类型T的数组。

  • 变量作用域

lambda表达式由3个部分:
1.一个代码块;
2.参数;
3.自由变量的值,这是指非参数而且不在代码中定义的变量。

  • 处理lambda表达式

使用lambda表达式的重点是延迟执行。如果想要立即执行代码,完全可以可以直接执行,而无需把它包装在一个lambda表达式中。
原因:
1.在一个单独的线程中允许代码;
2.多次运行代码;
3.在算法的适当位置允许代码;
4.发生某种情况时执行代码;
5.指在必要时才运行代码。
假设,你想重复一个动作n次。将这个动作和重复次数传递带一个repeat方法:

repeat(10, () -> System.out.println("Hello, World!"));

在这里插入图片描述
在这里插入图片描述

6.4 内部类

内部类时定义在另一个类中的类。
使用原因:
1.内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据。
2.内部类可以对同一个包中的其他类隐藏起来。
3.当想要定义一个回调函数且不想编写大量代码时,使用匿名内部类比较便捷。
嵌套是一种类之间的关系,而不是对象之间按的关系。嵌套类有两个好处:命名控制和访问控制。

  • 使用内部类访问对象状态

以TimerTest示例,并抽象出一个TalkingClock类。构造一个语音时钟时需要提供两个参数:发布通告的间隔和开关铃声的标志。

  public class TalkingClock
    {
        private int interval:
        private boolean beep;
        
        public TalkingClock(int interval, boolean beep) { . . . }
        public void start() { . . . }
        
        public class TimePrinter implements ActionListener
        // an inner class
        {
            ...
        }
    }

需要注意,这里的TimePrinter类位于TalkingClock类内部。
actionPerformed方法在发生铃声之前检查了beep标志。

  public class TimePrinter implements ActionListener
    {
        public void actionPerformed(ActionEvent event)
        {
            System.out.println("At the tone, thetimeis" + new Date());
            if (beep) Toolkit.getDefaultToolkit().beep();
        }
    }

TimePrinter类没有实例域或者名为beep的变量,取而代之的是beep引用了创建TimePrinter的TalkingClock对象的域。
在这里插入图片描述

内部类的特殊语法规则
外围类引用的正规语法:OuterClass.this

例如:
    public void actionPerformed(ActionEvent event)
    {
            ...
        if (TalkingClock.this.beep) Toolkit.getDefaultToolkitO.beep();
    }
    采用下列语法格式更明确地编写内部对象的构造器:
        outerObject.new InnerClass(construction parameters)
    例如:
        ActionListener listener = this.new TimerPrinter();
    TalkingClock jabberer = new TalkingClock(1000, true);
    TalkingOock.TiiePrinter listener= jabberer.newTimePrinter();

内部类中声明的所有静态域都必须是final。
内部类不能用static方法。

  • 内部类是否有用、必要和安全
  • 局部内部类
  • 由外部方法访问变量

局部类不仅能够访问包含他们的内部类,还可以访问局部变量。

例如:将TalkingClock 构造器的参数 interval和 beep 移至 start方法中。
    public void start(int interval, boolean beep)
    {
        class TimePrinter implements ActionListener
        {
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("At the tone, the time is" +new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        }
        ActionListener listener = new TimePrinter();
        Timer t = new Timer(interval, listener);
        t.start();
    }

    流程:
        1.调用start方法
        2.调用内部类TimePrinter的构造器,一边初始化对象变量listener
        3.将listener引用传递给Timer构造器,定时器开始计数时,start方法结束。
        4.然后actionPerformed方法执行if
  • 匿名内部类

只创建这个类的一个对象,就不必命名,这种类被称之为匿名内部类。

   public void start(int interval, boolean beep)
    {
        ActionListener listener = new ActionListener()
        {
            public void actionPerformed(ActionEvent event)
            {
                System.out.println("At the tone, the time is" + new Date());
                if (beep) Toolkit.getDefaultToolkit().beep();
            }
        };
        Timer t = new Timer(interval, listener);
        t.start();
    }

含义:创建一个实现ActionListener接口的类的新对象,需要实现的方法actionPerformed定义在括号{}内。

语法格式:
    new SuperType(construction parameters)
    {
        inner class methods and data
    }
  • 静态内部类

将内部类声明为static。
考虑一下计算数组中的最小值和最大值的问题。同时计算出最小值和最大值。

double min = Double.POSITIVE_INFINITY;
double max = Double.NECATIVE_INFINITY;
for (double v : values)
{
if (min > v) min = v;
if (max < v) max = v;
}

class ArrayAlg
{
public static Pair minmax(doublet] values)
{

return new Pair(min, max) ;
}
}
使用方法:
Pair p = ArrayAlg.minmax(d);
System,out.println("min = " + p.getFirst());
System,out.println("max = " + p.getSecond());

6.5 代理

利用代理可以在运行时创建一个实现了一组给定接口的新类。

  • 何时使用代理

代理具有下列方法:
1.指定接口所需要的全部方法。
2.Object类中的全部方法。

  • 创建代理对象

想要创建一个代理对象,需要使用Proxy类的newProxyInstance方法。参数为:
1.一个类加载器。
2.一个Class对象数组,每个元素都是需要实现的接口。
3.一个调用处理器。

发布了14 篇原创文章 · 获赞 22 · 访问量 558

猜你喜欢

转载自blog.csdn.net/qq_41550921/article/details/103999206