抽象类和接口
一、抽象类
1.1 抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)
1.2 抽象类语法
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法。
1.3 抽象类特性
- 抽象类不能直接实例化对象
- 抽象方法不能是 private 的
注意:抽象方法没有加访问限定符时,默认是public。 - 抽象方法不能被final和static、private修饰,因为抽象方法要被子类重写
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰!
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
- 抽象类里面的非抽象方法可以直接被调用
1.4 抽象类的作用
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法。
那么有一个问题:普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此。但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景,实际工作不应该由父类完成,而应由子类完成。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。
很多语法存在的意义都是为了 “预防出错”,例如我们曾经用过的 final 也是类似。创建的变量用户不去修改,不就相当于常量嘛?但是加上 final 能够在不小心误修改的时候,让编译器及时提醒我们。
充分利用编译器的校验,在实际开发中是非常有意义的。
二、接口
2.1 接口的概念
在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。
电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备;
电源插座插孔上,可以插:电脑、电视机、电饭煲…所有符合规范的设备。
通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 语法规则
接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。
提示:
1. 创建接口时, 接口的命名一般以大写字母 I 开头。
2. 接口的命名一般使用 “形容词” 词性的单词。
3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。
2.3 接口使用
接口不能直接使用,必须要有一个”实现类”来”实现”该接口,实现接口中的所有抽象方法。
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
2.4 接口特性
- 接口类型是一种引用类型,但是不能直接new接口的对象
- 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
- 重写接口中方法时,不能使用default访问权限修饰(接口中是public,子类限定符要大于等于父类,所以不能是默认)
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
-
接口中不能有代码块和构造方法(因为接口本身不能实例化)
-
JDK1.8开始,接口中允许有可以实现的方法,但是这个方法只能是由default修饰的
-
接口中可以实现一个静态方法(默认是public)
-
接口也可以实现向上转型,从而实现多态
实现了这个接口的类型都可以传过去。(但是调用的时候只能调用接口内的方法)
-
接口虽然不是类,但是每个接口也有字节码文件,接口编译完成后字节码文件的后缀格式也是.class
-
如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
2.5 实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。
另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”。
青蛙, 既能跑, 又能游(两栖动物)
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
提示, IDEA 中使用 ctrl + i 快速实现接口
上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口。
继承表达的含义是 is – a 语义, 而接口表达的含义是 具有 xxx 特性 。
这样设计有什么好处呢?时刻牢记多态的好处,让程序猿忘记类型。有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
例如, 现在实现一个方法, 叫 “散步”
在这个 walk 方法内部, 我们并不关注到底是哪种动物, 只要参数是会跑的, 就行。
甚至参数可以不是 “动物”, 只要会跑!
2.6 接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口,达到复用的效果。使用 extends 关键字。
通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”。此时实现接口创建的 Frog 类,就继续要实现 run 方法,也需要实现 swim 方法。
接口间的继承相当于把多个接口合并在一起。
2.7 抽象类和接口的区别
抽象类和接口都是 Java 中多态的常见使用方式。都需要重点掌握。同时又要认清两者的区别。(重要!!!常见面试题)
核心区别: 抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法。
如之前写的 Animal 例子。此处的 Animal 中包含一个 name 这样的属性,这个属性在任何子类中都是存在的。因此此处的 Animal 只能作为一个抽象类,而不应该成为一个接口。
再次提醒:
抽象类存在的意义是为了让编译器更好的校验,像 Animal 这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了 Animal 的实例,编译器会及时提醒我们。
三、Object类
3.1 什么是Object类?
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
范例:使用Object接收所有类的对象
所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。如下:
对于整个Object类中的方法需要实现全部掌握。
我们先来熟悉这几个方法:toString()方法,equals()方法,hashcode()方法
3.2 获取对象信息:toString()方法
如果要打印对象中的内容,可以直接重写Object类中的toString()方法,之前已经讲过了,此处不再累赘:九、对象的打印
3.3 对象比较:equals()方法
在Java中,== 进行比较时:
1.如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同
2.如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同
3.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
Person类重写equals方法后,然后比较:
结论:比较对象中内容是否相同的时候,一定要重写equals方法。
3.4 hashCode方法
回忆刚刚的toString方法的源码:
我们看到了hashCode()这个方法,他帮我算了一个具体的对象位置,然后调用Integer.toHexString()方法,将这个地址以16进制输出。
hashCode方法源码:
该方法是一个native方法,底层是由C/C++代码写的。我们看不到。
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看示例代码:
注意事项:两个对象的hash值不一样。
像重写equals方法一样,我们也可以重写hashcode()方法。此时我们再来看看:
注意:
1.Object里面有hashCode()方法;而Objects是工具类(就像Arrays一样)
Object.hash():根据成员变量生成一个值:变量相同,hash值一定相同;但是hash值相同,变量不一定相同
这时:哈希值一样
结论:
1、hashcode方法用来确定对象在内存中存储的位置是否相同。
2、事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
四、接口使用实例
4.1 给对象数组排序
再给定一个学生对象数组,对这个对象数组中的元素进行排序(按分数降序)
按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?
仔细思考,不难发现,和普通的整数不一样,两个整数是可以直接比较的,大小关系明确。而两个学生对象的大小关系怎么确定?需要我们额外指定。
让我们的 Student 类实现 Comparable 接口,并实现其中的 compareTo 方法。
在 sort 方法中会自动调用 compareTo 方法。compareTo 的参数是 Object ,其实传入的就是 Student 类型的对象。
然后比较当前对象和参数对象的大小关系(按分数来算)。
- 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
- 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
- 如果当前对象和参数对象不分先后, 返回 0;
再次执行程序, 结果就符合预期了:
注意事项: 对于 sort 方法来说,需要传入的数组的每个对象都是 “可比较” 的,需要具备 compareTo 这样的能力。通过重写 compareTo 方法的方式,就可以定义比较规则。
为了进一步加深对接口的理解,我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程。(使用冒泡排序)
再次执行代码
4.2 Clonable接口和深浅拷贝
4.2.1 Clonable接口
Java 中内置了一些很有用的接口,Clonable 就是其中之一。
Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 “拷贝”。 但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出异常:CloneNotSupportedException
细节:
1.
2.
重写:
4.2.2深浅拷贝
浅拷贝 VS 深拷贝
Cloneable 拷贝出的对象是一份 “浅拷贝”
观察以下代码:
如上代码,我们可以看到,通过clone,我们只是拷贝了Person对象。但是Person对象中的Money对象,并没有拷贝。通过person2这个引用修改了m的值后,person1这个引用访问m的时候,值也发生了改变。这里就是发生了浅拷贝。
深拷贝:
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/93279.html