Java基础——抽象类和接口

谁践踏了优雅 2023-10-02 20:03 152阅读 0赞

一、抽象类(强制子类覆写方法)

抽象类是普通类的“超集”,只是比普通类多了一些抽象方法而已(抽象方法个数:0-N)

抽象方法所在的类必须是抽象类,子类若是继承了抽象类,必须覆写所有的抽象方法(子类是普通类)

Java中定义抽象类或者抽象方法使用 abstract 关键字

1、抽象方法所在的类必须使用abstract声明为抽象类

抽象方法指的是1、使用abstract关键字声明,只有函数声明,没有函数实现的方法(2、没有方法体的方法),称为抽象方法。抽象类中没有具体实现,延迟到子类实现

5eda5d0a2daa49b2ae668cb7ac6002b1.png

  1. public abstract class Sharp {
  2. public abstract void print();
  3. }
  4. public class Cycle extends Sharp{
  5. @Override
  6. public void print() {
  7. System.out.println("●");
  8. }
  9. }
  10. public class Square extends Sharp{
  11. @Override
  12. public void print() {
  13. System.out.println("■");
  14. }
  15. }
  16. public class Test {
  17. public static void test(Sharp sharp){
  18. sharp.print();
  19. }
  20. public static void main(String[] args) {
  21. test(new Cycle());
  22. test(new Square());
  23. }
  24. }

094ce6185ed3438688dee4c0a861bc23.png

子类必须覆写所有的抽象方法,否则就会报错4f19f036917d486e8a2b8c74620847cb.png

2、若一个类使用abstract关键字声明为抽象类,无法直接通过该类实例化对象,哪怕该类中一个抽象方法都没有。(当一个类是抽象类,不管有无抽象方法,该类本身就是一个抽象的概念,没法具体到某个特定的实例)只能通过子类向上转型变为抽象父类的引用。

f33a360c41924754af6833477a5dd587.png

3、子类继承了抽象类,就必须强制子类覆写抽象类中的所有抽象方法(子类是普通类)。也满足单继承局限,一个子类只能extends一个抽象类。

  1. abstract class A{
  2. public abstract void printA();
  3. }
  4. //抽象类B继承抽象类A
  5. //A中的抽象方法,B可以覆写,也可以不覆写
  6. //如果B覆写A的抽象方法,则C就不需要再继续覆写A的抽象方法
  7. //如果B没有覆写A的抽象方法,则C既需要覆写B的抽象方法,也需要覆写A的抽象方法
  8. abstract class B extends A{
  9. public abstract void printB();
  10. }
  11. public class C extends B{
  12. @Override
  13. public void printA() {
  14. }
  15. @Override
  16. public void printB() {
  17. }
  18. }

4、抽象类是普通类的“超集”(普通类有的内容,抽象类全都有),只是比普通类多了一些抽象方法而已。抽象类虽然没法直接实例化对象,但也存在构造方法,子类在实例化时,仍然遵从继承的规则,先调用父类的构造方法,而后调用子类的构造方法。

  1. abstract class BaseTest{
  2. public BaseTest(){
  3. print();
  4. }
  5. abstract void print();
  6. }
  7. public class Fun extends BaseTest{
  8. private int num = 10;
  9. @Override
  10. void print() {
  11. //num = 0;
  12. System.out.println("num = " + num);
  13. }
  14. public static void main(String[] args) {
  15. Fun fun = new Fun();
  16. }
  17. }

二、接口

接口中只有全局常量(1%)和抽象方法(99%)—>更加纯粹的抽象概念。

接口使用关键字 interface 声明接口,子类使用 implements 实现接口(必须覆写所有的抽象方法)

1、接口使用的场景

(1)接口表示具备某种能力/行为,子类实现接口时不是满足 is a 关系,而是具备这种行为或者是能力。接口允许多实现,一个类可以具备多个能力,同时实现多个父接口。若子类是普通类并想实现多个父接口,子类需要覆写所有的抽象方法。

  1. //游泳接口,表示具备游泳的能力
  2. public interface ISwim {
  3. public abstract void swim();
  4. }
  5. //跑的接口 ,表示具备跑的能力/行为
  6. public interface IRun {
  7. public abstract void run();
  8. }
  9. //飞的接口,表示具备飞的能力/行为
  10. public interface IFly {
  11. public abstract void fly();
  12. }
  13. public class Rabbit implements IRun{
  14. @Override
  15. public void run() {
  16. System.out.println("兔子在跑");
  17. }
  18. }
  19. public class Dog implements IRun,ISwim{
  20. @Override
  21. public void run() {
  22. System.out.println("狗在跑");
  23. }
  24. @Override
  25. public void swim() {
  26. System.out.println("狗在游泳");
  27. }
  28. }
  29. public class Duck implements IRun,ISwim,IFly{
  30. @Override
  31. public void fly() {
  32. System.out.println("鸭子在飞");
  33. }
  34. @Override
  35. public void run() {
  36. System.out.println("鸭子在跑");
  37. }
  38. @Override
  39. public void swim() {
  40. System.out.println("鸭子在游泳");
  41. }
  42. }
  43. public class Test {
  44. public static void main(String[] args) {
  45. // 接口也不能直接实例化对象,需要向上转型
  46. IRun run = new Rabbit();
  47. IRun run1 = new Dog();
  48. IRun run2 = new Duck();
  49. run.run();
  50. run1.run();
  51. run2.run();
  52. ISwim swim = new Dog();
  53. ISwim swim1 = new Duck();
  54. swim.swim();
  55. swim1.swim();
  56. IFly fly = new Duck();
  57. fly.fly();
  58. }
  59. }

(2)接口表示一种规范或者标准。“USB接口”

  1. interface USB {
  2. //插入方法
  3. public abstract void plugIn();
  4. //工作方法
  5. public abstract void work();
  6. }
  7. class Mouse implements USB{
  8. @Override
  9. public void plugIn() {
  10. System.out.println("安装鼠标驱动~~");
  11. }
  12. @Override
  13. public void work() {
  14. System.out.println("鼠标开始正常工作!");
  15. }
  16. }
  17. class KeyBoard implements USB {
  18. @Override
  19. public void plugIn() {
  20. System.out.println("安装键盘驱动中~~");
  21. }
  22. @Override
  23. public void work() {
  24. System.out.println("键盘开始正常工作 ~ ");
  25. }
  26. }
  27. public class Computer {
  28. public static void main(String[] args) {
  29. Computer computer = new Computer();
  30. Mouse mouse = new Mouse();
  31. KeyBoard keyBoard = new KeyBoard();
  32. computer.fun(mouse);
  33. computer.fun(keyBoard);
  34. }
  35. public void fun(USB usb){
  36. usb.plugIn();
  37. usb.work();
  38. }
  39. }

2、接口中只有全局常量和抽象方法,因此接口中public abstract ->抽象方法;static final->常量;全都可以省略。(只能在接口中省略,抽象类不行!!)

05ce43ae6e834ec69c050aea872df51b.png

3、接口和类之间的关系

接口和接口之间也存在继承关系(可以是多继承),但接口坚决不能继承一个类

  1. interface IA{
  2. void testA();
  3. }
  4. interface IB{
  5. void testB();
  6. }
  7. //IC接口可以同时继承多个父接口,并继承了所有的抽象方法
  8. interface IC extends IA,IB{
  9. }
  10. //子类(普通子类)在实现IC接口时,必须覆写所有的抽象方法
  11. class IcTest implements IC{
  12. @Override
  13. public void testA() {
  14. }
  15. @Override
  16. public void testB() {
  17. }
  18. }

若一个类既需要继承一个类,同时实现多个接口时,先使用extends继承一个类,而后使用implements实现多个接口

  1. interface IA{
  2. void testA();
  3. }
  4. interface IB{
  5. void testB();
  6. }
  7. abstract class Person{
  8. }
  9. class China extends Person implements IA,IB{
  10. @Override
  11. public void testA() {
  12. }
  13. @Override
  14. public void testB() {
  15. }
  16. }

4、接口的命名规范

(1)为了区分接口和类,命名接口使用 I 开头,IRun , ISwim

(2)子类实现一个接口时,命名以相应的接口开头,以impl 结尾。如果子类实现多个父接口,不需要此规范来命名

如:若是 IRun的子类,RunImpl

三、Object类

全名称:包名.类名(java.lang.Object)

1、Object 类是Java中所有类的默认父类,无需使用 extends 来定义。只要是 class 声明的类都有一个 父类,即 Object 类。

因为Object 类是所有类的父类,使用 Object 引用来接收所有的类型,参数最高统一化。Java中所有类型都可以发生向上转型变成 Object 类。

void fun(Object obj){

// obj 可以接收任意类型的对象

}

  1. public static void main(String[] args) {
  2. //Object类是所有类(JDK本身的类,自定义的类)的父类
  3. Object obj1 = "123";
  4. Object obj2 = new Person();
  5. Object obj3 = new String();
  6. }

2、Object类中所有方法子类全都继承下来了,如toString方法

System.out.println(任意的引用数据类型)都可以打印->默认都调用toString()方法,Object类存在toString()。

a53a9cedda1b4a1a8167d5a1eda463c8.png

fbdd5c9f7d3b4b6b9750edf95bf9293b.png e5fd0bf960564391a8a5b848027e35f2.png

当子类覆写了object类的toString方法,obj2 引用就调用覆写后的方法a4cc98f22a08407a8f17450c675c8cd4.png

c00e4eb685f544bab8126650d9c48497.png

3、Java中引用数据类型之间的 相等比较使用equals方法!不能使用“==”,“==”比较的是地址

efde74ec4cb44364b53e4563609f5ce4.png

  1. public class ObjectTest {
  2. public static void main(String[] args) {
  3. Student student1 = new Student("小羊", 18);
  4. Student student2 = new Student("小朱", 16);
  5. Student student3 = new Student("小羊", 18);
  6. //"=="比较的是数值,对于引用数据类型来讲,保存的内容就是一个地址,
  7. // “==”在这里比较的是地址
  8. //false
  9. // System.out.println(student1 == student2);
  10. // //false
  11. // System.out.println(student1 == student3);
  12. // System.out.println(student1);
  13. // System.out.println(student2);
  14. // System.out.println(student3);
  15. //若需要比较两个对象的属性值是否相同,就需要按照比较规则覆写equals方法
  16. System.out.println(student1.equals(student2));
  17. System.out.println(student1.equals(student3));
  18. }
  19. }
  20. class Student {
  21. private String name;
  22. private int age;
  23. public Student(String name, int age) {
  24. this.name = name;
  25. this.age = age;
  26. }
  27. @Override
  28. public boolean equals(Object obj) {
  29. //1.若当前对象就是obj
  30. if (this == obj) {
  31. return true;
  32. }
  33. //2.此时当前对象和obj指向的对象确实不是一个地址,
  34. // 就需要向下转型,比较Student类中特有的属性值是否相同,
  35. // 向下转型先要规避风险 instance of
  36. if (obj instanceof Student) {
  37. Student student = (Student) obj;
  38. //this.name.equals(student.name)比较的是当前对象和传入对象对name这一String类型的属性值的比较
  39. //String类中equals方法已经覆写了,不需要我们再人为操作
  40. return this.name.equals(student.name) && this.age == student.age;
  41. }
  42. return false;
  43. }
  44. }

4、Object类不仅是所有类(class)的父类,JDK对Object类做了扩展,即Object类可以接收所有引用数据类型的对象(接口、数组、类)。因此在Java中,若一个方法参数或者返回值是Object类型,说明该参数或者返回值可以是任意引用数据类型(数组、类、接口)

  1. interface ITest{
  2. void print();
  3. }
  4. class TestImpl implements ITest{
  5. @Override
  6. public void print() {
  7. System.out.println("TestImpl类的print方法" );
  8. }
  9. }
  10. public class ObjectTest {
  11. public static void main(String[] args) {
  12. //接口引用
  13. ITest i1 = new TestImpl();
  14. //整形数组引用
  15. int[] data = new int[10];
  16. //只要是引用数据类型,都可以使用Object来接收
  17. Object obj1 = i1;
  18. Object obj2 = data;
  19. }
  20. }

四、JDK内置的两大重要接口

接口优先原则,当一个场景中既可以使用抽象类也可以使用接口定义时,优先考虑使用接口(更灵活)

1、Comparable接口(java.lang.Comparable):

当一个类实现了Comparable接口,表示该类具备了可比较的能力。

  1. public class ComparableTest {
  2. public static void main(String[] args) {
  3. Person[] people = new Person[]{
  4. new Person("小明",22),
  5. new Person("小李",20),
  6. new Person("小王",18)
  7. };
  8. Arrays.sort(people);
  9. System.out.println(Arrays.toString(people));
  10. }
  11. }
  12. class Person{
  13. String name;
  14. int age;
  15. public Person(String name, int age) {
  16. this.name = name;
  17. this.age = age;
  18. }
  19. }

61c47a9b5bca490e91852dd51a1810e4.png

由于Person这个类型是自定义类型,对于编译器来说,不像int这样大小关系一目了然,到底哪个Person对象大,哪个Person对象小,编译器无从得知。要让Person这个类型具备可比较的能力,也就是让JDK知道Person对象“谁大谁小”,就需要让Person类 实现Comparable接口,覆写抽象方法Compare to()。

8ebe1660c5644043978ddd5a4f32fe3a.png

  1. public class ComparableTest {
  2. public static void main(String[] args) {
  3. Person[] people = new Person[]{
  4. new Person("小王",18),
  5. new Person("小明",22),
  6. new Person("小李",20),
  7. };
  8. Arrays.sort(people);
  9. System.out.println(Arrays.toString(people));
  10. }
  11. }
  12. class Person implements Comparable{
  13. String name;
  14. int age;
  15. public Person(String name, int age) {
  16. this.name = name;
  17. this.age = age;
  18. }
  19. @Override
  20. public int compareTo(Object o) {
  21. if(this == o){
  22. return 0;
  23. }
  24. if (o instanceof Person){
  25. Person per = (Person) o;
  26. //根据年龄比较大小,从小到大顺序排列
  27. // return this.age - per.age;
  28. //从大到小逆序排列
  29. return per.age - this.age;
  30. }
  31. //报错,抛出异常
  32. throw new IllegalArgumentException("不是Person类型,无法比较");
  33. }
  34. @Override
  35. public String toString() {
  36. return "Person{" +
  37. "name='" + name + '\'' +
  38. ", age=" + age +
  39. '}';
  40. }
  41. }

72ad8ff50f7942bc80c7075b70db44aa.png

2、Cloneable接口(java.lang.Cloneable):

要让一个类具备可复制的能力,实现Cloneable接口,覆写clone方法

ff0e16ca5ff54c629c0356514245a86d.png

cbe9a6954b854da9898ad1120b4dd405.png 类似Cloneable接口,这种接口称之为“标记”接口,这个接口本身内部没有任何抽象方法,只有打上这个标记的子类才具备克隆的能力。

  1. public class CloneableTest {
  2. public static void main(String[] args) {
  3. Animal animal1 = new Animal("多利");
  4. Animal animal2 = animal1.clone();
  5. System.out.println(animal1.equals(animal2));
  6. System.out.println(animal1 == animal2);
  7. }
  8. }
  9. class Animal implements Cloneable{
  10. String name;
  11. public Animal(String name) {
  12. this.name = name;
  13. }
  14. public Animal clone(){
  15. Animal newAnimal = null;
  16. //调用的是Object类提供的clone方法
  17. try {
  18. //强转
  19. newAnimal =(Animal) super.clone();
  20. } catch (CloneNotSupportedException e) {
  21. e.printStackTrace();
  22. }
  23. return newAnimal;
  24. }
  25. @Override
  26. public boolean equals(Object obj) {
  27. if(this == obj){
  28. return true;
  29. }
  30. if (obj instanceof Animal){
  31. Animal animal = (Animal) obj;
  32. return this.name.equals(animal.name);
  33. }
  34. throw new IllegalArgumentException("不是Animal类,无法比较");
  35. }
  36. }

2.2深浅拷贝

(1)浅拷贝:克隆的对象和原对象确实是两个独立的对象,对象的内部若包含了其他类的引用,克隆后的对象b2包含的其他类的引用并没有产生新的对象。 b1.a 和b2.a指向相同的对象

63cd464f05bb4e9f907ca1ee20344dfe.png

(2)深拷贝:b2克隆对象内部包含的其他类的引用也产生了新的对象,b1.a 和b2.a指向不同的对象。Java中深拷贝的实现方式:1.递归使用clone方法;2序列化(json字符串)

25cda18e215e4e818b41edcdc050fa0a.png

2.3 调用clone方法产生的对象,不会调用构造方法。

在Java中产生一个新对象有两种方式:

(1)最普通最常见的是通过构造方法产生对象 new 类();->当有new关键字,就在对上开辟该类相应属性的空间,给属性赋默认值。

(2)通过clone()产生对象,调用clone时,JVM会开辟与原对象内存大小完全相同的新空间,并将对象中属性的值从原对象中复制一份。(不推荐使用这种方式产生对象)

发表评论

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

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

相关阅读

    相关 Java基础——抽象接口

    一、抽象类(强制子类覆写方法) 抽象类是普通类的“超集”,只是比普通类多了一些抽象方法而已(抽象方法个数:0-N) 抽象方法所在的类必须是抽象类,子类若是继承了抽象类,必须