目录
前言
继承和多态就是面向对象的三大特征之二,今天我们就一起来学习怎么使用继承和多态。此外,我还会补充一些其他知识,如static、代码块、final使用等相关知识点。文章很长,耐心观看一定有所获的
1. static
static叫做静态,可以修饰成员变量,成员方法
1.1 static修饰成员变量
成员变量按照有无static修饰,可以分为两种:
- 类变量:也称为静态成员变量,有static修饰,属于类的,不属于某个具体的对象,是所有对象所共享的
- 实例变量:就是普通的成员变量,无static修饰,属于某个具体的对象
我们既可以通过对象来访问(对象.类变量),也可以通过类名来访问(类名.类变量),不过更推荐使用类名来访问
1.2 类变量的使用场景
在开发中,如果某个数据只需要一份,而且希望它能被共享(访问、修改),我们就可以用使用类变量来记住它
例子:要求记录用户创建了多少个对象
1.3 static 修饰成员方法
成员方法按照有无static修饰,可以分为两种:
- 类方法:也称为静态成员方法,有static修饰,属于类的。静态成员一般是通过静态方法来访问的
- 实例方法:就是普通的成员方法,无static修饰,属于某个具体的对象
我们既可以通过对象来访问(对象.类方法),也可以通过类名来访问(类名.类方法),不过更推荐使用类名来访问
注意事项:
- 类方法可以直接访问类成员(变量和方法),但不可以直接访问实例成员(变量和方法)
- 实例方法既可以直接访问类成员,也可以直接访问实例成员
- 实例方法中可以出现this关键字,类方法中不可以出现this关键字
关于最后一点,仔细想想,因为类方法它是属于类的方法,this是要拿到当前对象的,而类是不依赖对象的,所以类方法中不可以出现this关键字
2. 代码块
代码块是类的五大成分之一(成员变量、构造方法、方法、代码块、内部类),使用{ }定义的一段代码就是代码块。我们通常会把代码块分为四种:
-
普通代码块
-
构造块
-
静态块
-
同步代码块(博主也不会,后面学到会再讲的)
public static void main(String[] args) {
//普通代码块:直接用{}来定义,比较少见
{
int a = 10;
System.out.println(a);
}
int b = 20;
System.out.println(b);
}
(二)构造块:也叫做实例代码块。它是用{ }定义在类中的代码块,不加修饰符
特点:每次创建对象时,都会执行构造块,而且它会在构造器前执行
作用:和构造器一样,都是用来完成对象的初始化的
我们能发现,每次创建对象时,构造块都会跟着执行一次,而且会先于构造器执行
(三)静态代码块:定义在类中使用static修饰的代码块。
特点:在类加载时执行一次,与对象无关,无论产生多少对象,静态代码块只在类加载时执行一次。
作用:对类进行初始化
通过打印结果我们能发现: 静态代码块只执行了一次,它是在类被加载后就执行了,而且它比构造块更早执行
3. 继承
继承,在生活中我们可能会这样使用这个词:某人继承了ta父母的遗产;而在Java中,继承的意思也大体相同
3.1 为什么会有继承
在Java中,我们会对现实世界中复杂的事物抽象成类,并定义它们的属性和功能;但即使是再复杂的事物,它们之间也可能存在共性:狗和猫都是动物,它们能跑能跳,能进食会逗人笑(skr~
我们来看看下面这个代码
我们定义了两个类:Student类和Teacher类,他们的成员变量一模一样的,也有一个完全相同的成员方法。对于这些共性,我们可以设计一个People类,把相同的成员放在里面,再使用继承让三者建立父子关系,实现代码的复用
在上图中,Student类和Teacher类都继承了People类。那么People类就是父类,Student类和Teacher类就是People类的子类,继承之后,子类就可以使用父类中非私有的成员。继承主要解决的问题是:共性的抽取,实现代码的复用
3.2 继承的语法
我们可以使用extends关键字,让一个类和另一个类建立起父子关系
继承的特征
- 子类会将父类中的非私有成员继承到子类中来
- 子类在继承后必须要添加新的、有别于父类的成员,这样继承才有意义
Java是单继承的,Java中的类不支持多继承,但是它支持多层继承
即使可以多层继承,一般我们不希望超出三层继承关系,如果继承太多层,那应该对代码进行重构了。如果我们想限制一个类被继承,那么可以在它前面添加final关键字(下面会讲到)
3.3 继承后成员的访问
子类访问父类成员,遵循就近原则
在子类方法中或者通过子类对象访问父类成员变量时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
- 总结:子类自己有优先自己的,如果没有则向父类中找。
在子类方法中或者通过子类对象访问父类成员方法时:
-
通过子类对象访问父类与子类中不同名方法时,优先访问自己的成员方法,如果没找到,就访问父类的成员方法,还是没找到就会报错。
-
通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同 ( 重载) ,根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错;
如果我们想摆脱就近原则,想要访问跟子类同名的父类成员变量,那么就得使用super关键字
super关键字
Java提供了super关键字,它可以让我们在子类方法中访问父类的成员
要注意的是,super只能在非静态方法中使用,在static修饰的方法中,使用static会报错;
super用来在子类方法中,访问父类成员
3.4 子类的构造器
我们还是用上面的代码来演示,并在父类和子类中加入无参构造器,再生成一个子类对象,看看会发生什么
通过打印结果我们可以得到子类构造器的特点:子类的全部构造器,都会先调用父类的构造器,接着再执行自己的构造器
注意:
- 在默认情况下,子类全部构造器的第一行代码都是super( )(就算不写也默认有),它会调用父类的无参构造器
- 如果父类没有无参构造器,即我们在父类中定义了有参构造器,那么我们必须在子类构造器的第一行手写上super(……),指定去调用父类的有参构造器,否则会报错
- super(……)只能在子类构造器中出现一次,而且不能和this( )同时出现
super和this的联系
我们需要对this补充一个知识点:在任意类的构造器中,是可以通过this(……)去调用该类的其他构造器的,用法如下:
相同点:
- 都只能在类的非静态方法中使用,用来访问非静态成员
- 在构造器中调用时,必须在第一条语句,而且不能同时出现
不同点:
- this是对象的引用;super则相当于子类中父类继承下来的引用
- 在构造器中:this(……)用于调用其他构造器;super(……)用于调用父类构造器,而且它们俩不能同时在构造器中出现
- 构造器中一定会存在super(……),但是this(……)我们自己不写则没有
继承中的代码块
我们在父类和子类中分别定义静态代码块,构造块,构造器,再创建两个对象,我们来观察一下代码执行顺序
运行结果如下
通过打印结果我们可以得到下面结论:
- 实例化对象时,父类的静态代码块最先执行,紧接着是子类的静态代码块
- 然后就是父类的构造块和构造器,最后是子类的构造块和构造器
- 第二次实例化对象时,父类和子类的静态代码块不会再执行了
4. 多态
4.1 多态的定义
多态是继承情况下的一种现象,表现为:对象多态、行为多态。通俗来讲,就是去完成某个行为时,不同对象去完成时会产生不同的状态
对象多态:比如一个人有多种身份,ta可以是学生,也可以是孩子、公民……
行为多态:比如考试,有的人考的成绩很好,有的人稍差……
4.2 多态实现的条件
- 必须在继承体系下
- 子类要对父类中的方法进行重写
- 存在父类引用子类对象
方法重写
重写(override),也叫做覆盖、覆写。重写是子类对父类中非静态、非private、非final、非构造器等的重新编写。方法名、返回值和形参都不能变,只重写内部代码实现
重写的规则
- 子类重写父类方法时,一般必须跟父类方法的“外壳”一致:方法名、返回值类型、参数列表
- 被重写的方法返回值类型可以不一样,但是必须是具有父子关系(也称协变类型)
- 访问修饰符的权限必须比父类被重写的方法权限更高,这样才能正常重写
- 我们可以在重写方法上面标注上@Override,这样编译器可以帮我们矫正一些重写小错误
在认识重写后,我们就可以来写出多态:1.继承;2.子类要对父类中的方法进行重写;3.父类引用子类对象
People s = new Student();
这个就是父类引用子类对象,我们可以这样来理解:学生是属于人,而且人的范围更大,学生的范围比较小;这种现象也有一个称呼:向上转型,即用父类引用来创建一个子类对象
格式一:父类类型 对象名 = new 子类类型()
识别技巧:编译看左边,运行看右边。意思是,我们在用对象去使用方法时,得看父类里有没有定义该方法,而到真正运行时,就会走到子类里重写的方法
格式二:方法传参——public void test(父类类型){ },调用该方法时用子类类型的对象作为参数
格式三:方法返回——public 父类类型 test( ){ return new 子类类型;}
格式二:
public void test1(People p) {
}
格式三:
public People test2() {
return new Student();
}
要注意的是:多态是对象、行为的多态,在Java中,成员变量是不谈多态的。当父类和子类都有同名成员变量时,通过父类引用,就只能引用父类自己的成员变量
4.3 多态的好处
- 可拓展性更强(右边的对象是解耦合的),如果想要更换对象类型,可以直接更换,不需要更改下面的大量代码。——解耦合,类似可拆卸玩具车,我们可以任意更换零件,让玩具车跑得更快,此时该玩具车就是解耦合的
- 定义方法时,使用父类类型作为形参,可以接收一切子类对象,扩展性更强、更便利
4.4 多态的缺陷
当然,多态也存在一定的缺陷:多态下不能调用子类独有的功能(因为在编译阶段只有父类里存在该方法才能通过编译,否则会报错)
要想解决这个问题,就得使用强制类型转换,俗称向下转型
但是向下转型我们用得比较少,而且不安全,万一转换失败,运行时就会报错
所以Java中提供了instanceof关键字,如果该表达式为true,则可以安全转换
5. final
final是Java中的一个关键字,表示最终的意思,我们可以用它来修饰变量、方法、类
- 修饰变量,表示常量(该变量只能被赋值一次)
- 修饰方法,表示该方法不能被重写
- 修饰类,表示此类不能被继承
//final用法的演示
public class Test {
//1.final修饰变量,则变为常量,有且仅能赋值一次
/* 变量:
一:局部变量
二:成员变量
1.静态成员变量
2.实例成员变量
*/
final int a = 1;
a = 2; //报错,第二次赋值
//常量:public static final修饰的成员变量,名称要全部大写,多个单词用下划线链接
public static final String STUDENT_NAME = "flmz";
//2.final修饰方法,方法不能被重写
class C {
public final void test() {
}
}
class D extends C {
//报错
@Override
public void test() {
}
}
//3.final修饰类,类就不能被继承了
final class A {}
class B extends A{} //报错
}
final修饰变量的时候要注意:
- final修饰基本类型的变量时,变量储存的数据不能被改变
- final修饰引用类型的变量时,变量储存的地址不能被改变,但是地址所指向对象的内容是可以被改变的
public class Test {
final int a = 10;
a = 20; //报错,不能改数据
final int[] array = {10, 20};
array = null; //报错,不能改地址
array[0] = 20; //改地址指向的内容,可以
}
结语
本篇博客为大家讲解了继承和多态的相关知识,还对static、代码块、super、方法重写、final进行了解释,内容很多,知识点连贯性很强。希望大家能喜欢这篇文章,有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!