Java学习笔记 第十天
第一章 继承
1.1 概述:
当多个类中存在相同属性和行为时,可将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。其中,多个类可以称为子类,单独那一个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:的关系。例如,图中兔子属于食草动物,食草动物属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的定义: 就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。主要用于解决共性抽取。
1.2 继承的格式
在继承的关系当中,“子类就是一个父类”。也就是说,子类可以被当做父类看待
例如:父类是员工,子类是讲师,那么**“讲师就是一个员工”**。关系:is-a。
通过extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:
// 定义父类的格式:(一个普通的类定义)
public class 父类{
...
}
// 定义子类的格式:
public class 子类 extends 父类{
...
}
代码演示如下:
// 定义一个员工:父类
public class Employee {
public void method(){
System.out.println("方法执行!");
}
}
// 定义了一个员工的子类:讲师
public class Teacher extends Employee {
}
// 定义了员工的另一个子类:助教
public class Assistant extends Employee{
}
public class Demo01Extends {
public static void main(String[] args) {
// 创建了一个子类对象
Teacher teacher = new Teacher();
// Teacher类当中虽然什么都没写,但是会继承来自父类的method方法
teacher.method();
// 创建另一个子类助教的对象
Assistant assistant = new Assistant();
// Assistant类当中虽然什么都没写,但是也会继承来自父类的method方法
assistant.method();
}
}
1.3 继承后的特点—成员变量
- 成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的。代码如下:
// 定义父类
class Fu{
int numFu = 5;
}
// 定义子类
class Zi extends Fu{
int numZi = 10;
public void show(){
// 继承而来且不重名,故可直接访问
System.out.println("父类当中的numFu = " + numFu);
System.out.println("子类当中的numZi = " + numZi);
}
}
// 定义测试类
class ExtendDemo02{
public void main(String[] args){
// 创建子类对象
Zi zi = new Zi();
// 调用子类中的show方法
zi.show();
}
}
演示结果:
父类当中的numFu = 5
子类当中的numZi = 10
- 成员变量重名
如果子类父类中出现重名的成员变量,这时的访问是有影响的(按照就近原则选取成员变量,如果没有就向上查找,而绝不会向下查找)。详细来说,此时创建子类对象访问重名的成员变量时,有以下两种访问方式:
(1).直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找。
(2).间接通过成员方法访问成员变量:
该方法属于谁,就优先用谁,没有则向上查找
// 定义父类
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
// 使用的是本类当中的,不会向下找子类的
System.out.println(num);
}
}
// 定义子类
public class Zi extends Fu{
int numZi = 20;
int num = 200;
public void methodZi(){
// 因为本类当中有num,所以这里用的是本类的num,如果没有num,才会向上查找num
System.out.println(num);
}
}
// 定义测试类
public class Demo01ExtendsField {
public static void main(String[] args) {
// 创建一个父类对象
Fu fu = new Fu();
System.out.println(fu.numFu); // 10 只能使用父类的东西,没有任何子类的内容
System.out.println("==========================");
// 创建一个子类对象
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
System.out.println("==========================");
System.out.println(zi.num); // 优先子类,200
// System.out.println(zi.abc); // 到处都没有,编译报错!
// 这个方法是子类的,优先用子类的,如果没有,再向上查找
zi.methodZi(); // 200
// 这个方法是在父类当中的
zi.methodFu(); // 100
}
}
子父类中出现了同名的成员变量时,在子类中需要访问父类中非私有成员变量时,需要使用super关键字,修饰父类成员变量,类似于之前学过的this。
使用格式:
super.父类成员变量名
- 区分子类方法中重名的三种变量
局部变量: 直接写局部变量名
成员变量: this.成员变量名
父类的成员变量: super.成员变量名
// 定义父类
public class Fu {
int num = 10;
int num2 = 100;
}
// 定义子类
public class Zi extends Fu {
int num = 20;
int num3 = 300;
public void method(){
// int num = 30;
System.out.println(num); // 30 局部变量
System.out.println(this.num); // 20 本类的成员变量
System.out.println(super.num); // 10 父类的成员变量
System.out.println(super.num2); // 100 即使不重名,访问父类里的成员变量也可以用super
System.out.println(this.num3); // 300 即使不重名,访问该类的成员变量也可以用this
}
}
// 定义测试类
public class Demo01ExtendsField {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method();
}
}
小贴士: Fu类中的成员变量是非私有的,子类中可以直接访问。若Fu类中的成员变量私有了,子类是不能直接访问的。通常编码时,我们遵循封装的原则,使用private修饰成员变量,那么如何访问父类的私有成员变量呢?对!可以在父类中提供公共的getXxx方法和setXxx方法。
1.4 继承后的特点—成员方法
1.4.1 成员方法不重名
如果子类父类中出现不重名的成员方法,对象调用方法时,会先在子类中查找有没有对应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。简单来说就是,访问规则依然按照就近原则访问,且绝对不会向下查找。
代码如下:
// 定义父类
class Fu{
public void fuShow(){
System.out.println("Fu类当中的show方法执行");
}
}
// 定义子类
class Zi extends Fu{
public void ziShow(){
System.out.println("Zi类当中的show方法执行");
}
}
// 定义测试类
public class ExtendsDemo04{
public static void main(String[] args){
Zi zi = new Zi();
// 子类中没有show方法,但是可以找到父类方法去执行
zi.show()
}
}
1.4.2 成员方法重名—方法重写
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写(Override)。
子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现。
注意: Java当中只有方法才会覆盖重写,成员变量不会发生覆盖重写
重写(Override): 方法的名称一样,参数列表【也一样】。
重载(OverLoad): 方法的名称一样,参数列表【不一样】。
方法的覆盖重写特点: 创建的是子类对象,按照就近原则,当然优先使用子类方法。
代码如下:
// 定义父类
class Fu{
public void show(){
System.out.println("Fu show");
}
}
// 定义子类
class Zi extends Fu{
// 子类重写了父类的show方法
@Override
public void show(){
System.out.println("Zi show");
}
}
// 定义测试类
public class ExtendsDemo05{
public static void main(String[] args){
Zi zi = new Zi();
// 子类当中有show方法,只执行重写后的show方法
zi.show();
}
}
方法重写的5大注意事项:
-
【1】.必须保证父子类之间的方法名称相同, 参数列表也相同(参数个数,参数类型必须完全相同)。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。但是推荐写上,以防出现错误 -
【2】.子类方法的返回值必须【小于等于】父类方法的返回值范围
详细来说,分以下几种情况:
1. 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
2. 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类(java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类)
3. 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
4. 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
-
【3】.子类方法的权限必须【大于等于】父类方法的权限修饰符
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空 -
【4】.子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
-
【5】.子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。
-
【6】.父类中的私有方法不可以被覆盖。
1.4.2 方法重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:
// 定义父类
class Phone{
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
// 定义子类
class NewPhone extends Phone{
// 重写父类的showNum成员方法
@Override
public void showNum){
// 调用父类已有的功能要用super关键字
super.showNum(); // 把父类的show方法拿过来重复利用
// 自己子类再来添加更多内容
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
// 定义测试类
public class ExtendsDemo06{
public static void main(String[] args){
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
注意:
这里在进行重写时,用到super.父类成员方法,表示调用父类的成员方法。
1.5 继承后的特点—构造方法
首先我们要回忆两个事情,构造方法的定义格式和作用。
1.构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
2.构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个super(),表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。代码如下:
// 定义父类
class Fu{
private int n;
// 定义父类构造方法
public Fu(){
System.out.println("Fu()");
}
public Fu(int num){
System.out.println("父类构造方法!");
}
}
// 定义子类
class Zi extends Fu{
// 定义子类构造方法
public Zi{
// super() 在调用父类构造方法,即使不添加,编译器也会默认帮我们添加上
super(20); 在调用父类重载的构造方法
System.out.println("Zi()");
}
}
// 定义测试类
public class ExtendsDemo07{
public static void main(String[] args){
Zi zi = new Zi();
}
}
输出结果:
父类构造方法!
Zi()
继承关系中父子类构造方法的访问特点
- 子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造
- 子类构造可以通过super关键字来调用父类重载构造
- super的父类构造调用,必须是子类构造方法的第一个语句。也就是说,不能一个子类构造调用多次super构造
总结:
1.6 super关键字的三种用法
1.在子类的成员方法中访问父类的成员变量
2.在子类的成员方法中访问父类的成员方法
3.在子类的构造方法中访问父类的构造方法
在第三种方法中要注意:
super(…)必须是子类构造方法的第一个语句。也就是说,不能一个子类构造调用多次super构造
总结: 俩成员,一构造
// 定义父类
public class Fu {
int num = 10;
public void method() {
System.out.println("父类方法");
}
}
// 定义子类
public class Zi extends Fu{
int num = 20;
public Zi(){
super();
}
public void methodZi(){
System.out.println(super.num); // 父类当中的num
}
public void method(){
super.method(); // 访问父类中的method
System.out.println("子类方法");
}
}
1.7 this关键字的三种用法
1.在本类的成员方法中,访问本类的成员变量
2.在本类的成员方法中,访问本类的另一个成员方法
3.在本类的构造方法中,访问本类的另一个构造方法
在第三种方法当中要注意:
A.this(…)调用也必须是构造方法的第一个语句,即只能调用唯一一个
B.super和this两种调用不能同时使用,不能同时使用。
1.8 static静态方法中不可以使用this和super
首先,
无论是static修饰的变量,还是static修饰的方法,我们都知道他们是属于类本身的,不是属于某一个对象的,当声明一个对象时,并不产生static变量和方法的拷贝。也就是说,用static修饰的变量和方法在类加载的时候,只分配一块存储空间,所有此类的对象都可以操控此块存储空间;
其次,
super的用法跟this类似,this代表对本类对象的引用,指向本类已经创建的对象;而super代表对父类对象的引用,指向父类对象;
静态优先于对象存在;
综上,
因为静态优先于对象存在,所以方法被静态修饰之后方法先存在,而方法里面要用到super指向的父类对象,但是所需的父类引用对象晚于该方法出现,也就是super所指向的对象没有,当然就会出错。
综上,静态方法中不可以出现super关键字。
简而言之,this和super是属于对象范畴的东西,而静态方法是属于类范畴的东西
参考博文链接:link.
1.9 super和this关键图解
// 定义父类
public class Fu {
int num = 10;
public void method(){
System.out.println("父类方法");
}
}
// 定义子类
public class Zi extends Fu{
int num = 20;
@Override
public void method(){
super.method(); // 调用了父类方法
System.out.println("子类方法");
}
public void show(){
int num = 30;
System.out.println(num); // 30
System.out.println(this.num); // 20
System.out.println(super.num); // 10
}
}
// 定义测试类
public class Demo {
public static void main(String[] args) {
Zi zi = new Zi();
zi.show();
zi.method();
}
}
第二章 易混淆概念思考
本人由于之前学过一丁点Python语言,所以在几类变量之间理解与Python当中有所混淆,这里进行区分总结(如有错误,请予以私信、评论等指正)。ps:再次声明,本人只是学生,请以批判的眼光看待所发文章
首先声明,Java当中没有Python当中所谓的全局变量的概念
-
成员变量、this
在类当中,方法外定义的变量是成员变量,类似于Python当中的实例属性,作用域是整个类(即可以在整个类中都被访问到)。
但是不同于Python的是,Python当中访问实例属性,一定是通过self.实例属性来访问。而Java当中通常情况下都可直接访问成员变量,只有在当局部变量和成员变量重名的时候,才会使用和self功能相似的this关键字,用this.成员变量来访问,这里使用this关键字的目的仅仅是用于区分成员变量和局部变量。而this实际上是代表所在类当前对象的内存地址 -
类变量(类属性,static修饰)
在类当中,方法外,用static关键字修饰的变量就是类变量,类变量相当于Python当中的类属性,和Python中的类属性理解相同,类变量作用域和成员变量一样都是整个类,可通过对象名或类名来调用,但是推荐使用类名来调用类变量,以强调这是类变量 -
类方法(静态方法,static修饰)
用static关键字修饰的成员方法就是类方法,和类变量一样,可通过对象名或类名来调用,但是推荐使用类名来调用类方法,以强调这是类方法 -
局部变量
1.形参(形式参数)
作用域:在整个方法内有效
2.方法局部变量 (方法内定义)
从定义这个变量开始到方法结束这一段时间内有效 -
继承和super关键字
当类之间产生了关系以后,如果子类和父类当中的成员变量不重名,则在访问时是没有影响的。但是,当子类和父类当中出现重名的**成员变量(成员方法)**时候,这时的访问是有影响的。此时,在子类当中需要访问父类中非私有成员变量时,需要使用super关键字,修饰父类成员变量,类似于之前学过的this关键字,作用在于区别父类当中的成员变量(成员方法)和子类当中的成员变量(成员方法) -
this和super关键字的详细汇总
this关键字三种用法:
1.当在本类当中成员变量和方法的局部变量相同时,可以用this.成员变量来加以和方法的局部变量进行区分
2.和Python当中一样,在本类的成员方法A中,可以通过用this.成员方法B,来在成员方法A中访问成员方法B
3.在本类的构造方法中,访问本类的另一个构造方法,代码示范如下;
// 定义父类
public class Fu{
int num = 30;
}
public class Zi extends Fu {
int num = 20;
public Zi(){
// super(); // 这一行不再赠送
this(123); // 本类的无参构造,调用本类的有参构造
// this(1,2); // 错误写法
}
public Zi(int n){
this(1,2);
}
public Zi(int n,int m){
}
}
注意:
在上面第三点用this访问另外一个构造方法时候有以下两点考虑:
A.this(…)调用也必须是构造方法的第一个语句,即只能调用唯一一个。
B.super和this两种调用不能同时使用,不能同时使用。
super关键字三种用法
1.在子类的成员方法中访问父类的成员变量,用以区别变量名相同时的情形
2.在子类的成员方法中访问父类的成员方法,用以区别成员方法名相同时的情形
3.在子类的构造方法中访问父类的构造方法
// 定义父类
public class Fu {
int num = 10;
public void method(){
System.out.println("父类方法");
}
}
// 定义子类
public class Zi extends Fu{
int num = 20;
public Zi(){
super();
}
public void methodZi(){
System.out.println(super.num); // 父类当中的num
}
public void method(){
super.method(); // 访问父类中的method
System.out.println("子类方法");
}
}