概述
文章目录
- 前言
- Part 1????
- 1.1 为什么需要继承
- 1.2 继承概念
- 1.3 继承语法
- Part 2????
- 2.1 父类成员访问
- 2.1.1 子类访问父类成员变量
- 2.1.2 子类访问父类成员方法
- 2.2 super
- 2.2.1 super关键字
- 2.2.2子类构造方法
- 2.3 super和this
- Part 3????
- 3.1 再谈初始化
- 3.2 protected关键字
- 3.3 继承方式
- 3.4 final关键字
前言
什么是继承?
????继承是面向对象程序设计使代码可以复用的最重要的手段,是由子类继承父类,并能够访问父类的成员变量以及方法,也就是对共性的抽取,实现代码复用,以便提高编程的效率。
Part 1????
1.1 为什么需要继承
Java中使用类对现实世界中的实体进行描述,类经过实例化之后的产物对象,????可以参考我们之前提到的洗衣机案例。可以用来表示现实中的实体,但是在我们现实生活中有很多错综复杂的例子,任何事物直接会存在一些关联,这个时候就需要用到继承。
//狗类
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+"汪汪叫~~~");
}
}
//猫类
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+"喵喵叫~~~");
}
}
通过上述代码我们可以看到我们设计的狗类和猫类存在很多重复的类,如下图⬇️
在上述代码中狗狗和猫咪都有名字,年龄,体重。我们可以将这些重复的也就是这些共同的属性进行抽取,面对对象思想中提出了继承的概念,专门用来进行共性抽取,实现代码复用。
1.2 继承概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用。
例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。如上图所述,Dog和Cat都是继承类Animal类,我们将Animal类称为父类/基类或者是超类,而Dog和Cat称为Animal的子类或者是派生类。在实现了继承关系之后,子类可以访问调用父类的中的成员,子类只需要关心自己类中新增的成员即可。
1.3 继承语法
在Java中如果表示类之间的继承关系,需要借助exends关键字,具体如下:
修饰符 class 子类 extends父类{
//...
}
public class Animal{
String name;
int age;
public void eat(){
System.out.println(name+"正在吃饭");
}
public void sleep(){
System.out.println(name+"正在睡觉");
}
}
class Dog extends Animal{
void bark(){
System.out.println(name + "汪汪汪~~~");
}
}
class Cat extends Animal{
void mew(){
System.out.println(name + "喵喵喵~~~");
}
}
public class Test{
public static void main(String[] args){
Dog dog=new Dog();
//dog类中并没有定义任何的成员变量,name和age属性是从父类Animal中继承下来的
dog.name="坦克";
dog.eat();
dog.sleep();
Cat cat=new Cat();
cat.name="咪咪";
cat.eat();
cat.sleep();
//cat类也是同狗类一样
}
}
注意两点:
1.子类会将父类中的成员变量或者成员方法继承到子类中
2.子类继承父类之后,必须要新添自己特有的成员,以体现出和基类的不同,否则没有必要去继承。
Part 2????
2.1 父类成员访问
在继承体系中,子类将父类中的方法和属性都继承下来了,那子类中能否直接访问父类中继承下来的成员呢?
2.1.1 子类访问父类成员变量
1.子类和父类存在不同名的成员变量
class A{
int a;
int b;
}
class B extends A{
int c;
}
public class Main {
public static void main(String[] args) {
B p=new B();
p.a=10;
p.b=20;
p.c=30;
System.out.println(p.a);//访问从父类继承的a
System.out.println(p.b);//访问从父类中继承的b
System.out.println(p.c);//访问子类的c
}
}
2.子类和父类存在同名的成员变量
class G{
int a;
int b;
int c;
}
class F extends G{
int a;
char b;
}
public class Test2 {
public static void main(String[] args) {
F p=new F();
p.a=11;
p.b='b';
p.c=13;
System.out.println(p.a);//访问的是子类还是父类中的a?
System.out.println(p.b);//访问的是父类的b还是我们在子类新增的b?
System.out.println(p.c);//c子类中没有,所以访问的一定是父类中的c
}
}
在子类方法中或者通过子类对象访问成员时:
- 如果访问的成员变量子类有,优先访问自己的成员变量
- 如果访问的成员变量子类无,则访问父类继承下来的,如果父类也没有,则编译报错
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
总结:成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
2.1.2 子类访问父类成员方法
1.成员方法名字不同
class A{
int a;
int b;
public void methodA(){
System.out.println("父类中的A方法");
}
}
class B extends A{
int c;
public void methodB(){
System.out.println("子类中的B方法");
}
}
public class Main {
public static void main(String[] args) {
B p=new B();
p.methodA();
p.methodB();
}
}
总结:成员方法没有同名的时候,在子类方法中通过子类对象访问方法时,优先访问自己的,自己没有时再去父类中找。
2.成员方法名字相同时
class G{
int a;
int b;
int c;
public void methodA(){
System.out.println("父类中的方法A");
}
public void methodB(){
System.out.println("父类中的方法B");
}
}
class F extends G{
int a;
char b;
public void methodA(int a){
System.out.println("子类中的方法A");
}
public void methodB(){
System.out.println("子类中的方法B");
}
}
public class Test2 {
public static void main(String[] args) {
F p=new F();
p.methodA();//名字相同,但是没有传参数,所以访问的是父类的方法A
p.methodA(10);//有int参数,所以是子类的方法A
p.methodB();//访问的是子类的方法B
}
}
总结:
- 通过子类对象访问父类与子类中不同方法名时,优先在子类中找,找到则访问,否则在父类中查找,找到则访问,没有即报错
- 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(也就是重载),根据调用方法中的参数进行合适的访问,如果没有则报错。
????那么就产生了一个问题:如果子类中存在与父类相同的成员时,那应该如何在子类中访问父类相同名称的成员呢?
在介绍下一个知识点之前我们需要申明一下重载
重载的概念是对方法进行重新编写,方法名相同,但是参数不同,不是说重载一定在同一个类中,如果是在继承关系中,也可以看作是重载。
2.2 super
在我们写代码时,难免会由于使用场景导致子类和父类中会出现相同的成员名,如果出现这一情况是无法直接访问的,所以在Java中提供了super关键字,super关键字的主要作用就是在子类中访问父类的成员
2.2.1 super关键字
class Base{
int a;
int b;
public void methodA(){
System.out.println("父类中的A方法");
}
public void methodB(){
System.out.println("父类中的B方法");
}
}
class Derived extends Base{
int a;//与父类中的成员变量同名并且类型相同
int b;//与父类中成员变量同名但是类型不同
//与父类中methodA()构成重载
public void methodA(int a){
System.out.println("子类中的A方法");
}
//与父类中的methodB()构成重写
public void methodB(){
System.out.println("子类中的B方法");
}
public void methodC(){
//对于同名的成员变量,直接访问,访问的都是子类的
this.a=10;
this.b=20;
//this为当前引用
System.out.println(this.a);
System.out.println(this.b);
System.out.println("华丽的分割线=====================");
//访问父类的成员变量时,需要借助super关键字
//super是获取到子类对象中从父类继承下来的部分
super.a=101;
super.b=202;
System.out.println(super.a);
System.out.println(super.b);
//当父类和子类中构成重载的方法,直接通过参数列表来分清访问的是父类还是子类的方法
methodA();//没有传int参数,访问的是父类的methodA()
methodA(10);//传了int参数,访问的是子类的methodA()
System.out.println("华丽的分割线=====================");
//如果在子类中要访问重写的父类方法,需要借助super关键字
methodB();//直接访问的是子类的方法,无法访问到父类的方法
super.methodB();//通过super关键字可以直接访问到父类的方法
}
}
public class Test3 {
public static void main(String[] args) {
Derived derived=new Derived();
derived.methodC();
}
}
大家看到以上代码一时间肯定有点迷糊,可以搭配下面的这张图进行理解,对于super这个关键字来说,我们需要将它理解为是用于在子类中访问父类的成员变量以及方法,以体现代码的可读性⬇️
- super.a 在子类中访问父类的成员变量
- super.methodB() 在子类中访问父类的方法
- super不能指代父类的父类,只能指代其直接父类,意思是你只能用super来访问自己父类的属性和方法,如果你的父类也是继承过来的,你无法再去访问父类的父类,也就是爷爷类。
super只能在非静态方法中使用,为什么呢?
答:大家仔细想想,我们的super是不是只有依赖对象的引用才能进行访问?但是静态成员和方法是不依赖于对象的,这样就会产生冲突
2.2.2子类构造方法
父子父子,自然是先有父再有子。在继承关系中,我们在构造子类的时候,需要先帮助父类进行构造?怎么帮助,调用父类的构造方法,帮助父类进行初始化其成员,一定要调用其构造方法
class Animal{
}
class Dog extends Animal{
public Dog() {
super();
}
}
public class Test4 {
public static void main(String[] args) {
Dog dog=new Dog();
}
}
大家看上面的代码,我的父类中没有提供任何的属性和方法,我的子类在继承父类之后,在子类中利用super来帮助父类进行构造,我们把suerp();取消掉看看呢?
代码也没有任何的报错,这是为什么呢?????
答:这是编译器自己默认已经提供了一个构造方法,所以super();不写不会报错,默认会有这么一行。
我们通过以下的例子来看看super什么情况下有,什么情况下可以无.
case1:父类没有参数的构造方法,子类没有参数的构造方法
case2:父类没有参数的构造方法,子类有参数的构造方法
//case1
class A {
public A() {
System.out.println("父类不带参数的构造方法");
}
}
class B extends A{
public B() {
// super();//利用super去调用父类的构造方法
System.out.println("子类不带参数的构造方法");
}
}
public class Test5 {
public static void main(String[] args) {
B b=new B();
}
}
//case2
class A {
public A() {
System.out.println("父类不带参数的构造方法");
}
}
class B extends A{
public B(int a) {
//super();//利用super去调用父类的构造方法
System.out.println("子类不带参数的构造方法");
}
}
public class Test5 {
public static void main(String[] args) {
B b=new B(10);
}
}
//以上只要是我们的父类构造方法无参,super();都可以不用写,因为编译器已经提供一个隐藏的的super();方法,不写并不会报错。
case3:父类有参数的构造方法,子类没有参数的构造方法
class A{
public A(int a) {
System.out.println("父类带有参数的构造方法");
}
}
class B extends A{
public B() {
super(a:10);
System.out.println("子类不带参数的构造方法");
}
}
public class Test5 {
public static void main(String[] args) {
B b=new B();
}
}
只要我的父类构造方法中带了参数,在子类调用其父类构造方法时,并需要写super(),括号内需要给到值。
case4:父类有参数的构造方法,子类有参数的构造方法
class A{
public A(int a) {
System.out.println("父类带有参数的构造方法");
}
}
class B extends A{
public B(int b) {
super(10);
System.out.println("子类不带参数的构造方法");
}
}
public class Test5 {
public static void main(String[] args) {
B b=new B(20);
}
}
子类和父类分别调用不同的构造方法通过上述的图片可以看到,带了参数的父类构造方法,在子类中想要访问,必须要写super,否则就无法访问。
介绍完了方法,那么如何访问父类的成员变量呢?能否直接赋值呢?????
//方法1
class Animal{
String name;
int age;
public Animal(String name, int age) {//父类的构造方法带有两个参数
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
String name;
int age;
public Dog(){
super("妞妞",19);//通过super直接给父类的两个成员进行赋值,注意看此时的构造方法是没有参数的
}
public void methodC(){//这里是在子类中定义的一个方法,以便于我们观察到父类成员的值
System.out.println(super.name);
System.out.println(super.age);
}
}
public class Test5 {
public static void main(String[] args) {
Dog dog =new Dog();//实例化出子类对象,这里面没有给值,请注意
dog.methodC();//通过调用子类的方法
System.out.println("=========");//分割线
dog.name="王八";//注意这里是在给子类自己的成员变量赋值
dog.age=22;//注意这里是在给子类自己的成员变量赋值
System.out.println(dog.name);
System.out.println(dog.age);
}
}
分割线的上方就是我们通过super调用父类的构造方法,帮助其完成初始化。
下方就是我们给子类自身的成员赋值之后的结果
//方法2
class Animal{
String name;
int age;
public Animal(String name, int age) {//父类自己的构造方法带有两个参数
this.name = name;
this.age = age;
}
}
class Dog extends Animal{
String name;
int age;
public Dog(String name, int age) {//这里在调用父类的构造方法,帮助其完成初始化,这里是有参数的
super(name, age);
}
public void methodC(){//这里是在子类中定义的一个方法,以便于我们观察到父类成员的值
System.out.println(super.name);
System.out.println(super.age);
}
}
public class Test4 {
public static void main(String[] args) {
Dog dog=new Dog("坦克",10);//这里是有值的,请注意
dog.methodC();通过调用子类的方法
System.out.println("========");
dog.name="小七";//注意这里是在给子类自己的成员变量赋值
dog.age=19;//注意这里是在给子类自己的成员变量赋值
System.out.println(dog.name);
System.out.println(dog.age);
}
}
其实上述的两种方法实现的结果都是一样的,具体是哪一块不同呢,我截取出来大家看看。
1️⃣
2️⃣
所以我们需要记住一句话????:在继承关系中,当我们在构造子类的时候,一定要帮助父类进行构造,帮助父类进行初始化,一定要调用其构造方法,就需要我们的super来完成。这句话时是继承的第一步,要学会灵活使用
大家看看上面的代码中,除了在父类代码块中我们提到了父类,在子类构造方法中并没有写任何关于父类的代码,但是在构造子类对象时,先执行父类的构造方法,然后执行子类的构造方法。这是因为子类对象中的成员是由2部分组成的,还记得我们在一开始说子类和父类的关系吗->在实现了继承关系之后,子类可以访问调用父类的中的成员,子类只需要关心自己类中新增的成员即可。<- 没错,就是由从父类继承下来的和自己新增的部分组成。在自然社会中,肯定是先有父,再有子,一个新生儿的诞生必须要有父类,所以在构造子类对象的时候,先调用父类的构造方法,将从父类继承下来的成员构造完成,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整。
说了这么多,我们来总结一下(参照上面两张图)????:
- 如果父类显示定义无参数或者默认的构造方法,在子类构造方法第一行默认是有隐含的super调用,即调用父类构造方法。
- 如果父类构造方法是带参数的,此时需要为子类显示定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
- 在子类构造方法中,super(…)调用父类构造时,super(…)语句必须要是第一条,否则会报错
- super(…)只能在子类构造方法中出现一次,并且不能和this同时出现。
2.3 super和this
super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,具体他们之间有什么区别呢?
????相同点:
- 都是Java中关键字
- 只能在类的非静态方法中使用,用来访问非静态方法和成员,因为我们的静态方法和成员是不依赖于对象的,直接通过类名访问。
- 在构造方法中调用时,super和this必须是构造方法中的第一条语句,并且不能同时存在
????不同点:
1.this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用。
2.在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
3.在构造方法中:this(…)用于调用本类的构造方法,super(…)用于调用父类的构造方法,两种调用不能同时在构造方法中出现
4.构造方法中一定会存在super(…)调用,用户没有写编译器也会增加,但是this(…)用户不写则没有(切记需要手动添加)✔️
我们还是按照代码的方式来给大家演示一下。
class Animal{
String name;
int age;
public void eat(){
System.out.println(name+" 吃骨头");
}
}
class Dog extends Animal{
String name;
int age;
public void eat(){
System.out.println(name+" 吃狗粮");
}
public void method(){
super.age=10;
super.name="旺财";
System.out.println("我的年龄是:"+super.age);
System.out.println("我的名字是:"+super.name);
super.eat();
System.out.println("华丽的分割线===========");
this.age=20;
this.name="雪球";
System.out.println("我的年龄是:"+this.age);
System.out.println("我的名字是:"+this.name);
this.eat();
}
}
public class Main {
public static void main(String[] args) {
Dog dog =new Dog();
dog.method();
}
}
在上述代码中,我们创建了一个父类和一个子类,super和this的作用就是用来分别调用属于他们自己的方法以及成员。
大家应该还记得只要是对象就会在堆上开辟空间,方法是在栈上的,所以每一个对应的成员在堆上的位置就不同。而我们在之前说过,子类是有两部分组成一部分是继承的父类成员和方法,另一部分就是自己的成员和方法,我们只需要关注子类新增的成员和方法即可。而this就是用来访问当前对象里的成员以及方法。
Part 3????
3.1 再谈初始化
我们提到了静态代码块,构造方法,实例代码块,他们之间的运行顺序是什么样的,谁先被执行?????
先看一组没有继承关系的代码运行结果
class Person{
{
System.out.println("实例代码块");
}
static
{
System.out.println("静态代码块");
}
public Person(){
System.out.println("不带参数的构造方法");
}
}
public class Test {
public static void main(String[] args) {
Person person=new Person();
}
}
????在没有继承关系的代码中:
1.静态代码块最先执行,并且只执行一次,在类加载阶段执行
2.当创建了对象之后,才会执行实例代码块,是依靠对象而产生,实例代码块执行之后才到构造方法执行
有继承关系的代码
class Person{
{
System.out.println("Person:实例代码块");
}
static
{
System.out.println("Person:静态代码块");
}
public Person(){
System.out.println("Person:不带参数的构造方法");
}
}
class Student extends Person{
public Student() {
System.out.println("Student:不带参数的构造方法");
}
{
System.out.println("Student:实例代码块");
}
static{
System.out.println("Studnet:静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Student student1=new Student();
System.out.println("===========");
Student student2=new Student();
}
}
在main方法中,我们实例化了两次子类对象,这两次对不同的代码块又有什么作用呢?????
搭配总结来看图????
1.父类的静态代码块是优先于子类代码块执行的,并且静态代码块是所有代码块最早执行的。
2.父类实例代码块和父类构造方法紧接着执行
3.子类的实例代码块和子类构造方法紧接着再执行
4.第二次实例化子类对象时,父类和子类的静态代码块都不会执行,参照第2次实例化对象,意思我再实例化一次子类对象,父类和子类的静态代码块都不会再出现了。
3.2 protected关键字
在上一节我们提到过了封装的概念,其中包括几个访问限定修饰符,public ,private,default以及protected
private:修饰的成员变量或者是成员方法只能在当前类中使用,类外是不可以使用的
default:默认权限,指的是,只在当前包中使用,出了这个包就不能使用
public:公开的,意味在哪里都是可使用的
那protected呢,就是不同包中的子类是可以访问的
具体如何使用我们看下代码的实现
package demo1;
public class Testdemo1 {
protected int a=22;
public static void main(String[] args) {
Testdemo1 testdemo1=new Testdemo1();
System.out.println(testdemo1.a);
}
}
package demo1;
public class Testdemo2 {
public static void main(String[] args) {
Testdemo1 testdemo1=new Testdemo1();
System.out.println(testdemo1.a);
}
}
以上是同一个包中的不同类,实例化对象后访问成员变量并没有出现报错,那么在继承关系中呢?
package demo2;
import demo1.Testdemo1;
public class Testdemo3 extends Testdemo1 {
public void func(){
System.out.println(super.a);//2.所以重新写了一个方法
}
public static void main(String[] args) {//1.静态方法里面不能有super
Testdemo1 testdemo1=new Testdemo1();
}
}
我们新建了一个demo2的包,并新建了一个类继承Testdemo1,成员变量通过super是可以正常访问的。所以当成员变量是被protected修饰之后,是可以实现在不同包中访问使用的。
这里补充一个点 关于private的在继承关系中的访问
这里的父类Base中是有一个被private修饰的成员变量name,由于是被private修饰,在子类中是出现报错的,那是否被继承了呢?
答:是被继承了,但是无法被访问。
总结????:在我们设计代码的时候,对类要尽量做到封装,对于外部不要暴露出太多,只需要留给挑事者必要的信息就足够,所以尽量使用private来修饰成员变量或者是方法。
3.3 继承方式
关于Java的继承方式只支持一下几种:⬇️
一般不会出现超过三层的继承关系,如果需要从语法上进行限制,就需要用到final关键字
3.4 final关键字
我们简单来说下final关键字,final关键字可以用来修饰变量、成员方法以及类,final有点类似C语言中的const,一旦被final修饰,就变成了常量。
public static void main(String[] args) {
final int A=10;//常量
A=99;//常量的变量名要大写
}
同样的如果类不想被其他类继承在类名的前面加上final,这个类就无法被继承了。
这里还是补充一点
以下的两个代码,哪个是正确的?
1还是2?
public static void main2(String[] args) {
final int[] array={1,2,3};
//以下哪个是对的
1.array =new int[10];
2.array[0]=19;
}
答案是是2,代码2是正确的,为什么呢?
final int[] array={1,2,3};—>是不是在堆上为这个array数组开辟了一块空间
我final修饰的是什么,是array。 final int[] array={1,2,3};证明此时array里面保存的值是不能改变的,换句话说array这个引用指向的对象是不能改变的,但是里面的值可以。
array =new int[10];array已经被final修饰,是无法改变的,但是把下表0/1/2中任意一个数改变是可以做到的。
最后
以上就是年轻小笼包为你收集整理的初识Java之继承前言Part 1????Part 2????Part 3????的全部内容,希望文章能够帮你解决初识Java之继承前言Part 1????Part 2????Part 3????所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复