前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Java 学习】:抽象类&接口

【Java 学习】:抽象类&接口

作者头像
用户11316099
发布2024-10-15 20:39:52
1350
发布2024-10-15 20:39:52
举报
文章被收录于专栏:学习之路

1. 抽象类

1.1 抽象类是什么

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

我们先来看一个简单的例子🌰

代码语言:javascript
复制
// 抽象类和抽象方法需要被 abstract 关键字修饰
abstract class Animal {
    // 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
    abstract void eat();
    // 抽象类也是类,也可以增加普通方法和属性
    public double getArea(){
        return area;
    }
    protected double area; // 面积
}

初看上面是不是没啥问题,但是当我们对这个类进行实例化的时候,就会发现:

像这样的类是不是就没有包含足够的信息来描绘一个具体的对象,因此也就不能直接去实例化对象了。那我们应该怎么解决这个实例化问题呢?

代码语言:javascript
复制
// 抽象类和抽象方法需要被 abstract 关键字修饰
abstract class Animal {
    // 抽象类中的方法一般要求都是抽象方法,抽象方法没有方法体
    abstract void eat();
    // 抽象类也是类,也可以增加普通方法和属性
    public double getArea(){
        return area;
    }
    protected double area; // 面积
}
public class Test{
    public static void main(String[] args) {
        Animal animal = new Animal() {
            @Override
            void eat() {
                System.out.println("重写");
            }
        };
    }
}

只需要对 abstract类的抽象方法实例化之后进行重写即可。

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

1.2 抽象类特性

🍉抽象类不能直接实例化对象,无法创建对象,抽象类是被子类来继承的

代码语言:javascript
复制
Animal animal = new Animal();
// 编译出错
Error:(30, 23) java: Animal是抽象的; 无法实例化

🥑抽象方法不能是 private 的

代码语言:javascript
复制
abstract class Animal {
    abstract private void eat(); //抽象方法不能是 private 的
}
// 编译出错
Error:(4, 27) java: 非法的修饰符组合: abstract和private

🥝抽象方法不能被final和static修饰,因为抽象方法要被子类重写

代码语言:javascript
复制
public abstract class Animal {
  abstract final void methodA();
  abstract public static void methodB();
}
// 编译报错:
// Error:(20, 25) java: 非法的修饰符组合: abstract和final
// Error:(21, 33) java: 非法的修饰符组合: abstract和static

🍋‍🟩抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰

代码语言:javascript
复制
abstract class Animal {
    abstract void eat();
    // 抽象类也是类,也可以增加普通方法和属性
    public double getArea(){
        return area;
    }
    protected double area; // 面积
}

class Dog extends Animal{
    @Override
    void eat() { 
        // 重写
    }
}

abstract class Cat extends Animal{
    
}

🥬其他特性:

  • 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  • 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
1.3 抽象类的应用

🍅那既然一个类不能直接实例化,那这种抽象类存在的意义是什么呀🤔?我们接着往下看

🍅抽象类存在的一个最大意义就是被继承,当被继承后就可以利用抽象类实现多态。

代码示例如下:

代码语言:javascript
复制
class Dog extends Animal{
    @Override
    void eat() {
        System.out.println("小狗吃东西");
    }
}

public class Test {
    public static void main(String[] args) {
        //Animal animal = new Animal(); // 抽象类虽然无法直接实例化
        // 但可以把一个普通类对象传给一个抽象类的引用呀,即父类引用指向子类对象
        Animal animal = new Dog(); // 这称作:向上转型
        /*Dog dog = new Dog();
        Animal animal = dog; // 这是向上转型的另一种写法*/

        animal.eat();         // 通过父类引用调用被子类重写的方法
    }
}

向上转型的具体,我们之前在多态那篇博客【Java 基础】:三大特征之多态-CSDN博客那就已经讲过,就不过多讲解了,只需要知道向上转型是:父类引用指向子类对象

1.4 抽象类的总结

抽象类是类和类之间的共同特征,将这些共同特征进一步形成抽象类,由于类本身不存在,所以抽象类无法创建对象。 类到对象是实例化,对象到类是抽象 抽象方法不能被 final 修饰,因为抽象方法就是被子类实现的

  • 采用 abstract 关键字定义的类就是抽象类,采用 abstract 关键字定义的方法就 是抽象方法
  • 抽象的方法只需在抽象类中,提供声明,不需要实现
  • 如果一个类中含有抽象方法,那么这个类必须定义成抽象类。抽象类中不一定有抽象方法,抽象方法必须出现在抽象类中
  • final和abstract不能同时同时使用,这两个关键字是对立的
  • 抽象方法不能被private修饰
  • 抽象类的子类可以是抽象类。也可以是非抽象类
  • 一个非抽象的类,继承抽象类,必须将抽象类中的抽象方法进行覆盖/重写/实现

抽象方法表示没有实现的方法,没有方法体的方法

  1. 没有方法体,以分号结尾
  2. 前面的修饰符列表中有abstract关键字,比如public abstract void dosome();

但是不能说java语言中没有方法体的方法都是抽象方法。 因为Object类中就有很多方法都没有方法体,都是以“;”结尾的,但他们都不是抽象方法

2. 接口

2.1 接口的概念

🍑抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)🤔。

📝接口是Java中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成😎。

  • 接口是一种“引用数据类型”,完全抽象的,支持多继承,且一个接口可以继承多个接口,只有常量+抽象方法
  • 所有的元素都是public修饰的,抽象方法的public abstract可以省略,常量的public static final可以省略,方法不能有方法体

如何定义一个接口呢?下面我们来看一个栗子🌰

代码语言:javascript
复制
// 定义格式如下:
//[修饰符列表] interface 接口名{}

// 实例:
public interface Test{
    // 定义变量
    int a = 10;      // 接口当中的成员变量默认都是public static final

    // 抽象方法
    public abstract void metho(); // public abstract 是固定搭配,可以不写
    void method();  //  接口当中的成员方法默认都是public abstract, 更推荐用第二种来定义方法
}

提示:

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

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

代码语言:javascript
复制
/* 语法格式 */
/* class 类名称 implements 接口名称{
// ...
} */

//实例
interface USB {
    void openDevice(); // 默认是public的
    void closeDevice(); // 默认是public的
}

class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }
    @Override
    public void closeDevice() {
    }
    // ...
}

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

2.3 接口的特性

🍉接口类型是一种引用类型,但是不能直接new接口的对象

代码语言:javascript
复制
public class TestUSB {
  public static void main(String[] args) {
    USB usb = new USB();
 }
}
// Error:(10, 19) java: USB是抽象的; 无法实例化

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

代码语言:javascript
复制
//定义抽象方法的时候可以省略修饰符public abstract
public interface USB {
  // Error:(4, 18) java: 此处不允许使用修饰符private
  private void openDevice();
  void closeDevice();
}

🥝接口中的方法是不能在接口中实现的,只能由实现接口的类来实现

代码语言:javascript
复制
public interface USB {
  void openDevice();
 
  // 编译失败:因为接口中的方式默认为抽象方法
  // Error:(5, 23) java: 接口抽象方法不能带有主体
  void closeDevice(){
    System.out.println("关闭USB设备");
 }
}

🍋‍🟩重写接口中方法时,不能使用默认的访问权限

代码语言:javascript
复制
/* 语法格式 */
/* public class 类名称 implements 接口名称{
// ...
} */

//实例
interface USB {
    void openDevice(); // 默认是public的
}

class Mouse implements USB {
    @Override
    void openDevice() { //解决:函数前加个权限 public 即可
        System.out.println("打开鼠标");
    }
}
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限; 以前为public

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

代码语言:javascript
复制
interface USB {
    double brand = 3.0; // 默认被:final public static修饰
    void openDevice();
    void closeDevice();
}
public class Test {
    public static void main(String[] args) {
        System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的

        // 编译报错:Error:(12, 12) java: 无法为最终变量brand分配值
        USB.brand = 2.0; // 说明brand具有final属性,无法被再次赋值
    }
}

🍀支持多继承,且一个接口可以继承多个接口,每一个interface 都会生成一个class后缀名的文件

代码语言:javascript
复制
interface a{
}

interface b extends a{
}

interface c extends a,b{
}

🥬其他特性:

  • 接口中不能有静态代码块和构造方法
  • 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  • 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
  • jdk8中:接口中还可以包含default方法。
2.4 接口的应用

通过接口实现多态

🍌刚才我们是用抽象类来实现多态,那么现在我们来尝试使用接口去实现多态😎

代码语言:javascript
复制
interface Animal{
    int a = 10;   //接口当中的成员变量默认都是public static final
    int b = 23;
    void eat();  //接口当中的成员方法一般只能是抽象方法,默认是public abstract(JDK1.8以前)

    default void show() {
        System.out.println("接口中的其他方法");//接口中的其他方法也可以实现,但要用default修饰
    }
    public static void test() {
        System.out.println("这是接口当中的一个静态的方法");
    }
}

// 一个普通的类要想实现接口,可以用implement,
//因为接口也是抽象方法的,所以实现接口的这个类也要重写抽象方法
class Dog implements Animal{
    @Override
    public void eat() {
        System.out.println("小狗");
    }
}

class Cat implements Animal{
    @Override
    public void eat() {

    }
}

public class Test{
    public static void main(String[] args) {
        Animal[] animals = {new Dog(),new Cat()};
        for(Animal animal: animals){
            animal.eat();
        }
    }
}
2.5 比较器接口
Comparable 接口

假如有以下学生信息这样的代码,想根据学生年龄来进行排序

代码语言:javascript
复制
class Student {
    public String name;
    public int age;
    public Student(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return "["+this.name+":"+this.age+"]";
    }
  
}

public class Test {
    public static void main(String[] args) {
        Student[] student=new Student[]{
                new Student("zhangsan",21),
                new Student("lisi",17),
                new Student("wangwu",33),
        };

        System.out.println(Arrays.toString(student));
    }

}
  • 按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?
代码语言:javascript
复制
Arrays.sort(students);
System.out.println(Arrays.toString(students));

// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable

为什么会出现问题呢:

  • 和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系 怎么确定? 需要我们额外指定.
  • 如果要解决该问题,此时我们就要用到 Comparable接口,让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
代码语言:javascript
复制
class Student implements Comparable{
    public String name;
    public int age;
    public Student(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return "["+this.name+":"+this.age+"]";
    }
    @Override
    public int compareTo(Object o) {
        Student student=(Student) o;
        return this.age-student.age;
    }
}

实现comparable后要进行重写 compareTo 方法

sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.然后比较当前对象和参数对象的大小关系

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

但是在上述代码中存在一个问题,就是无法通过名字进行比较,那么此时我们就要用到comparator接口

Comparator 接口
  • comparator接口是一个用于比较两个对象大小的接口,它定义了一个抽象方法compare(T o1, T o2),根据o1和o2的大小返回一个整数值。Comparator接口位于java.util包中,它是一个泛型接口,可以指定比较的对象类型。
  • comparator接口的作用是提供一种自定义的比较规则,可以用于对没有实现comparable接口的类的对象进行排序,或者对实现了Comparable接口的类的对象进行不同的排序
  • comparator接口是另一个用于比较对象大小的接口,它定义了一个抽象方法compareTo(T o),根据this和o的大小返回一个整数值。Comparable接口位于java.lang包中,它也是一个泛型接口,可以指定比较的对象类型。
  • comparator接口接口的作用是提供一种自然的比较规则,通常用于实现类似于数字、字符串、日期等有固定大小顺序的类。实现了comparable的类可以直接使用Java中的排序功能对其进行排序,比如使用Collection.sortArrays.sort方法。
代码语言:javascript
复制
// 按照年龄排序
class Agecompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age- o2.age;
    }
}

// 按照名字排序
class Namecompare implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

// 使用方法
Arrays.sort(student,new Agecompare());
Arrays.sort(student,new Namecompare());

两种写法都叫做比较器,但是后者更加灵活,可以根据所需进行编写,而且两者可以共同存在

2.6 接口总结
  1. 接口是抽象化的,不能实例化
  2. 接口抽象方法必须实现,默认修饰 public abstract
  3. 接口属性是被 public static final 修饰,静态的、不可修改的
  4. 接口没有构造方法
  5. 接口有静态方法,含有方法体,主要特点是使用接口名调用静态方法
  6. 接口有默认方法,含有方法体,主要目的是为了扩展性和复用性

3. Object 类

Object类是Java中java.lang包下的核心类,Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。

案例:(使用Object类型接收所有类的对象)

代码语言:javascript
复制
class A{};
class B{};
public class object_lei {
        public static void main(String[] args) {
            function(new A());
            function(new B());
        }
        public static void function(Object obj) {
            System.out.println(obj);
        }
}

// 执行结果:
Test.A@3b07d329
Test.B@404b9385

Object 类属于java.lang包,此包下的所有类在使用时无需手动导入,系统会在程序编译期间自动导入

3.1 Object 类的常用方法

方法名

作用

clone()

保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

getClass()

final方法,返回Class类型的对象,反射来获取对象。

toString()

该方法用得比较多,一般子类都有覆盖,来获取对象的信息。

finalize()

该方法用于释放资源。因为无法确定该方法什么时候被调用,很少使用。

equals()

比较对象的内容是否相等

hashCode()

该方法用于哈希查找,重写了equals方法一般都要重写hashCode方法。这个方法在一些具有哈希功能的Collection中用到。

wait()

在后面线程那里会设计,使当前线程等待该对象的锁,当前线程必须是该对象的拥有者,也就是具有该对象的锁。wait()方法一直等待,直到获得锁或者被中断。 wait (long timeout) 设定一个超时间隔,如果在规定时间内没有获得锁就返回。

notify()

该方法唤醒在该对象上等待的某个线程。

notifyAll()

该方法唤醒在该对象上等待的所有线程。

3.2 clone 方法

Clonable 和 深拷贝

对象克隆是一种创建对象的精确副本的方法。 Object类的clone()方法用于克隆对象java.langCloneable()接口必须由我们要创建其对象克隆的类实现。如果我们不实现Cloneable()接口,clone()方法将生成CloneNotSupportedExceptionequals

为什么要使用clone()方法?

clone()方法保存用于创建对象的精确副本的额外处理任务。 如果我们使用new关键字执行它,它将需要执行大量的处理,这就是为什么我们使用对象克隆。

对象克隆的优点:

  • 少处理任务
代码语言:javascript
复制
// clone 的基本语法:
protected Object clone() throws CloneNotSupportedException

// 案例使用
// 在 Object 的子类Person 中要完成重写
class P implements Cloneable{
    public int age;
    public P(int age){
        this.age=age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public String toString() {
        return "Preson="+this.age;
    }
}

public class CloneTest {
    public static void main(String[] args) throws CloneNotSupportedException {
        P p1=new P(10);
        // 此时就要注意,object是父类,要完成向下转型,那么我们就要强转
        P p2=(P) p1.clone();
        System.out.println(p1.toString());
        System.out.println(p2.toString());
    }
}


// 结果输出
Preson=10
Preson=10
  • 从上面的例子可以看出,两个引用变量都有相同的值。

因此,clone()将对象的值复制到另一个对象。 因此在实际应用中我们不需要编写显式代码将对象的值复制到另一个对象。如果通过new关键字创建另一个对象并将另一个对象的值赋给这个对象,则需要对该对象进行大量处理。 所以为了节省额外的处理任务,我们使用clone()方法。

3.3 toString 方法
  • 🍎在Object类里面定义toString()方法的时候返回的对象的哈希code码(对象地址字符串);
  • 可以通过重写toString()方法表示出对象的属性。

之前在这篇文章【Java 基础】类和对象(构造&this&封装&static&代码块已经讲过了,此处就不再过多讲解

代码语言:javascript
复制
// Object类中的toString()方法实现:
public String toString() {
  return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
3.4 equals 方法

在Java中,==进行比较时:

  1. 如果==左右两侧是基本类型变量,比较的是变量中值是否相同
  2. 如果==左右两侧是引用类型变量,比较的是引用变量地址是否相同
  3. 如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
代码语言:javascript
复制
class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		this.age = age ;
        this.name = name;
    }
}

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


public class Test {
	public static void main(String[] args) {
		Person p1 = new Person("gaobo", 20);
		Person p2 = new Person("gaobo", 20);
		int a = 10;
		int b = 10;
		System.out.println(a == b);       // 输出true
		System.out.println(p1 == p2);      // 输出false
		System.out.println(p1.equals(p2));    // 输出false
	}
}

Person类重写equals方法后,然后比较:

代码语言:javascript
复制
class Person {
	...
		@Override
		public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (this == obj) {
			return true;
		}
		// 不是Person类对象
		if (!(obj instanceof Person)) {
			return false;
		}

		Person person = (Person)obj; // 向下转型,比较属性值
		return this.name.equals(person.name) && this.age == person.age;
	}
}
  • 所以,引用类型的数据在进行比较时,应该先覆写equals方法,不然比较的还是两个对象的堆内存地址值,必然不会相等

📖 总结

Java中接口和抽象类的定义语法分别为interface与abstract关键字。

相同点:

  • 都不能被实例化 ,接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。

不同点:

  1. 抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;接口中的方法默认使用public修饰
  2. 抽象类可有成员变量,接口只能有 public static final 修饰的常量:接口成员变量默认为public static final,必须赋初值,不能被修改。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;
  3. 实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
  4. 接口强调特定功能的实现,而抽象类强调所属关系。
  5. 抽象类可有构造函数,接口没有构造函数:抽象类可以包含方法、构造方法,方法可以实现,但是构造方法不能用于实例化,主要用途是被子类调用。接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体。

【*★,°*:.☆( ̄▽ ̄)/$:*.°★* 】那么本篇到此就结束,希望我的这篇博客可以给你提供有益的参考和启示,感谢大家支持!!!祝大家天天开心💞 💞 💞

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 抽象类
    • 1.1 抽象类是什么
      • 1.2 抽象类特性
        • 1.3 抽象类的应用
          • 1.4 抽象类的总结
          • 2. 接口
            • 2.1 接口的概念
              • 2.2 接口的使用
                • 2.3 接口的特性
                  • 2.4 接口的应用
                    • 2.5 比较器接口
                      • Comparable 接口
                      • Comparator 接口
                    • 2.6 接口总结
                    • 3. Object 类
                      • 3.1 Object 类的常用方法
                        • 3.2 clone 方法
                          • 3.3 toString 方法
                            • 3.4 equals 方法
                            • 📖 总结
                            领券
                            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档