【Java】抽象类、接口和Object类

本文详细介绍了抽象类和接口在Java中的概念、语法、特性及应用场景。抽象类用于捕捉子类的通用特性,提供模板,不可实例化,但可以包含普通方法和属性。接口则定义了公共行为规范,类实现接口必须实现所有抽象方法。一个类可以实现多个接口,而抽象类可以有多个抽象方法。此外,文章还探讨了对象排序、克隆以及Object类的方法如equals和hashCode。最后,对比了抽象类与接口的区别,强调了它们在多态和设计模式中的不同角色。

1. 抽象类

1.1 什么是抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

1.2 抽象类语法

// 抽象类:被abstract修饰的类
public abstract class Shape {
  // 抽象方法:被abstract修饰的方法,没有方法体
  abstract public void draw();
  abstract void calcArea();
  // 抽象类也是类,也可以增加普通方法和属性
  public double getArea(){
    return area;
 }
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.3 抽象类特性

我们可以接着用继承里面的画图形的例子来看。

//1. 抽象类不是一个具体的类,不可以进行实例化。(不能new)
abstract class Shape {  //有一个方法是抽象的,类也必须要是抽象类
//2. 抽象类当中可以定义和普通类一样的数据成员或者方法
    public int a;
    public static int b;
    public void test() {
        System.out.println();
    }
    public abstract void drow(); // 抽象方法
}
//3. 当一个普通类A继承了一个抽象类B之后,此时普通类A一定要重写抽象类B里面的抽象方法。
class Cycle extends Shape {
    @Override
    public void drow() {
        System.out.println("画○!");
    }
}
//4. 和第三点相反,如果一个抽象类C继承了一个抽象类B,此时可以不重写抽象了哦B的抽象方法。
 class Rect extends Shape {

    @Override
    public void drow() {
        System.out.println("画◇!");
    }
}
//5. 不管怎么样,只要其他类再次继承,你需要重写所有积攒的(继承的)方法都要重写。
//6. 抽象类最大的意义就是为了被继承。
//7. final和abstract不能共存,抽象方法也不可以是有static修饰的 或者 private
class Flower extends Shape {

    @Override
    public void drow() {
        System.out.println("画❀!");
    }
}

class  Triangle extends Shape {

    @Override
    public void drow() {
        System.out.println("画△!");
    }
}

class A extends Rect {

    @Override
    public void drow() {
        System.out.println();
    }

}
public class TestDemo {
    public static void draMap(Shape shape) {
        shape.drow();
    }

    public static void main(String[] args) {
        Cycle cycle = new Cycle();
        draMap(cycle);
        draMap(new Rect());
        draMap(new Flower());
        draMap(new Triangle());
    }
}

1.4 抽象类的作用

抽象类是用来捕捉子类的通用特性的,是被用来创建继承层级里子类的模板。 现实中有些父类中的方法确实没有必要写,因为各个子类中的这个方法肯定会有不同;而写成抽象类,这样看代码时,就知道这是抽象方法,而知道这个方法是在子类中实现的,所以有提示作用。

使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。

充分利用编译器的校验, 在实际开发中是非常有意义的

2. 接口

2.1 接口的概念

接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 语法规则

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

interface Ishape {

    //public int a; 报错:在接口里面定义需要初始化
    public int b = 1;
    public static int c = 2;
    public static final int d = 3;
    public abstract void draw();
   
}

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

2.3 接口使用

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

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

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

2.4 接口特性

//1. 接口的关键字 interface
interface Ishape {

    //public int a; 报错:在接口里面定义需要初始化
    public int b = 1;
    public static int c = 2;
    public static final int d = 3;
    //2. 接口当中的成员变量默认是public static final 的
    //3. 接口当中的抽象方法默认是 public abstract 的
    public abstract void draw();

    //4. 接口当中可以定义实现了的方法。要么被default修饰 要么被static修饰。
    //5. 接口依然不可以实例化
    /*
    public static void test2() {
        System.out.println();
    }*/
    //7. default方法是接口的默认方法 当然 你也可以在实现类当中 重写接口的这个默认方法
    /*default public void test() {
        System.out.println("这就是一个默认的方法");
    }*/

}
//6. 接口和类的关系,可以使用implements, 此时这个类 要重写接口当中的所有的抽象方法
class Cycle implements Ishape {

    @Override
    public void draw() {
        System.out.println("画○!");
    }
}

class Rect implements Ishape {
    @Override
    public void draw() {
        System.out.println("画◇!");
    }
}

class Flower implements Ishape {

    @Override
    public void draw() {
        System.out.println("画❀!");
    }
}

class  Triangle implements Ishape {

    @Override
    public void draw() {
        System.out.println("画△!");
    }
}

public class TestDemo1 {
    public static void drawMap(Ishape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
       // Ishape ishape = new Ishape();
        Ishape ishape1 = new Cycle();
        Ishape ishape2 = new Rect();
        Ishape ishape3 = new Flower();

        drawMap(ishape1);
        drawMap(new Cycle());
        drawMap(ishape2);
        drawMap(ishape3);
    }
}

2.5 实现多个接口

::一个类可以实现多个接口,使用逗号隔开。同时所有接口当中的抽象方法,必须进行重写。
::一个类可以继承一个抽象类/普通类,同时可以实现多个接口。但是 要先继承然后实现接口。

interface IA {
    void test1();
}

interface IB {
    void test2();
}

interface IC {
    void test3();
}
interface ID extends IA,IB,IC {
    void test5();
}

class A implements IA,IB {
    @Override
    public void test1() {
    }
    @Override
    public void test2() {
    }
}

abstract class B implements IA,IB{
    public abstract void test4();
}

class C extends B implements IC {
    @Override
    public void test1() {
    }
    @Override
    public void test2() {
    }
    @Override
    public void test3() {
    }
    @Override
    public void test4() {
    }
}

 2.6 接口间的继承

 ::当一个接口使用extends 拓展了其他的接口,此时当一个类实现了这个接口,需要重写所有的接口。

interface IA {
    void test1();
}

interface IB {
    void test2();
}

interface IC {
    void test3();
}
interface ID extends IA,IB,IC {
    void test5();
}
class DD implements ID {
    @Override
    public void test1() {
    }
    @Override
    public void test2() {
    }
    @Override
    public void test3() {
    }
    @Override
    public void test5() {
    }
}

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

2.7 接口使用实例

1. 给对象数组排序

class Student {
    public  String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

public class TestDemo {

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",18,88);
        students[1] = new Student("李四",19,78);
        students[2] = new Student("王五",20,45);
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
    }
}

 ----》我们发现结果报错了。watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

此时我们就要用到第一个接口Comparable。
此时要做的就是重写compareTo方法。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.
然后比较当前对象和参数对象的大小关系(按分数来算).
如果当前对象应排在参数对象之前, 返回小于 0 的数字;
如果当前对象应排在参数对象之后, 返回大于 0 的数字;
如果当前对象和参数对象不分先后, 返回 0; 

注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 "可比较" 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.

class Student implements Comparable<Student>{
     
    //...

    @Override
    public int compareTo(Student o) {
        if(this.age > o.age) {
            return 1;
        }else if(this.age == o.age) {
            return 0;
        }else{
            return -1;
        }
    }
}

-->我们发现此时可以正常排序,使用的是年龄大小排序。

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

 或者

写一个比较器:

class Student {
    public  String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

}
//年龄比较器
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.age-o2.age;
    }
}
//分数比较器
class ScoreComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score-o2.score);
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("张三",18,88);
        students[1] = new Student("李四",79,78);
        students[2] = new Student("王五",50,45);

        //对类的侵入非常小
        AgeComparator ageComparator = new AgeComparator();
        ScoreComparator scoreComparator = new ScoreComparator();

        System.out.println("排序前:"+Arrays.toString(students));
        Arrays.sort(students,ageComparator);
        System.out.println("age排序后:"+Arrays.toString(students));
        Arrays.sort(students,scoreComparator);
        System.out.println("score排序后:"+Arrays.toString(students));

        /*AgeComparator ageComparator = new AgeComparator();
        int ret = ageComparator.compare(students[0],students[1]);
        System.out.println(ret);

        ScoreComparator scoreComparator = new ScoreComparator();
        ret = scoreComparator.compare(students[0],students[1]);
        System.out.println(ret);*/
    }
}

2.8 Clonable 接口和深拷贝

Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 "拷贝". 但是要想合法调用 clone 方法, 必须要先
实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.

class Person implements Cloneable {//空接口,标记接口,
    public int id;

    public Person(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + id + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(8);
        System.out.println(person);

        Person person1 = (Person) person.clone();
        System.out.println(person1);
}

浅拷贝 VS 深拷贝

浅拷贝:

class Money {
    public double money = 88.8;
}

class Person implements Cloneable {//空接口,标记接口,
    public int id;
    Money m = new Money();

    public Person(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + id + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(8);
        System.out.println(person);

        Person person1 = (Person) person.clone();
        System.out.println(person1);

        System.out.println("修改一下money");
        person1.m.money = 99.9;
        System.out.println(person.m.money);
        System.out.println(person1.m.money);
    }
}

深拷贝:

class Money implements Cloneable {
    public double money = 88.8;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {//空接口,标记接口,
    public int id;
    Money m = new Money();

    public Person(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + id + '\'' +
                '}';
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person personCopy = (Person) super.clone();
        personCopy.m = (Money) this.m.clone();
        return personCopy;
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person(8);
        System.out.println(person);

        Person person1 = (Person) person.clone();
        System.out.println(person1);
        System.out.println();

        System.out.println("修改一下money");
        person1.m.money = 99.9;
        System.out.println(person.m.money);
        System.out.println(person1.m.money);
    }
}

深拷贝与浅拷贝的区别:

他两的最根本区别就是在于是否zhenzheng获取一个对象的复制实体,而不是引用。

==》浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址,

==》深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存,

使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。

2.9 抽象类和接口的区别

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

抽象类存在的意义是为了让编译器更好的校验, 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_20,color_FFFFFF,t_70,g_se,x_16

3. Object类 

Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

class A {
}

class B {
}


public class TestDemo2 {

    public static void func(Object obj) {
        System.out.println(obj);
    }

    public static void main(String[] args) {
        func(new A());
        func(new B());
    }
}

---》结果 

watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAVGhlRGV2aWNl,size_19,color_FFFFFF,t_70,g_se,x_16

所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。

我们可以在编译器中找到。

2.2 获取对象信息

    如果要打印对象中的内容,可以直接重写Object类中的toString()方法。

2.3 对象比较equals方法

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

class A {

    public int id;

    public A(int id) {
        this.id = id;
    }

    public boolean equals(Object obj){
        if(obj == null) {
            return false;
        }
        if (this == obj){
            return true;
        }
        if(!(obj instanceof A)){
            return false;
        }
        A tmp = (A)obj;
        return true;
    }
}

class B {

}


public class TestDemo2 {

    public static void main(String[] args) {

        A a1 = new A(1);
        String str = "123";
        System.out.println((a1.equals(str)));
    }
}

结论:比较对象中内容是否相同的时候,一定要重写equals方法。

2.4 hashcode方法

hashcode方法源码:  public native int hashCode();

(该方法是一个native方法,底层是由C/C++代码写的。我们看不到。)

class Person {
    public String name;
    public int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
  }
}
public class TestDemo4 {
    public static void main(String[] args) {
    Person per1 = new Person("11", 20) ;
    Person per2 = new Person("11", 20) ;
    System.out.println(per1.hashCode());
    System.out.println(per2.hashCode());
    }
}

//执行结果
460141958
1163157884

注意事项:两个对象的hash值不一样。
像重写equals方法一样,我们也可以重写hashcode()方法。此时我们再来看看。

class Person {
   public String name;
   public int age;
   public Person(String name, int age) {
   this.name = name;
   this.age = age;
   }
   @Override
   public int hashCode() {
      return Objects.hash(name, age);
   }
}

public class TestDemo4 {
    public static void main(String[] args) {
    Person per1 = new Person("11", 20) ;
    Person per2 = new Person("11", 20) ;
    System.out.println(per1.hashCode());
    System.out.println(per2.hashCode());
    }
}

//执行结果
460141958
460141958

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

2.5 接收引用数据类型

在之前已经分析了Object可以接收任意的对象,因为Object是所有类的父类,但是Obejct并不局限于此,它可以接收所有数据类型,包括:类、数组、接口。

范例:使用Object来接受数组对象

public static void main(String[] args) {
  // Object接收数组对象,向上转型
  Object obj = new int[]{1,2,3,4,5,6} ;
  // 向下转型,需要强转
  int[] data = (int[]) obj ;
  for(int i :data){
     System.out.println(i+"、");
}
}

而Object可以接收接口是Java中的强制要求,因为接口本身不能继承任何类。

范例:使用Object接收接口对象

interface IMessage {
     public void getMessage() ;
}
class MessageImpl implements IMessage {
     @Override
     String toString() {
     return "I am small biter" ;
     }
     public void getMessage() {
          System.out.println("比特欢迎你");
     }
}
public class Test {
      public static void main(String[] args) {
          IMessage msg = new MessageImpl() ; // 子类向父接口转型
          Object obj = msg ; // 接口向Obejct转型
          System.out.println(obj);
          IMessage temp = (IMessage) obj ; // 强制类型转换
          temp.getMessage();
          }
}

Object真正达到了参数的统一,如果一个类希望接收所有的数据类型,就是用Object完成,在Java中,泛型就是底层就是通过Object来实现的。

END

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值