• 抽象类和接口


    文章目录

    1.抽象类

    2.Object类

    3.接口

    4.两个重要接口的深度剖析

    5.为什么重写equals方法还要重写hashcode

    一.抽象类

    抽象类被abstract修饰,抽象类当中可以含有抽象类方法,抽象类的方法不能写大括号也可以含有非抽象类方法,抽象类方法在抽象类当中不具体实现,但是抽象类被继承以后,需要在子类中重写。

    1. abstract class Xing
    2. {
    3. String name;
    4. abstract public void draw(); //被abstract修饰的方法称为抽象方法
    5. abstract void count();
    6. Xing(String name)
    7. {
    8. this.name=name;
    9. }
    10. }
    11. class Cycle extends Xing
    12. {
    13. //String name;
    14. String silly;
    15. public Cycle(String name,String silly)
    16. {
    17. super(name);
    18. this.silly=silly;
    19. }
    20. int age;
    21. public void draw()
    22. {
    23. System.out.println(name+"正在画三角形");
    24. }
    25. void count()
    26. {
    27. System.out.println(name+"正在数三角形");
    28. }
    29. }

    2.抽象类方法在没有修饰限定访问符时,默认为public,但是它的修饰限定访问符不能是private,也不能被final,static修饰,因为它需要在子类中重写。

    3.抽象类不能创建新的对象

    4.抽象类里可以含有普通方法和属性。

    5.继承抽象类的类在重写抽象类里的方法时,方法的修饰符必须要大于等于抽象类里的抽象方法的修饰符

    二.Object类

    1.Object是所有类的父类

    1. public static void main2(String[] args) {
    2. Object e = new Person();
    3. }

    2.Object类中的 equals方法

    我们先来看一段代码

    1. public static void main(String[] args) {
    2. Dog dog1=new Dog("wang",7);
    3. Dog dog2=new Dog("wang",7);
    4. System.out.println(dog1.equals(dog2));
    5. }

    大家先来猜一下这段代码它最终打印出来的结果?因为两个对象的内容一样,我相信有一部分人会猜测结果是true,

    那么为什么会是这样一种结果呢?我们可以点进去看一下Object类中的equals方法的具体实现

     我们不难看到其实比较的是dog1和被dog2赋值的obj这两个引用的值,那么它们肯定是不相等的。

    我们可以在子类中将这个方法按照自己的想法进行重写,

    1. public boolean equals(Object obj) {
    2. if (this == obj) {
    3. return true;
    4. }
    5. if (obj == null) {
    6. return false;
    7. }
    8. if (!(obj instanceof Dog)) {
    9. return false;
    10. }
    11. Dog a = (Dog)obj;
    12. if (this.name.equals(a.name) && this.age == a.age) { 8
    13. return true;
    14. }
    15. }

     有同学可能会问8行中的equals方法不还是比较两个引用的值吗?其实这里的equals方法不是Object中的equals方法了,它被底层代码进行了重写。

    3.hasCode  方法

    hasCode方法可以计算出一个对象具体的位置

    我们可能会认为两个对象内容一样,那么他们的地址就是相同的

    1. public class Text1 {
    2. public static void main(String[] args) {
    3. Animal dog1=new Animal("小红",5);
    4. Animal dog2=new Animal("小红",5);
    5. System.out.println(dog1.hashCode());
    6. System.out.println(dog2.hashCode());
    7. }
    8. }

    我们再看一下打印结果:

    但是我们可以看到即使这两个对象的内容完全相同,但是他们的地址也不相同,像重写toString()方法一样,我们重写一下hascode()方法。

    1. public class Animal {
    2. public String name;
    3. public int age;
    4. public Animal(String name, int age) {
    5. this.name = name;
    6. this.age = age;
    7. }
    8. @Override
    9. public int hashCode() {
    10. //return super.hashCode();
    11. return Objects.hash(name,age);
    12. }
    13. }

    我们再来看一下打印结果

    结论:1.hashCode()方法用来确定对象在内存中存储的位置是否相同

           2.hasCode()在散列表中才有用,在其它请况下没用,在散列表中hashCode()的作用是获取对象的散列码,进而确定该对象在该散列表中的具体位置。

    三.接口

    1.接口的形式通过关键字interface来定义

    1. interface name
    2. {
    3. void eat();
    4. void fly();
    5. void swim(); //这三个都被默认成public abstract
    6. int b=3; //这个被默认成public static final
    7. }

    2.接口里面所有的方法都必须是抽象的方法,方法都被默认成了public abstract,接口里不能有静态代码块和构造方法,可以有变量,变量会被默认成public static final的形式

    3.接口需要被类实现。所有接口里的方法都需要在类中被重写,重写的时候不能使用default做修饰访问限定符

    4.类中需要实现该接口的时候用implements

    1. interface Swim
    2. {
    3. void swim();
    4. }
    5. class Fish extends Animal implements Swim
    6. {
    7. //String name="qin";
    8. public Fish(String name)
    9. {
    10. super(name);
    11. }
    12. public void swim()
    13. {
    14. System.out.println(name+"正在游泳");
    15. }
    16. }

    可以一次连接多个接口 用,间隔

    1. interface Swim
    2. {
    3. void swim();
    4. }
    5. interface Run
    6. {
    7. void run();
    8. }
    9. class Dog extends Animal implements Run,Swim
    10. {
    11. //String name="wang";
    12. public Dog(String name)
    13. {
    14. super(name);
    15. }
    16. public void run()
    17. {
    18. System.out.println(name+"正在跑");
    19. }
    20. public void swim()
    21. {
    22. System.out.println(name+"正在游泳");
    23. }
    24. }

    6.默认方法

    可以为接口方法提供一个默认实现,但必须用default修饰这样一个方法

    1. interface Swim
    2. {
    3. default void run()
    4. {
    5. System.out.println(111);
    6. }
    7. }

    解决默认方法冲突

    那么当一个超类和一个接口都实现了同一个方法时,在这种情况下只会考虑超类方法,接口的默认方法被忽略。

    1. interface Run
    2. {
    3. default void run()
    4. {
    5. System.out.println(333);
    6. }
    7. }
    8. class Animal
    9. {
    10. public void run()
    11. {
    12. System.out.println(222);
    13. }
    14. class Dog extends Animal implements Run
    15. {
    16. String name="wang";
    17. }
    18. public class text6 {
    19. public static void main(String[] args) {
    20. Dog dog=new Dog();
    21. dog.run();
    22. //dog.eat();
    23. }

    当两个接口都实现了同一个默认的方法,我们用哪个就在类里重写哪个

    1. interface Swim
    2. {
    3. default void run()
    4. {
    5. System.out.println(111);
    6. }
    7. }
    8. interface Run
    9. {
    10. default void run()
    11. {
    12. System.out.println("接口里的方法");
    13. }
    14. }
    15. class Dog implements Run,Swim
    16. {
    17. String name="wang";
    18. public void run()
    19. {
    20. System.out.println(111);//重写的是Swim中的方法
    21. }
    22. }
    23. public class text6 {
    24. public static void main(String[] args) {
    25. Dog dog=new Dog();
    26. dog.run();
    27. //dog.eat();
    28. }
    29. }

     

    7.在类实现这个接口,并重写这个接口的抽象方法时,这个重写的方法必须是public修饰的,因为我们可以把接口当成一个特殊的类,那么实现这个接口的类就相当于是子类,接口就是父类,当子类要重写父类的方法时,方法修饰符必须要大于等于父类方法的修饰符,因为接口类里的方法时public abstract,所以实现这个接口的类里的方法必须是public。

    8.接口里不能有构造方法(接口不能被实例化)和静态代码块

    9.接口里可以含有具体实现的静态方法

    1. public interface IEO {
    2. void draw();
    3. default public void cut()
    4. {
    5. System.out.println("接口里默认提供的方法");
    6. }
    7. public static void fun()
    8. {
    9. System.out.println("接口里可以含有静态的方法静态方法");
    10. }
    11. }

    10.接口可以当成是一个特殊的类,多个类去实现这个接口,就相当于继承这个类 ,也就意味着可以发生多态

    四.两个重要的接口:

    1 .comparable

    我们大家先来看一段代码:

    1. public static void main1(String[] args) {
    2. Students[]arr=new Students[3];
    3. arr[0]=new Students("wang",13);
    4. arr[1]=new Students("xiao",14);
    5. arr[2]=new Students("xiang",15);
    6. sort(arr);
    7. System.out.println(Arrays.toString(arr));

    像这段代码如果不加任何接口我们是无法对其排序的,因为编译器无法识别到底是按照name排序还是按照年龄排序,这时我们可以用一下comparable这个接口,使Students这个类具备可以比较的功能。

    1. class Students implements Comparable<Students>
    2. {
    3. String name;
    4. int age;
    5. public Students(String name, int age)
    6. {
    7. this.name=name;
    8. this.age=age;
    9. }
    10. @Override
    11. public String toString() {
    12. return ("["+name+","+age+"]");
    13. }
    14. @Override
    15. public int compareTo(Students o) {
    16. if(this.age-o.age>0)
    17. {
    18. return 1;
    19. }
    20. else if(this.age-o.age<0)
    21. {
    22. return -1;
    23. }
    24. else
    25. {
    26. return 0;
    27. }
    28. }

    我们可以按照年龄排序,这是我们就在这个类里面重写comparaTo这个方法,这是我们就可以排序了。


     那么大家再来想一个问题,如果我们按照姓名来比较呢?那么势必我们就需要改动刚刚重写的按照年龄排序的compareTo方法,这很不方便,那么接下我我就给大家介绍一个比较器,这个就用起来很方便了。

    按照年龄比较 

    1. class Namecomparator implements Comparator<Students>
    2. {
    3. @Override
    4. public int compare(Students o1, Students o2) {
    5. return (o1.name.compareTo(o2.name));
    6. }
    7. }
    8. class Students
    9. {
    10. String name;
    11. int age;
    12. public Students(String name, int age)
    13. {
    14. this.name=name;
    15. this.age=age;
    16. }
    17. @Override
    18. public String toString() {
    19. return ("["+name+","+age+"]");
    20. }
    21. }
    22. public static void main(String[] args) {
    23. Students[]arr=new Students[3];
    24. arr[0]=new Students("li",16);
    25. arr[1]=new Students("jing",17);
    26. arr[2]=new Students("rui",18);
    27. Agecomparator age= new Agecomparator();// 创建一个Agecomparator对象
    28. //Namecomparator age2=new Namecomparator();
    29. sort(arr,age); //注意这里传的是两个引用,一个是Students类的,另一个Agecomparator对象的引用
    30. System.out.println(Arrays.toString(arr));
    31. }

    按照姓名比较 

    1. class Namecomparator implements Comparator<Students>
    2. {
    3. @Override
    4. public int compare(Students o1, Students o2) {
    5. return (o1.name.compareTo(o2.name));
    6. }
    7. }
    8. class Students
    9. {
    10. String name;
    11. int age;
    12. public Students(String name, int age)
    13. {
    14. this.name=name;
    15. this.age=age;
    16. }
    17. @Override
    18. public String toString() {
    19. return ("["+name+","+age+"]");
    20. }
    21. }
    22. public static void main(String[] args) {
    23. Students[]arr=new Students[3];
    24. arr[0]=new Students("li",16);
    25. arr[1]=new Students("jing",17);
    26. arr[2]=new Students("rui",18);
    27. //Agecomparator age= new Agecomparator();
    28. Namecomparator age2=new Namecomparator();//创建一个Namecomparator 的对象
    29. sort(arr,age2);//传两个引用
    30. System.out.println(Arrays.toString(arr));
    31. }

     

     第二个接口:cloneable

    1.浅拷贝

    1. class Math implements Cloneable
    2. {
    3. public int n=11;
    4. @Override
    5. protected Object clone() throws CloneNotSupportedException {
    6. return super.clone();
    7. }
    8. }
    9. class Person implements Cloneable
    10. {
    11. public int m=10;
    12. public Math math=new Math();
    13. @Override
    14. protected Object clone() throws CloneNotSupportedException {
    15. return super.clone();
    16. }
    17. public static void main(String[] args) throws CloneNotSupportedException{
    18. Person person2=new Person();
    19. person2.math.n=19;
    20. Person person3=(Person)(person2.clone());
    21. System.out.println(person2.math.n);
    22. System.out.println(person3.math.n);
    23. }

     那么为什么会是这样一种结果呢?我们不妨一起来分析一下

     我们看到当我们person1的对象克隆以后,但是math的值没有变,还是指向n=11,所以person2.math.n修改为19的时候,person3.math.n的值也会成为19,我们并不希望它们的结果都改变,所以我们考虑一下深拷贝。

    我们先创建一个tmp变量用来引用克隆person1后的后的对象,然后我们在克隆一下math的对象。这时math的值就改变了,然后再把tmp的值赋值给person2,

     这时通过person2把n的值改为19,person1的n就不会改变了

    1. class Math implements Cloneable
    2. {
    3. public int n=11;
    4. @Override
    5. protected Object clone() throws CloneNotSupportedException {
    6. return super.clone();
    7. }
    8. }
    9. class Person implements Cloneable
    10. {
    11. public int m=10;
    12. public Math math=new Math();
    13. @Override
    14. protected Object clone() throws CloneNotSupportedException {
    15. Person tmp=(Person)(super.clone());
    16. tmp.math= (Math) (this.math.clone());
    17. return tmp;
    18. }
    19. }
    20. public class text10 {
    21. public static void main(String[] args) throws CloneNotSupportedException{
    22. Person person1=new Person();
    23. Person person2=(Person)(person1.clone());
    24. person2.math.n=19;
    25. System.out.println(person1.math.n);
    26. System.out.println(person2.math.n);
    27. }

    五.为什么重写equals方法还要重写hashcode方法?

    通常情况下,我们重写equals方法,如果两个对象属性值相同,那么我们就认为它们两个对象是同一个对象,但是这两个对象的hash值不一样,如果我们把他们放在set容器中,两个对象都可以放进去,可是set容器最大的特性就是去重,明明我们认为这是两个相同的对象,这就会产生问题,

    举个例子:我们建立一个Student类,然后重写equals方法

    1. public class Student {
    2. public String name;
    3. public int age;
    4. public Student(String name, int age) {
    5. this.name = name;
    6. this.age = age;
    7. }
    8. @Override
    9. public boolean equals(Object o) {
    10. if (this == o) return true;
    11. if (o == null || getClass() != o.getClass()) return false;
    12. Student student = (Student) o;
    13. return age == student.age && Objects.equals(name, student.name);
    14. }
    15. /*@Override
    16. public int hashCode() {
    17. return Objects.hash(name, age);
    18. }
    19. */
    20. public String getName() {
    21. return name;
    22. }
    23. public void setName(String name) {
    24. this.name = name;
    25. }
    26. public int getAge() {
    27. return age;
    28. }
    29. public void setAge(int age) {
    30. this.age = age;
    31. }
    32. }
    1. Student student=new Student("小海",1);
    2. Student student1=new Student("小海",1);
    3. System.out.println(student.hashCode()==student1.hashCode());

     这两个对象的属性值一样,但是hash不一样

    因此我们需要重写hashcode方法,让这两个属性值完全一样的对象hash值也一样。

    1. @Override
    2. public int hashCode() {
    3. return Objects.hash(name, age);
    4. }

    我们再打印一下结果:

    六.接口和抽象类的区别

      (1)接口里的方法都是抽象方法,而抽象类里的方法可以包含普通方法

      (2)接口用interface表示,而抽象类用abstract表示

       (3)抽象类里可以含有变量,而接口里只能存在常量

       (4)抽象类里的抽象方法控制符不能是private,其他的都可以,但是接口里的抽象方法控制符默认是public abstract

       (5)接口里不能含有构造方法,而抽象类里可以

       (6)一个类可以实现多个接口,但是只能继承一个抽象类

       (7)抽象类的设计目的是代码的复用,而接口的核心是定义行为,即类实现接口可以做什么

  • 相关阅读:
    云计算系统与传统计算系统的比较
    spring-boot-starter-validation数据校验全局异常拦截处理
    基于51单片机的超声波测距及温度显示
    Ant-Design-Pro-V5 :ProTable自定义搜索菜单操作栏和搜索事件、列表工具栏操作。
    PyTorch笔记 - Seq2Seq + Attention 算法
    [资源推荐]看到一篇关于agent的好文章
    YOLOv8改进:HIC-YOLOv8复现魔改HIC-YOLOv5,助力小目标检测(Small Object Detection)
    【计算机视觉40例】案例10:隐身术
    【(数据结构)— 单链表的实现】
    内网渗透之Windows认证(二)
  • 原文地址:https://blog.csdn.net/m0_70386582/article/details/126572879