Java抽象类和接口使用梳理
作者:Word码鸭 发布时间:2022-10-02 08:38:46
抽象类
什么是抽象类呢?在现实生活中,我们说“人类”,我们无法对应到具体某个人,同样的,“动物类”、“图形类”这些无法映射到具体的对象的类就是抽象类。
抽象类是普通类的超集,意思就是普通类有的抽象类也有,只是它比普通类多了一些抽象方法而已。这些抽象方法可以有一个,也可以有多个。
它存在的意义就是让子类来覆写它的抽象方法,抽象类和抽象方法的实现使用 abstract 关键字实现:
此时Sharp类就是抽象类,可以看到它的图标和普通类都有所不同。
抽象方法
示例:
public abstract void print();
抽象方法所在的类一定是抽象类,抽象方法只有方法声明,没有方法体{}
注意区分没有方法体和方法体为空的情况
public abstract void print();//这是没有方法体
public abstract void print(){ } //这是方法体为空,它是普通方法
抽象类三大原则
1.抽象类无法直接实例化对象。例如上面的 Sharp 类,Sharp sharp = new Sharp();//这是错误的
2.子类继承抽象类,必须覆写抽象类中的所有抽象方法(前提是子类是普通类)
Triangle是一个普通类,此时没覆写方法就报错了,这时我们使用alt + enter 快捷键,点击第一行,再点击ok,就覆写了父类的抽象方法
但是当子类依然是抽象类时,可以选择不覆写抽象方法
3.final 和 abstract 不能同时使用、private 和 abstract 不能同时使用
抽象类存在的最大意义就是被继承,而且它仍然满足继承关系的 is a 原则
abstract class Person
class Chinese extends Person √
class Dog extends Person × //因为Dog not is a Person
同时抽象类任然受到单继承的局限,此时我们就引出了接口来打破这两个局限
接口
Java使用 interface 关键字定义接口,接口中只有全局常量和抽象方法(JDK之前)JDK8扩展了default方法。子类使用 implements 实现接口
一般接口的命名使用大写 ' I ' 字母开头,子类命名以 Impl 结尾
全局常量:
public static final int NUM = 10;
抽象方法:
public abstract String msg( );
接口使用原则
1.接口中只有 public 权限,且只有全局常量和抽象方法,所以 abstract、static、final、public这些关键字在接口内部都可以省略
public interface IMassage {
int NUM = 10;//全局常量
String msg();//抽象方法
}
2.接口没有单继承限制,子类可以同时实现多个父接口,多个接口之间使用逗号分隔。此时子类必须实现父类接口中所有的抽象方法
public interface IMassage {
int NUM = 10;//全局常量
String msg();//抽象方法
}
interface INews{
void getNews();
}
//子类
public class MessageImpl implements IMassage,INews{
public String msg() {
return null;
}
public void getNews() {
}
}
接口可以使用 extends 继承多个父接口,下面的示例若类要实现接口 C ,就必须覆写 A、B、C中所有的抽象方法
interface A{
void testA();
}
interface B{
void testB();
}
interface C extends A,B{
}
3.接口依然不能直接实例化对象,需要通过向上转型实现
public class MessageImpl implements IMassage,INews{
public String msg() {
return "hello JAVA";
}
public void getNews() {
System.out.println("hello n~");
}
public static void main(String[] args) {
IMassage m = new MessageImpl();
System.out.println(m.msg());
}
}
//输出:hello JAVA
m只能调用msg方法,不能调用INews接口类定义的方法,需要用父类间的相互转换实现调用
INews n = (INews)m;
n.getNews();
//输出:hello n~
4.子类若既要继承类又要实现接口,就先继承,后实现接口
public class D extends A implements X,Y{ }
JDK两大内置接口
java.lang.Comparable 比较接口
引入示例:使用排序方法比较Student类中的对象
public class Student {
private String name;
private int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "Student{" +
"name=" + name+'\''+ ",age="+ age + '}';
}
public static void main(String[] args) {
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",20);
Student s3 = new Student("王五",30);
Student[] students = {s3,s1,s2};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
运行结果:
程序报错,因为Student类是自定义类型,当使用Arrays.sort方法对自定义类型进行排序时,自定义类型需要实现 Comparable 才具有可比较的能力
因此我们将上述示例做如下改动:
public class Student implements Comparable<Student>{
private String name;
private int age;
public Student(String name,int age){
this.name = name;
this.age = age;
}
public String toString(){
return "Student{" +
"name=" + name+'\''+ ",age="+ age + '}';
}
@Override
public int compareTo(Student o) {
if (this.age == o.age){
return 0;
}else if (this.age < o.age){
return -1;
}
return 1;
}
public static void main(String[] args) {
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",20);
Student s3 = new Student("王五",30);
Student[] students = {s3,s1,s2};//乱序放入数组
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
//输出结果:
[Student{name=张三',age=18}, Student{name=李四',age=20}, Student{name=王五',age=30}]
可以看到数组按照年龄的升序排序,达到了预期效果。如果想要按照年龄降序排列,只需要修改 compareTo 方法中的一和负一
实现Comparable接口,必须覆写它的compareTo方法,该方法返回的数字:
=0 表示当前对象等于目标对象 o
>0 表示当前对象等于目标对象 o
<0 表示当前对象等于目标对象 o
java.lang.Cloneable 克隆接口
在程序中,克隆是指复制一个新的对象,而这个新对象的属性值是从旧对象中拷贝过来的
Cloneable 接口是一个标记接口,本身没有任何抽象方法,当一个类实现了 Cloneable 接口,就表示该类具备了克隆的能力,这个能力是JVM赋予的,要知道在堆上开辟空间和对象创建都由JVM实现。
我们需要覆写 Object 类的 clone 方法,点击向上转型的图标我们就能看到 Object 提供的该方法
可以看到,clone 方法没有方法体,用 native 关键字修饰,叫做本地方法,clone方法不是Java语言实现的,而是C++实现的,要知道JVM就是由C++实现的。所以本地方法就表示Java调用了C++的同名方法,此处只是方法声明,具体的方法实现是在C++中。所以虽然它没有方法体,但它并不是抽象方法。
这里我们就能知道一个小知识点:没有方法体的方法不一定就是抽象方法
代码示例:
public class Cat implements Cloneable{
private String name;
@Override
protected Cat clone() throws CloneNotSupportedException {
return (Cat) super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
Cat c1 = new Cat();
c1.name = "喵喵";
Cat c2 = c1.clone();
System.out.println(c1 == c2);
System.out.println(c2.name);
}
}
输出结果:
可以看到,输出 false表示c1和c2不是同一个地址,也就是说在堆上为c2开辟了一个新空间,将c1的值拷贝给了c2
对象的深浅拷贝
我们先看一个示例:
class A{
int num;
}
public class B implements Cloneable{
A a = new A();
@Override
protected B clone() throws CloneNotSupportedException {
return (B)super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
B b1 = new B();
B b2 = b1.clone();
System.out.println(b1 == b2);
System.out.println(b2.a.num);
b1.a.num = 100;
System.out.println(b2.a.num);
}
}
输出:false
0
100
根据结果我们看到,将b1.a的值改为100后,b2.a也随之变化,就是说b1.a 和 b2.a 指向了相同的对象
这就是浅拷贝,拷贝出的 b1 对象只是拷贝了 b1 自身, 而没有拷贝内部包含的 a 对象. 此时 b1 和 b2 中包含的 a 引用仍然是指向同一个对象. 此时修改一边, 另一边也会发生改变.
我们将代码做如下修改:
class A implements Cloneable{
int num;
@Override
protected A clone() throws CloneNotSupportedException {
return (A)super.clone();
}
}
public class B implements Cloneable{
A a = new A();
@Override
protected B clone() throws CloneNotSupportedException {
B b = new B();
b.a = a.clone();
return b;
}
public static void main(String[] args) throws CloneNotSupportedException {
B b1 = new B();
B b2 = b1.clone();
System.out.println(b1 == b2);
System.out.println(b2.a.num);
b1.a.num = 100;
System.out.println(b2.a.num);
}
}
结果:false
0
0
我们让A类也实现克隆接口,可以看到b1.a的修改没有影响b2.a,说明此时b1和b2内部包含的a对象也是不同的,这种拷贝就叫做深拷贝
在Java中,实现深拷贝的方式有两种:一种是递归实现Cloneable,上述的例子就是递归实现的;另外一种就是通过序列化(Serializable 接口)来进行拷贝。这两种方法现在已经不常用了,现在实现深拷贝是将对象转为json字符串
抽象类和接口的区别
来源:https://blog.csdn.net/m0_58672924/article/details/122822590


猜你喜欢
- 这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适
- 一、先上效果图借用某东App的效果,如下。折叠时的效果:展开时的效果:二、ChipGroup和ChipchipGroup和chip之前写过博
- 不论是float 还是double都是浮点数,而计算机是二进制的,浮点数会失去一定的精确度。Java在java.math包中提供的API类B
- 一、基本概念:线程、进程1.1、进程与线程的具体介绍线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同
- 前言什么是mybatis二级缓存?二级缓存是多个sqlsession共享的,其作用域是mapper的同一个namespace。即,在不同的s
- 介绍装饰者模式(Decorator Pattern):动态地给一个对象增加一些额外的职责,增加对象功能来说,装饰模式比生成子类实现更为灵活。
- 本文实例讲述了C#实现获取鼠标句柄的方法,分享给大家供大家参考。具体实现方法如下:一、调用user32.dll(1)引用using Syst
- 如果要想使一个catch block能抓获多种数据类型的异常对象的话,怎么办?C++标准中定义了一种特殊的catch用法,那就是” catc
- 消息的保存路径消息发送端发送消息到 broker 上以后,消息是如何持久化的?数据分片kafka 使用日志文件的方式来保存生产者和发送者的消
- // 声明LocationManager对象 LocationManager loctionManager; // 通过系统服务,取得Loc
- activity_list.xml文件代码如下:<?xml version="1.0" encoding=&quo
- 本节我们主要介绍 Ribbon 的一些常用配置和配置 Ribbon 的两种方式。常用配置1. 禁用 Eureka当我们在 RestTempl
- 本文实例讲述了Java使用DateFormatter格式化日期时间的方法。分享给大家供大家参考,具体如下:Java版本:1.8开始impor
- 北京时间 2018 年 3 月 1 日早上,如约发布的 Spring Boot 2.0 在同步至 Maven 仓库时出现问题,导致在 Git
- 本文实例为大家分享了java实现图片分割指定大小的具体代码,供大家参考,具体内容如下1.使用工具:ThumbnailsThumbnails
- 本文实例讲述了Android编程实现手机自带内部存储路径的获取方法。分享给大家供大家参考,具体如下:我有一台中兴的Android手机,型号是
- 关于ListBoxListBox是WinForm中的列表控件,它提供了一个项目列表(一组数据项),用户可以选择一个或者多个条目,当列表项目过
- 队列在编程语言中是如何定义的呢?小编与大家分享自己的经验。队列的定义队列是限制结点插入操作固定在一端进行,而结点的删除操作固定在另一端进行的
- 1.前言本来想拿学校机房的Android编辑器直接粘到自己电脑上用,发现它的eclipse是32位的,而我的JDK是64位的,于是想到干脆装
- 这篇文章主要介绍了Springboot整合Shiro的代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,