目录
一、继承
1.1.继承的概念
1.1.1.什么是继承?
所谓的继承,通俗的讲,就是子承父业。
就像我所提及的类,是把各个对象的共同点抽象出来,变成一个类。但是实际上,就像上面所说的狗类一样,里面的拉布拉多也是可以变成一个类的,所以,我们实际上可以把拉布拉多、哈士奇这些类中的共同性的东西在抽取出来,变成一个类,然后让拉布拉多等类继承这个共性的类,这样,可以省略很多重复性质的代码。
1.1.2.为什么要有继承?
提高代码的复用性,省略编写重复性代码的行为。
1.2.继承的使用
1.2.1.怎么使用继承
语法格式: class A extends B {}
被继承的类: 父类、基类、超类
继承父类的类: 子类、派生类
1.2.2.注意事项
1.子类一旦继承父类,有权使用父类的非静态内容
2.在Java中一个class只允许继承一个class,不允许同时承多个类(单继承)
3.因为是单继承,所以,Java中的继承对代码复用性的提升并不高,我们推荐使用依赖。
4.当我们使用继承的时候,我们可以让父类再继承一个类,这样就可以变成伪多继承了。
5.但我们使用伪多继承的时候,一般最多不超过三层继承。
1.2.3.单继承的优缺点
优点
简单,不够灵活
缺点
不便于后期维护
1.2.4.面向对象的设计原则之一
1.开闭原则:对修改关闭;对扩展开放
子类中可以扩展子类中独有的内容
1.3.Object类
当一个类没有规定继承的情况下,所有的类都默认继承了Object类(超类、基类)。
我们可以类比成中国人都是炎黄子孙。所以Object类是所有类的父类
1.3.1.Object类的方法
clone():克隆模式 一般可实现Cloneable结合使用
finalize():GC垃圾回收器回收时需要调用的方法
hashcode():获取当前对象的hash值
getClass():获取某个指定类的对应的Class对象
toString():获取当前对象的内容【常用】
equals():比较两个对象是否相等【常用】
wait、notify、notifyAll:在多线程中使用
1.4.super
1.4.1.子类的实例化
创建子类对象:先创建父类对象,在创建子类对象。
在这样创建的父类对象在内存中可以说是包含在子类对象所属内存空间中的,因为它没有一个确切的对象名来指向自身。
本质上,当我们一个类继承了另外一个类的时候,我们创建这个类的实例化对象的时候,就会先创建父类对象,再创建子类对象。
1.4.2.this和super
super类似于this,区别在于,this中保存的是自身的内存地址;但是super中保存的是对象自身实例化的时候依据父类开辟的内存空间地址。
this:指代当前new的对象
super:指代父类对象
1.4.3.super.的使用
当父类,子类出现了同名方法的时候,我们就需要使用super.来特指调用父类的方法。
不可省略
避免(父类和字类出现了同名变量和方法)
可省略
剩下的情况都是可以省略的(省略的位置既可以是this又可以是super)
1.4.4.super()的使用
super()的使用和this()的使用大同小异,使用方式相同,只是指向的引用不同
创建子类对象的时候,默认会在子类的构造器中的第一行存在super()调用父类的构造器,子类在被创建的时候必须要保证父类被加载。
1.4.5.注意事项
1.在构造器中,this()和super()只能存在一个。这是因为super()和this()都需要写到构造器的第一行,有冲突。
2.当一个构造器中,存在了this()的时候,那么这个构造器中的super()就不存在了,不会被默认调用。
也就是说,当我们出现this()和super()需要同时存在的时候,省略super
3.在静态方法中,不能使用this
这是因为,静态方法是在类被加载的时候加载,而我们的this和super则是在创建对象的时候才会执行。因此,在类加载的时候,会发现,this和super找不到。
1.5.继承的内存结构
在内存中,当我们执行到某一个实例化代码的时候,会直接先通过本身的class类中定义的成员变量开辟空间并初始化;之后发现继承的时候,会根据自己父类的成员变量开辟一块新的空间并进行初始化。
这个变量在开辟空间的时候,会定义一个this指向自己的内存地址;同时也会定义一个super指向自己依据父类开辟的内存空间。
至于两者的方法,则是保存在方法区中,有且只有一份。
1.5.1.方法区和永久区的区别
永久区是一个概念,是我们将永久区的概念实现了之后,取名为方法区。
1.6.区分同名总结
当我们的局部、子类中、父类中有同名变量或方法的时候
2.6.1.默认情况
就近原则,现在局部中寻找,找不到就在所属的子类中寻找,最后再去父类中寻找。
1.6.2.需要特定指定
找子类:this.子类实例变量/实例方法(没有同名则可以省略)
当我们具有同名问题的时候,如果我们需要使用子类中的方法,此时,我们就可以通过this.来指定我们要使用的是子类的方法;
找父类:super.父类的实例成员/实例方法(没有同名则可以省略)
如果我们要使用父类的方法,我们就需要通过super.来跳过子类寻找,直接找到父类的该方法。
1.6.重写
当我们的父类中定义的方法不满足字类的需求,比如,猫猫和狗狗都会叫,但是两者的叫声不同,对于父类而言,他只定义了猫猫跟狗狗叫,却没有明确的表明,猫猫是喵喵叫;狗狗是汪汪叫,这就造成了父类有方法,但是不满足字类需求的情况发生。
所以,对于这种情况而言,我们可以通过在字类中重写方法来解决。
1.6.1.判断重写
判断一个方法是否被重写
方式一:我们可以通过编译器左边的符号进行判断,重写方法的左侧符号是一个字母o加上一个向上的箭头。
方式二:对于重写的方法,我们需要在方法上方写一行代码@Override
1.6.2.重写的特征
== 方法签名必须一致
<= 返回值的类型
若返回值的类型为基本数据类型或void,必须保持一致
若返回值的数据类型为引用数据类型,则要求子类中的返回值类型必须小于或等于父类中被重写方法的返回值类型
>= 权限修饰符
子类中的权限修饰符一定要大于或等于父类中的修饰符
1.必须要发生继承关系
2.必须保证同名
3.实在不清楚自己的编写是否产生了重名的问题,我们可以在子类的方法上面编写代码@Override来验证,不报错,则说明是重写方法。
4.简单的理解,我们可以把重写理解为屏蔽
5.当然了,我们也可以使用父类的方法,此时就需要使用super.
1.6.3.不能被重写的方法
1.被private修饰符修饰的方法不能被重写,可以被继承,但是无权访问
2.被final修饰符修饰的方法不能被重写
3.被static修饰符修饰的方法不能被重写,但是我们可以在子类中编写一个方法名相同的方法,只要这个方法也是静态的,即可。
这是因为静态的方法是归属于自己的,只要我们编写的方法是静态的,那么这个静态方法本来就只归属于这个类,编译器就不会把这个同名的类当作被重写的方法了
1.6.4.重写toString
当我们打印输出一个对象的时候,默认情况下,会打印该对象的toString方法,如果该对象中有toString方法,就直接调用该方法;如果没有就会去父类中寻找,如果还是没有,则会去父类的父类中寻找,直到找到最终的父类,也就是Object,如果Object都没有该方法,则会报错。
object中有该方法,该方法的作用是打印输出(当前类的全限定名 + @ +哈希code值对应的十六进制数)。
而我们一般希望我们调用toString方法的时候,我们一般以往可以展示该对象中的所有信息,所以我们可以利用重写来实现。
创建一个Animal类,在这个类中有一些属性,type、name、weight,添加一个带参构造器,在主方法中创建一个对象,调用toString方法。
public class Animal {
//字段
pubilc String type;
pubilc String name;
pubilc float weight;
//空构造
public Animal () {
}
//带参数构造
public Animal(String type, String name, float weight) {
this.type = type;
this.name = name;
this.weight = weight;
}
//主方法
public static void main(String[] args) {
//创建实例
Animal an1 = new Animal("Dog", "哈士奇", 1.25F);
System.out.println(an1.toString());
}
}
使用Object中的toString方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
运行结果为com.yjxxt.lesson05rewrite.Animal@776ec8df
对toString进行重写之后,调用toString方法
public String toString() {
return "type:" + type + "---->name:" + name + "---->weight:" + weight;
}
运行结果为type:Dog---->name:哈士奇---->weight:1.25
1.6.5.重写equals方法
== 用来比较是否相同
基础数据类型,比较数据值是否相等
引用数据类型,比较的是引用的数据类型的地址值是否相等
equals方法 用来比较两个对象是否相等的。
public boolean equals(Object obj) {
return (this == obj);
}
我们可以发现在Object中的equals方法本质上还是按照==的比较来得出结果的。
但是我们对于引用数据类型,按照人的思维来讲,当应用数据类型中保存的内容是一致的,那么我们就认为这两个引用数据类型的数据是相同的,而在计算机中,只要内存地址不一样,那就是不同的数据。
我们也可以通过方法重写来解决这个问题。
public boolean equals(Object obj) {
//判断两者的内存地址,当内存地址相同时,必然为相同的,直接返回true,不需要再判断对象中保存的内容了
if (this == obj) {
return true;
}
//判断传入的类型和调用该方法的类型是否一致,这里以Animal类举例
if (obj instanceof Animal) {
//当类型一致的时候,本质上我们传入的值是多态写法,类型为Object,此时强转是完全可以保证数据不发生丢失的
Animal aniObj = (Animal) obj;
//对里面的内容进行比较
if ((this.type).equals(aniObj.type) && (this.name).equals(aniObj.name) && this.weight == aniObj.weight) {
return true;
}
}
return false;
}
1.6.6.注意
定义JavaBean的时候需要从写toString和equals
1.7.疑惑
1.7.1.为什么父类要写这样无用代码
父类中实际上是我们编写的子类的共同特征的总结,也就是说,满足这个特征的才可以被称为是该父类的子类,就像哈士奇不可能是猫类的子类一样,即使都有毛发,都会叫。
也可以说,父类就是对子类的一些约束。可能有一些无用的方法,但是只有有这个方法的,才是该类的子类