回想类和对象的完结,仿佛还在昨天…
Java中有三个重要的概念:封装、继承和多态
那么在之前的面对对象中,我们讲解了封装的概念在实现上采用private修饰成员变量/方法,对外隐藏实现的细节,只提供公开的方法。是公开的方法和私有的属性之间结合实现的。
在Java中使用类对现实世界中的实体来进行描述,类经过实例化之后的产物对象,则可以用来表示现实中的实体,但是现实世界错综复杂,事物之间可能会存在一些关联,那么我们在设计程序的时候就要考虑到继承
比如:狗和猫,它们都是哺乳动物
然后我们用Java语言来实现这种关系,我们单独写两个类,一个是Cat,一个是Dog
public class Cat {
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃猫粮");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void mew(){
System.out.println(name+"在喵喵喵~");
}
}
public class Dog {
String name;
int age;
float weight;
public void eat(){
System.out.println(name+"正在吃狗粮");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
void Bark(){
System.out.println(name+"在汪汪汪!");
}
}
我们观察上面两个类就会发现猫和狗的代码中存在大量重复
那么我们能否将这些共性抽取出来呢?
面向对象思想中提出了继承的概念,专门用来进行共性的抽取,实现代码的复用。
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类的特性的基础上进行扩展,增加新功能,以此产生新的类,我们称之为派生类。继承呈现了面向对象程序设计的层次结构,体现了又简单到复杂的认知过程。
继承主要解决的问题就是:共性的抽取,实现代码复用
我们接着上面的例子,猫和狗都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到代码的复用
上述图中,Dog和Cat都继承了Animal类,其中:Animal类称为父类/基类或者超类,Dog和Cat可以称为Animal的子类/派生类,继承之后,子类可以复用父类中的成员,子类在实现的时候只需要关心自己新增加的成员就可以了
父类
public class Animal {
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
子类猫
public class Cat extends Animal {
void mew(){
System.out.println(name+"喵喵喵");
}
}
子类狗
public class Dog extends Animal {
void bark(){
System.out.println(name+"汪汪汪");
}
}
用来测试的一个类
public class Test1 {
public static void main(String[] args) {
Dog dog = new Dog();
//dog类中并没有定义任何成员变量,name和age属性肯定使从父类Animal中继承下来的
System.out.println(dog.name);
System.out.println(dog.age);
//dog访问的eat()和sleep()方法也是从Animal中继承下来的
dog.eat();
dog.sleep();
dog.bark();
}
}
在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?
public class Base {
int a;
int b;
}
public class Derived extends Base {
int c;
public void method(){
a = 10;//访问从父类中继承下来的a
b = 20;//访问从父类中继承下来的b
c = 30;//访问子类自己的c
}
}
public class Base {
int a=10;
int b=101;
int c=100;
}
class Derived extends Base{
int a; //这里与父类中成员a同名,且类型是相同的
char b; //与父类中成员b同名,但类型不同
public void method(){
a = 100;//访问父类继承的a,还是子类自己新增的a?
b = 101;//访问父类继承的b,还是子类自己新增的b?
c = 102;//子类中没有c,访问的肯定是从父类中继承下来的c
//d=103;//这里编译会失败,因为父类和子类都没有定义成员变量d
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.method();
System.out.println(derived.a);
System.out.println(derived.b);
System.out.println(derived.c);
}
}
运行结果如下
我们可以看到这里的a是100,b是一个char字符‘e’,c为102
这里的值都被覆盖了,而我们再去IDEA中观察
父类中的a,b都失去了光彩,变成了灰色,只有c是还有高亮的,说明这里a和b没用到
在子类方法中 或者 通过子类对象访问成员时:
成员变量访问遵循就近原则,自己如果有的话就访问自己的,如果没有则向父类中找。
比如:你和你爸各自都有一款相同的手机,平时用的话你肯定优先用自己的,没电了才回去用爸爸的。
public class Base{
public void methodA(){
System.out.println("Base中methodA()");
}
}
class Derived extends Base {
public void methodB() {
System.out.println("Derived中的methodB方法");
}
public void methodC(){
methodB();//访问子类自己的methodB()
methodA();//访问父类继承的methodA()
//methodD();这里会编译失败,在整个继承体系中没有发现方法methodD()
}
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有的时候再去父类当中找,
public class Base {
public void methodA(){
System.out.println("Base中的methodA");
}
public void methodB(){
System.out.println("Base中的methodB");
}
}
class Derived extends Base{
public void methodA(int a){
System.out.println("Derived中的method(int)方法");
}
public void methodB(){
System.out.println("Derived中的methodB方法");
}
public void methodC(){
methodA();//这里没有传参,访问的是父类中的methodA
methodA(20);//传递了int参数,访问子类中的methodA(int)
methodB();//直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodC();
}
}
问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
我们在进行程序设计的时候,子类和父类可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,我们应该如何操作呢?直接访问时无法做到的,但Java中提供了super关键字,该关键字主要作用就是:在子类方法中访问父类的成员。
public class Base{
int a;
int b;
public void methodA(){
System.out.println("Base中的methodA");
}
public void methodB(){
System.out.println("Base中的methodB");
}
}
class Derived extends Base{
int a;//与父类中成员变量同名且类型相同
char b;//与父类中成员变量同名但类型不同
//与父类中的methodA构成重载
public void methodA(int a){
System.out.println("Derived中的method方法");
}
//与基类中methodB构成重写
public void methodB(){
System.out.println("Derived中的methodB方法");
}
public void methodC(){
//对于同名的成员变量,直接访问时,访问的都是子类的
a = 100;//等价于:this.a = 100
b = 101;//等价于:this.b = 101
//注意:this是当前对象的引用
//访问父类的成员变量的时候,需要借助super关键字
//super是获取到子类对象中从基类继承下来的父类部分
super.a = 200;
super.b = 201;
//父类和子类中构成了重载的方法,直接可以通过参数列表区分清楚访问父类还是子类
methodA();//没有传参,访问父类中的methodA
methodA(20);//传递int参数,访问子类中的methodA(int)
//如果在子类中要访问重写的父类方法,则需要借助super关键字
methodB();//直接访问,则永远访问的是子类中的methodA,基类的无法访问到
super.methodB();//访问父类的methodB方法
}
public static void main(String[] args) {
Derived derived = new Derived();
derived.methodC();
}
}
运行结果如下
在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可
[注意事项]
父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法
public class Base {
public Base(){
System.out.println("父类Base的构造函数");
}
}
class Derived extends Base{
public Derived(){
//super();//注意子类构造方法中默认会调用基类的无参构造方法:super();
//用户没有写super();给父类初始化的时候,编译器会自动添加,
// 而且super();必须是子类构造方法中的第一条语句
//并且只能出现一次
System.out.println("子类Derived的构造函数被调用了");
}
public static void main(String[] args) {
Derived d =new Derived();
}
}
运行结果如下
在子类构造方法中,并没有写任何关于父类构造函数的代码,但是在构造子类对象的时候,要先执行基类的构造方法,然后执行子类的构造方法,因为:
子类对象中成员是由两部分组成的,基类继承下来的以及子类新增加的那一部分。
父子必然是先有父再有子,所以构造子类对象的时候,先要调用基类的构造函数,将从基类继承下来的成员构造完整,然后再调用自己的构造函数及那个子类自己的新增加的成员初始化完整
[注意]:
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员方法,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?
这里对this不太熟悉的小伙伴可以看看我之前写的博客哦this的用法
在类和对象的博客中,我们提到了代码块的概念和用法,比较重要的有实例代码块和静态代码块。传送门代码块
在没有继承关系时候的执行顺序我们用代码再来梳理一遍
public class Person {
public String name;
public int age;
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("构造方法被调用了!");
}
{
System.out.println("实例代码块被调用了!");
}
static {
System.out.println("静态代码块被调用了!");
}
public static void main(String[] args) {
Person p1 = new Person("葛玉礼",19);
System.out.println("======分割线=======");
Person p2 = new Person("小红",19);
}
}
1.静态代码块先执行,并且只执行一次,在类加载阶段开始执行
2.当有对象创建时,才会执行实例代码块,实例代码块执行完成后再轮到构造方法执行
[继承关系上的执行顺序]
public class Person{
public String name;
public int age;
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("Person类的构造方法开始执行了");
}
{
System.out.println("Person类的实例代码块执行了!");
}
static {
System.out.println("Person类的静态代码块执行了!");
}
}
class Student extends Person{
public Student(String name, int age) {
super(name, age);
System.out.println("Student的构造方法被调用了!");
}
{
System.out.println("Student的实例代码块被调用了!");
}
static {
System.out.println("Student的静态代码块被调用了!");
}
public static void main(String[] args) {
Student student1 = new Student("葛玉礼",19);
System.out.println("=======================");
Student student2 = new Student("Gremmie",18);
}
public static void main1(String[] args) {
Person person1 = new Person("葛玉礼",19);
System.out.println("======分割线=======");
Person person2 = new Person("小红",19);
}
}
main执行结果
main1的执行结果
我们来分析一下执行结果,得出如下结论
在类和对象章节中,为了实现封装特性,Java中引入了访问限定符,主要限定:
类或者类中成员能否在类外或者其他包中被访问
那么父类中不同访问权限的成员,在子类中的可见性又是什么样子的呢?
这是我创建的两个包和四个类,B是父类,C和D都是继承B的子类
TestC是用来测试类C一些属性的类
//为演示父类中不同访问权限在子类中的可见性,为了简单起见,类B中就不设置成员方法了
public class B {
private int a;
protected int b;
public int c;
int d;
}
//ProtectedTest包中
//同一个包中的子类
public class D extends B{
public void method(){
//super.a = 10;这里编译会报错,父类private成员在相同包的子类中不可见
super.b = 20;//父类中protected成员在相同包子类中可以直接访问
super.c = 30;//父类中public成员在相同包子类中可以直接访问
super.d = 40;
}
}
//ProtectedTest2的包中
//不同包中的子类
public class C extends B {
public void method(){
//super.a = 10;这里编译报错,父类中private成员在不同包子类中不可见
super.b = 20;//父类中protected修饰的成员在不同包继承的子类中可以直接访问
super.c = 30;//父类中public修饰的成员在不同包子类中可以直接访问
//super.d = 40//父类中默认访问权限修饰的成员在不同包子类中不能直接访问
}
}
//ProtectedTest2的包中
//不同包中的子类
public class TestC {
public static void main(String[] args) {
C c = new C();
c.method();
//System.out.println(c.a);
//这里编译会报错,父类中private成员在不同包其他类中不可见
//System.out.println(c.b);
//这里也会报错,父类中protected成员在不同包其他不是继承的类中不能直接访问
System.out.println(c.c);
//父类中public成员在不同包其他类中可以直接访问
//System.out.println(c.d);
//父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
}
//输出结果为:30
}
注意:父类中private成员变量虽然在子类中不能直接访问,但是它依旧继承到子类中了
那么我们什么情况下用到哪一种访问限定修饰符呢?
我们希望类要尽量做到“封装”,即隐藏内部实现细节,只暴露出必要的信息给类的调试者
因此我们在使用的时候应该尽可能的使用比较严格的访问权限,例如一个方法如果能使用private,就尽量不要使用public
另外,还有一种简单粗暴的做法:将所有字段设置为private,将所有方法设置为public,不过这种方式属于是对访问权限的滥用,还是更希望大家写代码的时候认真思考,该类提供的字段方法到底该给谁使用(即,是类内部自己用,还是类的调用者使用,还是子类使用,都要考虑清楚)
但在Java中,只支持以下几种继承方式
注意:Java中不支持多继承
时刻牢记,我们写的类是现实事物的抽象,而我们真正在工作中所遇到的项目往往更加复杂,可能会涉及到一系列复杂的概念,都需要我们使用代码来表示,所以我们在现实项目中写的类也会有很多,类之间的关系也会更加复杂
但是即使如此,我们并不希望类之间的继承层次太复杂, 一般我们不希望出现超过三层的继承关系。如果继承层次太多,就需要考虑对代码的整体结构进行修改了
如果向从语法上进行限制继承,不想继承某些方法或是变量,我们就可以使用final关键字
final关键字可以用来修饰变量、成员方法以及类
表示常量(即不能修改)
final int a = 10;
a = 20;//编译出错
表示此类不能被继承
final public class Animal{
...
}
public class Bird extends Animal {
...
}
//编译出错
Error:(3,27)java:无法从最终的Animal中进行继承
我们平时使用的String字符串类,就是用final修饰的,不能被继承
表示该方法不能被重写
这里我在之前的类关系中提到过相关的概念Java类关系详解
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。
组合并没有涉及到特殊的语法(诸如extends这样的关键字),仅仅是将一个类的实例作为另外一个类的字段
继承表示对象之间是is-a的关系,比如狗是动物,猫是动物
**组合表示对象之间是has-a的关系,**比如:汽车有轮胎、发动机、方向盘
//轮胎类
class Tire{
//...
}
//发动机类
class Engine{
//...
}
//车载系统类
class VehicleSystem{
//...
}
public class Car {
private Tire tire;//可以复用轮胎中的属性和方法
private Engine engine;//可以复用发动机中的属性和方法
private VehicleSystem vs;可以复用车载系统中的属性和方法
//...
}
//奔驰是汽车
class Benz extends Car{
//将汽车中包含的:轮胎,发动机、车载系统全部继承下来
}
组合和继承都可以实现代码的复用,应该使用继承还是组合,需要根据实际应用场景来选择,一般建议:能用组合尽量用组合
继承到这里就结束啦,下一篇为大家讲解多态
希望能帮到你
感谢阅读!