JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)

JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

希望通过博客和大家相互交流,相互学习,如有错误,请评论区指正

文章目录

  • 面向对象编程
    • 一、包
    • 二、继承
      • 举例
      • 那么到底什么是继承?
          • 基本语法
          • super关键字
      • 子类到底继承了父类的什么?
      • 将上面的例子写成继承的形式
      • 访问限定修饰符
      • 复杂的继承
    • 三、多态
      • 向上转型
        • 发生向上转型的时机
      • 动态绑定 / 运行时绑定
      • 方法的重写
      • 向下转型
      • 多态
    • 四、抽象类
    • 五、接口
      • 详解
      • cloneable接口和深拷贝
        • 浅拷贝
        • 深拷贝
      • 实现接口
        • 为什么是空接口

面向对象编程 一、包 在一个文件夹中不能创建两个文件名相同的文件
当一个大型程序交由数个不同的程序开发人员进行开发时,用到相同的类名是很有可能的,在java程序开发中为了避免上述事件,提供了一个包的概念(package),使用方法很简单,只需要在写的程序第一行使用 package 关键字来声明一个包。
包是组织类的一种方式,使用包就是为了保证类的唯一性
之前我们在用Scanner时就需要导java.util.Scanner这个包,用import关键字(导入系统包),那么我们在导自己的包的时候就用的是package
java.lang这个包下的所有东西都不需要进行手动导入
OOP语言三大特征:继承,封装,多态
二、继承 Java中使用了类就是为了将现实中的一些事务进行抽象,有时现实中的一些事务直间存在着一些关联,那么我们在创建类的时候就可以让他们有关联
举例
比如动物,狗,小鸟, 现在创建他们的类:
在三个不同的java文件中
public class Animal { public String name; public int age; public void eat() { System.out.println("Animal->eat"); } }

public class Bird { public String name; public int age; public void eat() { System.out.println("Bird->eat"); } public void fly() { System.out.println("Bird->fly"); } }

public class Dog { public String name; public int age; public void eat() { System.out.println("Dog->eat"); } public void run() { System.out.println("Dog->run"); } }

我们会发现它们有部分共同的属性:name,age,当然也有一部分他们各自所特有的属性,相同的属性如果每个类都去定义一遍就感觉有好多部分都在重复,里面的类似的方法也在重复(eat方法),代码冗余
这几个类之间都一定的关联关系:
  1. 这几个类都具有name和age属性,而且意义相同
  2. 这几个类都有eat方法,行为相同
  3. Bird和Dog都属于Animal
因为有这样的关联关系,我们就可以让 Bird 和 Dod 继承 Animal,减少代码冗余,简化代码,逻辑更清晰,并达到代码复用的效果
那么到底什么是继承?
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力
Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类
这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
比如可以先定义一个类叫车,车有以下属性:车体大小,颜色,方向盘,轮胎,而又由车这个类派生出轿车和卡车两个类,为轿车添加一个小后备箱,而为卡车添加一个大货箱。
被继承的类称为基类,超类,父类,继承的类称为派生类,子类
基本语法
class 子类 extends 父类 {}

注意:
  • 继承关键字: extends
  • 单继承:一个子类只能继承一个父类
  • 父类private的字段和方法子类无法访问
  • 子类在构造的时候要先帮助父类进行构造
  • 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
super关键字 super代表父类对象的引用(和this语法相同),有以下应用:
  • 调用父类的构造方法 (必须放到第一行)
  • 访问父类的属性
  • 调用父类的成员方法
子类到底继承了父类的什么?
子类继承了父类的除了构造方法外的一切
将上面的例子写成继承的形式
public class Animal { public String name; public int age; public void eat() { System.out.println("Animal->eat"); } }

public class Bird extends Animal{ public void fly() { System.out.println("Bird->fly"); } }

public class Dog extends Animal { public void run() { System.out.println("Dog->run"); } }

这样继承之后,Bird里面虽然没写name和age,以及eat方法,但是因为它继承了父类Animal,所以它也具有父类的属性,也有父类的eat,Dog同理
访问限定修饰符
范围 private default(包访问权限) protected public
1 同一个包中的同一个类
2 同一个包中不同类
3 不同包中的子类
4 不同包中的非子类
复杂的继承
在情况比较复杂的情况下,我们可能还需要复杂的继承来实现目的,如下:
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

这样的继承方式称为多层继承, 即子类还可以进一步的再派生出新的子类.
虽然继承很方便,但我们在实际应用中类之间的继承方式不能太过复杂,否则代码可读性就会降低,一般不希望出现超过三层的继承关系
可以用final来修饰类,此时这个类就不能被继承了
三、多态 向上转型
向上转型就是将子类对象赋值给父类引用, (父类引用 引用 子类对象)
如下图所示:
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

Dog dog = new huntaway(); // 父类引用 引用 子类对象

注意:
通过父类的引用只能访问父类自己的方法或属性
发生向上转型的时机
  1. 直接赋值
  2. 传参
  3. 返回值返回
动态绑定 / 运行时绑定
当父类引用 引用 子类对象,并且子类对象中有和父类同名的方法,当用父类引用去调用同名构造方法的时候就会发生运行时绑定,用到的是子类中的同名方法
为什么叫运行时绑定?
我们通过以下代码来看看
// 三段代码在三个java文件中
public class Animal { public String name; public int age; public Animal(String name) { this.name = name; System.out.println(this.name); } public void eat() { System.out.println("Animal->eat"); } }

public class Dog extends Animal{ public int aaa; public Dog(String name) { super(name); } public void eat() { System.out.println(this.name + "->eat"); } }

public class Demo { public static void main(String[] args) { Animal dd = new Animal("大大"); dd.eat(); Animal xx = new Dog("小小"); xx.eat(); } }

运行结果
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

通过反编译看看
javac Demo.java// 编译 javap -c Demo// 反编译Java代码

反编译结果
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

我们会发现在编译时第二次调用eat()的时候仍然调用的是Animal类中的方法,但是执行出来的结果却是执行Dog类中的eat()产生的结果,这就是因为发生了运行时绑定
注意:
  • 编译器检查有哪些方法,看的是Animal这个类型
  • 执行时,究竟执行的是父类方法还是子类方法,看的是Dog类型
  • 构造方法中也会发生运行时绑定(当创建子类对象调用子类构造方法时,会先调用父类的构造方法,而如果父类的构造方法中又调用了父类和子类中同名的方法,就会发生运行时绑定,实际执行的是子类中的重写方法)
方法的重写
刚刚上面的eat方法其实就是重写
子类实现父类的同名方法,并且参数的类型和个数完全相同这种情况就称为重写/覆写/覆盖(Override)
  1. 方法名相同
  2. 参数列表相同
  3. 返回值类型相同
  4. 父类有同名的方法
重写方法需要注意:
  1. 需要被重写的方法不能被final修饰,被final修饰的方法称为密封方法,不可修改
  2. 被重写的方法,访问限定符不能是private
  3. 被重写的方法和重写的方法的访问限定符可以不一样,子类当中重写的方法的访问权限要 >= 父类中被重写的方法的访问权限
  4. static修饰的静态方法不能被重写
向下转型
向下转型就是子类引用 引用 父类对象
如下代码示例:
public class Animal { public String name; public int age; public Animal(String name) { this.name = name; System.out.println(this.name); } public void eat() { System.out.println("Animal->eat"); } }

public class Dog extends Animal{ public int aaa; public Dog(String name) { super(name); } public void eat() { System.out.println(this.name + "->eat"); } public void run() { System.out.println("dog->run"); } }

public class Demo { public static void main(String[] args) { Animal dd = new Dog("小小"); dd.eat(); Dog dog = (Dog)dd; dog.run(); } }

运行结果:
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

这就是向下转型
但是我们应该要注意,向下转型是非常不安全的,通常不建议使用
因为有可能Animal本身引用的是Dog类型的对象,结果想转成cow类型,这就有问题了,Dog类型是不能转成cow类型的,我们再想将Animal转回去的话就应该先判断一下Animal本质上是不是cow类型,再决定要不要转
可以用运算符instanceof来判断该引用是否为某个类的实例化对象
Animal animal = new Dog("小小"); if (animal instanceof Cow) { Cow cow = (Cow)animal; }

如果类型不匹配的话就会抛出类型转换异常ClassCastException
多态
多态就是当父类引用 引用 子类对象,并且父类和子类中有同名的构造方法,那么两个父类引用去调用同名构造方法时,产生不同的效果
如下代码示例:
public class Animal { public String name; public int age; public void eat() { System.out.println("Animal->eat"); } }

public class Dog extends Animal{ public int aaa; public void eat() { System.out.println("dog->eat"); } }

public class Bird extends Animal{ public void eat() { System.out.println("bird->eat"); } }

public class Demo { public static void main(String[] args) { Animal animal = new Dog(); Animal animal1 = new Bird(); animal.eat(); animal1.eat(); } }

运行结果:
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

虽然调用的都是eat,但是却不是同一个eat
四、抽象类 在实际开发中,如果父类中的方法并没有什么实际的执行逻辑,而是希望用到子类中同名的重写方法来完成需求,那么我们就可以把它设计成一个抽象方法(abstract method),包含抽象方法的类称为抽象类(abstract class).
如下:
abstract class parent { public abstract void func(); }

  • 抽象方法没有方法体,不执行具体代码
  • 包含抽象方法的类,就得用abstract修饰,称为抽象类
注意:
  1. 抽象类不能被实例化
  2. 抽象类中的数据成员和普通类没有区别(除了多一个抽象方法外,与其他类基本没有区别,也可以包含字段,非抽象方法)
  3. 抽象类主要就是用来被继承的
  4. 如果一个类继承了抽象类,那么这个类就必须要重写抽象类中的抽象方法
  5. 抽象类 A 继承 了抽象类B,那么A可以不重写B中的方法,但A如果再被继承,继承A的那个类还是要重写这个抽象方法的
  6. 抽象类或抽象方法不能被final修饰
  7. 抽象类不能被private修饰
五、接口 详解
接口(interface)比抽象类更抽象,是抽象类的更进一步. 抽象类中还可以包含非抽象方法和字段. 而接口中包含的方法都是抽象方法, 字段只能包含静态常量.
如下:
interface Animal { public static final int a = 10; public abstract void func(); }

这里的public static final 和 public abstract 只是为了说明问题,实际开发中建议省略掉
  • 接口用关键字interface来修饰
  • 接口不能被实例化(为了解决多继承问题)
  • 接口中的方法都是抽象方法(可以省略abstract), 并且这些方法都是public的(public可以省略)
interface Animal { int a = 5; void func(); }

  • 接口当中其实也可以有具体实现的方法,这个方法是被default修饰的 JDK1.8加入
interface Animal { default void func() { System.out.println("具体实现的方法"); } }

  • 接口中的成员变量默认是public static final,接口中的方法public abstract
  • 一个类继承一个接口,此时不是“扩展”,而是“实现”(implements)
public class Dog implements Animal {}

接口的含义其实就是让其具有某种特性
cloneable接口和深拷贝
首先来看看之前在拷贝数组时用到的clone()方法
import java.util.Arrays; public class CloneableDemo { public static void main(String[] args) { int[] arr = {1,2,3,4,5,6}; int[] arr2 = arr.clone(); arr2[0] = 666; System.out.println(Arrays.toString(arr)); System.out.println(Arrays.toString(arr2)); } }

运行结果:
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

在这个过程中,通过arr2来修改第一个下标的值,对arr里面的数值并未产生影响,可以看到通过clone()方法也实现了对数组的拷贝,但其实clone()方法是一个浅拷贝
浅拷贝 例如arr里面放的是引用类型
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

如果arr里面放的是引用类型的话,那么通过clone()方法只拷贝了对象的引用,并未拷贝对象,造成藕断丝连的情况,只是浅拷贝
而我们想要达到的效果应该是下图这样的深拷贝
深拷贝 JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

如果想用clone()方法克隆自定义类型,就需要实现接口
实现接口
但是当我们去实现Cloneable接口时,却发现Cloneable接口是一个空接口
JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)
文章图片

为什么是空接口 Cloneable接口在源码当中是没有抽象方法的
空接口:也把它叫做标记接口,只要一个类实现了这个接口,那么就标记这个类是可以进行clone的,然后在这个类中重写clone方法就行了
public class Person implements Cloneable { public String name; public int age; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }

欢迎大家关注!!!
一起学习交流 !!!
让我们将编程进行到底!!!
【JavaSE|【JavaSE】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)】--------------原创不易,请三连支持-----------------

    推荐阅读