【java】抽象类和接口(了解,进阶,到全部掌握)

郑州吴彦祖772 2024-10-25 12:35:02 阅读 73

各位看官早安午安晚安呀

如果您觉得这篇文章对您有帮助的话

欢迎您一键三连,小编尽全力做到更好

欢迎您分享给更多人哦

大家好我们今天来学习Java面向对象的的抽象类和接口,我们大家庭已经来啦~

一:抽象类

1.1:抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的

如果

一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类

。 比如

在打印图形例子中

,

我们发现

,

父类

Shape

中的

draw

方法好像并没有什么实际工作

,

主要的绘制图形都是由

Shape的各种子类的 draw

方法来完成的

.

像这种没有实际工作的方法

,

我们可以把它设计成一个

抽象方

(abstract

method)

,

包含抽象方法的类我们称为

抽象类(abstract class)

1.2:抽象类语法

//

抽象类:被

abstract修饰的类,

抽象类也是类,也可以增加普通方法和属性

public abstract class

Shape

{

public int a;

// 抽象方法:被abstract修饰的方法,没有方法体

abstract public

void

draw

();

}

1.3 抽象类特性

1. 抽象类不能直接实例化对象

2. 抽象方法不能被 private,final,static修饰,因为抽象方法要被子类重写;private(只能在自己的类里面使用,密封方法) ,final(不能被继承)和static(静态方法,不依赖对象)

3:抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰(子类的子类要重写你俩的抽象方法(出来混,迟早都要还的))

4:被重写的方法不能比父类的访问权限更低(这是重写的要求)

4.

抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类

5.

抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

第三条我说明一下:

<code>abstract class Shape{

public int a;

public abstract void draw();

}

abstract class A extends Shape{

public abstract void test();

}

class B extends A{

@Override

public void test() {

}

@Override

public void draw() {

}

}

只单独一个抽象方法也可以:(不过这继承毫无意义呀)

abstract class A extends Shape{

}

1.4:抽象类的作用:

(抽象类就是为了被继承,多了一层校验)

抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类. 然后让子类重写抽象类中的抽象方法.

有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?

确实如此. 但是使用抽象类相当于多了一重编译器的校验

使用抽象类的场景就如上面的代码

,

实际工作不应该由父类完成

,

而应由子类完成

.

那么此时如果不小心误用成父类 了,

使用普通类编译器是不会报错的

.

但是父类是抽象类就会在实例化的时候提示错误

,

让我们尽早发现问题

.

很多语法存在的意义都是为了 "预防出错", 例如我们曾经用过的 final 也是类似

.

创建的变量用户不去修改

,

不就相当于常量嘛?

但是加上

final

能够在不小心误修改的时候

,

让编译器及时提醒我们

.

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

二:接口

2.1 接口的概念

(抽象类是特别类,接口是特殊的抽象类(限制更多)(就是利用向上转型,向下转型,动态绑定))

在现实生活中,接口的例子比比皆是,比如:笔记本上的

USB

口,电源插座等。

2.2:接口的语法

接口的定义格式与定义类的格式基本相同,将

class

关键字换成

interface

关键字,就定义了一个接口

public interface

接口名称

{

// 抽象方法

public abstract

void

method1

();

// public abstract

是固定搭配,可以不写(一般都不写,但是默认有)

成员变量默认被public static final 修饰

public

void

method2

();

abstract

void

method3

();

这两种真不好

void

method4

();

// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁

}

2.3 接口使用

注意:子类和父类之间是

extends

继承关系,类与接口之间是

implements

实现关系。

我们一般说实现接口,继承父类

接口不能直接使用,必须要有一个

"

实现类

"

"

实现

"

该接口,实现接口中的所有抽象方法

public class

类名称

implements

接口名称

{

// ...

}

接下来我们利用接口实现一个操作:

请实现笔记本电脑使用

USB

鼠标、

USB

键盘的例子

1. USB

接口:包含打开设备、关闭设备功能

2.

笔记本类:包含开机功能、关机功能、使用

USB

设备功能

3.

鼠标类:实现

USB

接口,并具备点击功能

4.

键盘类:实现

USB

接口,并具备输入功能

// USB接口

//

interface USB {

void openDevice();//默认被 public abstract修饰

void closeDevice();

}

// 鼠标类,实现USB接口

class Mouse implements USB {

@Override

public void openDevice() {

System.out.println("打开鼠标");

}

@Override

public void closeDevice() {

System.out.println("关闭鼠标");

}

public void click(){

System.out.println("鼠标点击");

}

}

// 键盘类,实现USB接口

class KeyBoard implements USB {

@Override

public void openDevice() {

System.out.println("打开键盘");

}

@Override

public void closeDevice() {

System.out.println("关闭键盘");

}

public void inPut(){

System.out.println("键盘输入");

}

}

// 笔记本类:使用USB设备

class Computer {

public void powerOn(){

System.out.println("打开笔记本电脑");

}

public void powerOff(){

System.out.println("关闭笔记本电脑");

}

public void useDevice(USB usb){//传鼠标或者键盘的引用,向上转型

usb.openDevice();

if(usb instanceof Mouse){//

Mouse mouse = (Mouse)usb;//向下转型

mouse.click();//实现鼠标特有的功能

}else if(usb instanceof KeyBoard){

KeyBoard keyBoard = (KeyBoard)usb;//向下转型

keyBoard.inPut();//实现键盘特有的功能

}

usb.closeDevice();

}

}

// 测试类:

public class TestUSB {

public static void main(String[] args) {

Computer computer = new Computer();

computer.powerOn();//打开笔记本电脑

// 使用鼠标设备

computer.useDevice(new Mouse());//

// 使用键盘设备

computer.useDevice(new KeyBoard());

computer.powerOff();//关闭笔记本电脑

}

}

2.4.接口特点:

抽象类前4条她都有(但是接口有更多的特点,不然怎么是特殊的抽象类呢(小编自己说的))

前四条(差不多)

1.

接口类型是一种引用类型,但是不能直接

new

接口的对象(不能实例化对象)

2.

如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类(和抽象类差不多)

3:

 抽象方法不能被 private,final,static修饰

4:.

重写接口中方法时,不能使用默认的访问权限(不能比接口里面的方法的访问权限更低)

5:接口中的方法默认被public abstract 修饰,成员变量默认被 public static final 修饰(不加也默认),其他修饰符会报错

6. 接口中不能有静态代码块和构造方法(这一点抽象类可以有这些)

7.

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

.class

USB接口

8. jdk8中:接口中还可以包含

default

方法。

2.5:实现多个接口

Java

中,类和类之间是单继承的,一个类只能有一个父类,

Java中不支持多继承,但是一个类可以实现多个接口

。下面通过类来表示一组动物

首先我定义一个动物类(就只定义变量(大家公用嘛),活动让子类通过实现接口来重写)

<code>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 Cat extends Animal implements IRunning {

public Cat(String name) {

super(name);

}

@Override

public void run() {

System.out.println(this.name + "正在用猫腿跑");

}

}

鱼, 是会游的.

class Fish extends Animal implements ISwimming {

public Fish(String name) {

super(name);

}

@Override

public void swim() {

System.out.println(this.name + "正在用尾巴游泳");

}

}

青蛙还是两栖的呢:既会跑又会游泳(那就实现两个接口)

class Frog extends Animal implements IRunning, ISwimming {

public Frog(String name) {

super(name);

}

@Override

public void run() {

System.out.println(this.name + "正在蛙跳");

}

@Override

public void swim() {

System.out.println(this.name + "正在蹬腿游泳");

}

}

鸭子:会跑,会游泳,会飞(实现三个接口)

class Duck extends Animal implements IRunning, ISwimming, IFlying {

public Duck(String name) {

super(name);

}

@Override

public void fly() {

System.out.println(this.name + "正在用鸭翅膀飞");

}

@Override

public void run() {

System.out.println(this.name + "正在用鸭腿跑");

}

@Override

public void swim() {

System.out.println(this.name + "正在漂在水上");

}

}

上面的代码展示了

Java

面向对象编程中最常见的用法

:

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

超级重要!!!!!!

接下来就是向上转型

(只要是你实现了这个IRunning接口,就可以被IRunning引用你的对象,你只需要把你的引用传过来,就发生了向上转型)

然后我传过去猫猫,青蛙,鸭子这些对象的引用(利用向上转型),

一个引用,传递过来的对象不同,所表现的行为不同,这就是多态的思想

public class TestInterface {

public static void running(IRunning iRunning){

iRunning.run();

}

public static void main(String[] args) {

Cat cat = new Cat("猫猫");

running(cat);

Duck duck = new Duck("鸭子");

running(duck);

Frog frog = new Frog("青蛙");

running(frog);

}

}

甚至机器人只要实现了这个接口,他也可以通过这个引用实现这个动作(只要实现了这个接口和是不是动物没关系)

<code> Robot robot = new Robot();

running(robot);

class Robot implements IRunning{

@Override

public void run(){

System.out.println("正在用机器腿跑");

}

}

这样设计有什么好处呢

?

时刻牢记多态的好处

,

让程序猿

忘记类型

.

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

2.6 接口间的继承

类与类之间的继承只能单继承,但是接口之间的继承可以多继承(相当于把多个接口合并在了一起)(一个类也能继承多个接口)

说到合并(静态代码块也是合并)

接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字

<code>interface IRunning {

void run();

}

interface ISwimming {

void swim();

}

// 两栖的动物, 既能跑, 也能游

interface IAmphibious extends IRunning, ISwimming {

}

class Frog implements IAmphibious {

...

}

2.7抽象类和接口的区别

抽象类和接口都是 Java 中多态的常见使用方式,

二者的区别前面我也讲述了一下(下面呢)

通过表格再说一下

核心区别

:

抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用

(

不必重写

)而且抽象类里面有构造方法 

而接口中不能包含普通方法,

子类必须重写所有的抽象方法,(而且成员变量都是全局常量)接口里面没有构造方法

如之前写的

Animal

例子

.

此处的

Animal

中包含一个

name

这样的属性

,

这个属性在任何子类中都是存在的

.

因此 此处的 Animal

只能作为一个抽象类

,

而不应该成为一个接口

class Animal {

protected String name;

public Animal(String name) {

this.name = name;

}

}

三:object类

Object

Java

默认提供的一个类。

Java里面除了Object

类,所有的类都是存在继承关系的。默认会继承

Object类

。即所有类的对象都可以使用Object

的引用进行接收。

例如:

所以在开发之中,

Object

类是参数的最高统一类型。但是

Object

类也存在有定义好的一些方法。如下:

我们先了解这几种方法

3.1:equals方法

首先我们看一下object类里面实现的equals方法(很显然就是两个引用在比较,就是比较两个地址是否一样)

既然是这样那么这个equals方法就和普通的比较没什么区别了(所以只要我们要用的话就只能重写这个方法(除非你就想这么用))

<code> public static void main(String[] args) {

Person person1 = new Person("zhangsan",18);

Person person2 = new Person("zhangsan",18);

Person person3 = person1;

System.out.println(person1 == person2);//其实就是两个引用在比较

//那我们换equals方法来比较呢?

System.out.println(person1.equals(person2));//false

System.out.println(person1.equals(person3));地址相等才相等(你来指向一个对象)

}

那么我们就重新写一下这个方法:

<code> @Override

public boolean equals(Object obj) {//返回true或者false

if (obj == null) {

return false ;

}

if(this == obj) {

return true ;

}

// 不是Person类对象

if (!(obj instanceof Person)) {//判断obj是否是Person的实例(obj这个引用是否指向了Person类的对象)

return false ;

}

Person person = (Person) obj ; // 向下转型,比较属性值

return this.name.equals(person.name) && this.age==person.age ;

}

}

接下来再运行一下:

显然就可以了

3.2.hascode方法:

回忆刚刚的toString方法的源码:

public

String

toString

() {

return

getClass

().

getName

()

+

"@"

+

Integer

.

toHexString

(

hashCode

());(进化过处理的地址)

}

hashCode()

这个方法,帮我们算了一个具体的

对象位置

,这里面涉及数据结构,但是我们还没学数据结构,没法讲述,所以我们只能说它是个内存地址。然后调用Integer.toHexString()

方法,将这个地址以

16

进制输出

hashcode方法源码:(该方法是一个

native

方法,底层是由

C/C++

代码写的。我们看不到)

示例:

<code> Person person1 = new Person("zhangsan",18);

Person person2 = new Person("zhangsan",18);

System.out.println(person1.hashCode());

System.out.println(person2.hashCode());

但是我们想要把两个名字相同,年龄相同的对象,存储在同一个位置,这个时候我们就要重写hashcode()方法了

结果:

总结:

hashcode方法用来确定对象在内存中存储的位置是否相同,equals方法用来确定该对象的内容是否一样(我们重写的)

四:比较两个对象的大小以及排序对象数组:

4.1:比价两个对象的大小

引用类型不能直接这么比较

public class Test{

public static void main(String[] args) {

Student student1 = new Student("zhangsan",18);

Student student2 = new Student("zhangsan",18);

//System.out.println(student1 > student2);//引用类型不能这样比较

}

}

如果要比较的话:要实现一个接口(并且重写它的比较方法)

<code>class Student implements Comparable<Student> {

String name;

int age;

public Student(String name, int age) {

this.name = name;

this.age = age;

}

@Override

public int compareTo(Student o) {

return this.age - o.age;

}

}

Student student1 = new Student("zhangsan",18);

Student student2 = new Student("zhangsan",12);

System.out.println(student1.compareTo(student2));

//结果为6

比较名字大小的时候我们直接可以用String类的CompareTo进行比较(String类已经对它进行重写了(后面在认识String类的时候我会讲到))

System.out.println(student1.name.compareTo(student2.name));

4.2:排序对象数组

我们就用数组排序的方法Arrays.sort排序

public static void main(String[] args) {

Student []students = new Student[3];

students[0] = new Student("zhangsan",18);

students[1] = new Student("wangwu",20);

students[2] = new Student("lisi",17);

Arrays.sort(students);

System.out.println(Arrays.toString(students));

}

但是编译器报错了,你要排序但是你要按照什么排序呢?年龄?还是名字?

这里我们可以看到他要把数组里边的元素类型强转为Comparable类型(所以我们就要去建立这个联系,实现这个接口)

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.

<code>package Demo1;

import java.util.Arrays;

class Student implements Comparable<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 + '\'' +code>

", age=" + age +

'}';

}

@Override

public int compareTo(Student o) {

return this.age - o.age;

}

}

public class Test {

public static void main(String[] args) {

Student[] students = new Student[3];

students[0] = new Student("zhangsan", 18);code>

students[1] = new Student("wangwu", 20);

students[2] = new Student("lisi", 17);

Arrays.sort(students);

System.out.println(Arrays.toString(students));

}

}

结果:

在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.

4.3:我们自己实现一个(模拟实现一个)冒泡排序呢

注意,单独比较年龄或者姓名之后,这个类整体会被交换(而不是单独交换年龄或者姓名)(你们一起被交换)

<code>public class Test {

public static void bubbleSort(Comparable []comparables){

for (int i = 0; i < comparables.length-1; i++) {

int flag = 1;

for (int j = 0; j < comparables.length-1-i; j++) {

//这个明显是错误的(要去全部转换啊,)(Comparable 类型没有age这个变量,这个就是一个向上转型)

/*int tmp = 0;

tmp = comparables[i].age;

comparables[i].age = comparables[i+1].age;

comparables[i+1].age = tmp;

flag = -1;*/

if( comparables[i].compareTo(comparables[i+1]) > 0){

Comparable tmp = comparables[i];

comparables[i+1] = comparables[i];

comparables[i] = tmp;

flag = -1;

}

}

if(flag ==1){

break;

}

}

}

public static void main(String[] args) {

Student[] students = new Student[3];

students[0] = new Student("zhangsan", 18);

students[1] = new Student("wangwu", 20);

students[2] = new Student("lisi", 17);

bubbleSort(students);

System.out.println(Arrays.toString(students));

/*Arrays.sort(students);

System.out.println(Arrays.toString(students));*/

}

}

或者我们根本就不用实现接口呢(其实是因为String类帮我们实现过了)

用String类的compareTo方法

<code>public class Test {

public static void bubbleSort1(Student[]students){

for (int i = 0; i <students.length-1; i++) {

int flag = 1;

for (int j = 0; j < students.length - 1 - i; j++) {

if (students[j].name.compareTo(students[j + 1].name) > 0) {

Student tmp = students[j];

students[j] = students[j + 1];

students[j + 1] = tmp;

flag = -1;

}

}

if(flag ==1){

break;

}

}

}

Comparable接口的耦合性是比较强的

当一个类实现了 Comparable 接口,它的比较逻辑就被硬编码在了类的定义中。这意味着如果需要改变比较逻辑,你必须修改这个类的源代码。这种实现方式的耦合性较强

目前我理解的是因为:Comparable的里的方法是compareTo,但是要比较名字的时候,Student实现Comparable接口,重写compareTo方法,你怎么在这个方法里面去比较字符串大小?(字符串大小比较需要String类重写的 compareTo方法去比较)

总不能compareTo里面嵌套compareTo吧?

这个时候我们就需要一个实现

另一个接口:Comparator接口(提供了更大的灵活性和解耦能力)

具体实现:

class NameComparator implements Comparator<Student>{

@Override

public int compare(Student o1, Student o2) {

return o1.name.compareTo(o2.name);

}

}

class AgeComparator implements Comparator<Student>{

@Override

public int compare(Student o1, Student o2) {

return o1.age - o2.age;

}

}

public static void main(String[] args) {

Student[] students = new Student[3];

students[0] = new Student("zhangsan", 18);

students[1] = new Student("wangwu", 20);

students[2] = new Student("lisi", 17);

AgeComparator ageComparator = new AgeComparator();

System.out.println(ageComparator.compare(students[0], students[1]));

NameComparator nameComparator = new NameComparator();

System.out.println(nameComparator.compare(students[0], students[1]));

}

}

冒泡呢?(就改了比较大小的呗,交换的步骤都不变)

<code> public static void bubbleSort2(Student[]students){

for (int i = 0; i <students.length-1; i++) {

int flag = 1;

for (int j = 0; j < students.length - 1 - i; j++) {

NameComparator nameComparator= new NameComparator();

if (nameComparator.compare(students[j],students[j+1]) > 0) {

Student tmp = students[j];

students[j] = students[j + 1];

students[j + 1] = tmp;

flag = -1;

}

}

if(flag ==1){

break;

}

}

}

五:克隆:

浅拷贝:

深拷贝:

class Money implements Cloneable{

int m;

public Money(int m) {

this.m =m ;

}

@Override

protected Object clone() throws CloneNotSupportedException {

return super.clone();

}

}

public class clonePreviousTest {

public static void main(String[] args) throws CloneNotSupportedException {

Person person1 = new Person("liHua", 18,new Money(10));

Person person2 = (Person) person1.clone();

person1.money.m = 100;

System.out.println(person1.money.m);

System.out.println(person2.money.m);

}

}

class Person implements Cloneable {

String name;

int age;

Money money;

@Override

protected Object clone() throws CloneNotSupportedException {

Person tmp = (Person) super.clone();

tmp.money = (Money) this.money.clone();

return tmp;

}

public Person(String name, int age,Money money) {

this.name = name;

this.age = age;

this.money = money;

}

@Override

public String toString() {

return "Person{" +

"name='" + name + '\'' +code>

", age=" + age +

'}';

}

}

深拷贝这里就是全部都进行了拷贝,包括我们自定义的类型的值,由于异常我们现在还没有学习,具体的克隆我们放在异常的那一节进行讲解~~~

上述就是 Java面向对象之多态的全部内容了,能看到这里相信您一定对小编的文章有了一定的认可,接口的出现,其实也是一个老大带几个小弟的过程,我们的大家庭就这么水灵灵的又增加啦~

有什么问题欢迎各位大佬指出

欢迎各位大佬评论区留言修正

您的支持就是我最大的动力​​​!!!!



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。