java 抽象类与接口——接口

矫情吗;* 2023-10-05 23:29 129阅读 0赞

文章目录

    1. 接口
    • 1.1 接口的概念
    • 1.2 语法规则
    • 1.3 接口的使用
    • 1.4 接口的特性
      1. 5 实现多个接口
    • 1.6 接口间的继承
    • 1.7 接口使用实例
    • 1.8 Clonable 接口和深拷贝
    • 1.9 抽象类和接口的区别
    1. Object类
    • 2.1 获取实例对象
    • 2.2 对象比较equals方法
    • 2.3 hashcode方法

1. 接口

1.1 接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,手机的Type—c接口等。
d75f2257229d4ca29f45ed39692e7087.png

a556bf95f8d544b482555f7c11e95a18.png

电脑的USB口上,可以插:U盘、鼠标、键盘…所有符合USB协议的设备。

手机Type—c接口上,可以插:耳机、充电线…所有符合规范的设备。

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型

1.2 语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成 interface 关键字,就定义了一个接口。

  1. interface IShape{
  2. public abstract void draw();
  3. }
  4. public class Test {
  5. }

这里定义了一个draw方法(抽象方法),因为被abstract修饰所以不需要写出是怎样实现的。

  1. 创建接口时, 接口的命名一般以大写字母 I 开头。
  2. 接口的命名一般使用 “形容词” 词性的单词。
  3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性。

可以实现一个静态方法

  1. interface IShape{
  2. public abstract void draw();
  3. default public void func() {
  4. System.out.println("YYDS!");
  5. }
  6. public static void func1() {
  7. System.out.println("java!");
  8. }
  9. }

接口不能实例化,那该怎么使用?

类与接口之间使用implements来实现多个接口。

  1. class A implements IShape {
  2. @Override
  3. public void draw() {
  4. System.out.println("*******");
  5. }
  6. }

1.3 接口的使用

接口不能直接使用,必须要有一个”实现类”来”实现”该接口,实现接口中的所有抽象方法。

  1. public class 类名称 implements 接口名称{
  2. // ...
  3. }

【注意】

子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。

  1. interface USB {
  2. public void openDevice();//打开
  3. public void closeDevice();//关闭
  4. }
  5. class KeyBoard implements USB{
  6. @Override
  7. public void openDevice() {
  8. System.out.println("打开键盘~");
  9. }
  10. @Override
  11. public void closeDevice() {
  12. System.out.println("关闭键盘~");
  13. }
  14. public void inPut(){
  15. System.out.println("键盘输入~");
  16. }
  17. }
  18. class Mouse implements USB{
  19. @Override
  20. public void openDevice() {
  21. System.out.println("打开鼠标~");
  22. }
  23. @Override
  24. public void closeDevice() {
  25. System.out.println("关闭鼠标~");
  26. }
  27. public void click() {
  28. System.out.println("鼠标点击~");
  29. }
  30. }
  31. public class Computer {
  32. public void powerOn(){
  33. System.out.println("打开笔记本电脑");
  34. }
  35. public void powerOff(){
  36. System.out.println("关闭笔记本电脑");
  37. }
  38. public void useDevice(USB usb) {
  39. usb.openDevice();
  40. if (usb instanceof Mouse) {
  41. Mouse mouse = (Mouse) usb;
  42. mouse.click();
  43. } else if (usb instanceof KeyBoard) {
  44. KeyBoard keyBoard = (KeyBoard) usb;
  45. keyBoard.inPut();
  46. }
  47. usb.closeDevice();
  48. }
  49. public static void main(String[] args) {
  50. Computer computer = new Computer();
  51. computer.powerOn();
  52. //使用鼠标设备
  53. computer.useDevice(new Mouse());
  54. // 使用键盘设备
  55. computer.useDevice(new KeyBoard());
  56. computer.powerOff();
  57. }
  58. }

ca066092d22f40b4ab5a84a0faa32a42.png

1.4 接口的特性

  1. 接口类型是一种引用类型,但是不能直接new接口的对象

编译器报错
230043c5347747ff841da41fa3fbb494.png

  1. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其他修饰符都会报错)。

可见编译器报错。
65fef7905c7f4b0f91c38b5f6f09918e.png

  1. 接口中不能有方法的具体实现。

可见编译器报错。
60eef2f8a578424698dc5ff7f9bc8ed7.png

  1. 重写接口中方法时,不能使用默认的访问权限修饰

编译器报错。
在这里插入图片描述

  1. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量。

    interface USB {

    1. public void openDevice();//打开
    2. public void closeDevice();//关闭
    3. public static final int brand = 1;

    }

  1. 接口中不能有静态代码块和构造方法

报错
8427b7efbaf74e5585a4bdf9ceeff7d3.png

  1. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  2. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
  3. jdk8中:接口中还可以包含default方法

1. 5 实现多个接口

Java中不支持多继承,但是一个类可以实现多个接口。

implements来实现,每个接口之间用逗号来连接。

  1. interface A1 {
  2. public void func1();
  3. }
  4. interface B1 {
  5. public void func2();
  6. }
  7. class C1 implements A1,B1 {
  8. @Override
  9. public void func1() {
  10. }
  11. @Override
  12. public void func2() {
  13. }
  14. }
  15. public class Test {
  16. }

下面来看一个例子。

  1. interface IFlying {
  2. public void fly();
  3. }
  4. interface IRunning {
  5. public void run();
  6. }
  7. interface ISwimming {
  8. public void swim();
  9. }
  10. class Animals {
  11. protected String name;
  12. public void eat() {
  13. System.out.println("吃食物~");
  14. }
  15. public Animals(String name) {
  16. this.name = name;
  17. }
  18. }
  19. class Dog extends Animals implements IRunning, ISwimming{
  20. public Dog(String name) {
  21. super(name);
  22. }
  23. @Override
  24. public void run() {
  25. System.out.println(this.name + "正在跑~");
  26. }
  27. @Override
  28. public void swim() {
  29. System.out.println(this.name + "正在游泳~");
  30. }
  31. }
  32. class Bird extends Animals implements IFlying{
  33. public Bird(String name) {
  34. super(name);
  35. }
  36. @Override
  37. public void fly() {
  38. System.out.println(this.name + "正在飞~");
  39. }
  40. }
  41. class Frog extends Animals implements IRunning, ISwimming{
  42. public Frog(String name) {
  43. super(name);
  44. }
  45. @Override
  46. public void run() {
  47. System.out.println(this.name + "正在跑~");
  48. }
  49. @Override
  50. public void swim() {
  51. System.out.println(this.name + "正在游泳~");
  52. }
  53. }
  54. class Duck extends Animals implements IFlying, IRunning, ISwimming{
  55. public Duck(String name) {
  56. super(name);
  57. }
  58. @Override
  59. public void fly() {
  60. System.out.println(this.name + "正在飞~");
  61. }
  62. @Override
  63. public void run() {
  64. System.out.println(this.name + "正在跑~");
  65. }
  66. @Override
  67. public void swim() {
  68. System.out.println(this.name + "正在游泳~");
  69. }
  70. }
  71. class Robot implements IRunning {
  72. private String name;
  73. public Robot(String name) {
  74. this.name = name;
  75. }
  76. @Override
  77. public void run() {
  78. System.out.println(this.name + "正在用轮子跑");
  79. }
  80. }
  81. public class Test1 {
  82. public static void walk(IRunning iRunning) {
  83. iRunning.run();
  84. }
  85. public static void main(String[] args) {
  86. walk(new Dog("小狗"));
  87. walk(new Duck("鸭子"));
  88. walk(new Frog("青蛙"));
  89. walk(new Robot("机器人"));
  90. }
  91. }

f116ef16c3514a05b49033b9d00b3271.png

狗是一种动物, 具有会跑、游泳的特性。
鸟是一种动物,具有会飞的特性。
青蛙也是一种动物, 既能跑, 也能游泳。
鸭子也是一种动物, 既能跑, 也能游, 还能飞。

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。

1.6 接口间的继承

接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

接口可以继承一个接口,达到复用的效果,使用 extends 关键字。

  1. interface Iflying {
  2. public void fly();
  3. }
  4. interface Iswimming {
  5. public void swim();
  6. }
  7. interface Ifunc extends Iflying, ISwimming {
  8. public void func();
  9. }
  10. interface Ifunc1 extends Ifunc {
  11. public void func1();
  12. }

接口间的继承相当于把多个接口合并在一起

1.7 接口使用实例

  1. class Student {
  2. public String name;
  3. public int age;
  4. @Override
  5. public String toString() {
  6. return "Student{" +
  7. "name='" + name + '\'' +
  8. ", age=" + age +
  9. '}';
  10. }
  11. public Student(String name, int age) {
  12. this.name = name;
  13. this.age = age;
  14. }
  15. }
  16. public class Test {
  17. public static void main(String[] args) {
  18. Student[] student = new Student[3];
  19. student[0] = new Student("zhangsan", 19);
  20. student[1] = new Student("lisi", 17);
  21. student[2] = new Student("wangwu", 20);
  22. Arrays.sort(student);
  23. System.out.println(Arrays.toString(student));
  24. }
  25. }

上面代码是按照学生的姓名比较的?还是按照学生的年龄比价的?

我们要让Student类可支持比较大小。

现在分别写出按照年龄和姓名排序的方法:

  1. import java.util.Arrays;
  2. import java.util.Comparator;
  3. class StudentSort implements Comparable<StudentSort>{
  4. public String name;
  5. public int age;
  6. public StudentSort(String name, int age) {
  7. this.name = name;
  8. this.age = age;
  9. }
  10. @Override
  11. public String toString() {
  12. return "Student{" +
  13. "name='" + name + '\'' +
  14. ", age=" + age +
  15. '}';
  16. }
  17. @Override
  18. public int compareTo(StudentSort o) {
  19. if(this.age - o.age > 0) {
  20. return 1;
  21. }else if(this.age - o.age < 0) {
  22. return -1;
  23. }else {
  24. return 0;
  25. }
  26. //return 0;
  27. }
  28. }
  29. class AgeComparator implements Comparator<StudentSort> {
  30. @Override
  31. public int compare(StudentSort o1, StudentSort o2) {
  32. return o1.age - o2.age;
  33. }
  34. }
  35. class NameComparator implements Comparator<StudentSort> {
  36. @Override
  37. public int compare(StudentSort o1, StudentSort o2) {
  38. return o1.name.compareTo(o2.name);
  39. }
  40. }
  41. public class TestSort {
  42. public static void main(String[] args) {
  43. StudentSort[] students = new StudentSort[3];
  44. students[0] = new StudentSort("zhangsan", 10);
  45. students[1] = new StudentSort("lisi", 40);
  46. students[2] = new StudentSort("wangwu", 5);
  47. AgeComparator ageComparator = new AgeComparator();//按照年龄排序
  48. Arrays.sort(students, ageComparator);
  49. System.out.println(Arrays.toString(students));
  50. NameComparator nameComparator = new NameComparator();//按照姓名排序
  51. Arrays.sort(students, nameComparator);
  52. System.out.println(Arrays.toString(students));
  53. }
  54. }

7f24b10dbaab440d884a98c50343e7ab.png

输出的第一行是年龄有小到排序的,第二行是姓名有小到大排序的。

1.8 Clonable 接口和深拷贝

Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 “拷贝”。但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出 CloneNotSupportedException 异常。

  1. class Person1 implements Cloneable{
  2. public int id;
  3. protected Object clone() throws CloneNotSupportedException {
  4. return super.clone();
  5. }
  6. @Override
  7. public String toString() {
  8. return "Person{" +
  9. "id=" + id +
  10. '}';
  11. }
  12. }
  13. public class Test {
  14. public static void main(String[] args) throws CloneNotSupportedException {
  15. Person1 person = new Person1();
  16. person.id = 99;
  17. Person1 person1 = (Person1)person.clone();
  18. person1.id = 100;
  19. System.out.println(person);
  20. System.out.println(person1);
  21. }
  22. }

c373416920ac4c89a315c75e57dec0a1.png

浅拷贝:

先来看一段代码

  1. class Money {
  2. public double money = 12.5;
  3. }
  4. class Person implements Cloneable{
  5. public int id;
  6. public Money money = new Money();
  7. @Override
  8. protected Object clone() throws CloneNotSupportedException {
  9. return super.clone();
  10. }
  11. @Override
  12. public String toString() {
  13. return "Person{" +
  14. "id=" + id +
  15. '}';
  16. }
  17. }
  18. public class Test {
  19. public static void main(String[] args) throws CloneNotSupportedException {
  20. Person person = new Person();
  21. Person person1 = (Person)person.clone();
  22. System.out.println("person:" + person.money.money);
  23. System.out.println("person1:" + person.money.money);
  24. }
  25. }

d50ff9cf6371428bac396ed58e334218.png

如果将person1中的money的值改变会发生什么呢?

  1. public class Test {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Person person = new Person();
  4. Person person1 = (Person)person.clone();
  5. person1.money.money = 199;
  6. System.out.println("person:" + person.money.money);
  7. System.out.println("person1:" + person.money.money);
  8. }
  9. }

d172343366664441a953c547e9cac0ea.png

根据运行结果会发现,person1的值修改后。person的值也跟着一起修改了。
这种情况就叫做浅拷贝。

很明显这并不是想要得到的结果,那该怎么做呢?

深拷贝:

  1. class Money implements Cloneable{
  2. public double m = 12.5;
  3. @Override
  4. protected Object clone() throws CloneNotSupportedException {
  5. return super.clone();
  6. }
  7. }
  8. class Person implements Cloneable{
  9. public int id;
  10. public Money money = new Money();
  11. @Override
  12. protected Object clone() throws CloneNotSupportedException {
  13. Person tmp = (Person) super.clone();
  14. tmp.money = (Money) this.money.clone();
  15. return tmp;
  16. }
  17. @Override
  18. public String toString() {
  19. return "Person{" +
  20. "id=" + id +
  21. '}';
  22. }
  23. }
  24. public class Test {
  25. public static void main(String[] args) throws CloneNotSupportedException {
  26. Person person = new Person();
  27. Person person2 = (Person)person.clone();
  28. person2.money.m = 1999;
  29. System.out.println("person:"+person.money.m);
  30. System.out.println("person2:"+person2.money.m);
  31. }
  32. }

50f3182996be479588e99c48ba8e3bef.png

1.9 抽象类和接口的区别

抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。

如之前写的 Animal 例子, 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的。因此此处的 Animal 只能作为一个抽象类,而不应该成为一个接口。

  1. class Animal {
  2. protected String name;
  3. public Animal(String name) {
  4. this.name = name;
  5. }
  6. }

抽象类存在的意义是为了让编译器更好的校验,像 Animal 这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了 Animal 的实例,编译器会及时提醒我们。

2. Object类

Object是Java默认提供的一个类,所有类的对象都可以使用Object的引用进行接收。

  1. class Person {
  2. }
  3. class Student {
  4. }
  5. public class Test {
  6. public static void func(Object object) {
  7. System.out.println(object);
  8. }
  9. public static void main(String[] args) {
  10. func(new Person());
  11. func(new Student());
  12. }
  13. }

3cda2cd2a9884e7e90fb2eca04db6a2d.png

对于整个Object类中的方法需要实现全部掌握。
本小节当中,我们主要来熟悉这几个方法:toString()方法equals()方法hashcode()方法

2.1 获取实例对象

如果要打印对象中的内容,可以直接重写Object类中的toString()方法,之前已经讲过了,此处不再赘述。

  1. // Object类中的toString()方法实现:
  2. public String toString() {
  3. return getClass().getName() + "@" Integer.toHexString(hashCode());
  4. }

2.2 对象比较equals方法

在Java中,== 进行比较时:

  • a.如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同
  • b.如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
  • c.如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:

    // Object类中的equals方法
    public boolean equals(Object obj) {

    1. return (this == obj); // 使用引用中的地址直接来进行比较

    }

因为是按照地址进行比较的,是两块不同的空间,所以比较出来的结果不相等。

  1. class Person {
  2. private String name;
  3. private int age;
  4. public Person(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. }
  9. class Student {
  10. }
  11. public class Test {
  12. public static void main(String[] args) {
  13. Person person1 = new Person("张三", 19);
  14. Person person2 = new Person("张三", 19);
  15. System.out.println(person1.equals(person2));
  16. }
  17. }

597bbe2013ae43af8e2880d3e47e8c0b.png

4a71905fec904fa790fb60e8a7134775.png

调试后可见person1和person2所在的位置不一样。

要换一种比较的方法。

按照自己的逻辑重写一下。

  1. class Person {
  2. private String name;
  3. private int age;
  4. public Person(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. @Override
  9. public boolean equals(Object obj) {
  10. Person person = (Person) obj;
  11. if (this.name.equals(person.name) && this.age == person.age) {
  12. return true;
  13. }else {
  14. return false;
  15. }
  16. }
  17. }
  18. class Student {
  19. }
  20. public class Test {
  21. public static void main(String[] args) {
  22. Person person1 = new Person("张三", 19);
  23. Person person2 = new Person("张三", 19);
  24. System.out.println(person1.equals(person2));
  25. }
  26. }

7f0bbdd04bad47d480025ffa6e45b951.png

2.3 hashcode方法

hashCode()这个方法,他帮我算了一个具体的对象位置。由于这里面涉及数据结构,现在只是简单的认识一下。

两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法。

我们可以来看示例代码:

  1. class Person {
  2. private String name;
  3. private int age;
  4. public Person(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. }
  9. class Student {
  10. }
  11. public class Test {
  12. public static void main(String[] args) {
  13. Person per1 = new Person("zhangsan", 20) ;
  14. Person per2 = new Person("zhangsan", 20) ;
  15. System.out.println(per1.hashCode());
  16. System.out.println(per2.hashCode());
  17. }
  18. }

在这里插入图片描述

可以看到两个值不一样大。

这里和equals一样需要重写一下。

  1. import java.util.Objects;
  2. class Person {
  3. private String name;
  4. private int age;
  5. public Person(String name, int age) {
  6. this.name = name;
  7. this.age = age;
  8. }
  9. @Override
  10. public int hashCode() {
  11. return Objects.hash(name, age);
  12. }
  13. }
  14. class Student {
  15. }
  16. public class Test {
  17. public static void main(String[] args) {
  18. Person per1 = new Person("zhangsan", 20) ;
  19. Person per2 = new Person("zhangsan", 20) ;
  20. System.out.println(per1.hashCode());
  21. System.out.println(per2.hashCode());
  22. }
  23. }

8cce903c868b473f898c9a3d6642110b.png

可以发现两个值一样大了。

  • hashcode方法用来确定对象在内存中存储的位置是否相同
  • 事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置

发表评论

表情:
评论列表 (有 0 条评论,129人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java抽象接口

    抽象类 > 本章节为基础补充 > 抽象方法:只有方法的名字,没有方法的实现(abstract) > 要点:抽象类的所有方法,继承了他的子类,都必须去实现他的方法,除

    相关 抽象接口

       对于面向对象编程来说,抽象是它的一大特征之一。在Java中,可以通过两种形式来体现OOP的抽象:接口和抽象类。这两者有太多相似的地方,又有太多不同的地方。很多人在初学的时

    相关 抽象接口

    我在求职一份Android应用开发实习生时,一家公司的面试官问了我这样的一道题目:“请谈谈你对抽象类与接口的理解?”我当初的回答,仅是简单解释了什么是抽象类,什么是接口,而至于

    相关 接口抽象

    抽象类 包含抽象方法的叫做抽象类,抽象类中也可以含普通方法。 接口 接口没有提供任何实现,而抽象类中还可以有一些实现。 接口中可以包含成员变量,比如说int x=5;但

    相关 抽象接口

    抽象类与接口 抽象类的作用 抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而

    相关 接口抽象

    JAVA基础之接口与抽象类的区别 1. java不能多继承,一个类只能继承一个抽象类;但是可以实现多个接口; 2. 继承抽象类是一种IS-A的关系,实现接口是一种LIKE