猿创征文 | Java进阶详解抽象类及常用接口

Posted 署前街的少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了猿创征文 | Java进阶详解抽象类及常用接口相关的知识,希望对你有一定的参考价值。

目录

一、抽象类

二、接口

三、Object类

3.1 toString()方法

3.2 hashcode()方法

3.3 equals()方法

四、常用接口

4.1 Comparable接口(比较)

4.2 Comparator接口(比较)

4.3 Cloneable接口(拷贝)

4.4 浅拷贝与深拷贝


一、抽象类

在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

public class TestDemo 
    public static void main(String[] args)
        Circle c = new Circle();
        c.setR(5);
        c.getArea();
        Squre s = new Squre();
        s.setLength(10);
        s.getArea();
    

//抽象类
abstract class Shape
    private int size;
    //抽象方法
    abstract public void getArea();

class Circle extends Shape
    private int r;
    public int getR() 
        return r;
    
    public void setR(int r) 
        this.r = r;
    
    //重写抽象方法
    @Override
    public void getArea() 
        double  area = r*r*r*4.0/3;
        System.out.println("此圆形的面积是: "+area);
    

class Squre extends Shape
    private int length;
    //重写抽象方法
    @Override
    public void getArea() 
        double area = length*length;
        System.out.println("此正方形的面积是: "+area);
    
    public int getLength() 
        return length;
    
    public void setLength(int length) 
        this.length = length;
    
  • 抽象类的特性

    • 抽象类中使用abstract修饰类和抽象方法,这个方法没有具体的实现,抽象类中可以包含普通类所能包含的成员,抽象类所存在的最大意义就是被继承。

    • 抽象类方法不能是私有的,如果一个普通类继承了抽象类,那么必须重写抽象类中的抽象方法,不能被static和final修饰,因为抽象方法要被子类继承。

    • 抽象类中不一定包含抽象方法,但是包含抽象方法的一定是抽象类,抽象类之间的相互继承不需要重写抽象方法。

二、接口

  • 接口的定义

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

  • 接口的使用

//接口的定义
interface USB 
    void openDevice();
    void closeDevice();

//实现接口
class Mouse implements USB 
    @Override
    public void openDevice() 
        System.out.println("打开鼠标");
    
    @Override
    public void closeDevice() 
        System.out.println("关闭鼠标");
    
    public void click()
        System.out.println("鼠标点击");
    

//实现接口
class KeyBoard implements USB
//实现接口中的抽象类
    @Override
    public void openDevice() 
        System.out.println("打开键盘");
    
    @Override
    public void closeDevice() 
        System.out.println("关闭键盘");
    
    public void inPut()
        System.out.println("键盘输入");
    
  • 注意事项

    • ❗ 接口不能够直接使用,必须有一个类来实现接口,并实现接口中的所有抽象方法

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

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

    • ❗ 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现 ,JDK1.8 开始允许有可以实现的方法,但这个方法只能是default 修饰的,类在实现该接口时,不需要重写该默认方法。

    具体作用: 当我们进行业务扩展时,需要在接口中新增方法。如果新增的这个方法写成普通方法的话,那么需要在该接口所有的实现类中都重写这个方法。如果新增的方法定义为default类型,就不需要在所有的实现类中全部重写该default方法,哪个实现类需要新增该方法,就在哪个类中进行实现

    • 重写接口中方法时,不能使用default访问权限修饰

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

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

    • 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

    • 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

  • 实现多个接口

    • 一个类实现多个接口

    interface USB  
        void openDevice();
        void closeDevice();
    
    interface ULine
        void lineInsert();
    
    class Mouse implements USB,ULine
        @Override
        public void openDevice() 
            System.out.println("打开鼠标");
        
        @Override
        public void closeDevice() 
            System.out.println("关闭鼠标");
        
        @Override
        public void lineInsert() 
            System.out.println("插入鼠标线");
        
    

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

    • 一个类继承一个父类,同时实现多个接口

    public class TestDemo3 
        public static void main(String[] args) 
            Duck duck = new Duck("yaya");
            walk(duck);
            Brid brid = new Brid("gugu");
            walk(brid);
        
        public static void walk(IRunning running) 
            System.out.println("去散步");
            running.run();
        
    
     class Animal 
            protected String name;
            public Animal(String name) 
                this.name = name;
           
    
    interface IFlying 
            void fly();
    
    interface IRunning 
          void run();
    
    interface ISwimming 
            void swim();
    
    class Duck extends Animal implements IFlying,IRunning,ISwimming
        public Duck(String name) 
            super(name);
        
        @Override
        public void fly() 
            System.out.println("飞飞飞");
        
        @Override
        public void run() 
            System.out.println("鸭子嘎嘎跑");
        
        @Override
        public void swim() 
            System.out.println("游游游");
        
    
    class Brid extends Animal implements IRunning,ISwimming,IFlying
        public Brid(String name) 
            super(name);
        
        @Override
        public void  fly() 
            System.out.println("鸟儿飞");
        
    ​
        @Override
        public void run() 
            System.out.println("鸟儿跑");
        
    ​
        @Override
        public void swim() 
            System.out.println("鸟儿游");
        
    
    • 接口中的多态

    public class TestDemo3 
        public static void main(String[] args) 
            Duck duck = new Duck("yaya");
            walk(duck);
            Brid brid = new Brid("gugu");
            walk(brid);
        
        public static void walk(IRunning running) 
            System.out.println("去散步");
            running.run();
        
    

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

  • 接口之间的继承

一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

interface IRing 
    void run();
 
interface ISing 
    void swim();
 
interface IAmphibious extends IRunning, ISwimming 
class Frog implements IAmphibious 
    @Override
    public void run() 
        System.out.println("跑啊跑");
    
    @Override
    public void swim() 
        System.out.println("游啊游");
    

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

✅抽象类和接口的区别??

区别抽象类(abstract)接口(interface)
1结构组成普通类+抽象方法抽象方法+全局变量
2权限各种权限public
3子类使用使用extends关键字继承抽象类使用implements关键字实现接口
4关系一个抽象类可以实现若干接口接口不能继承抽象类,但是可以使用extends关键字继承多个接口
5子类限制一个子类只能继承一个抽象类一个子类可以实现多个接口

三、Object类

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

public class TestDemo5 
    public static void main(String[] args) 
        function(new Person());
        function(new Student());
    
    public static void function(Object obj)
        System.out.println(obj);
    

class Person
    private int age;
    private String name;

class Student
    private int grade;
    private String sno;

Object类中提供的一些默认方法

3.1 toString()方法

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

toString()方法一般需要通过重写之后进行使用。

3.2 hashcode()方法

  • 返回对象的hash代码值

源码:

public native int hashCode();

重写hashCode() 方法

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

public class TestDemo6 
    public static void main(String[] args) 
        Per per1 = new Per("gaobo",20);
        Per per2 = new Per("gaobo", 20);
        System.out.println(per1.hashCode());
       /* 
        注意事项:哈希值一样。
        结论:
        1、hashcode方法用来确定对象在内存中存储的位置是否相同
        2、事实上hashCode() 在散列表中才有用,在其它情况下没用。
        在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。
        */
        System.out.println(per2.hashCode());
    

3.3 equals()方法

  • 比较的是地址

// Object类中的equals方法
​
public boolean equals(Object obj)
​
     return (this == obj);
   // 使用引用中的地址直接来进行比较
   

✅如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的

重写equals()方法

@Override
 public boolean equals(Object obj) 
     //判断是否为空
        if (obj == null) 
            return false ;
         if(this == obj) 
            return true ;
        
        // 不是Person类对象
        if (!(obj instanceof Per)) 
            return false ;
        
        Per per = (Per) obj ; // 向下转型,比较属性值
        return this.name.equals(per.name) && this.age==per.age ;
    
    
/*  @Override
    public boolean equals(Object obj) 
        Per per = (Per)obj;
        //String类调用的是自身的equals,
        // s1跟s2两者比较的规则则是按照String类重写后的equals方法来比较,
        //很显然,String类的比较规则是按照值来比较的,因此结果会输出true。
        if(this.name.equals(per.name)&&this.age == per.age)
                return true;
        
        return false;
​
    

*/

编译器自动生成重写的hashcode()和equals()方法

@Override
public boolean equals(Object o) 
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Per per = (Per) o;
        return age == per.age &&
                Objects.equals(name, per.name);
    
    @Override
    public int hashCode() 
        return Objects.hash(name, age);
   

在object类中,hashcode()方法是本地方法,返回的是对象的地址值,而object类中的equals()方法比较的也是两个对象的地址值,如果equals()相等,说明两个对象地址值也相等,当然hashcode()也就相等了.**但是hashcode() 相同时,equals()不一定相同**

✅✅重写equals方法时,也必须重写hashcode()方法吗?

答:必须,hashCode 和 equals 两个方法是用来协同判断两个对象是否相等的,采用这种方式的原因是可以提高程序插入和查询的速度,当重写equals方法后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

✅✅ == 和 equals 的区别是什么?

答:

对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

  • 基本类型:比较的是值是否相同;

  • 引用类型:比较的是引用是否相同

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

对于equals() 方法,根据源码可以得知 : equals() 的本质上就是true

public boolean equals(Object obj) 
        return (this == obj);

所以equals()方法 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

四、常用接口

4.1 Comparable接口(比较)

❓  在学习数组时,Arrays类中的sort方法可以对对象数组进行排序 , 那下面的对象数组能不能用Arrays.sort排序呢?

class Student   
    String name;
    int age;
    public Student(String name, int age) 
        this.name = name;
        this.age = age;
    
    @Override  
    public String toString()  
        return "Student" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    

public class test4 
    public static void main(String[] args) 
        Student[] students = new Student[]  
              new Student("zhangsan", 13),
              new Student("lisi", 23),
              new Student("able", 17),
        ;
        Arrays.sort(students);
        System.out.println(Arrays.toString(students));
       

此时编译器并不知道到底是按姓名还是年龄进行排序,当sort方法对对象所属的类进行排序时,对象所属的类必须实现Comparable接口,通过参考文档可知,Comparable接口中仅有一个抽象方法。

那么我们就可以实现Comparable接口,并实现和重写compareTo方法

class Student implements Comparable<Student>
    public int age;
    public String name;
    
    public Student(int age, String name) 
        this.age = age;
        this.name = name;
    
    @Override
    public String toString() 
        return "Student" +
                "age=" + age +
                ", name='" + name + '\\'' +
                '';
    
    //重写compareTo方法
    @Override
    public int compareTo(Student o) 
        if (this.age - o.age > 0)
            return 1;
        else
        if (this.age - o.age < 0)
            return -1;
        else
            return 0;
    
     public static void main1(String[] args) 
        Student student = new Student(16, "liba");
        Student student1 = new Student(13, "zhangsan");
        System.out.println(student.toString());
        System.out.println(student1.toString());
        if (student.compareTo(student1) > 0) 
            System.out.println("student > student1");
         else 
            System.out.println("student < student1");
        
    
​

此时可以得到按年龄进行排序的结果:

我们知道在Arrays.sort(students); 中是传了一个学生对象数组,在调用Arrays对对象数组排序的时候,其实就调用了我们的Comparable接口中的compareTo方法对数组的每个元素进行了排序和比较,在Java中对于引用数据类型的比较或者排序,一般都要用到使用Comparable接口中的compareTo() 方法

按姓名排序时,重写的compareTo方法

@Override 
 public int compareTo(Student o)    // this.代表对当前对象的引用,o.代表对参数对的引用
        if (this.name.compareTo(o.name) > 0)//String类中重写了compareTo方法,可直接使用
             return 1;  
        else if (this.name.compareTo(o.name) < 0) 
             return -1;
        else 
             return 0;
    
    //如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    //如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    //如果当前对象和参数对象不分先后, 返回 0;

🎈 缺点:一旦重写了comparable()方法,那么就只能对一种参数类型进行比较,把方法写死了,此时就需要使用Comparator接口 ❗

4.2 Comparator接口(比较)

这里是Arrays.sort中只有一个参数的方法

当实现Comparator接口时,可以使用两个参数重载的方法实现排序,包含一个比较器类型的参数

首先通过参考文档了解Comparator接口,我们需要重写的是compare()方法

所以就像Comparable 接口一样,我们只要实现了Comparator接口,并重写Comparator里的compare方法就可以实现对学生对象数组的排序

比如我们上面的年龄比较就可以写成这样

class Student  
    String name;
    int age;
    public Student(String name, int age) 
        this.name = name;
        this.age = age;
    
 
    @Override  
    public String toString() 
        return "[" + this.name + ":" + this.age + "]";
    

 // 实现Comparator接口中的compare方法
class AgeComparator implements Comparator<Student>  // 年龄比较器
    @Override 
    public int compare(Student o1, Student o2) 
        return o1.age - o2.age;
    // 反正返回的也是数字,当o1.age>o2.age时返回大于零的数,即o1对象排在o2对象的后面,升序排列,我们之前用Comparable接口时也可以这样简写
    

public class test4 
    public static void main(String[] args) 
        Student[] students = new Student[] 
                new Student("zhangsan", 13),
                new Student("lisi", 23),
                new Student("able", 17),
        ;
        AgeComparator ageComparator = new AgeComparator(); 
        Arrays.sort(students, ageComparator); 
        // 用类Arrays.sort对students数组进行排序,这里传了两个参数(学生对象和所对应的年龄比较器)
        System.out.println(Arrays.toString(students));
    

同样,当我们按照姓名进行排序时,也可以使用此接口

class Student 
    String name;
    int age;
 
    public Student(String name, int age) 
        this.name = name;
        this.age = age;
    
    @Override  
    public String toString() 
        return "[" + this.name + ":" + this.age + "]";
    

class NameComparator implements Comparator<Student>  // 姓名比较器
    // 实现Comparator接口中的compare方法
    @Override  
    public int compare(Student o1, Student o2) 
        return o1.name.compareTo(o2.name); 
// 因为name是String类型,也是一个引用类型,也要用到compareTo方法,此时的compareTo方法是String类里重写的方法
    

public class test4 
    public static void main(String[] args) 
        Student[] students = new Student[] 
                new Student("zhangsan", 13),
                new Student("lisi", 23),
                new Student("able", 17),
        ;
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students, nameComparator);  
        System.out.println(Arrays.toString(students));
      

Comparable接口和Comparator接口都是Java中用来比较和排序引用类型数据的接口,要实现比较,就需要实现他们所各种对应的compareTo方法或者compare方法。

Comparator使用起来更加灵活,所以我更倾向于使用比较器:Comparator

4.3 Cloneable接口(拷贝)

  • 对象在内存当中的存储

class Student 
    public int age = 15;
    @Override 
    public String toString() 
        return "Student" +
                "id=" + id +
                '';
    

public class test3 
    public static void main(String[] args) 
        Student student1 = new Student();
        System.out.println(student1);
    

此时如果在堆内存中对student1对象拷贝一份,如果使用

Student student2 = student1;

这只是我们在栈上重新定义了一个引用变量student2,并指向了堆上的student1对象,并没有对我们的student1实现拷贝,改变student2.age会影响student.age 的值。

所以我们需要重写Object类中的clone方法进行克隆,在使用clone方法之前,需要实现Cloneable接口

由源码和参考文档可知,Cloneable是一个空接口即标记接口,如果有其他类继承该接口,说明该类的对象是可以被克隆的。

  • 要克隆的这个对象的类必须实现 Cloneable 接口

  • 类中重写 Objectclone() 方法

  • 处理重写clone方法时的异常情况

  • clone方法需要进行强转(比较特殊,先记住就好)

class Student implements Cloneable
    public int age = 10;
    @Override
    protected Object clone() throws CloneNotSupportedException 
        return super.clone();
    
    @Override
    public String toString() 
        return "Person" +
                "age=" + age +
                '';
    

public class Demo2 
    public static void main(String[] args) throws CloneNotSupportedException
        Student student = new Student();
        Student student2 = (Student)student.clone();   //返回值为Object需要进行强制类型转换
        System.out.println(student.age);
        System.out.println(student2.age); 
        student2.age = 18;
        System.out.println(student.age);
        System.out.println(student2.age);
    

此时在内存当中就是这样,student1和student2 中的两个age是相互独立的,student2的age发生改变不会影响student1 的内容。此时我们就成功实现了对象的拷贝

4.4 浅拷贝与深拷贝

  • 浅拷贝

✅根据上边Cloneable接口使用介绍我们已经详细了解了,此时我们提出了一个问题,如果在Student类当中再定义一个引用类型,那么又该如何拷贝呢?

class Teacher
    int number = 20;

class Student implements Cloneable
    public int age = 10;
    Teacher teacher = new Teacher();
    @Override
    protected Object clone() throws CloneNotSupportedException 
        return super.clone();
    
​
    @Override
    public String toString() 
        return "Person" +
                "age=" + age +
                '';
    

public class Demo2 
    public static void main(String[] args) throws CloneNotSupportedException
        Student student = new Student();
        Student student2 = (Student)student.clone();   //返回值为Object需要进行强制类型转换
​
        System.out.println(student.teacher.number);
        System.out.println(student2.teacher.number);
​
        student.teacher.number = 100;
        
        System.out.println(student.teacher.number);
        System.out.println(student2.teacher.number);
​
    

此时,student 中teacher的改变也引起了 student2中地址的改变,此种拷贝就好像只拷贝了student.teacher.number 的地址,并未重新复制一块内存出来,此种拷贝就叫做浅拷贝

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

  • 深拷贝

刚刚我们通过实现Cloneable接口、重写clone方法对Student类实现了拷贝,那么同理我们也可以用这样的办法对Teacher类对象进行拷贝.

class Teacher implements Cloneable
    int number = 20;
    @Override
    protected Object clone() throws CloneNotSupportedException 
        return super.clone();
    

class Student implements Cloneable
    public int age = 10;
    public Teacher teacher = new Teacher();
    @Override
    protected Object clone() throws CloneNotSupportedException 
        //  此时我们在进行 “(Student) student.clone();” 操作,
        //  我们在堆上对student克隆拷贝出来一个新对象,并让引用变量tmp指向新对象
        Student tmp = (Student) super.clone();
        // 用this.teacher.clone()对引用变量teacher所指向的Teacher类对象进行克隆
        tmp.teacher = (Teacher) this.teacher.clone();
        return tmp;
    
    @Override
    public String toString() 
        return "Person" +
                "age=" + age +
                '';
    

public class Demo2 
    public static void main(String[] args) throws CloneNotSupportedException
        Student student = new Student();
        // 此时的student.clone返回Student类对象的引用tmp,student2 就指向了原来tmp所指向的对象
        Student student2 = (Student)student.clone();  
​
        System.out.println(student.teacher.number);
        System.out.println(student2.teacher.number);
        student.teacher.number = 100;
        System.out.println(student.teacher.number);
        System.out.println(student2.teacher.number);
​
    

 此时的内存结构图为: 

上面的拷贝就把引用变量teacher所指向的Teacher类的对象也在堆中拷贝了一份,这就是深拷贝, 深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

深拷贝:创建一个新对象,然后将当前对象的各种成员属性复制到该新对象,无论该成员属性是值类型的还是引用类型,都复制独立的一份,引用类型也会复制该引用类型所指向的对象。

以上是关于猿创征文 | Java进阶详解抽象类及常用接口的主要内容,如果未能解决你的问题,请参考以下文章

猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)

猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)

猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)

猿创征文|前端进阶必备——WebSockt实现聊天室(附源码)

猿创征文|一篇打通架构设计,Java设计模式10,建造者模式

猿创征文| Unity进阶InputSystem