9. 抽象类和接口
- [9.1 抽象类](#9.1 抽象类)
-
- [9.1.1 抽象类概念](#9.1.1 抽象类概念)
- [9.1.2 抽象类语法](#9.1.2 抽象类语法)
- [9.1.3 抽象类的特性](#9.1.3 抽象类的特性)
- [9.1.4 抽象类的作用](#9.1.4 抽象类的作用)
- [9.2 接口](#9.2 接口)
-
- [9.2.1 接口的概念](#9.2.1 接口的概念)
- [9.2.2 语法规则](#9.2.2 语法规则)
- [9.2.3 接口使用](#9.2.3 接口使用)
- [9.2.4 接口特性](#9.2.4 接口特性)
- [9.2.5 实现多个接口](#9.2.5 实现多个接口)
- [9.2.6 接口的继承](#9.2.6 接口的继承)
- [9.2.9 抽象类和接口的区别](#9.2.9 抽象类和接口的区别)
- [9.3 Object类](#9.3 Object类)
-
- [9.3.1 获取对象方法](#9.3.1 获取对象方法)
- [9.3.1 对象比较equals方法](#9.3.1 对象比较equals方法)
- [9.3.2 hashcode方法](#9.3.2 hashcode方法)
9.1 抽象类
9.1.1 抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为** 抽象类(abstract class)**.
9.1.2 抽象类语法
包含抽象方法的类,必须也拿abstract修饰 ,此时这个类也叫抽象类
java
abstract class Shape {
// 抽象方法
public abstract void draw();
}
9.1.3 抽象类的特性
- 抽象类不能被实例化
- 如果一个普通类继承了一个抽象类,那么此时这个普通类 必须重写这个抽象方法
java
class Cycle extends Shape {
// 一定要重写父类的这个抽象方法
@Override
public void draw() {
System.out.println("⚪");
}
}
- 在一个普通类继承了抽象类,如果再被继承,那这个普通类必须同时重写这两个类
java
abstract class A extends Shape {
public abstract void testA();
}
class B extends A {
@Override
public void testA() {
}
@Override
public void draw() {
}
}
- 抽象类和 普通类 的区别在于:
- 可以和普通类一样 有成员变量、成员方法
- 多了抽象方法
- 多了不能实例化
-
什么情况下 要设计为抽象类
如果这个类 不能描述一个而具体的对象,那么就可以设置为抽象类
比如:Animal这个类
-
抽象类当中可以包含构造方法,这个构造方法并不是实例化这个抽象类的时候使用,因为他就不能被实例化。那么这个构造方法,主要是在子类当中让子类调用,帮助父类进行初始化
java
abstract class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
public Student() {
super("zhangsan", 10);
}
}
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
9.1.4 抽象类的作用
抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
9.2 接口
9.2.1 接口的概念
在现实生活里,接口的例子比如有:笔记本上的USB口,电源插座等。
而USB口可以插 U盘、鼠标、键盘等所有符合USB协议的设备。
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用 。在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
9.2.2 语法规则
- 定义一个接口的时候使用关键字interface来定义
提示:
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 "形容词" 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
9.2.3 接口使用
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
IUSB接口:
java
public interface IUSB {
void openDevice();
void closeDevice();
}
Mouse类:
java
public class Mouse implements IUSB{
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
public void click() {
System.out.println("点击鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
}
Keyboard类:
java
public class KeyBoard implements IUSB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
public void inPut() {
System.out.println("键盘输入");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
}
Computer类:
java
public class Computer {
public void powerOn() {
System.out.println("打开电脑");
}
public void powerOff() {
System.out.println("关闭电脑");
}
public void useDevice(IUSB iusb) {
iusb.openDevice();
// instanceof :测试它左边的对象是否是它右边的类的实例 ,返回boolean类型
// A(对象) instanceof B(类)
if (iusb instanceof Mouse) {
Mouse mouse = (Mouse) iusb;
mouse.click();
}else if(iusb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) iusb;
keyBoard.inPut();
}
iusb.closeDevice();
}
}
Test类:
java
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
computer.useDevice(new Mouse());
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
9.2.4 接口特性
- 接口当中的方法 如果没有被实现, 那么他默认就是一个抽象方法
- 在接口当中的方法不能有具体的实现
- 如果有具体的实现,那么必须是由default修饰或者是static修饰
java
interface Ishape {
public int a = 10;
public abstract void draw();
//在接口当中的方法不能有具体的实现
//如果有具体的实现,那么必须是由default修饰或者是static修饰
public default void test() {
System.out.println("ds");
}
public static void func() {
}
}
- 接口当中定义成员变量 默认都是public static final的
java
public int a = 10;
public static final int b = 100;
int aa = 10; // 可以不加public static final 直接定义int aa = 10;
int bb = 20;
- 接口当中的抽象方法 默认都是public abstract修饰的
java
public abstract void draw();
void fun1();
- 接口类型是一种引用类型,是不可以被实例化
- 类和接口之间的关系 可以使用implements来关联
java
interface IShape {
void draw();
}
class Rect implements IShape{
@Override
public void draw() {
System.out.println("矩形");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("⚪");
}
}
class Flower implements IShape {
@Override
public void draw() {
System.out.println("❀");
}
}
public class Test {
public static void func(IShape iShape) {
iShape.draw();
}
public static void main(String[] args) {
//IShape iShape = new IShape();
IShape iShape1 = new Flower();
IShape iShape2 = new Rect();
IShape iShape3 = new Cycle();
func(iShape1);
func(iShape2);
func(iShape3);
IShape[] iShapes = {iShape1,iShape2,iShape3};
}
}
- 接口也是可以产生字节码文件的(.class)
- 接口中不能有静态代码块和构造方法
java
public interface USB {
// 编译失败
public USB(){
}
{} // 编译失败
void openDevice();
void closeDevice();
}
- 一个类 可以继承一个抽象类/普通类 同时还可以实现这个接口
java
abstract class AA {
}
class CC extends AA implements IUSB {
}
9.2.5 实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物.
java
public abstract class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public abstract void eat();
}
提供一组接口,分别表示会飞,会跑,会游泳
会飞的接口:
java
public interface IFly {
void fly();
}
会跑的接口:
java
public interface IRun {
void run();
}
会游泳的接口:
java
public interface ISwim {
void swim();
}
创建鸟,狗,鸭子,机器人这个类:
鸟:
java
public class Bird extends Animal implements IFly,IRun{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+" 正在用两个翅膀飞");
}
@Override
public void eat() {
System.out.println(this.name + "正在吃鸟粮");
}
@Override
public void run() {
System.out.println(this.name+ " 正在用两个小腿跑");
}
}
狗:
java
public class Dog extends Animal implements ISwim,IRun{
public Dog(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name+" 正在用4条腿游泳");
}
@Override
public void eat() {
System.out.println(this.name+ " 正在吃狗粮");
}
@Override
public void run() {
System.out.println(this.name+" 正在用4条腿跑");
}
}
鸭子:
java
public class Duck extends Animal implements IFly,IRun,ISwim{
public Duck(String name) {
super(name);
}
@Override
public void eat() {
System.out.println(this.name+" 正在吃鸭粮");
}
@Override
public void fly() {
System.out.println(this.name+" 正在用鸭翅膀飞");
}
@Override
public void run() {
System.out.println(this.name+"正在用鸭腿跑");
}
@Override
public void swim() {
System.out.println(this.name+"正在用鸭腿游泳");
}
}
机器人:
java
public class Robot implements IRun{
@Override
public void run() {
System.out.println("机器人在跑");
}
}
Test类:
java
public class Test {
public static void func1(Animal animal) {
animal.eat();
}
public static void testFly(IFly iFly) {
iFly.fly();
}
public static void testSwim(ISwim iSwim) {
iSwim.swim();
}
public static void testRun(IRun iRun) {
iRun.run();
}
public static void main(String[] args) {
func1(new Duck("小黄鸭"));
testFly(new Duck("小黄鸭"));
testSwim(new Duck("小黄鸭"));
testFly(new Bird("布谷"));
func1(new Dog("旺财"));
// testFly(new Dog("旺财")); // 报错 狗没有Ifly接口
testRun(new Robot());
}
}
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。一个类继承一个父类,同时实现多种接口。
9.2.6 接口的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.
java
// 两栖的动物, 既能跑, 也能游
public interface IAmphibious extends IRun,ISwim {
void test1();
}
再创建一个Frog类接口实现run方法和swim方法:
java
public class Frog extends Animal implements IAmphibious{
public Frog(String name) {
super(name);
}
@Override
public void eat() {
}
@Override
public void test1() {
}
@Override
public void run() {
}
@Override
public void swim() {
}
}
接口间的继承相当于把多个接口合并在一起。
9.2.9 抽象类和接口的区别
- 抽象类当中,可以包含和普通类一样的成员变量和成员方法,但是接口 当中的成员变量只能是public static final 的,方法只能是public abstract的
- ** 一个类只能继承一个抽象类,但是能够同时实现多个接口**,所以解决了Java当中不能进行多继承的特性。
9.3 Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
Object是所以类的父类,意味着可以发生向上转型,能接受所以类的对象。
java
class Person {
public String name;
}
class Student extends Person {
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
//Object 是所以类的父类,意味着可以发生向上转型
Object obj = new Person();
Object obj1 = new Student();
}
}
9.3.1 获取对象方法
如果要打印对象中的内容,可以直接重写Object类中的toString()方法、
java
class Person {
public String name = "haha";
@Override
public String toString() {
return "name: "+ name;
}
}
class Student extends Person {
}
public class Test {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person);
//Object 是所以类的父类,意味着可以发生向上转型
/*Object obj = new Person();
Object obj1 = new Student();*/
}
}
9.3.1 对象比较equals方法
在Java中,进行比较时:
a.如果左右两侧是基本类型变量,比较的是变量中值是否相同
b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
java
// Object类中的equals方法
public boolean equals(Object obj) {
return (this == obj); // 使用引用中的地址直接来进行比较
}
java
class Person {
public String name;
public Person(String name){
this.name = name;
}
@Override
public String toString() {
return "name: "+ name;
}
@Override
public boolean equals(Object obj) {
//发生动态绑定
Person tmp = (Person) obj;
return tmp.name.equals(this.name);
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("zhangsan");
Person person2 = new Person("zhangsan");
System.out.println(person1 == person2);
//调用了object方法,所以要在Person类重写equals方法
System.out.println(person1.equals(person2));
String str1 = "zhangsan";
String str2 = "zhangsan";
System.out.println(str1.equals(str2));
}
}
比较对象中内容是否相同的时候,一定要重写equals方法。
9.3.2 hashcode方法
重写hashCode之后哈希值就会相同
java
import java.util.Objects;
class Person {
public String name;
public Person(String name){
this.name = name;
}
@Override
public String toString() {
return "name: "+ name;
}
/*@Override
public boolean equals(Object obj) {
//发生动态绑定
Person tmp = (Person) obj;
return tmp.name.equals(this.name);
}*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
public class Test {
public static void main(String[] args) {
Person person1 = new Person("zhangsan");
Person person2 = new Person("zhangsan");
System.out.println(person1.hashCode());
System.out.println(person2.hashCode());
System.out.println(person1.equals(person2));
}
注意:
- hashcode方法用来确定对象在内存中存储的位置是否相同
- 事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。