目录
一、继承的概念
extends含义:继承、延展、扩展
定义:子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使得子类具有父类相同的行为。
生活中的继承:
兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
食草动物和食肉动物又是属于动物类。
所以继承需要符合的关系是:is-a,父类更通用,子类更具体。
虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。
二、为什么需要继承?(为什么出现继承?)什么时候应该继承?
那我们老一代程序员为什么设计方法,当然是解决代码块的重用性/复用性。很久很久以前当一个程序在一个类中写了一个银行存取钱的程序的时候,他发现每次存完钱,要写一个查询余额的逻辑,取完钱也要在写一个查余额的逻辑,转账玩也要写一个查余额的逻辑等等。发现有大量重复的代码逻辑(程序员都很懒的)。然后他为了代码少,将重复的代码给提取出来,放到一个特定的结构中(方法),然后只需要再需要查余额那一块的调用那个特定的逻辑(方法调用)即可。
同样设计类也是为了模板中属性和方法的复用性(当创建每一个对象的时候复用类中的属性和方法,或者说给每一个对象给一份类中的属性和方法)。同样的思路,当我们创建(描述)一个对象的时候发现要用到很多属性和方法。下次我们再次需要创建一个相类似的对象的时候,同样用到相同的属性和方法。比如描述一个人,名字是张三,年龄是18,体重是60kg,会基本的吃喝拉撒。下次我们看见一个李四,想去描述他也是类似的这样做,名字是李四,年龄是22,体重是60kg,会基本的吃喝拉撒。然后我们就想,能不能把相同的抽象,然后就把那些相同属性名,行为(方法)进行抽取,放到一个统一的结构中(类),这就设计出来了类。每个对象复用类中属性名和方法。不同的值可以在对象创建后给赋值。
同理,有一天这个程序员发现设计的类用的不是那么好了,他发现为了描述男人,设计了一个类,为了描述一个女人也设计了一个类,发现这两个类中也有很多相同的部分,比如都有属性名、名字、年龄、体重、基本的吃喝拉撒方法。他就摸摸自己的脑瓜子,能不能像生活中那样,让他们再往顶层归属分类一下(人类,Person),这时候就又提取出来一个类(父类/基类),让这个类和其下的男人类,女人类(子类)发生关系(属于关系),这里我们叫继承。子类继承父类,这样我们就把父类的共有的属性和方法就能调用了/复用了。
总结继承的好处:
1.减少了代码的冗余,提高了代码的复用性
2.便于功能的拓展
3.为之后多态的使用,提供了前提。
为什么我们要用继承,当然是它的那几个好处呗。
三、类的继承格式以及继承了什么?
类的继承格式:
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:
class 父类 {//父类也叫超类、基类、superclass
}
class 子类 extends 父类 {//这是子类,子类也叫派生类、subclass
}
继承了什么:
子类继承了父类的所有属性和方法(但是不能继承构造方法,这里包括所有私有的,和静态的属性和方法)
这些公共的操作是继.承过来的,但是如果子类需要有一些特有的操作,此时应该在子类中单独定义。
四、代码演示继承的由来:
代码中的继承同样也是来自于生活中,并抽象成java中的一种格式/结构。
请看现实生活中的继承:下图
企鹅类:
public class Penguin {
private String name;
private int id;
public Penguin(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
老鼠类:
public class Mouse {
private String name;
private int id;
public Mouse(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
从这两段代码可以看出来,代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高(维护性主要是后期需要修改的时候,就需要修改很多的代码,容易出错),所以要从根本上解决这两段代码的问题,就需要继承,将两段代码中相同的部分提取出来组成 一个父类:
公共父类:
public class Animal {
private String name;
private int id;
public Animal(String myName, int myid) {
name = myName;
id = myid;
}
public void eat(){
System.out.println(name+"正在吃");
}
public void sleep(){
System.out.println(name+"正在睡");
}
public void introduction() {
System.out.println("大家好!我是" + id + "号" + name + ".");
}
}
这个Animal类就可以作为一个父类,然后企鹅类和老鼠类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:
企鹅类:
public class Penguin extends Animal {
public Penguin(String myName, int myid) {
super(myName, myid);
}
}
老鼠类:
public class Mouse extends Animal {
public Mouse(String myName, int myid) {
super(myName, myid);
}
}
总结,当出现成百上千个子类的时候,这个动物父类优势就明显出来了。提高了类的复用性。一旦子类继承了父类以后,子类就获取了父类中声明的结构:属性、方法。
五、java中关于继承的规定(重要):
1.一个类可以被多个类继承。
2.java中类的单继承性:一个类只能有一个父类,但是可以多层继承。
3.子父类是相对的概念。
4.子类直接继承的父类,称为直接父类,间接继承的父类称为间接父类。
5.子类继承父类后,就获取了直接父类以及所有间接父类中的声明的属性和方法。
六、java中子类有没有继承父类的私有属性?
总结:
从继承的概念来说,private和final不被继承。Java官方文档上是这么说的(以后不建议这么说)。
从内存的角度来说,父类的一切都被继承(从父类构造方法被调用就知道了,因为new一个对象,就会调用构造方法,子类被new的时候就会调用父类的构造方法,所以从内存的角度来说,子类拥有一个完整的父类)。子类对象所引用的内存有父类变量的一份拷贝。
如图所示,父类为Person类,子类为Student类。首先明确子类不能继承父类的构造方法。这就是为什么子类的默认的构造方法会自动调用父类的默认的构造方法。
在子类的构造方法中通过super()方法调用父类的构造方法。也就是,在构造子类的同时,为子类构造出跟父类相同的域。如此就在子类的对象中,也拥有了父类声明的域了。
代码演示:
Person 父类:
package com.fan.domain;
public class Person {//作为父类被继承复用的
String name;
private int age;//验证父类私有属性能不能被子类继承
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//验证父类私有方法能不能被子类继承
private void show(){
System.out.println("父类私有方法");
}
public void eat(){
show();//调用私有方法
}
}
Student 子类:
package com.fan.domain;
public class Student extends Person {
int score;//成绩属性
/*
public void setAge(int age) {
this.age = age;
}
* */
//当我们继承了父类的setAge方法后,此方法中有一个this.age,此处的age就是本类的属性
//而在测试类能正常使用setAge方法,则证明私有属性能被参数赋值到。所以说私有属性被子类继承了。
}
StudentTest 测试类
package com.fan.domain;
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
student.setAge(10);//测试私有属性age能不能被继承
System.out.println(student.getAge());//10
System.out.println("=================");
student.eat();
}
}
七、继承的体现:
1.从代码验证来看,私有的结构(属性/方法等)也被子类继承到了,但是由于封装性的影响,使得子类不能直接调用父类的结构而已。
2.子类继承父类以后,还可以声明自己特有的属性和方法:实现功能的拓展
需要注意的是 Java 不支持多继承,但支持多层继承:
Java 的继承是单继承,但是可以多层继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
如下图:。
继承案例:
修改上面Student类:
学生类:
package com.fan.domain;
public class Student extends Person {
int score;//成绩属性
/*
public void setAge(int age) {
this.age = age;
}
* */
public int testPrivate() {//年龄-3的一个模拟操作
return getAge() - 3;//getAge()间接的获取了父类的私有属性age
}
//当我们继承了父类的setAge方法后,此方法中有一个this.age,此处的age就是本类的属性
//而在测试类能正常使用setAge方法,则证明私有属性能被参数赋值到。所以说私有属性被子类继承了。
}
说明:
return getAge() - 3;//getAge()间接的获取了父类的私有属性age
八、继承的最顶级父类:Object类
说明:
1.所有我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
2.所有的java类(除java.lang.Object类之外)都直接或者间接的继承于java.lang.Object类。
3.意味着,所有的Java类具有java.lang.Object类声明的功能。
九、继承中的方法重写:
重写:override/overwrite
1.重写定义:
子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。(即外壳不变,核心重写!)
2.应用:
重写以后,当创建子类对象以后,通过子类对象调用父类中的同名同参数的方法时,实际执行的是子类重写后的方法。
3.重写的规则:(面试问题)
方法的声明:
权限修饰符 返回值类型 方法名(参数列表){
//方法体
}
约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法
①:子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
②:子类重写的方法的权限修饰符不小于(大于等于)父类被重写的方法的权限修饰符
(这好比,我们要做一个煎饼果子,先做一个小的,然后在其上面做一个大的把原先的覆盖掉,这时候是不是要把后面的范围(面积)做大点/或者相同大小,才能盖住,这个大小就是权限修饰符,同理我们穿衣服要盖住我们里边的衣服,是不是要穿的比里面的衣服相同大写或者比里面的大,才能保证里面的不被看见)
2.1特殊情况:子类不能重写父类中声明为private权限的方法。
③:返回值类型:
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void.
- 父类被重写的方法的返回值是A类型,则子类重写的方法的返回值类型只能是A类或A类的子类。
- 父类重写的方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值必须是相同的,都是double.
④子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
3.1 Java中父类的私有方法不被子类重写
父类中的私有方法在子类中拥有但不能被访问,既然子类都无法访问,当然就无法重写了。
代码演示:
public class Test {
public static void main(String[] args) {
Cat c1 = new Cat();
c1.function();
}
}
class Animal {
// 父类的私有方法,不被子类看见
private void show() {
System.out.println("父类私有方法");
}
}
class Cat extends Animal {
public void function() {
show();
}
// 此处为子类自定义,而不叫重写,因为子类无法看到且无法访问父类的私有show()方法
private void show() {
System.out.println("子类的私有方法");
System.out.println("--------------");
}
}
打印结果:
子类的私有方法
结论:
(1)父类的私有方法,可理解为被子类继承,但是不被子类可见,不可访问。
(2)子类和父类的同名私有方法,为子类自定义方法,不是重写父类的私有方法。
(3)解释:A继承A爸的密码箱,但是见不到密码箱(大小,颜色,重量等都不知道),A如果要自己做个密码箱,不是仿照A爸的密码箱,而是A特有的密码箱(大小,颜色,重量都和A爸不一样)。
3.2 Java中父类的静态方法不被子类重写
代码演示静态方法可以被继承,但是不能被覆盖/重写:
public class Test {
public static void main(String[] args) {
/**
* 结论:
* 静态方法可以被继承,但是不能被覆盖,即不能重写。
* */
Son.staticMethod(); // 运行结果:Father staticMethod
}
}
class Father {
public static void staticMethod() {
System.out.println("Father staticMethod");
}
}
class Son extends Father {
}
public class Test {
public static void main(String[] args) {
Father.staticMethod(); // 运行结果:Father staticMethod
/**
* 结论:
* 类执行了自己申明的静态方法。
* 该子类实际上只是将父类中的同名静态方法进行了隐藏,而非重写。
* */
Son.staticMethod(); // 运行结果:Son staticMethod
Father father = new Son();
/**
* 结论:
* 父类引用指向子类对象时,只会调用父类的静态方法。
* 父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
* */
father.staticMethod(); // 运行结果:Father staticMethod
}
}
class Father {
public static void staticMethod() {
System.out.println("Father staticMethod");
}
}
class Son extends Father {
public static void staticMethod() {
System.out.println("Son staticMethod");
}
}
总结:
1.在Java中静态方法可以被继承,但是不能被覆盖,即不能重写。
2.如果子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。
3.父类引用指向子类对象时,只会调用父类的静态方法。所以,它们的行为也并不具有多态性。
4.重写其他注意事项或规定:
(1):如果不能继承一个类,则不能重写该类的方法。
(2): 子类和父类的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写),即声明为 static 的方法不能被重写,但是能够被再次声明。
(3):声明为 final 的方法不能被重写。
(4):子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
(5):子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
(6): 构造方法不能被重写。
可以通过代码验证:
Person 父类:
package com.fan.domain;
public class Person {//作为父类被继承复用的
String name;
private int age;//验证父类私有属性能不能被子类继承
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
private void show(){
System.out.println("父类私有展示方法");
}
public void sleep(){
System.out.println("父类睡觉");
}
public void eat(){
show();//调用私有方法,不能被重写
sleep();//调用公共方法,被子类重/覆盖了
}
}
Student 子类:
package com.fan.domain;
public class Student extends Person {
int score;//成绩属性
//这个方法不是重写;如果是重写,则可以通过:子类对象.show()调用,并显示“子类私有方法”
//下面通过测试类进行验证
public void show(){//private 方法
System.out.println("子类私有展示方法");
}
//重写的父类sleep方法,非private 方法
public void sleep(){
System.out.println("子类睡觉");
}
}
测试类StudentTest :
package com.fan.domain;
public class StudentTest {
public static void main(String[] args) {
Student student = new Student();
System.out.println("=================");
student.eat();
}
}
结果:
=================
父类私有展示方法
子类睡觉
两个方法看起来都是重写,实际上父类的私有方法没有被重写。