【JavaCore · I】第六章_接口、lambda表达式与内部类

367 阅读6分钟

6.1_接口

6.1.1_接口示例

  • 接口不是类,而是对类的一组需求描述。
  • Arrays.sort()只可对已实现Comparable接口的类的对象进行排序。
  • 接口中的方法默认为public
  • 接口可定义常量,但不能定义实例域。在 Java1.8 之前,不能在接口中实现方法。
class Employee implements Comparable<Employee> {
    public int compareTo(Employee other) {
        return Double.compare(salary, other.salary);
    }
}

为什么不能在 Employee 类中直接提供 compaTo() 方法,而必须实现 Comparable 接口呢?

这是因为Java是一种强类型语言。在调用方法时,编译器将会检查这个方法是否存在。

Integer、Double 的静态方法

  • static int compare(int x, int y)
  • static int compare(double x, double y)

compareTo 方法的反对称规则

先看一段代码

class Manager extends Employee {
    public int compareTo(Employee other)  { 
        Manager otherManager = (Manager) other; // NO
    }
}
  • 这不符合反对称规则,如果 x 是一个 Employee 对象,y 是一个 Manager 对象,调用 x.compareTo(y) 不会抛出异常,因为 x 实现的是泛型为 Employeecomparable 接口。可将 Manager 看做雇员。

故,在 compareTo 方法逻辑之前都应该进行以下检测:

if(getClass() != other.getClass())  throw new ClassCastException();

6.1.2_接口的特性

  • 不能构造接口对象,但可声明接口变量。
    • Comparable c; // Ok
  • 可使用instanceOf检查一个对象是否实现了某个接口。
    • if(anObject instanceOf Comparable) {...}
  • 接口可被扩展。
    public interface i1 {...}
    public interface i2 extends i1 {...}
    
  • 接口的域(非实例域)被自动设为public static final
  • 每个类可实现多个接口,但只能继承自一个超类。

6.1.3_接口与抽象类

为什么不将 Comparable 直接设计成抽象类。

假设 Employee 扩展了 Person 类,那就不能再扩展 Comparable 类了。该类的对象也就失去了可比较性。

6.1.4_静态方法

Java8中后,允许在接口中定义静态方法。

public interface Path  {
    public static Path get(String first, String... more) { 
        return Fi1eSystems.getDefault().getPath(first, more);
    }
}

6.1.5_默认方法

  • 可用default修饰一个接口方法的默认实现。
  • 默认方法可调用任何其他方法。
    public interface Collection { 
        int size(); // An abstract method 
        default boolean isEmpty() { return size() = 0; } 
        .. 
    } 
    
    • 好处:实现默认方法就不用再写 isEmpty 方法的逻辑。

6.1.6_解决默认方法冲突

在Java中,如果现在一个接口中将一个方法 f() 定义为默认方法,然后又在超类中/另一个接口中定义了同样(同名、相同参数类型)的方法,则以以下规则处理:

  • 超类优先。
  • 接口冲突。
    interface Named { 
        default String getName() { 
            return getClass().getName() + "_" + hashCodeO; 
        } 
    }
    
    一、如果有一个类同时实现了这两个接口会怎么样呢? 
    class Student implements Person, Named {...}
    
    答:Java 编译器会报告一个错误,程序员必须在这个方法中,选择两个冲突方法中的一个
    class Student implements Person, Named { 
        public String getName() { 
            return Person.super.getName(); 
        }
    }
    
    二、假设 Named 接口没有为 getName 提供默认实现:
    interface Named { String getNameO; } 
    Student 类会从 Person 接口继承默认方法吗? 
    
    答:同一
    

另一种情况(类优先原则)

一个类扩展了一个超类,同时实现了一个接口,并从超类和接口继承了相同的方法。例如,假设 Person 是一个类, Student 定义为:

class Student extends Person implements Named {. . . } 

在这种情况下, 只会考虑超类方法,接口的所有默认方法都会被忽略

6.2_接口示例

6.2.1_接口与回调

  • 回调(callback)是一种指出某个特定事件发生时应该采取的动作的程序设计模式。

6.2.2_Comparator 接口

String 类实现了 Comparable 接口,故 String.compareTo() 方法可按字典序比较字符串。
但现在要让字符串的以字符串长度排序呢?

Array.sort()有另一个版本,它的参数是一个数组和一个比较器 Comparator

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

满足上述需求,可这样实现比较器

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

使用方法

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

6.2.3_对象克隆

  • Object 类的 clone 方法被声明为 protected。且拷贝方式为浅拷贝。
  • 浅拷贝:拷贝一个对象的子对象的引用,得到的拷贝对象会共享这个子引用指向的实例信息。
  • 深拷贝:则反之。
    class Employee implements Cloneable {
        public Employee cloneO throws CloneNotSupportedException { 
        // call Object,clone() 
        Employee clonedEmp = (Employee) super.clone(); 
        // clone mutable fields 
        clonedEmp.hireDay = (Date) hireDay.clone();
        return clonedEmp;
    }
    
  • 如果在一个没有实现 Cloneable 接口的对象上调用 clone()方法 , Object 类 的 clone 方法就会拋出一个 CloneNotSupportedException

注意的问题: Employee 类定义了 clone 方法,任何人都可以用它 来克隆 Manager 对象,能成功吗?这取决于 Manager 类的域。

  • 所有数组类型都有一个 publicclone() 方法
int[] luckyNumbers = { 2, 3, 5, 7,11, 13 }; 
int[] cloned = luckyNumbers.clone(); 
cloned[5] = 12; // doesn't change luckyNumbers[5] 

6.3_lambda 表达式

有关lambda表达式的学习可移步segmentfault.com/a/119000001…,这里讲得比较全面。

6.3.1_为什么引入 lambda 表达式

  • lambda表达式是一个可传递的代码块。

6.3.2_lambda 表达式的语法

  • 即使 lambda 表达式没有参数, 仍然要提供空括号,就像无参数方法一样:0 -> { for (inti = 100;i >= 0;i ) System.out.println(i); }
  • 如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。例如:
Comparator<String> comp 
        = (first, second) // Same as (String first, String second)
            -> first.length() - second.length();
  • 无需指定 lambda 表达式的返回类型。

6.3.3_函数式接口

  • 只含有一个被显式声明的抽象方法的接口。 Comparator 就是只有一个方法的接口,故可进行以下操作。
    Arrays.sort(words, 
            (first, second) -> first.length() - second.length()); 
    

6.3.4_方法引用

有现成的方法可以完成你想要传递到其他代码的某个动作则可使用方法引用来完成。

Timer t = new Timer(1000, event -> System.out.println(event));
Timer t = new Timer(1000, Systei.out::println); 
  • 表达式System.out::println是一个方法引用(method reference), 它等价于 lambda 表达式 x -> System.out.println(x)
Arrays.sort(strings,String::conpareToIgnoreCase) 

规律

  • object::instanceMethod

  • Class::staticMethod

  • Class::instanceMethod

    • 在前 2 种情况中,方法引用等价于提供方法参数的 lambda 表达式。比如System.out::println 等价于 x -> System.out.println(x) 。类似地,Math::pow 等价于(x,y) -> Math.pow(x, y)
    • 对于第 3 种情况, 第 1 个参数会成为方法的目标。例如,String::compareToIgnoreCase 等同于(x,y)-> x.compareToIgnoreCase(y)

6.3.5_构造器引用

  • 构造器引用与方法引用很类似,只不过方法名为 new

6.3.6_变霣作用域

lambda 表达式的体与嵌套块有相同的作用域。

Path first = Paths.get(7usr/Mn"); 
Couparator<String> comp = 
    (first, second) -> first.length() - second.length(); 
    // Error: Variable first already defined

在一个 lambda 表达式中使用 this 关键字时, 是指创建这个 lambda 表达式的方法的 this 参数。例如,考虑下面的代码

public class Application() { 
    public void init() { 
        ActionListener listener = event -> { 
        System.out.println(this.toString());
        ...
    }
    ...
}
  • 表达式this.toString()会调用 Application 对象的 toString方法, 而不是 ActionListener 实例的方法。