文章图片
希望通过博客和大家相互交流,相互学习,如有错误,请评论区指正
文章目录
- 面向对象编程
-
- 一、包
- 二、继承
-
- 举例
- 那么到底什么是继承?
-
-
- 基本语法
- 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方法),代码冗余
这几个类之间都一定的关联关系:
因为有这样的关联关系,我们就可以让 Bird 和 Dod 继承 Animal,减少代码冗余,简化代码,逻辑更清晰,并达到代码复用的效果
- 这几个类都具有name和age属性,而且意义相同
- 这几个类都有eat方法,行为相同
- Bird和Dog都属于Animal
那么到底什么是继承?
继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力比如可以先定义一个类叫车,车有以下属性:车体大小,颜色,方向盘,轮胎,而又由车这个类派生出轿车和卡车两个类,为轿车添加一个小后备箱,而为卡车添加一个大货箱。
Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类
这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
被继承的类称为基类,超类,父类,继承的类称为派生类,子类
基本语法
class 子类 extends 父类 {}
注意:
super关键字 super代表父类对象的引用(和this语法相同),有以下应用:
- 继承关键字: extends
- 单继承:一个子类只能继承一个父类
- 父类private的字段和方法子类无法访问
- 子类在构造的时候要先帮助父类进行构造
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
- 调用父类的构造方法 (必须放到第一行)
- 访问父类的属性
- 调用父类的成员方法
子类继承了父类的除了构造方法外的一切
将上面的例子写成继承的形式
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 | 不同包中的非子类 | √ |
在情况比较复杂的情况下,我们可能还需要复杂的继承来实现目的,如下:
文章图片
这样的继承方式称为多层继承, 即子类还可以进一步的再派生出新的子类.
虽然继承很方便,但我们在实际应用中类之间的继承方式不能太过复杂,否则代码可读性就会降低,一般不希望出现超过三层的继承关系
可以用final来修饰类,此时这个类就不能被继承了
三、多态 向上转型
向上转型就是将子类对象赋值给父类引用, (父类引用 引用 子类对象)如下图所示:
文章图片
Dog dog = new huntaway();
// 父类引用 引用 子类对象
注意:
通过父类的引用只能访问父类自己的方法或属性发生向上转型的时机
- 直接赋值
- 传参
- 返回值返回
当父类引用 引用 子类对象,并且子类对象中有和父类同名的方法,当用父类引用去调用同名构造方法的时候就会发生运行时绑定,用到的是子类中的同名方法
为什么叫运行时绑定?
我们通过以下代码来看看
// 三段代码在三个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();
}
}
运行结果
文章图片
通过反编译看看
javac Demo.java// 编译
javap -c Demo// 反编译Java代码
反编译结果
文章图片
我们会发现在编译时第二次调用eat()的时候仍然调用的是Animal类中的方法,但是执行出来的结果却是执行Dog类中的eat()产生的结果,这就是因为发生了运行时绑定
注意:
- 编译器检查有哪些方法,看的是Animal这个类型
- 执行时,究竟执行的是父类方法还是子类方法,看的是Dog类型
- 构造方法中也会发生运行时绑定(当创建子类对象调用子类构造方法时,会先调用父类的构造方法,而如果父类的构造方法中又调用了父类和子类中同名的方法,就会发生运行时绑定,实际执行的是子类中的重写方法)
刚刚上面的eat方法其实就是重写
子类实现父类的同名方法,并且参数的类型和个数完全相同这种情况就称为重写/覆写/覆盖(Override)
重写方法需要注意:
- 方法名相同
- 参数列表相同
- 返回值类型相同
- 父类有同名的方法
向下转型
- 需要被重写的方法不能被final修饰,被final修饰的方法称为密封方法,不可修改
- 被重写的方法,访问限定符不能是private
- 被重写的方法和重写的方法的访问限定符可以不一样,子类当中重写的方法的访问权限要 >= 父类中被重写的方法的访问权限
- 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();
}
}
运行结果:
文章图片
这就是向下转型
但是我们应该要注意,向下转型是非常不安全的,通常不建议使用
因为有可能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();
}
}
运行结果:
文章图片
虽然调用的都是eat,但是却不是同一个eat
四、抽象类 在实际开发中,如果父类中的方法并没有什么实际的执行逻辑,而是希望用到子类中同名的重写方法来完成需求,那么我们就可以把它设计成一个抽象方法(abstract method),包含抽象方法的类称为抽象类(abstract class).
如下:
abstract class parent {
public abstract void func();
}
- 抽象方法没有方法体,不执行具体代码
- 包含抽象方法的类,就得用abstract修饰,称为抽象类
- 抽象类不能被实例化
- 抽象类中的数据成员和普通类没有区别(除了多一个抽象方法外,与其他类基本没有区别,也可以包含字段,非抽象方法)
- 抽象类主要就是用来被继承的
- 如果一个类继承了抽象类,那么这个类就必须要重写抽象类中的抽象方法
- 抽象类 A 继承 了抽象类B,那么A可以不重写B中的方法,但A如果再被继承,继承A的那个类还是要重写这个抽象方法的
- 抽象类或抽象方法不能被final修饰
- 抽象类不能被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));
}
}
运行结果:
文章图片
在这个过程中,通过arr2来修改第一个下标的值,对arr里面的数值并未产生影响,可以看到通过clone()方法也实现了对数组的拷贝,但其实clone()方法是一个浅拷贝
浅拷贝 例如arr里面放的是引用类型
文章图片
如果arr里面放的是引用类型的话,那么通过clone()方法只拷贝了对象的引用,并未拷贝对象,造成藕断丝连的情况,只是浅拷贝
而我们想要达到的效果应该是下图这样的深拷贝
深拷贝
文章图片
如果想用clone()方法克隆自定义类型,就需要实现接口
实现接口
但是当我们去实现Cloneable接口时,却发现Cloneable接口是一个空接口
文章图片
为什么是空接口 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】面向对象编程必备技能,你学会了吗(继承、多态、抽象类、接口详解)】--------------原创不易,请三连支持-----------------
推荐阅读
- 01|小饶学编程之JAVA SE第一部分——面向对象(10抽象类和接口)
- JavaSE|JavaSE——面向对象(五)(抽象类,接口)
- 团队vue基础镜像选择思考
- 网络安全|开源世界的第一信息安全系统—OpenSSL
- 敖丙大佬的《吐血整理》-顶级程序员书单集 JAVA
- http|HTTP 流量拷贝测试神器 GoReplay
- mvnd打包快到飞起
- 基础算法
- CS 2113 Engineering