今天的博客主要是来总结Java的抽象类和接口方面的知识。
目录
抽象类
什么是抽象类
如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来并不是所有的类都是用来描绘对象的。
抽象类语法
在Java中,一个类如果被 abstract 修饰则称这个类为抽象类,抽象类中被 abstract 修饰的方法成为抽象方法,抽象方法不用给出具体的方法体。
举例:
public abstract class Shape{
// 抽象方法,被 abstract 修饰的方法,没有方法体
abstract public void shape();
abstract public void calcArea();
// 抽象类也是类,所以也可以有普通方法
public double getArea(){
return area;
}
public double area;
}
抽象类的特性
下边出现的示例可以参考上边的举例!!!
1.抽象类不能直接实例化对象
// 编译出错(Shape 是抽象的,无法实例化)
Shape shape = new Shape();
2.抽象方法不是 private(如果是 private 的,就不能被覆写)
// 编译出错(非法的修饰符组合:abstract 和 private)
abstract class Shape{
abstract private void draw();
}
3.抽象方法不能被 final 和 static 修饰(因为抽象方法要被覆写)
// 编译出错(非法的修饰符组合:final 和 abstract ; static 和 abstract)
public abstract class Shape{
abstract final void methodA();
abstract public static void methodB();
}
4.抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰(@Override 为重写注解)
abstract class Shape{
abstract public void shape();
abstract public void calcArea();
public double getArea(){
return area;
}
public double area;
}
public class Text extends Shape{
@Override
public void shape(){
System.out println("这是一个三角形");
}
@Override
public coid calcArea(){
System.out.println("这个三角形的面积为10平方厘米");
}
}
5.抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6.抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类是普通类的超集,抽象类中仍然可以存在普通方法和构造方法,且子类实例化时仍然满足继承的原则,先调用父类的构造方法再调用子类的构造方法。
抽象类的作用
其实继承、重写方法这些普通类也可以实现,所以我们大可使用普通类来被继承,为什么还要用抽象类呢?确实,普通类也可以被继承,普通方法也可以被重写,但是被继承、方法被重写这些并不是抽象类的最大的作用。使用抽象类相当于多了一重编译器的校验,在实际操作中某些工作不应该有父类完成,而应该由子类完成,那么此时要是用成父类,就会出现错误,但是这种错误编译器是不会报错的,但是父类是抽象类的话在实例化的生活就会提示错误,这样我们就尽早发现了问题。所以使用抽象类是充分利用编译器的校验,这在实际开发中是非常有意义的。
接口
在生活中,我们几乎可以在任何地方看到接口,比如笔记本电脑上的USB口,电源插座,手机充电口等等……
什么是接口
可以看出,接口就是公共的行为规范标准,在大家实现时,只要符合规范标准,就可以通用。在Java中,接口可以看做是多个类的公共规范,是一种引用数据类型。
接口的语法
接口的定义格式与普通类的定义格式几乎相同,只是把普通类中的 class 换成了 interface 关键字,这样就定义了一个接口。
与抽象类类似,接口内部的方法需要用 abstract 修饰,但是接口中不能存在普通方法和构造方法,当然这时候接口内部就只有抽象方法和全局变量,所以这个时候 abstract 关键字就可以省略。
在接口中 public 、abstract 、static 、final 统统可以省略
举例:
public interface 接口名称{
// 下列方法中虽然有的方法没有被public 或者 abstract 修饰,但是其实还是有的,只不过是被省略了。
// 在接口中方法的写法可以有以下几种,最推荐使用的是第四种,因为第四种写起来更加简洁。
public abstract void method1();
public void method2();
abstract void method3();
void method4();
}
接口的使用
接口不能直接使用,必须有一个类来实现接口,实现接口时要覆写接口中的所有抽象方法。
实现接口使用关键字 implements
格式:
public class 子类名称 implements 接口名称{
// 类内部
}
注意:
- 子类和父类之间是 extends 继承关系,类和接口之间是 implements 实现关系。
- 众所周知,继承不能实现多继承 ,但是接口却可以被多重实现,所以我们可以使用接口来实现类的多继承。
- 接口也可以使用 extends 来继承一个接口。
- 接口不能使用 extends 继承类。
- 如果一个子类同时要继承一个父类和实现一个接口,那么先使用 extends 继承父类,再使用 implements 实现接口。
- 从JDK8开始,接口中也允许存在普通方法,接口中的普通方法使用 default 关键字定义,有方法体,子类实现接口之后可以直接使用接口中的普通方法。
接口的特性
1.接口类型是一种引用类型,但是不能直接 new 接口的对象
// 出现错误,USB 是抽象的,无法实例化
public class Test{
public static void main(String[] args){
Usb usb = new Usb(); // USB 是一个接口
}
}
2. 接口中的每一个方法都是 public 的抽象方法,即接口中的方法会被隐式的制定为 public abstract(只能是 public abstract ,其他的修饰符都会报错,因为抽象方法是要被覆写的,其他的修饰符权限没有 public 权限大)
public interface USB{
// 出现错误,此处不允许使用修饰符 private
private void openDevice();
void closeDevice();
}
3.接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
public interface USB{
void openDevice();
// 编译失败,因为接口中的方法默认为抽象方法
// 出现错误,接口抽象方法不能有方法体
void closeDevice(){
System.out.println("关闭USB设备");
}
}
4.重写接口中的方法时,不能使用默认的访问权限
public interface USB {
void openDevice(); // 默认是public的
void closeDevice(); // 默认是public的
}
public class Mouse implements USB {
@Override
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 子类重写方法时权限不能低于本来的权限,这里默认的权限为 default 小于 public
void openDevice() {
System.out.println("打开鼠标");
}
}
5. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
public interface USB{
double brand = 3.0;
void openDevice();
void closeDevice();
}
public class Test{
public static void main(String[] args){
// 可以用过接口名访问,说明是 static 的
System.out.println(USB.brand);
// 出现错误,无法为最终变量 brand 分配值
// 说明 brand 是final 的
USB.brand = 2.0;
}
}
6.接口中不能有静态代码块和构造方法
public interface USB{
// 编译失败
public USB(){
}
// 编译失败
static{
}
void openDevice();
void closeDevice();
}
7.接口虽然不是类,但是接口编译之后的字节码文件的后缀仍然是 .class
8.如果类没有实现接口中的所有抽象方法,则该类必须设置为抽象类
怎么实现接口
在上文中,我们提到了接口的多重实现,那么怎么进行接口的多重实现呢?下面我们来举例说明:
先定义一个动物类:
class Animal{
protected String name;
public Animal(String name){
this.name = name;
}
}
再定义一组接口,表示动物具有的能力:
// 会飞
interface IFlying{
void fly();
}
// 会跑
interface IRunning{
void run();
}
// 会游泳
interface ISwimming{
}
创建具体的类:
猫,会跑
class Cat extends Animal implements IRunning{
public Cat(String name){
super(name);
}
@Override
public void run(){
System.out.println(this.name + "正在跑");
}
}
鱼,会游泳
class Fish extends Animal implements ISwimming{
public Fish(String name){
super(name);
}
@Override
public void swim(){
System.out.println(this.name + "正在游泳");
}
}
青蛙,会跑,也会游泳
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name){
super(name);
}
@Override
public void run(){
System.out.println(this.name + "正在跑");
}
@Override
public void swim(){
System.out.println(this.name + "正在游泳");
}
}
上面的青蛙就是一次实现了两个接口,所以要实现这两个接口中的所有抽象方法。
接口之间的继承
像是上边例子中的青蛙,既会跑又会游泳,他就要实现两个接口,我们可以把他整理一下,让他只实现一个接口就好了,那我们就要再创建一个接口,继承 IRunning 和 ISwimming。
interface IRunning{
void run();
}
interface ISwimming{
void swim();
}
interface IAmphibious extends IRunning ,ISwimming{
}
class Frog implements IAmphibious {
// 实现 run() 方法和 swim() 方法
}
接口间的继承就相当于把多个接口合并在一起。
抽象类和接口的区别
抽象类和接口都是Java中多态的常见使用方式,我们应该对其进行重点掌握,并且明确这两者的区别。
核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有抽象方法。
区别 | 抽象类(abstract) | 接口(interface) | |
1 | 结构组成 | 普通类 + 抽象方法 | 抽象方法 + 全局常量 |
2 | 权限 | 各种权限 | public |
3 | 子类使用 | 使用 extends 关键字继承抽象类 | 使用 implements 关键字实现接口 |
4 | 关系 | 一个抽象类可以实现若干个接口 | 接口不能继承抽象类,但是接口可以使用 extends 关键字继承多个父接口 |
5 | 子类限制 | 一个子类只能继承一个抽象类 | 一个子类可以实现多个接口 |
Object类
既然说到抽象类和接口,那就顺便也介绍一下 Object 类,Object 类是 Java 默认提供的一个类,在 Java 中,除了 Object 类,所有的类都是存在继承关系的。默认都会继承 Object 父类。即 Object 类是所有类的父类,并且不需要显示 extends。
获取对象信息
如果要打印对象中的内容,可以直接重写 Object 类中的 toString() 方法。当将类的对象传入 pritln 方法进行输出时,默认就会调用 toString() 。
// Object 类中的 toString() 方法实现:
public String toString(){
return getClass().getName() + "@" +Integer.toHexString(hashCode());
}
对象比较的equals方法
在 Java 中,== 进行比较时:
1.如果 == 左右两边是基本类型变量,比较的是变量中值是否相同;
2.如果 == 左右两边是引用类型变量,比较但是引用变量地址是否相同;
3.如果要比较对象中的内容是否相同,必须要重写 Object 中的 equals 方法,因为 equals 方法默认也是比较引用类型变量地址比较的,所以在使用 equals 方法时,要对 equals 方法进行重写。
// Object 类中的 equals方法
public boolean equals(Object obj){
return (this == obj);
}
下面是举例:
// 进行比较时未重写 equals 方法
class Person{
private String name ;
private int age ;
public Person(String name, int age) {
this.age = age ;
this.name = name ;
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("gaobo", 20) ;
Person p2 = new Person("gaobo", 20) ;
int a = 10;
int b = 10;
System.out.println(a == b); // 输出true
System.out.println(p1 == p2); // 输出false
System.out.println(p1.equals(p2)); // 输出false
}
}
// 进行比较时重写了 equals 方法
class Person{
private String name ;
private int age ;
public Person(String name, int age) {
this.age = age ;
this.name = name ;
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false ;
} if(this == obj) {
return true ;
}
if (!(obj instanceof Person)) {
return false ;
}
Person person = (Person) obj ; // 向下转型,比较属性值
return this.name.equals(person.name) && this.age==person.age;
}
}