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

1.Comparable接口

Arrays类中的 sort 方法承诺可以对 对象数组 进行排序,但要求满足下列前提:对象所属的类必须实现了Comparable接口。

  1. /* 接口中所有的方法会自动的属于public,因此在接口中声名方法时,不必提供关键字public*/
  2. public interface Comparable{
  3. int compareTo(Object other);
  4. }

任何实现 Comparable 接口的类都需要包含 compareTo 方法,并且这个方法的参数必须是一个Object对象,返回一个整型数值。接口中还有一个没有明确说明的附件要求:这个 compareTo 方法必须确实比较两个两个对象的内容,并返回比较的结果。x.compareTo(y) 当 x < y 时,返回一个负数;x = y 时,返回 0 ;否则返回正数。

实现接口的步骤:
1.将类声明为实现给定的接口。使用 implement 关键字 class Employee implement Comparable
2.对接口中所有方法进行定义。

假设有一个 User对象,要根据他的年龄行排序

  1. /* 可以为泛型 Comparable 接口,提供一个类型参数 */
  2. class User implement Comparable<User>{
  3. @Override
  4. public int compareTo(User o) {
  5. return Integer.compare(age, o.age);
  6. }
  7. }

为什么不直接在 User 中提供一个 compareTo 方法,而是必须实现 Comparable 接口呢?主要原因是 Java 是一种强类型语言。在调用方法的时候,编译器将会检查这个方法是否存在,sort方法中可能存在下面的语句。

  1. if(a[i].compareTo(a[j]) > 0){
  2. ...
  3. }

为此编译器必须确认 a[i] 一定有 compareTo 方法,如果 a 是一个 Comparable 对象的数组,就可以确保拥有 compareTo 方法,因为每个实现 Comparable 接口的类都必须提供这个方法的实现。

  1. package com.yuan.pojo;
  2. import java.util.Objects;
  3. public class User implements Comparable<User>{
  4. private String name;
  5. private String gender;
  6. private int age;
  7. private String phone;
  8. public User(String name, String gender, Integer age, String phone) {
  9. this.name = name;
  10. this.gender = gender;
  11. this.age = age;
  12. this.phone = phone;
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public String getGender() {
  21. return gender;
  22. }
  23. public void setGender(String gender) {
  24. this.gender = gender;
  25. }
  26. public Integer getAge() {
  27. return age;
  28. }
  29. public void setAge(Integer age) {
  30. this.age = age;
  31. }
  32. public String getPhone() {
  33. return phone;
  34. }
  35. public void setPhone(String phone) {
  36. this.phone = phone;
  37. }
  38. @Override
  39. public int compareTo(User o) {
  40. return age - o.age;
  41. }
  42. }
  1. public class UserTest {
  2. public static void main(String[] args) {
  3. User[] users = new User[3];
  4. users[0] = new User("liu", "男", 23,"ddd");
  5. users[1] = new User("si", "男", 21,"ddd");
  6. users[2] = new User("yuan", "男", 27,"ddd");
  7. Arrays.sort(users);
  8. for (User u : users) {
  9. System.out.println(u);
  10. }
  11. }
  12. }

2.接口的特性

  1. 接口不是类,尤其是不能使用 new 运算符实例化一个接口

new Comparable(); // error
尽管不能构造接口的对象,却能声名接口的变量
Comparable c;
接口变量必须引用实现了接口的类对象,
class User implement Comparable<User>
c = new User()

  1. 如同使用 instanceof 检查一个对象是否是属于某个特定类一样,也可以使用 instanceof 检查一个对象是否实现了某个接口。

if(otherObject **instanceof** Comparable ){...}

  1. 与可以建立类的继承关系一样,接口也可以被扩展。这里允许存在多条从具有较高通用性的接口到较高专用性的接口的链。
  • 例如,假设有一个 Moveable 接口:

    1. public interface Moveable {
    2. void move(double x, double y);
    3. }
  • 然后,可以以他为基础扩展一个叫做 Powered 接口

    1. public interface Powered extends Moveable {
    2. double miles();
    3. }
  1. 虽然在接口中不能包含实例域或静态方法,但却可以包含常量。如:
    1. public interface Powered extends Moveable {
    2. double miles();
    3. /* a public static final constant */
    4. double SPEED_LIMT = 95; // 接口中的域会被自动设置为 public static final
    5. }
    尽管每个类只可以拥有一个超类,但却可以实现多个接口。这就为定义类的行为提供了极大的灵活性。使用逗号将实现的各个接口分开。class User implement Comparabl, Cloneable

3.默认方法

3.1 定义默认方法

可以为接口方法提供一个默认实现,使用 default 修饰符标记这样一个方法

  1. public interface Comparable<T>{
  2. default int compareTo(T other){return 0;}
  3. }

当然这并没有太大用处,用为 Comparable 的每一个实际实现都会覆盖这个方法。

3.2 解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况呢?Java 为解决这种二义性提供了相应的规则。
1)超类优先:若超类提供了一个具体的方法,同名且具有相同参数的默认方法会被忽略。

  1. public class Person {
  2. ...
  3. public String getName(){
  4. return Name;
  5. }
  6. }
  7. public interface Named{
  8. default String getName(){
  9. return getClass().getName() + "_" + hashCode();
  10. }
  11. }
  12. class Student extends Person implements Named{
  13. // Student 会从超类 Person 那继承 getName 方法,忽略 接口 Named 中的 getName 方法
  14. }

2)接口冲突:如果一个类实现了两个接口,这两个接口里具有同名参数相同 的方法,则必须覆盖这个方法来解决冲突。

  1. public interface Person {
  2. default String getName(){
  3. return getClass().getName() + "_" + hashCode();
  4. }
  5. }
  6. public interface Named{
  7. default String getName(){
  8. return getClass().getName() + "_" + hashCode();
  9. }
  10. }
  11. class Student extends Person implements Named{
  12. /*
  13. 只需要在 Student 类中提供一个 getName 方法,
  14. 在这个方法中,可以先择两个冲突方法中的一个
  15. */
  16. public String getName(){
  17. retuen Person.super.getName();
  18. }
  19. }

4.Comparator接口

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

  1. String[] str = {"d", "a", "v"};
  2. /* String 类实现了Comparator*/
  3. Arrays.sort(str);
  4. for (String s: str) {
  5. System.out.println(s); // -> a d v
  6. }

现在我们希望按照长度递增的的顺序对字符串进行排序,而不是按照字典顺序进行排序。肯定不能让 String 类用两种不同的方式来实现 CompareTo 方法,更何况 String 类页不应该由我们来修改
要处理这种情况,Arrays.sort 方法还有第二个版本 Arrays.sort(T[] a, Comparator<T>),由一个数组和一个比较器作为参数,比较器是实现了 Comparator 接口的实例。

  1. public interface Comparator{
  2. int compare(T first, T second);
  3. }

4.1使用比较器比较两个字符串

实现 Comparator 接口

  1. public class LengthComparator implements Comparator<String> {
  2. @Override
  3. public int compare(String s1, String s2) {
  4. return s1.length() - s2.length();
  5. }
  6. }

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

  1. // 创建比较器实例
  2. LengthComparator comp = new LengthComparator();
  3. // 通过示例调用 compare 方法,两个参数为要比较的字符串
  4. if(comp.compare(words[i], words[j]) > 0) {...}

与 compareTo 相比,compare 方法要在比较器对象上调用,而不是在字符串本身上调用。

尽管 LengthComparator 对象没有状态,不过还是要建立这个对象的一个实例,我们需要这个实例来调用 compare 方法——它不是一个静态方法。

4.1对数组进行排序

要对一个数组进行排序,需要在 Arrays.sort 方法中传入一个 LengthComparator 对象作为比较器。

  1. String[] str = {"Peter", "Jam", "Mary"};
  2. LengthComparator comp = new LengthComparator();
  3. Arrays.sort(str, comp);
  4. // println -> Jam bMary Peter