《Java核心技术卷I》java接口

更新时间:2021-03-09 15:28:34 点击次数:1204次
接口
1、接口的概念
接口用来描述类应该做什么,而不指定他们应该怎么做。
接口不是类,而是对希望符合这个接口的类的一组需求。不可以实例化(new)一个接口
一个类可以实现零或多个接口
接口的所有方法都是public抽象方法,所以可以省略关键字public abstract(例外:默认方法)
让类实现接口通常需要下面两个步骤:
使用关键字implements实现接口:
public class Employee implements Comparable {}
重写接口中的所有方法。此时要显式声明方法为public
声明一个接口的关键字是:interface,它用来取代class的位置:public interface 接口名 {}
2、小插曲:Comparable接口和compareTo方法
2.1 概述
Comparable接口中的compareTo方法将返回一个整数。如果两个数不相等,则返回一个正值或者有一个负值(这取决于两个数哪个大,如果调用方小于参数,则返回负值)如果相等,则返回0.
需要注意:整数的范围不宜过大,避免造成溢出(减去一个负数时会变成加法运算)如果确定参数是非负整数,或者两者的绝对值都不超过(Integer,MAX_VALUE-1)/2,就不会出现问题。否则,可以调用Ingeter.compare方法
compareTo方法中的相减技巧不适用于浮点类型。因为两者的差值在返回int类型时四舍五入可能会变成0. 可以借助Double.compare方法实现
2.2 compareTo方法与equals方法
Comparable接口的文档建议compareTo方法应该与equals方法兼容。也就是说,当x.equals(y)时,x.compareTo(y)就应该返回0. JavaAPI中绝大多数都遵从了这个建议,只有BigDecimal例外:
x = new DigDecimal("1.0");
y = new DigDecimal("1.00");
x.equals(y); // false
x.compareTo(y); // true 之所以如此可能是由于没有明确的方法来确定这两个数字哪一个更大
2.3 compareTo方法的对称性以及继承中的问题
如果反转compareTo方法的调用者和参数时,返回的结果也应该翻转(但是具体的值不一定)。
和equals方法一样,在继承中会遇到对称性问题:
假设现在有父类Employee及其子类Manager,如果Employee实现了Comparable<Employee>,而不是Comparable<Manager>,那么其子类Manager要覆盖compareTo,就必须做好比较经理和员工的准备,而绝对不能仅仅将员工强转为经理(因为这么做会违反“反对称原则”:当翻转调用者和参数时会出现强转异常!)
public Manager extends Employee {
    public int compareTo(Employee other) {
        Manager otherManager = (Manager) other; // 不要这么写
        // ...
    }
}
解决方式:
如果不同子类中比较有不同含义,就应该将属于不同类的对象之间的比较视为非法,并在每个compareTo方法开始时进行getClass检测:
if (this.getClass() != other.getClass()) throw new ClassCastException();
如果存在能够比较子类对象的通用算法,那么可以在超类中提供一个compareTo方法,在一开始使用instanceof检测子类,并将方法声明为final,保证子类不串改语义。
3、接口的特点
接口不是类,不可以实例化一个接口。应该通过接口来引用一个实现了这个接口的类对象。
可以使用instanceof检查一个对象是否实现了某个接口:if (obj instanceof Comparable)
接口中不能包含实例字段,但可以包含常量,且可以省略public static final:
public interface Powered extend Moveable {
double SPEED_LIMIT = 95; // public static final的常量
}
任何实现了接口的类都自动地继承了常量,而不必采用类名(应该说是接口名)调用
一个类可以有多个接口,但是只能继承于一个父类。多接口算是java实现多继承的方式
一个类除了实现接口之外,还可以去继承其他类
多个父接口存在同名的抽象方法不会影响程序
接口可以定义默认方法。
4、接口作用简述
可以使项目分层,所有的层都面向接口开发,开发效率提高了(例子:回调模式,见下方)
可以理解为灯泡和灯座,两个不同的工厂做出来的不同的产品可以耦合
接口可以使代码和代码之间的耦合度降低,就像灯泡和灯座,变得“可插拔”
接口与回调
回调(callback)是一种程序设计模式。其可以指定某个特定事件发生时应该采取的操作。
例如Swing下的Timer类,可以完成定时任务。在构造定时器时,需要设置一个时间间隔,然后告诉定时器要定期做什么事。
在很多程序设计语言中,可以提供一个函数名来指定要做的事,而在java中采用的是面对对象的方法:我们可以传入某个类的对象,然后让定时器定期调用这个对象的某个方法。怎么确保传入的对象肯定有这个方法呢?只要这个对象的类实现了某个接口,就可以保证它一定实现了这个接口的某个方法。
5、接口与抽象类
5.1 接口存在的意义
之所以要出现接口,而不是让抽象类完成接口的工作,是因为Java的设计者选择了不支持多重继承。其主要原因是多重继承会让语言变得非常复杂,或会降低效率。而接口可以提供多重继承的大多数好处,且可以避免多重继承带来的复杂性和低效性。
5.2 两者的区别和联系
5.2.1 区别
接口的设计目的,是对类的行为进行约束;而抽象类的设计目的,是代码复用。
接口是对行为的抽象,而抽象类是对根源(类别)的抽象(人应该用抽象类,而能游泳应该是接口)。
一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。
接口里面只能对方法进行声明,抽象类既可以对方法进行声明也可以对方法进行实现(即编写方法的具体代码)。(但是接口可以实现默认方法)
5.2.2 联系
抽象类中的抽象方法不能缺省的public
接口其实是一个特殊的抽象类,特殊在接口是完全抽象的
一个非抽象的类实现接口,需要将接口中所有的的方法实现(通过方法重写)
抽象级别(从高到低):接口>抽象类>实现类。
5.2.3 共同点
都是上层的抽象层。
都不能被实例化
都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不提供具体的实现。
6、接口的静态方法和私有方法
Java8开始允许即可中添加静态方法,虽然这没什么毛病,但是有违接口作为抽象规范的初衷。
通常我们会将静态方法放在工具类中,这些工具了是接口的伴随类。(如标准库中的Collection/Collections或Path/Paths,但是在Java11中,Path接口实现了与工具类等价的方法,如此看来,Paths伴随类就不是必要的了)
在Java9中,接口可以定义private的方法。private方法可以是静态方法或非静态方法。
7、接口的默认方法
可以为接口的方法提供一个默认实现,使用default修饰符标记:
public interface Comparable<T> {
    default int compareTo(T other) {
        // ...
    }
}
对于Comparable接口来说,这可能没什么用,因为每一个子类都会覆盖这个默认实现
实现接口会继承默认方法
7.1 接口演化
默认方法的一个重要用途是“接口演化”。如果对一个接口添加一个新的非默认方法,那么可能会导致以前实现了该接口的类不能编译(因为没有重写接口的新方法)
7.2 默认方法冲突
如果现在接口中定义一个默认方法,又在父类或另一个接口中定义同名且相同参数的方法,则会产生冲突,但是并不是错误。Java解决方法冲突的规则如下:
父类优先。如果父类提供了一个具体方法,那么同名且拥有相同参数的默认方法会被忽略,但不会报错。
如果两个接口的默认方法冲突,则子类必须覆盖这个方法来解决冲突。需要注意的是,即使两个接口中只有一个接口实现了默认方法,另一个只定义了同名的抽象方法,子类也不会继承前者的默认方法,必须重写方法。
8、Comparator接口
我们无法修改java默认的比较规则(无法修改某个类的compareTo方法的实现)
有些时候我们想要自定义比较标准,而不采用默认的比较方式,此时就需要使用比较器(Comparator)
比较器是实现了Comparator接口的类的实例。
当我们想要按照长度比较字符串时,可以定义如下的实现Comparator<String>的类:
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
        return first.length() - second.length();
    }
}
利用lambda表达式可以更简洁的使用Comparator接口
9、对象克隆与Cloneable接口
克隆是一种深复制(deep copy),克隆后的对象是一个新对象,新对象与原对象处于不同的内存区域,同时其成员字段也会被深复制,这样,新对象和原对象才没有任何瓜葛。
9.1 标记接口
Cloneable是一个标记接口,它不包含任何方法,唯一的作用是用于在类型检查中通过instanceof检查
9.2 Object的clone()方法与protected
9.2.1 Object的clone()方法:
Object类中的clone方法声明为protected,源码如下:
protected native Object clone() throws CloneNotSupportedException;
java中的native关键字表示这个方法是个本地方法。而且native修饰的方法执行效率比非native修饰的高。
9.2.2 protected访问权限:
protected访问权限解释:
protected访问权限允许字段或方法被同一个包下的类访问。
此外,对于子类:private限制字段或方法只能在类内部使用,而protected则允许字段或方法能在子类内部使用,但是不允许通过子类的实例在子类外部使用。
9.2.3 Object的clone()方法与protected的爱恨情仇
如下方:
Object obj1 = obj.clone();会出错,因为此处不是在Object类内部使用,而是在CloneTest类;
也正因此,CloneTest cloneTest1 = (CloneTest) cloneTest.clone();没有问题。
而MyObject myObj1 = myObj.clone();报错也是同样的道理。如果在MyObject中实现方法,在方法内部调用this.clone(),则编译不会报错,不过clone()方法要求类实现Cloneable接口,没有接口会出现CloneNotSupportedException异常
class MyObject {
    public void test() throws CloneNotSupportedException {
        // 类内部中调用,编译没有问题,但是:
        // 因为没有实现Cloneable接口,运行时会出现CloneNotSupportedException异常
        Object clone = this.clone();
}
}   
public class CloneTest { // Object类的子类
public static void main(String[] args) throws CloneNotSupportedException {
Object obj = new Object();
// 错误:'clone()' has protected access in 'java.lang.Object'
Object obj1 = obj.clone();
MyObject myObj = new MyObject();
// 错误:'clone()' has protected access in 'java.lang.Object'
MyObject myObj1 = myObj.clone();
CloneTest cloneTest = new CloneTest();
// 没有问题:
CloneTest cloneTest1 = (CloneTest) cloneTest.clone();
}
}
9.3 使类能够调用clone()方法
在MyObject类中重写clone()方法,覆盖掉继承自父类的clone()方法,则编译通过,不再有因为protected引起的不可见问题
class MyObject implements Cloneable{ // 定义一个空类,Object类的子类
public void test() throws CloneNotSupportedException {
// 子类自身中调用,没有问题
Object clone = this.clone();
}
    // 注意要将权限改成public,否则不在同个包下且非子类的类中是无法调用protected方法的
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        MyObject myObj = new MyObject();  
        // 没有问题
        myObj.test();
        // 没有问题
        MyObject myObj1 = (MyObject) myObj.clone();
    }
}

本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责,本站只提供参考并不构成任何投资及应用建议。本站是一个个人学习交流的平台,网站上部分文章为转载,并不用于任何商业目的,我们已经尽可能的对作者和来源进行了通告,但是能力有限或疏忽,造成漏登,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

回到顶部
嘿,我来帮您!