目录
一、抽象类
1.概念
打印图形
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
public class Test{
public static void drawMap(Shape shape){
shape.draw();//动态绑定
}
public static void main(String[] args){
Shape shape1 = new Rect();//向上转型
Shape shape2 = new Flower();//向上转型
drawMap(shape1);
drawMap(shape2);
}
}
在这个打印图形例子中 , 我们发现 , 父类 Shape 中的 draw 方法好像并没有什么实际工作 , 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的 . 像这种没有实际工作的方法 , 我们可以把它设计成一个 抽象方法 (abstract method) , 包含抽象方法的类我们称为 抽象类 (abstract class)
2.语法规则
abstract class Shape {
abstract public void draw();
}
在 draw 方法前加上 abstract 关键字 , 表示这是一个抽象方法 . 同时抽象方法没有方法体 ( 没有 { }, 不能执行具体代码). 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类
因此,上面的打印图形的代码可以优化为:
abstract class Shape{
public abstract void draw();
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
@Override
public void draw(){
System.out.println("❀");
}
}
public class Test{
public static void drawMap(Shape shape){
shape.draw();//动态绑定
}
public static void main(String[] args){
Shape shape1 = new Rect();//向上转型
Shape shape2 = new Flower();//向上转型
drawMap(shape1);
drawMap(shape2);
}
}
3.一些注意事项
(1)抽象类是不可以被实例化的,因此,抽象类只能被继承
(2)抽象当中也可以包含和普通类一样的成员和方法
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
(3)一个普通类如果继承了一个抽象类,那么这个普通类当中需要重写这个抽象类的所有抽象方法
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
class Rect extends Shape{
@Override
public void draw(){
System.out.println("♦");
}
}
(4)一个抽象类A如果继承了一个抽象类B,那么这个抽象类A可以不实现抽象父类B的抽象方法;但是但A再次被一个普通类继承后,那么A和B这两个抽象类当中的抽象方法必须被重写(出来混总是要还的)
abstract class Shape{
public int a;
public void func(){
System.out.println("测试普通方法!");
}
public abstract void draw();//抽象方法
}
abstract class A extends Shape{
public abstract void funcA();
}
class B extends A{
@Override
public void funcA(){
......
}
@Override
public abstract void draw(){
......
}
}
(5)抽象类不能被final修饰,抽象方法也不可以被final修饰(final的功能是限制类被继承,但是抽象类的作用就是被继承,两者相互矛盾)
(6)抽象方法不能是 private 的
4.抽象类的作用
抽象类存在的最大意义就是为了被继承 . 抽象类本身不能被实例化, 要想使用 , 只能创建该抽象类的子类 . 然后让子类重写抽象类中的抽象方法。 普通的类也可以被继承呀, 普通的方法也可以被重写, 但是使用抽象类相当于多了一重编译器的校验
二、接口
1.阐述
接口是抽象类的更进一步 . 抽象类中还可以包含非抽象方法 , 和字段 . 而接口中包含的方法都是抽象方法 , 字段只能包含静态常量.
2.语法规则
在刚才的打印图形的代码中 , 我们的父类 Shape 并没有包含别的非抽象方法 , 也可以设计成一个接口
interface IShape {
//public abstract void draw();完整格式
void draw();//简化格式
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
public class Test {
public static void main(String[] args) {
IShape shape = new Rect();
shape.draw();
}
}
(1)使用 interface 定义一个接口
(2)接口中的方法一定是抽象方法, 因此可以省略 abstract;
(3)接口中的方法一定是 public, 因此可以省略 public
(4)Cycle 使用 implements 继承接口. 此时表达的含义不再是 "扩展", 而是 "实现"
扩展(extends) vs 实现(implements):扩展指的是当前已经有一定的功能了, 进一步扩充功能; 实现指的是当前啥都没有, 需要从头构造出来
(5)接口当中可以有静态方法
(6)接口当中的成员变量,默认是public static final修饰的
(7)当一个类实现了一个接口,就必须重写接口当中的抽象方法
(8)当一个类实现一个接口之后,重写这个方法的时候,这个方法前面必须加上public
interface IA{
int A = 10;
void funcA();//public abstract
}
class AClass implements IA{
public void funcA(){//子类的访问修饰权限符要>=父类
.......
}
}
(9)接口当中的普通方法,不能有具体的实现,如果非要实现,只能通过关键字default来修饰这个方法
interface IShape{
public abstract void draw();
default public void func(){
System.ou.println("helloworld");
}
}
(10)接口不能单独被实例化
(11)在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例
3.一些书写规范
(1) 我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
(2)接口的命名一般使用 "形容词" 词性的单词.
(3)阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
4.实现多个接口
有的时候我们需要让一个类同时继承自多个父类 . 这件事情在有些编程语言通过 多继承 的方式来实现的 . 然而 Java 中只支持单继承 , 一个类只能 extends 一个父类 . 但是可以同时实现多个接口 , 也能达到多继承类似的效果
class Animal{
protected String name;
public Animal(String name){
this.name = name;
}
}
interface IFlying{
void flying();
}
interface IRunning{
void running();
}
class Duck extends Animal implements IFlying,IRunning{
public Duck(String name){
super(name);
}
@Override
public void flying(){
System.out.println(this.name+" 正在飞!");
}
@Override
public void running(){
System.out.println(this.name+" 正在跑!");
}
}
class Cat extends Animal implements IRunning {
public Cat(String name) {
super(name);
}
@Override
public void running() {
System.out.println(this.name + " 正在跑");
}
}
public class TestDemo{
public static void runFunc(IRunning iRunning){
iRunning.running();
}
public static void main(String[] args){
runFunc(new Duck("haha"));
runFunc(new Cat("hehe"));
}
}
5.接口间的继承
从上面可以知道,类和类之间、类和接口之间的关系是implements操作的。接口和接口之间可以使用extends来操作它们的关系,这里的extends意为“拓展”。一个接口B通过extends来拓展另一个接口C的功能,此时当一个类D通过impkements实现这个接口B时,重写的方法不仅仅是B的抽象方法,还有它从C接口拓展来的功能(方法)
interface IA{
void funcA();
}
interface IB extends IA{//此时这里也有了IA的功能
void funcB();
}
class C implements IB{
@Override
public void funcB(){
System.out.println("hello");
}
@Override
public void funcB(){
System.out.println("world");
}
}
6.接口使用实例
(1)Comparable接口
import java.util.Arrays;
class Student implements Comparable<Student>{//比较学生
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
//和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系需要额外指定,∴我们让 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
@Override
public int compareTo(Student o) {
/*if(this.age > o.age){//谁调用这个方法,谁就是this
return 1;
}else if(this.age == o.age){
return 0;
}else{
return -1;
}*/
//return this.age - o.age;//从小到大排序
//return o.age - this.age;//从大到小比较
//比较分数
//return (int)(this.score - o.score);
//比较名字
return this.name.compareTo(o.name);//引用类型的比较必须借助它可比较的方法
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"zhangsan",98.3);
students[1] = new Student(6,"lisi",85.9);
students[2] = new Student(16,"wangwu",60.8);
System.out.println(Arrays.toString(students));
Arrays.sort(students);//默认从小到大排序,只有遇到排序自定义类型的时候才可以修改是升序排序还是降序排序
System.out.println(Arrays.toString(students));
}
}
注意:对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过 重写 compareTo 方法的方式, 就可以定义比较规则
这个接口的一个缺点是:对类的侵入型非常强,一旦代码写好了,不敢轻易改动
(2)Comparator接口(比较器)
import java.util.Arrays;
import java.util.Comparator;
class Student{//比较学生
public int age;
public String name;
public double score;
public Student(int age, String name, double score) {
this.age = age;
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(12,"zhangsan",98.3);
students[1] = new Student(6,"lisi",85.9);
students[2] = new Student(16,"wangwu",60.8);
System.out.println(Arrays.toString(students));
//AgeComparator ageComparator = new AgeComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(students,nameComparator);//默认从小到大排序,只有遇到排序自定义类型的时候才可以修改是升序排序还是降序排序
System.out.println(Arrays.toString(students));
}
}
Comparator接口灵活,对类的侵入性非常弱
但是我们在选择用哪种接口时,要看我们业务的处理
7.Clonable 接口和深拷贝
Object 类中存在一个 clone 方法 , 调用这个方法可以创建一个对象的 " 拷贝 ". 但是要想合法调用 clone 方法 , 必须要先实现 Clonable 接口 , 否则就会抛出 CloneNotSupportedException 异常
Cloneable接口是一个空接口,它是一个标志接口,代表当前这个类是可以被克隆的
s Person implements Cloneable{
public int age;
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.age = 99;
Person person2 = (Person)person.clone();//Person默认继承Object,理论上来说是可以调用克隆方法,但是它还比较特殊,调用克隆方法后还要重写克隆方法
System.out.println(person2);
System.out.println("------------------");
person2.age = 100;
System.out.println(person);
System.out.println(person2);
}
}
编译并运行该代码,输出如下:
从这个代码实现来看,是一份深拷贝,但是我们要知道,决定深拷贝还是浅拷贝不是方法的用途,而是代码的实现。我们来看一下这份代码的内存分布图
我们再来看一段代码:
class Money{
public double m = 12.3;
}
class Person implements Cloneable{
public int age;
public Money money = new Money();
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person2 = (Person)person.clone();//Person默认继承Object,理论上来说是可以调用克隆方法,但是它还比较特殊,调用克隆方法后还要重写克隆方法
System.out.println(person.money.m);
System.out.println(person2.money.m);
System.out.println("------------------");
person2.money.m = 99.99;
System.out.println(person.money.m);
System.out.println(person2.money.m);
}
}
编译并运行该代码,输出如下:
从这个代码实现来看,是一份浅拷贝,我们来看一下这份代码的内存分布图:
那么现在我们想把它变成深拷贝,如何实现呢?只需要将Money也克隆一份,如下代码:
class Money implements Cloneable{
public double m = 12.3;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age;
public Money money = new Money();
public void eat(){
System.out.println("吃!");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
//return super.clone();
//等价于:
//Person tmp = (Person)super.clone();
//return tmp;
Person tmp = (Person)super.clone();
tmp.money = (Money) this.money.clone();
return tmp;
}
}
public class TestDemp {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person2 = (Person)person.clone();
System.out.println(person.money.m);
System.out.println(person2.money.m);
System.out.println("------------------");
person2.money.m = 99.99;
System.out.println(person.money.m);
System.out.println(person2.money.m);
}
}
编译并运行该代码,输出如下:
我们来看一下这份代码的内存分布图
总结:决定深拷贝还是浅拷贝不是方法的用途,而是代码的实现
8.总结抽象类和接口的区别
No | 区别 | 抽象类 | 接口 |
1 | 结构组成 | 普通类+抽象方法 | 抽象方法+全局变量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | 使用extends关键字继承抽象类 | 使用implements关键字实现接口 |
4 | 关系 | 一个抽象类可以实现若干接口 | 接口不能继承抽象类,但是接口可以使用extends关键字继承多个父接口 |
5 | 子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |