1-06-1 接口

last modified: 2020/10/31


1-06-1 接口

1、接口

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

  2. 接口中的所有方法自动地属于public。

    • 因此,在接口中声明方法时,不必提供关键字public。
    • 但是,在实现接口时,必须把方法声明为public
  3. 接口不能含有实例域,但是接口可以定义常量。

  4. 提供实例域和方法实现的任务应该由实现接口的那个类来完成,可以将接口看成是没有实例域的抽象类。

  5. 要让一个类使用排序服务必须让它实现compareTo方法。

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

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

x = new Comparable(...); // ERROR

然而,尽管不能构造接口的对象,却能声明接口的变量:

Comparable x; // OK

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

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

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

if (anObject instanceof Comparable) {...}

虽然在接口中不能包含实例域或静态方法,但却可以包含常量。例如:

public interface Powered extends Moveable
{
	double milesPerGallon();
	double SPEED LIMIT = 95; // a public static final constant
}

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

尽管每个类只能够拥有一个超类,但却可以实现多个接口。

1.3 接口与抽象类

为什么Java程序设计语言还要不辞辛苦地引入接口概念?为什么不将Comparable直接设计成如下所示的抽象类。

abstract class Comparable // why not?
{
	public abstract int compareTo(Object other);
}

然后,Employee类再直接扩展这个抽象类,并提供compareTo方法的实现:

class Employee extends Comparable// why not?
{
	public int compareTo(Object other) { ...}
}

非常遗憾,使用抽象类表示通用属性存在这样一个问题:每个类只能扩展于一个类。假
设Employee类已经扩展于一个类,例如Person,它就不能再像下面这样扩展第二个类了:

class Employee extends Person,Comparable // Error

但每个类可以像下面这样实现多个接口:

class Employee extends Person implements Comparable // OK

有些程序设计语言允许一个类有多个超类,例如C++。我们将此特性称为多重继承( multiple inheritance)。而Java的设计者选择了不支持多继承,其主要原因是多继承会让语言本身变得非常复杂(如同C++),效率也会降低。
实际上,接口可以提供多重继承的大多数好处,同时还能避免多重继承的复杂性和低效性

1.4 静态方法

目前为止,通常的做法都是将静态方法放在伴随类中。

1.5 默认方法

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

public interface Comparable<T>
{
    default int compareTo(T other) { return 0; }
	// By default, all elements are the same
}
1.6 解决默认方法冲突

如果先在一个接口中将一个方法定义为默认方法,然后又在超类或另一个接口中定义了同样的方法,会发生什么情况?

  • 超类优先(类优先,接口次之)

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

    class Student extends Person implements Named { . ..}
    

    在这种情况下,只会考虑超类方法,接口的所有默认方法都会被忽略。在我们的例子中,Student从Person继承了getName方法,Named接口是否为getName提供了默认实现并不会带来什么区别。这正是“类优先”规则。
    “类优先”规则可以确保与Java SE7的兼容性。如果为一个接口增加默认方法,这对于有这个默认方法之前能正常工作的代码不会有任何影响。

    警告:千万不要让一个默认方法重新定义Object类中的某个方法。例如,不能为toString或equals定义默认方法,尽管对于List之类的接口这可能很有吸引力。由于“类优先”规则,这样的方法绝对无法超越Object.toString 或 Objects.equals。

  • 接口冲突:如果一个超接口提供了一个默认方法,另一个接口提供了一个同名而且参数类型(不论是否是默认参数)相同的方法,必须覆盖这个方法来解决冲突。

    考虑另一个包含getName方法的接口:

    interface Named
    {
        default String getName() { 
            return getClass().getName()+ "_" + hashCode();
                                 }
    }
    

    如果有一个类同时实现了这两个接口会怎么样呢?

    class Student implements Person,Named
    {
    	...
    }
    

    类会继承Person和Named接口提供的两个不一致的getName方法。并不是从中选择一个,Java编译器会报告一个错误,让程序员来解决这个二义性。只需要在Student类中提供一个getName方法。在这个方法中,可以选择两个冲突方法中的一个,如下所示:

    class Student implements Person,Named
    {
    	public String getName() { return Person.super.getName();}
    }
    

    现在假设Named接口没有为getName提供默认实现:

    interface Named
    {
    	string getName();
    }
    

    Student类会从Person接口继承默认方法吗?这好像挺有道理,不过,Java设计者更强调一致性。两个接口如何冲突并不重要。如果至少有一个接口提供了一个实现,编译器就会报告错误,而程序员就必须解决这个二义性。

2 接口示例

2.1 接口与回调

回调(callback)是一种常见的程序设计模式。

  • 在这种模式中,可以指出某个特定事件发生时应该采取的动作。
2.2 Comparator 接口

有一个数组和一个比较器(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();
    }
}

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

Comparator<String> comp = new LengthComparator();
if (comp.compare(words[i],words[j])> 0) ...

将这个调用与words[i].compareTo(words[j])做比较。这个compare方法要在比较器对象上调用,而不是在字符串本身上调用。

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

要对一个数组排序,需要为Arrays.sort方法传入一个LengthComparator对象:

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

现在这个数组可能是["Paul","Mary", "Peter"]或["Mary", "Paul", "Peter"]。
利用lambda表达式可以更容易地使用Comparator。

2.3 对象克隆
  1. Cloneable接口

    指示一个类提供了一个安全的clone方法。

  2. clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。

  3. 只有Employee类可以克隆Employee对象。这个限制是有原因的。

    • 想想看Object类如何实现clone。它对于这个对象一无所知,所以只能逐个域地进行拷贝。
    • 如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题
    • 但是如果对象包含子对象的引用,拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
  • 为了更直观地说明这个问题,考虑第4章介绍过的Employee类。图6-2显示了使用Object类的clone方法克隆这样一个Employee对象会发生什么。

    • 可以看到,默认的克隆操作是“浅拷贝”,并没有克隆对象中引用的其他对象。这个图显示了一个共享的Date对象。
  • 浅拷贝会有什么影响吗?这要看具体情况

    • 如果原对象和浅克隆对象共享的子对象是不可变的,那么这种共享就是安全的
    • 如果子对象属于一个不可变的类,如String,就是这种情况。
    • 或者在对象的生命期中,子对象一直包含不变的常量,没有更改器方法会改变它,也没有方法会生成它的引用,这种情况下同样是安全的。
  • 不过,通常子对象都是可变的,必须重新定义clone方法来建立一个深拷贝,同时克隆所有子对象。在这个例子中,hireDay域是一个Date,这是可变的,所以它也需要克隆。

  • 对于每一个类,需要确定:

    • 默认的clone方法是否满足要求;
    • 是否可以在可变的子对象上调用clone来修补默认的clone方法;
    • 是否不该使用clone
  • 实际上第3个选项是默认选项。如果选择第1项或第2项,类必须:

    • 1)实现Cloneable接口;

    • 2)重新定义clone方法,并指定public访问修饰符。

      注释:Object类中clone方法声明为protected,所以你的代码不能直接调用anObject.clone()。但是,不是所有子类都能访问受保护方法吗?不是所有类都是Object的子类吗?幸运的是,受保护访问的规则比较微妙(见第5章)。子类只能调用受保护的clone方法来克隆它自己的对象。必须重新定义clone为public才能允许所有方法克隆对象。

    • 在这里,Cloneable接口的出现与接口的正常使用并没有关系。具体来说,它没有指定clone方法,这个方法是从Object类继承的。这个接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很“偏执”,如果一个对象请求克隆,但没有实现这个接口,就会生成一个受查异常。

      注释:Cloneable接口是Java提供的一组标记接口(tagging interface)之一。(有些程序员`称之为记号接口(marker interface))。应该记得,Comparable等接口的通常用途是确保一个类实现一个或一组特定的方法。标记接口不包含任何方法;它唯一的作用就是允许在类型查询中使用

      instanceof:if (obj instanceof Cloneable) . . .
      

      建议你自己的程序中不要使用标记接口。

    即使clone的默认(浅拷贝)实现能够满足要求,还是需要实现Cloncable接口,将clone重新定义为public,再调用super.clone()。

    • 下面给出一个例子:
    class Employee implements Cloneable{
        //raise visibility level to public,change return type
        public Employee clone( throws CloneNotSupportedException
        {
            return (Employee) super.clone();
        }
    }
    

    与Object.clone提供的浅拷贝相比,前面看到的clone方法并没有为它增加任何功能。这里只是让这个方法是公有的。要建立深拷贝,还需要做更多工作,克隆对象中可变的实例域。
    下面来看创建深拷贝的clone方法的一个例子:

    class Employee implements Cloneable
    {
    	...
    	public Employee clone() throws CloneNotSupportedException
    	{
    		// call 0bject.clone()
    		Employee cloned = (Employee) super.clone();
            
    		// clone mutable fields
    		cloned.hireDay = (Date)hireDay.clone();
    		return cloned;
    	}
    }
    

    如果在一个对象上调用clone,但这个对象的类并没有实现Cloneable接口,Object类
    的clone方法就会抛出一个CloneNotSupportedException。当然,Employee和 Date类实现了Cloneable接口,所以不会抛出这个异常。不过,编译器并不了解这一点,因此,我们声明了这个异常。

  • 必须当心子类的克隆

  • 所有数组类型都有一个public的clone方法,而不是protected。

    • 可以用这个方法来建立一个新数组,包含原数组所有元素的副本。

      int[] luckyNumbers = {2,3,5,7,11,13};
      int[] cloned = luckyNumbers.clone();
      cloned[5] = 12; //doesn`t change luckyNumbers[5];
      
原文地址:https://www.cnblogs.com/nojacky/p/13909175.html