Java 之抽象类和接口

一 、抽象类

1.1 、什么是抽象类?

  • 就是当一个类不能描述具体的对象时,那么这个类就可以写成抽象类。比如说 Animal ,我们知道 Animal 不能非常清楚的描述一个具体的动物,所以可以把 Animal 写成抽象类。还有就是我们知道父类中的方法,我们在子类中可能需要重写,我们想,当调用重写方法时,肯定是去调用子类中的重写方法,所以父类中的重写方法中的方法体(即方法的实现那部分代码)就会显得很多余(就是用不到),那么我们就可以使用抽象类来让这个方法变为抽象方法,此时就不需要实现方法体了

1.2、抽象类的语法

  • 毕竟还是个类,所以还需要class修饰
  • 还需要加上 abstract关键字修饰
java 复制代码
public abstract class Animal(){

}

1.2.1、抽象类中的抽象方法

  • 使用abstract 修饰成员方法就能把这个方法变为抽象方法
  • 当我们把抽象类中的成员方法变为抽象方法时,那么这个抽象方法就不需要再实现方法体(就是不需要写方法里面的代码)
  • 并且在子类中必须要重写父类中的抽象方法,除非这个子类是抽象类,如果是抽象类就不需要重写,直到有普通类继承,就要重写
java 复制代码
public abstract void eat();

1.3、抽象类的特征

  • 使用 abstract 关键字修饰类
  • 抽象类是不能实例化对象的,但是他有什么用呢? 他是只能被继承,那么作为父类有什么优点呢?下文知晓,虽然不能实例化对象,但是可以用抽象类来定义引用,这点和普通类一样,都是为了实现类型统一,即让代码发生向上转型,进而发生多态
  • 为什么不能实例化对象呢? 因为抽象类是不能很具体的描述一个对象?并且 里面的抽象方法没有方法体
  • 抽象类中可以有和普通类一样的 普通的成员(包含成员变量和成员方法),也可以有抽象方法,也可以含有构造方法,但是构造方法不能变成抽象方法,因为抽象方法是被重写的,而构造方法是不能重写。
  • 因为抽象方法需要重写,所以不能使用 static ,private,final,修饰
java 复制代码
public abstract void Animal(){
   public int a ;
   public void eat(){
      sout("正在吃法....")
}
// 抽象方法不需要实现方法体
  public abstract void run();
  }
  • 含有抽象方法的类 ,一定是抽象类,否则报错
  • 抽象类中不一定必须得有抽象方法,没有抽象方法这个抽象类也能定义
  • 当一个普通类继承了抽象类后,这个普通类需要重写父类抽象类中的抽象方法
  • 如果一个抽象类A 继承了抽象类B,那么在抽象类A中可以不重写抽象类B中的方法,但是当一个普通类继承C 继承了这个抽象类A,此时在普通类C中不仅要重写抽象类B当中的方法还要重写抽象类A的方法

1.4、抽象类的好处

  • 抽象类中的抽象方法不需要实现方法体,使得代码变得简单
  • 抽象类中的抽象方法在(非抽象类)子类中一定需要重写,如果子类中没有重写抽象方法就会报错,所以帮我们效验代码的合法性。如果这个子类是抽象类,那么抽象类就不需要重写,直到有普通类继承,就要重写

1.5、抽象类和普通类的区别

  • 抽象类需要使用 abstract 修饰
  • 抽象类有抽象方法,并且抽象方法不需要实现方法体
  • 抽象类不能实例化对象

二、接口

  • 为什么要有接口?

我们知道,Java 是不支持多继承的(就是一个类继承多个类),为了弥补这个缺点,便有了接口,接口是支持实现多个的,并且接口是支持多继承的(就是一个接口继承多个接口)

java 复制代码
// 类和接口之间使用implements,就一个类实现俩个接口
 public class Dog implements IRunning,ISwimming{

}
java 复制代码
//接口与接口之间用extends,就是用一个接口继承(实现)两个接口
interface IAmphibious extends IRunning,ISwimming{

}
// 和上面的Dog 效果一样
public class Dog implements IAmphibious{

}

2.1、什么是接口?

  • 接口:顾名思义,就好像电脑的USB接口一样,全部的USB接口都是遵循一个通讯协议的,就像鼠标,键盘连接电脑的USB线,只要需要使用,拿起来插入电脑就行。
  • 所以接口是公共的一个功能,如哪个类需要使用,实现这个功能的接口就可以。我们知道 Dog 会跑, Fish 会游泳。那么我们知道,实现这两个方法不难啊,在Dog类中把 running 这个方法写为 Dog类中的特色方法不就行了吗,在Fish类中把swimming这个方法写成 Fish 类中的特色方法也不就行了吗,确实是可以,但是我们想一下,如果再有Cat呢? Cat 也会跑,那我们又要在Cat 类在中写一个running的特色方法,那么不就是会跑的动物,都需要我们写running的方法吗。那么有什么方法能让我们不写这个running呢?有人又说,那我们把 running 和 swimming 都写在父类Animal中,不就解决了吗?但是我们要知道,一个父类中的成员是所有子类所共有的成员,那么我问你,所有的动物都会游泳和跑吗?答案是不行的。所以此时就有了接口,一个接口我们可以设置一个功能,比如说跑,游泳,当我们需要用这个功能时,实现接口就行,比如说Dog ,Cat 实现跑这个接口,Fish 实现游泳这个接口就行

2.2 、接口的语法

  • 创建接口 用 interface 关键字 比如创建一个IRunning接口
java 复制代码
interface IRunning {

}
  • 在类实现接口时,使用 implements 关键字 类可以实现多个接口
java 复制代码
public class Dog implements IRunning,ISwimming{

}
  • 在接口继承接口时,使用extends关键字 接口可以继承多个接口
java 复制代码
interface IAmphibious extends IRunning,ISwimming{

}

2.3、接口的规则

  • 接口的名字一般第一个和第二个字母大写,并且第一个字母是I 。如IRunnig ,ISwimming。还有结尾是able的
  • 还有接口的访问权限和修饰符,默认是 public abstract interface,也就是说接口是抽象的
java 复制代码
public abstract interface IRunning{

}

2.4、接口的特性

  • 接口也是不能实例化对象的
  • 接口一般都是 public abstract interface 修饰的,也就是说是抽象的 ,换句话说,类实例化了这个接口,就必须重写接口 里面 的方法,除非这个类是抽象类就不需要重写,但是出来混迟早要还,什么意思呢?就是当有类继承这个抽象类时,就必须重写抽象类中的方法,还要重写接口里面的方法
  • 依然像类一样可以发生向上转型,动态绑定,多态

2.4.1、接口中的成员

  • 成员方法是 默认public abstract + 返回值类型 修饰的.换句话说接口里面的方法默认是抽象方法 ,抽象方法不需要实现方法体,并且在实例化接口的类中,必须重写接口里面的方法。在类中的重写方法,访问修饰限定符只能是public。但是如果非要实现这个抽象方法的话,使用static 或 default修饰后就能实现方法体
java 复制代码
public abstract void run();
  • 成员变量是默认 public static final 修饰的。换句话说,这个变量是常量,并且是静态的,所以定义的同时就要初始化 ,所以接口里面不需要构造方法并且也不能有静态代码块
java 复制代码
public static final a  = 10;

2.5、类和接口的关系

  • 类实现接口需要使用 implements 关键字
java 复制代码
public class Dog implements IRunning,ISwimming{

}
  • 因为接口是abstract修饰的,里面的成员方法也是abstract修饰的。所以在类中需要重写接口里面的方法
  • 如果是抽象类实现接口,就不需要重写,但是有类继承这个抽象类后,这个类就要重写抽象类里面的方法还有接口里面的方法
  • 换句话说,一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类

2.6、接口和接口的关系

  • 接口和接口之间的关系是继承,所用的关键字是 extends
  • 因为接口是抽象的,所以在 IAmphibious 接口中不需要重写 IRunning 中的方法,当有类实现了IAmphibious这个接口,就要重写IAmphibious接口和IRunning中的方法
  • 接口间的继承相当于把多个接口合并在一起
java 复制代码
interface IAmphibious extends IRunning{

}

2.7、普通类 和接口 的区别

  • 普通类是 访问修饰限定符 + class + 类名
  • 接口是默认(系统指定) public abstract interface + 接口名
  • 普通类中:有成员变量,成员方法,成员变量是 访问修饰限定符 + 类型 + 变量名,成员方法是 访问修饰限定符 + 返回值类型 + 方法名
  • 接口中 :有成员变量,成员方法,成员变量默认(系统指定)是 public + static + final +类型 +变量名 + 初始化,成员方法默认(系统指定)是 public abstract 返回值类型 + 方法名
  • 普通类中有构造方法
  • 接口中没有构造方法
  • 都不能够实例化对象

2.8、抽象类 和接口的区别

  • 抽象类是 public abstract class + 类名
  • 接口是默认(系统指定) public abstract interface + 接口名
  • 区别是 类用class 接口用interface
  • 抽象类中:可以有普通成员变量,普通成员方法,抽象方法
  • 接口中:有默认(系统指定)是 public + static + final +类型 +变量名 + 初始化 的成员变量,和 抽象方法
  • 抽象类中可以有构造方法
  • 接口中没有构造方法
  • 都不能实例化对象

2.9、Comparable接口

  • Comparable 是一个接口,他里面有个 compareTo 的抽象方法,作用是比较两个对象,我们如果需要这个接口的功能就可以实现这个接口
  • 可以简单的认为这个接口是功能接口
  • 有很多的类型都实现有这个接口,比如 String,Integer 等等,换句话说,这些类型都有属于自己的比较规则。
  • 但是有个缺点,你们发现没,就是一旦你的比较规则写好后,那么这个类实例化出来的对象就只能按照你的比较规则来走,什么意思呢?就是如果我在这个Student类中需要实现两个比较规则,一个比较规则是比较age,一个比较规则就是name。那么compareTo 就很难实现这个要求。
  • 还有就是这个比较规则写好后,这个类中其他的方法也调用这个比较规则,那么我想要改变这个比较规则的话,其他的方法中的代码也需要改变。
  • 那怎么办呢?,此时就有了下面的 Comparator 接口
java 复制代码
public class Student implements Comparable<Studen>{
   public int age;
   public String  name;
   public int compareTo(Student o)
   {
    //根据自己需求实现
     return o.age - this.age;
   }
}
  • 根据自己的需求实现 重写方法 comparaTo 的方法体
  • 还有就是在实现接口时,要加上比较比较 对象的类型 < Student >,如果没加重写方法的形参的 类型就是Object类型
    还有注意 compareTo 方法是一个形参的

2.10、Comparator接口

  • Comparator 也是一个用来比较对象的接口,里面有两个抽象方法,一个是compare ,另一个是equals。
  • compare 方法是两个形参的,那么我说这个有什么用呢? 我的意思是有两个形参,那么就意味着可以传两个对象,换句话说不像compareTo 那样需要依赖对象。这里的不依赖对象和static那个不依赖对象有点区别。这里的不依赖对象是不依赖对象去调用。
  • 所以说我们可以创建一个类专门用来实现一个比较规则,那么这种类就叫做比较器
java 复制代码
 class AgeCompare implements Comparator<Student>{
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
  • 实现这个接口时,也需要告诉这个接口,你要比较的对象的类型,否则重写方法中的形参的类型是Object类型
  • 这样写的好处是,你想有多少个比较规则都没问题

2.10.1、Comparable接口 和 Comparator接口 的区别

  • 我们知道上文所说,Comparable 中的compareTo是只有一个形参的
  • 而 Comparator 中的compare是有两个形参的。那么我说 compareTo 是需要依赖对象的 ,而compare是不依赖对象的。(这里的依赖对象和static的依赖对象不一样)
  • Comparable 和 Comparator 是可以共存的,默认的话就是调用comparable中的compareTo ,有需要的话就调用比较器。在什么时候用到这些比较规则呢?比如说,我实例化了三个对象,分别是 student1 ,student2,student3。并用students 数组接收这三个对象。当我们需要这个数组排序时,Student类中有两个成员变量,那么排序方法怎么知道我们要排序哪个变量呢? 此时的比较规则就起到了关键作用。排序方法会根据Student类中的compareTo 来进行排序。默认情况下,排序方法会判断要排序的类 有没有实现Comparable并重写compareTo方法,如果没有,就会抛异常。如果没有实现Comparable并重写compareTo方法,还可以通过给排序方法传入我们自己写的比较器。一句话:默认调compareTo ,调用传compare;
java 复制代码
public class Tect {
    public static void main(String[] args) {
       Student student1 = new Student(1,"1");
       Student student2 = new Student(2,"2");
       Student student3 = new Student(3,"3");
       Student[] students ={student1 ,student2 ,student3 };
          //默认使用Student类中的compareTo的比较方法
        Arrays.sort(students);
          //传入比较器,进行排序
        AgeCompare ageCompare = new AgeCompare();
        Arrays.sort(students,ageCompare);
    }
}
java 复制代码
class Tect {
  main(){
     Student student1 = new Student();
     Student student2 = new Student();
     //比较方法一:默认使用重写比较规则
     student1.compareTo(student2);
     //比较方法二:传入比较器,按比较器规则来
     AgeCompare agecompare = new AgeCompare();
     compare(student1,student2);
 }
}

这里如果我还需要比较Student中的name 的话,Comparable 中的compareTo就难以实现了,用Comparator 中的 compare 的话,还可以定义一个比较器,要使用就new对象,调用compare 传两个要比较的对象即可。这便弥补了Comparable的缺点

2.11、Clonable接口和深拷贝

  • 了解深拷贝我们先知道浅拷贝,那什么是拷贝呢?

2.11.1、什么是拷贝?

  • 拷贝就是复制出与原来一摸一样的东西
  • 那么代码怎么实现拷贝呢?
  • 我们知道Object中有一个拷贝方法,那么既然有了拷贝方法那么我们该如何调用呢?那不简单吗,因为Object是所有类的父类,所以new个对象直接调用clone方法就可以了啊。
java 复制代码
class Tect{
 public static void main(String[] args) {
        Student student1 = new Student(12,"张三");
        Student student2 = student1.clone;
    }
    }

按道理来说,这个代码应该就能执行了。但是实际是没有clone方法。这是为什么呢?原因很简单,因为Object类中的 clone 方法是 protected 修饰的,父类和子类不在一个包,虽然是子类,但不是在自己本身的类(Student)中使用,而是在其他的类(Tect)中使用 ,所以 protected Object clone() throws CloneNotSupportedException {

return super.clone();

}用不了。那怎么办呢?

  • 既然在Student中能使用,那么就重写clone方法 ,在方法中返回父类Object中的clone方法 不就行了吗
java 复制代码
class Student{
  protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
      }

此时我们还发现clone源码中,有throw...,这其实是异常,所以我们需要在main方法的后面说明这个方法是合法的

java 复制代码
class Tect{
 public static void main(String[] args) throws CloneNotSupportedException{
        Student student1 = new Student(12,"张三");
        Student student2 = student1.clone;
    }
    }

此时还没完,我们看源码,发现调用完父类的clone后,返回值是Object,所以需要强转

  • 当我们以为拷贝成功了,但实际是运行时抛了CloneNotSupportedException这个异常,那有异常就需要处理,那怎么处理呢?
    在需要拷贝的类 实现 Clonable接口,这个接口里面什么东西都没有,盲猜,他是用来标记这个类能被拷贝的。
java 复制代码
public class Student implements Cloneable{

}
  • 到这里就拷贝完成了

2.11.2、什么是浅拷贝?

  • 比如说,一个对象中有成员变量,成员方法,还有一个引用指向另一个对象。浅拷贝就是把成员变量,成员方法,引用,都拷贝到另一个对象,但是对象中的引用所指的那个对象没有拷贝
  • 这样就是浅拷贝,这样的话,我们看图,发现通过people1 ,people2 都能改变 对象的money所指的对象 ,那么就会非常不安全了

2.11.1、怎么深拷贝?

  • 深拷贝就是把对象里的引用所指的对象一起拷贝了
  • 那么这种深拷贝怎么通过代码实现呢?简单,就是在拷贝了的前提,再对money进行拷贝
java 复制代码
class Student implements Cloneable{
protected Object clone() throws CloneNotSupportedException {
            Student tem = (Student) super.clone();
            tem.money = (Money) this.money.clone();
            return tem;
        }
  }

在Student 中的重写clone中,在对象的拷贝的前提下,再拷贝一次对象里面的引用所指的对象

深拷贝和浅拷贝本质区别是代码怎么写,而不是取决于某个工具

三、内部类

  • 日常使用最多的是匿名内部类。
  • 内部类也是封装的一种体现
  • 什么是内部类,顾名思义是在内部里的类,那在谁的内部呢?在类的内部。
  • 那什么情况下需要定义内部类呢?
  • 我们知道类是对一个真实的事物进行描述。在一个类中,还有一部分可以抽取出来当做一个类的,此时就可以定义内部类了
java 复制代码
class OutClass {
   class InClass {
   
   }
}

3.1、什么是内部类?

  • 内部类是类中类,或者方法中类
  • 我们知道类中的成员有,成员变量,成员方法。如今增加了成员类,我们知道成员有静态成员,和非静态成员,那么内部类是否也是有静态内部类 和非静态内部类的呢?内部类的位置那么特殊,是成员,又是类,那么他是否具有这两种的特点呢?我们拭目以待吧。

3.2、内部类的分类

  • 静态内部类
  • 实例内部类
  • 局部内部类
  • 匿名内部类

3.3、静态内部类

  • 静态内部类和静态成员一样,都是使用static修饰的
java 复制代码
class OutClass{
   static class InClass{

   }
}
  • 作为成员,是被static修饰的,被static修饰后,最大的特点就是不依赖对象,变成类类了。所以可以通过外部类直接点就能使用静态内部类。
  • 作为类,那么他是可以实例化对象的,那么如何实例化对象呢?
java 复制代码
OutClass.InClass inClass = new OutClass.InClass();

此时引用能访问静态内部类里面的非静态成员,因为内部类里面静态成员是通过类名直接点访问的,而外部类的非静态成员变量,需要外部类实例化对象,并通过外部类的对象加点才能访问。外部的静态成员,是外部类类名直接点就访问。所以综上,通过内部类对象不能访问外部类的成员

  • 在静态内部类当中不能直接访问外部类当中的非静态的成员变量(硬要访问只能通过外部类对象来访问),换句话说在静态内部类中只能访问外部类中的静态成员

3.4、实例内部类

  • 什么是实例内部类,我们知道类中的非静态成员,也叫做实例成员。
  • 所以说内部类是需要依赖对象的,依赖谁的对象呢?依赖外部类的对象
  • 作为成员,是外部类的对象加点才能使用
  • 作为类,那么他是怎么实例化对象的呢?
java 复制代码
        //方法一
        OutClass2.InClass inClass2 = new OutClass2().new InClass();
        //方法二
        OutClass2 outClass2 = new OutClass2();
        OutClass2.InClass inClass3 =  outClass2.new InClass();

实例化出来的对象,也是只能调用内部类直接的非静态成员。因为内部类里面静态成员是通过类名直接点访问的,而外部类的非静态成员变量,需要外部类实例化对象,并通过外部类的对象加点才能访问。外部的静态成员,是外部类类名直接点就访问。所以综上,通过内部类对象不能访问外部类的成员

  • 外部类中的任何成员都可以在实例内部类中直接访问
  • 我们知道类是只能被 public 或者默认修饰的,但现在作为成员,因此也受protected private等访问限定符修饰
  • 在实例内部类中的非静态方法是默认实现了外部类实例化对象。也就是说,如果实例内部类中的成员变量 和外部类的成员变量重名了,我们知道,如果重名了肯定优先使用自己的(实例内部类的),但是我偏要访问外部类重名的变量。此时就可以直接外部类名 + this + 点 + 变量名,就能调用外部类的成员变量
  • 在外部类中,不能直接访问实例内部类的成员,如果非要访问,必须先创建实例内部类的对象。

3.5、匿名内部类

  • 顾名思义,匿名所以是没有名的内部类,在声名的同时完成实例化,通常在只使用一次的情况下定义。
  • 怎么定义?
java 复制代码
// SuperType 可以是接口,抽象类和普通类
new SuperType(){
  
}
  • 可以定义一个Super Type的引用去接收他
java 复制代码
SuperType superType = new SuperType(){
  
}
  • 匿名类中可以定义和正常类一样的成员变量。

3.6、局部内部类

  • 是定义在外部类的方法体中,这种类只能在定义的范围中使用( { } )。
java 复制代码
public class OutClass {
  public void tect(){
    class InClass{
   }
  }
} 
  • 局部内部类只能在所定义的方法体内部使用
  • 不能被public static 等修饰

四、Object类

一、toStiring 方法

  • 是用来打印的

二、equals 方法

  • 是用来比较对象的,根据自己的需求重写。
相关推荐
一 乐31 分钟前
心理咨询|学生心理咨询评估系统|基于Springboot的学生心理咨询评估系统设计与实现(源码+数据库+文档)
java·数据库·spring boot·后端·论文·毕设·学生心理咨询评估系统
EndingCoder35 分钟前
Next.js API 路由:构建后端端点
开发语言·前端·javascript·ecmascript·全栈·next.js·api路由
Java技术小馆41 分钟前
Gemini Storybook AI驱动的交互式故事创作
java·程序员·架构
2401_858286111 小时前
CD64.【C++ Dev】多态(3): 反汇编剖析单继承下的虚函数表
开发语言·c++·算法·继承·面向对象·虚函数·反汇编
码神本神1 小时前
(附源码)基于Spring Boot的4S店信息管理系统 的设计与实现
java·spring boot·后端
天天摸鱼的java工程师2 小时前
SpringBoot + Seata + MySQL + RabbitMQ:金融系统分布式交易对账与资金清算实战
java·后端·面试
pzzqq2 小时前
buildroot编译qt 5.9.8 arm64版本踩坑
开发语言·qt
别来无恙1492 小时前
Spring Boot文件上传功能实现详解
java·spring boot·文件上传
还债大湿兄2 小时前
基于Qt Property Browser的通用属性系统:Any类与向量/颜色属性的完美结合
开发语言·qt
Bug生产工厂2 小时前
手把手教你把三方支付接口打包(Java 版)
java·产品经理