接口和抽象类都是面对对象编程中实现抽象的两种核心机制,他们都用于定义规范并支持多态,但使用场景有所不同
一.总的区别
| 接口 | 抽象类 | ||
| 关键字 | interface(定义) implements(实现) | 关键字 | abstract class(定义) extends(继承) |
| 方法 | 默认都是抽象方法, 可以有default和static 来具体实现 | 方法 | 可以有抽象方法 也可以有具体实现的普通方法 |
| 实例化 | 不支持,接口是各种类的行为规范,相当于充电绳,什么设备都能充电,符合就拿来用 | 实例化 | 不支持,抽象类是各种类公共部分,相当于中国的电压都是220 |
| 变量 | 全是常量 | 变量 | 可以有各种访问修饰符,普通成员变量、常量 |
| 设计目的 | 能不能,就是一个规矩,你按照我的标准来,我不管你内部怎么实现充电的 | 设计目的 | 是不是,就是一个半成品,我搭好结构,你做剩下的事 |
| 继承 | 多个 | 继承 | 只能有一个 |
二.抽象类
2.1什么是抽象类
- 如果一个类里面没有足够的信息来描述对象就是抽象类
- 关键字 abstract 修饰的类是抽象类。
- 抽象类即抽象化的,不可描述的。
- 理论上讲,你可以把类定义任何事物和行为,但是不符合面向对象编程思想,既然是面向对象的,行为的对象定义成类,那么抽象类也应该是定义有意义的对象
2.2与普通类区别
- 抽象类不能进行实例化,即不能使用 new 关键字创建实例,但可以用super()被动调用
- 抽象类可以包含抽象方法,(可以没有具体的实现,一个类当中有抽象方法,这个类必须是抽象类)
- 如果一个普通类继承了这个抽象类,此时这个抽象类必须重写这个抽象类当中的抽象方法
- 抽象类的出现就是为了被继承
2.3抽象类里的构造函数
- 构造函数不是作用于实例化的,主要作用是对类属性初始化操作。抽象类可以定义变量,自然需要构造函数来赋值等操作,抽象类属于被继承才可以实现调用属性和方法。
- 在重写方法中,第一行有个默认代码 super(); 默认调用父类的构造方法,所以既然有了继承、重写,为了实现super(),自然需要构造函数。
- 抽象类属于特殊类,而Java规定每个类都需要构造函数,救急不救穷
2.4抽象类里的修饰符
在抽象类中,可以使用权限修饰符public、private、protected修饰方法和属性,默认public,但private 修饰的属性和方法不能被子类继承,同时private不能用来修饰抽象方法
可以使用静态关键字 static 修饰普通方法、代码块、成员变量
被final,private ,static修饰的方法不可以重写,深层上就是他们不共存
2.5抽象类方法重写
抽象类中定义的抽象方法必须实现重写,普通方法可以不重写,如果不实现抽象方法,则报错,
抽象类属于模板设计,什么是模板化设计,简而言之,就是其他子类继承了抽象类,抽象类属于子类的公共模块,模块内的东西更新了,其他子类自然也更新。
2.6抽象类的应用
总的目的:实现共有特性特征和行为
当我们把事物抽象出一个共性时,确定这个事物的最基本行为和属性都不会变,必须存在,就可以使用抽象类的抽象方法和属性定义。
比如我定义一个狗类动物的抽象类,这类动物会叫、会吃等这些都是基本属性和行为,新增其他狗类时,直接继承扩展基本方法,如果这个狗有其他特定行为,在自定义独有方法即可。强调不会改变,共有,而且必须存在的行为和属性,也就是无论什么狗都是会叫会吃的。
三.接口
3.1定义
interface是定义接口的关键字
- 接口是不可以进行实例化的,接口不是类,只是一种公共行为标准,只要符合规范就可以通用,在Java里接口是多个类的公共规范,是一种引用数据类型,但是不能直接new接口的对象,接口是对行为的抽象
- 接口里可以有成员变量,不管写没写都是由public static final修饰的,也就是他们都是静态成员,不可修改
- 接口里不能有静态代码块和构造方法
- 接口里可以有方法,但是都是抽象方法,不管写没写都是public abstract所修饰
- 如果接口里的方法有default修饰,可以有具体的实现(jdk8)
- 如果接口里的方法有static修饰也可以有具体的实现
- 不然是不能在接口里实现的,只能在实现接口的类来完成
3.2实现
- 接口和类之间可以使用关键字implements来实现,而且必须实现接口当中的抽象方法的重写,重写方法时不能使用默认的访问权限
- 一个接口虽然不是类,但是编译完成之后对应一个字节码文件,后缀格式也是.class,一般来说接口里的方法和属性不写修饰符号,创建接口时接口的命名一般以大写字母I开头
- 如果一个类不想实现这个接口当中的方法,此时这个类就可以被定义为抽象类,如果这个抽象类如果被继承,那么就得实现所有的没有被实现的方法
如果没有使用接口,需要一个又一个的实例化,然后存起来选择打印
如果使用接口只需进行小小的改动,然后直接打印,如果数据多了不仅方便而且代码也少
3.3实现多个接口
接口的出现就是要解决Java不能多继承的问题,接口和继承写好之后就可以任意调用了,如下
简单说,继承表达的是xxx是xxx的关系,接口是表达的xxxx的共性,不关心具体是谁
3.4接口与接口的关系
利用extends关键字,这里不是继承而是扩展
接口1{方法1}
接口2{方法2}
接口3 extends 接口1 ,接口2{方法3}
class D implements 接口3
{必须重写方法1,2,3}
3.5接口使用实例
1.Comparable
如果在Java里想比较两个数的大小可以直接用<这种逻辑判断
但是如果相比较两个学生,谁大谁小(比如年龄什么的),再用这个方法就不支持了
此时就要用Comparable
1.比较规则,需要引入Comparable
原型是Comparable<T>,这个T比较那个类型就写那个类
2.具体定义
- 需要对Comparable进行重写,然后内部的if其实等价于我注释掉的这行,它通过返回正负或者0来判断谁大谁小
- System.out.println(student1.compareTo(student2)),谁调用,谁就是this
3.Comparable的缺点
这个比较方式一般用于固定的比较,不适合灵活的比较,换一个动全身,用在有默认的比较上
4.尝试回忆,之前有Array方法
如果把Comparable接口和重写的方法注释掉,这时候会报类型错误
打开源代码发现,其实Arrays.sort()也是用上了Comparable
第一步,它强制转换成了Comparable,你如果不实现接口就不行
第二步,你不得不重写compareTo,因为实现接口要重写里面的方法
所以:只要是自定义的类型,涉及大小的比较一定要实现Comparable接口
2.Comparator
Comparator,因为数字大小是明确的,但对象有多个属性,Java 不知道你想按哪个排。Student 有 name 和 age,按哪个排? 必须你自己指定规则。也就是解决了
1.需要多种排序方式
2. 不能修改原类
3. 排序规则临时变
而Comparable只适用于固定的一种排序,需要有Comparable来补充解决
对比
Q:Comparable 和 Comparator 的区别?
A:Comparable 是内部比较器,被排序的类自己实现,只能有一种规则。Comparator 是外部比较器,单独定义,可以有多种规则,更灵活。
Q:什么时候用哪个?
A:类有默认排序方式用 Comparable(如 Integer 默认升序)。需要多种排序方式或不能修改原类时用 Comparator。
Q:返回值怎么理解?
A:compare(o1, o2) 返回负数 = o1 放前面,正数 = o1 放后面,0 = 相等不动。
3.Clone
![]()
这个Clone需要的步骤也是有点
首先,我们需要先解决为什么不能.点出克隆,因为clone隶属于object类,他并不是可以直接使用的,所以我们在Person类里面重写clone
其次,重写之后,返回值是object父类的,所以我们需要对结果强转一下,转完发现还是有问题,这里发现需要告诉jvm我这里这个是可以进行复制克隆的
最后,针对克隆失败的异常进行处理
对复制克隆做出修改,发现只克隆了Person对象没克隆Money对象
此时叫做浅拷贝,如果把Money也拷贝克隆了就是深拷贝------->改造、重写方法
改造前
super.clone() 只复制了 Person 对象本身,但 m 字段里存的是 引用地址, person1.m 和 person2.m 指向 同一个 Money 对象,改一个另一个也变。
改造之后:
tmp.m 指向新 Money ,和原对象彻底断开,就不会说改变一个另一个跟着改变
四.objiect类
1.object类是Java默认的一个类,Java里除了object类,所有的类都存在继承关系,会默认继承object类(万类之父类),所有类的对象都可以使用object
2.object有一些已经定义好的方法
- Object()
- getClass(): Class
- hashCode(): int *****************************
- equals(Object): boolean *****************************
- clone(): Object
- toString(): String *****************************
- notifyo: void
- notifyAlij: void
- wait(): void
- wait(long): void
- wait(long, int): void
- finalize():void
3.equals方法
- 一样的东西通过等号判断,发现他们并不相等,这时候先给结论,必须用equals方法进行判断。
- 但是,直接使用equals方法判断也不行,由于之前学过方法的重写,这时候想起来抽象类或者接口的方法都要重写呢,结果重写了之后就报正确
- 这里补充,Java里的==,如果全是基本类型变量,则是比较变量的值是否相同,如果两侧是引用数据类型变量,则比较的是引用变量地址是否相同
- 如果要比较对象中的内容,必须重写object里面的equals方法,因为equals方法默认是按照地址来进行比较的
4.hashCode
如果一样的话,hashCode会返回相同的值,但是前提要是同类里面自己和自己比,猫和狗是不互通的
如何通过方法交换实参的值
该法,不能够改变实参,不能够交换实参的值
调用 swap(a, b) 时,Java 把 a 的值 复制一份 给 x ,把 b 的值 复制一份 给 y 。方法里交换的只是这两个复制品(局部变量 x 和 y ),真正的 a 和 b 在 main 里纹丝不动。所以输出还是 10 20 。
调用 swap2(myValue1, myValue2) 时,Java 同样传的是值的拷贝——但这里拷贝的是引用(可以理解为对象的地址)。 myV1 和 myValue1 指向同一块内存中的同一个对象。所以通过 myV1.val 修改对象内部的字段,就等于修改了 myValue1.val 。交换的是对象里的数据,而不是引用本身。
也就是说,基本类型传的是值的拷贝,方法内改不了外面;对象引用传的也是引用的拷贝,但拷贝的引用指向同一个对象,所以改对象内部能影响到外面。能成功,是因为它交换的是对象里的字段;失败,是因为它试图交换局部变量的拷贝
五.内部类
5.1.静态内部类
首先实例化有点不太一样
然后静态内部类当中,只能访问外部类当中的静态成员,其他的成员可以通过.来引用访问
创建内部类的对象,不需要先创造外部类的对象
5.2.实例内布类
与5.1基本类似
其中,如果同名的话,要么实例化然后直接.调用,要么类.this.xxxxx
实例内部类包含两个this,一个是实例内部类自己的,一个是外部类名.this
5.3.匿名内部类
一般情况和接口相关连,一般的匿名内部类就如图
匿名内部类与接口
5.4.局部内部类
很少用,谁会在类里面写一个类
六.