第四章 继承
课前回顾
1.如何进行封装
首先将类中定义的成员属性全部修改为private修饰
然后对每一个属性提供一个对外访问的方法,也就是生成getter/setter方法
最后再对外访问的方法(getter/setter)中加入属性值验证
2.封装的好处
提高了代码的重用性
提高了代码的可维护性
保护了隐私,能够很好保护代码的实现逻辑
3.包的作用
package 包名; //一般来说包名都是小写字母组成,每个包之间使用'.'隔开
包的本质其实就是一个文件夹,包可以用来防止命名冲突,可以保护属性,可以对访问权限进行控制。
4.访问修饰符的控制权限
访问修饰符:public protected 默认修饰符 private
能够修饰类的访问修饰符: public 默认修饰符 private (只能用来修饰内部类)
public修饰的类整个工程中都可以访问,默认修饰符修饰的类只能在同一个包中访问
能够修饰成员的访问修饰符:public protected 默认修饰符 private
public修饰的成员整个工程都可以访问,protected修饰的成员只能在同一个包中或者子类中访问,默认修饰符修饰的成员只能在同一个包中访问,private修饰的成员只能在本类中访问
5.static修饰符使用范围
static能够用来修饰类,成员变量,方法以及静态代码块。需要注意的是static修饰类时只能修饰内部类
static修饰的变量称之为类变量(公开的类变量的访问语法:类名.变量名)
static修饰的方法称之为类方法(公开的类方法的访问语法:类名.方法)
static修饰的代码块称之为静态代码块,静态代码块在JVM第一次加载类时执行,而且只会执行一次。
第一节 继承(Inheritance)
1.概念
继承(Inheritance) 来自官方的说明
The idea of inheritance is simple but powerful: When you want to create a new class and there is already a class that includes some of the code that you want, you can derive your new class from the existing class. In doing this, you can reuse the fields and methods of the existing class without having to write (and debug!) them yourself.
继承的概念很简单但是很强大:当你要创建一个新类并且已经有一个包含所需代码的类时,可以从现有类中诞生新类。这样,你可以重用现有类的字段和方法,而不必自己编写(和调试!)它们。A subclass inherits all the members (fields, methods, and nested classes) from its superclass. Constructors are not members, so they are not inherited by subclasses, but the constructor of the superclass can be invoked from the subclass.
子类从其父类继承所有成员(字段、方法和嵌套类)。构造方法不是成员,因此它们不会被子类继承,但是可以从子类中调用父类的构造方法
A class that is derived from another class is called a subclass (also a derived class, extended class, or child class). The class from which the subclass is derived is called a superclass (also a base class or a parent class).从一个类诞生的类称为子类(也可以是诞生类、拓展类或子类)。诞生子类的类称为超类(也称为基类或父类)。
继承也是面向对象的三大特征之一
2.如何使用继承
语法
public class 子类名 extends 父类名 {}
示例
package com.cyx.inheritance;public class Father {public String name;public String sex;public void eat(){System.out.println("吃饭");}public void sleep(){System.out.println("睡觉");}
}package com.cyx.inheritance;public class Child extends Father{public void show(){//本类中未定义name,sex变量,但是却可以使用。说明name,sex变量是从父类中继承过来的System.out.println(name);System.out.println(sex);//本类中未定义eat(),sleep()方法,但是却可以使用。说明eat(),sleep()方法是从父类中继承过来的eat();sleep();}
}package com.cyx.inheritance;public class FatherTest {public static void main(String[] args) {Child child = new Child();child.name ="张三";child.sex = "男";child.eat();child.sleep();child.show();}
}
编译结果为:
继承的属性和方法
官方说明:
A subclass inherits all of the public and protected members of its parent, no matter what package the subclass is in. If the subclass is in the same package as its parent, it also inherits the package-private members of the parent.
不论子类在什么包中,子类会继承父类中所有的公开的和受保护的成员(包括字段和方法)。如果子类和父类在同一个包中,子类也会继承父类中受保护的成员。A subclass does not inherit the private members of its parent class. However, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass.
子类不会继承父类中的私有成员,尽管如此,如果父类有提供公开或者受保护的访问该字段的方法,这些方法也能在子类中被使用。
示例
package com.cyx.inheritance;public class Father {String name;public String sex;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}protected void eat(){System.out.println("吃饭");}public void sleep(){System.out.println("睡觉");}
}//跟Father在同一包中的子类
package com.cyx.inheritance;public class Child extends Father{public void show(){//本类中未定义name,sex变量,但是却可以使用。说明name,sex变量是从父类中继承过来的System.out.println(name);System.out.println(sex);//本类中未定义eat(),sleep()方法,但是却可以使用。说明eat(),sleep()方法是从父类中继承过来的eat();sleep();}
}//跟Father不在同一包中的子类
package com.cyx.inheritance.p1;import com.cyx.inheritance.Father;public class Child extends Father {public void show(){System.out.println(getName());//因为父类中的name变量受包保护,所以只能通过公开的方法来调用System.out.println(sex);eat();sleep();}
}
3.应用场景
描述程序员(Programmer)和医生(Doctor)的特征和行为
从类图中可以看出,Programmer和Doctor两个类中存在大量重复的代码,可以使用继承来进行优化,以减少重复的编码。
//Person父类
package com.cyx.inheritance.p2;public class Person {private String name;private String sex;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public void eat(){System.out.println("吃饭");}
}//Programmer子类
package com.cyx.inheritance.p2;public class Programmer extends Person{private int level;public int getLevel() {return level;}public void setLevel(int level) {this.level = level;}public void programming(){System.out.println("程序员编程");}
}// Doctor子类
package com.cyx.inheritance.p2;public class Doctor extends Person{private String professional;public String getProfessional() {return professional;}public void setProfessional(String professional) {this.professional = professional;}public void cure(){System.out.println("医生治病");}
}
子类与父类的关系是is - a关系,表示“XXX子类是一个XXX父类”。
举例:
解决方法类似于强制类型转换,对类进行转换:
如果一个对象赋值给其父类的引用,此时如果想要调用该对象的特有的方法,必须要进行强制类型转换
第二节 方法重写(Override)
1.概念
来自官方的说明
An instance method in a subclass with the same signature (name, plus the number and the type of its parameters) and return type as an instance method in the superclass overrides the superclass's method.
子类中的一个成员方法与父类中的成员方法有相同的签名(方法名加上参数数量和参数类型)和返回值类型的实例方法重写了父类的方法。
2.如何使用方法重写
The ability of a subclass to override a method allows a class to inherit from a superclass whose behavior is "close enough" and then to modify behavior as needed. The overriding method has the same name, number and type of parameters, and return type as the method that it overrides. An overriding method can also return a subtype of the type returned by the overridden method. This subtype is called a covariant return type.
子类重写方法的能力使类可以从行为“足够接近”的父类继承,然后根据需要修改行为。重写方法与被重写的方法具有相同的名称,数量和参数类型,并且返回值相同。重写方法还可以返回重写方法的类型的子类型。此子类型称为协变返回类型。When overriding a method, you might want to use the @Override annotation that instructs the compiler that you intend to override a method in the superclass. If, for some reason, the compiler detects that the method does not exist in one of the superclasses, then it will generate an error.
重写方法时,您可能需要使用@Override注释,该注释指示编译器您打算重写父类中的方法。如果出于某种原因,编译器检测到该方法在父类中不存在,则它将生成错误。
举例:
我们平时所用的返回值的数据类型其实也是一个类:
数据类型 | byte | short | int | long | float | double |
---|---|---|---|---|---|---|
返回值的数据类型类 | Byte | Short | Integer | Long | Float | Double |
这六个返回值数据类型对应的类都是Number的子类
package com.cyx.inheritance.number;public class NumberTest {public static void main(String[] args) {Byte b = 1; //相当于byte b = 1;Short s =1; //相当于short s = 1;Integer i = 1; // 相当于int i =1;Long l = 1L; //相当于long l = 1L;Double d = 1.0; //相当于double d = 1.0;Float f = 1.0f; //相当于float f = 1.0f;}
}
package com.cyx.inheritance.p2;public class Person {public Number getScore(){return 0;}
}package com.cyx.inheritance.p2;public class Doctor extends Person{//子类重写父类方法时,返回值类型可以是父类方法的返回值类型的子类@Override //该注解告诉编译器这是一个重写的方法,编译器会去检测父类中是否存在这样的方法,如果不存在,则将生成一个错误public Integer getScore(){ //Integer是Number的子类return 90;}
}
案例
几何图形都有面积和周长,不同的几何图形,面积和周长的算法也不一样。矩形有长和宽,通过长和宽能够计算矩形的面积和周长;圆有半径,通过半径可以计算圆的面积和周长。请使用继承相关的知识完成程序设计。
分析
a.几何图形包含了矩形和圆。几何图形都有面积和周长,因此几何图形可以定义为一个类,里面包含了面积和周长的计算方法
b.矩形是一种几何图形。矩形有长和宽,可以根据长和宽来计算面积和周长
c.圆是一种几何图形。圆有半径,可以根据半径来计算面积和周长
代码实现
package com.cyx.inheritance.shape;public class Circle extends Shape{private int radius;public Circle(int radius){this.radius = radius;}@Overridepublic Double calculatePerimeter() {return Math.PI * radius * radius;}@Overridepublic Double calculateArea() {return 2 * Math.PI * radius;}
}package com.cyx.inheritance.shape;/*** 矩形* */
public class Rectangle extends Shape{private int width;private int length;public Rectangle(int width,int length){this.width = width;this.length = length;}@Overridepublic Integer calculatePerimeter() {return (width + length) * 2;}@Overridepublic Integer calculateArea() {return width * length;}
}package com.cyx.inheritance.shape;/*** 圆**/
public class Circle extends Shape{private int radius;public Circle(int radius){this.radius = radius;}@Overridepublic Double calculatePerimeter() {return Math.PI * radius * radius;}@Overridepublic Double calculateArea() {return 2 * Math.PI * radius;}
}package com.cyx.inheritance.shape;public class ShapeTest {public static void main(String[] args) {Shape s1 = new Rectangle(10,9);System.out.println(s1.calculatePerimeter());System.out.println(s1.calculateArea());Shape s2 = new Circle(5);System.out.println(s2.calculatePerimeter());System.out.println(s2.calculateArea());}
}
编译结果为:
重写方法是访问修饰符的级别不能降低(public -> private),但可以提高(private -> public)。
3.super关键字
来自官方的说明
super关键字
If a constructor does not explicitly invoke a superclass constructor, the Java compiler automatically inserts a call to the no-argument constructor of the superclass. If the super class does not have a no-argument constructor, you will get a compile-time error. Object does have such a constructor, so if Object is the only superclass, there is no problem.如果子类的构造方法没有明确调用父类的构造方法,Java编译器会自动插入一个父类无参构造的调用。如果父类没有无参构造,你将得到一个编译时错误。object类有一个无参构造,因此,如果object类是该类的唯一父类,这就没有问题。
示例一:子类和父类中都没有定义构造方法
package com.cyx.inheritance;public class Father {String name;public String sex;public Father() {super(); //Father的父类是Object}
}package com.cyx.inheritance;public class Child extends Father{//如果一个类中没有定义构造方法,那么编译器将会给该类插入一个无参构造方法public Child(){super(); //如果子类构造方法中没有显式地调用父类的构造方法,那么编译器会自动插入一个父类的无参调用}
}
示例二:子类中有定义构造方法,父类没有定义构造方法
package com.cyx.inheritance;public class Father {String name;public String sex;// public Father() {
// super(); //Father的父类是Object
//
// }
}package com.cyx.inheritance;public class Child extends Father{//如果一个类中没有定义构造方法,那么编译器将会给该类插入一个无参构造方法public Child(){super(); //如果子类构造方法中没有显式地调用父类的构造方法,那么编译器会自动插入一个父类的无参调用}public Child(String name){super();this.name = name;}
}
示例三:子类和父类中都有定义构造方法
package com.cyx.inheritance;public class Father {String name;public String sex;public Father(String name,String sex){this.name = name;this.sex = sex;}
}package com.cyx.inheritance;public class Child extends Father{public Child(String name,String sex){//如果父类中定义了带参构造,并且没有定义无参构造,那么必须在子类的构造方法中显式地调用父类的带参构造super(name, sex);this.name = name;this.sex = sex;}
}
使用super调用父类的构造方法时,必须为这个构造方法的第一条语句
小结:子类重写构造方法时:
1.如果父类没有写带参构造方法,那么子类可以重写构造方法时可以不加super()。(编译器会自动补上)
2.如果父类写了带参的构造方法,那么子类在重写构造方法时必须在第一条语句写上super(父类的参数)
来自官方的说明
If your method overrides one of its superclass's methods, you can invoke the overridden method through the use of the keyword super. You can also use super to refer to a hidden field (although hiding fields is discouraged).
如果你的方法重写了父类的方法之一,则可以通过使用关键字super来调用父类中被重写的方法。你也可以使用super来引用隐藏字段(尽管不建议使用隐藏字段)
示例
package com.cyx.inheritance.p3;public class Person {protected String name;protected String sex;public String getSex() {return sex;}public void setSex(String sex) {this.sex = sex;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}package com.cyx.inheritance.p3;public class Student extends Person{private String name;public Student(String name) {this.name = name;}@Overridepublic String getName() {return name;}public void show(){System.out.println(this.name); //访问本类中定义的name变量System.out.println(super.name); //访问父类中定义的变量//如果子类中和父类中没有相同的成员变量此时使用this和super均可以调用父类的成员变量System.out.println(this.sex);System.out.println(super.sex);System.out.println(this.getName());System.out.println(super.getName());}
}package com.cyx.inheritance.p3;public class PersonTest {public static void main(String[] args) {Student s = new Student("张三");s.setSex("男");s.show();}
}
编译结果为:
小结:子类重写方法时:
- this.成员 访问的是本类(子类)中的成员 ; super.成员 访问的是父类中的成员
- 如要要调用子类中的private修饰的成员,需要在子类中重写getter方法,如果不在子类中重写,那么getter方法返回的成员就是父类中的成员
- 如果子类中没有写父类的变量,那么this和super均是调用的父类的成员变量
思考:如果子类中的静态方法与父类中的静态方法具有相同的签名,是否属于方法重写?
不属于方法重写,因为静态方法称之为类方法,跟对象无关,调用时只看对象的引用类型
package com.cyx.inheritance.p4;public class StaticFather {public static void show(){System.out.println("这是父类的静态方法");}}package com.cyx.inheritance.p4;public class StaticChild extends StaticFather{public static void show(){System.out.println("这是子类的静态方法");}
}package com.cyx.inheritance.p4;public class StaticTest {public static void main(String[] args) {StaticFather f = new StaticChild();//StaticFather是对象的引用类型,StaticChild是对象,静态方法只看对象的引用类型,和对象无关f.show();//所以要用静态方法可以直接写成:StaticFather.show();StaticChild.show();}
}
编译结果为:
4.万物皆对象
来自官方的说明
Excepting object, which has no superclass, every class has one and only one direct superclass (single inheritance). In the absence of any other explicit superclass, every class is implicitly a subclass of object.
除了没有父类的object之外,每个类都有一个且只有一个直接父类(单继承)。在没有其他任何显式超类的情况下,每个类都隐式为object的子类。
classes can be derived from classes that are derived from classes that are derived from classes, and so on, and ultimately derived from the topmost class, object. such a class is said to be descended from all the classes in the inheritance chain stretching back to object.
类可以派生自另一个类,另一个类又可以派生自另一个类,依此类推,并最终派生自最顶层的类object。据说这样的类是继承链中所有类的后代,并延伸到object。
所有类都是object的子类,因此,创建对象时都需要调用Object类中的无参构造方法,而Object本身就表示对象,因此创建出来的都是对象。
练习(使用继承完成)
动物都有名称和年龄,都需要吃东西、睡觉。狗也是一种动物,也有名称和年龄,狗吃的是骨头,睡觉时是趴着睡。马也是一种动物,也有名称和年龄,马吃的是草,睡觉的时候站着睡。
package com.cyx.inheritance.animals;public class Animals {protected String name;protected int age;public Animals(String name,int age){this.name = name;this.age = age;}protected void eat(){System.out.println("吃");}protected void sleep(){System.out.println("睡");}
}package com.cyx.inheritance.animals;public class Dog extends Animals{public Dog(String name,int age){super(name, age);}@Overrideprotected void eat() {System.out.println(name+"吃骨头");}@Overrideprotected void sleep() {System.out.println(name+"趴着睡觉");}
}package com.cyx.inheritance.animals;public class Horse extends Animals{public Horse(String name,int age){super(name, age);}@Overrideprotected void eat() {System.out.println(name+"吃草");}@Overrideprotected void sleep() {System.out.println(name+"站着睡觉");}
}package com.cyx.inheritance.animals;public class AnimalsTest {public static void main(String[] args) {Animals a = new Dog("狗",11);a.eat();a.sleep();Animals b = new Horse("马",13);b.eat();b.sleep();}
}
编译结果为:
某公司员工有内部员工和临时工两种。不论是内部员工还是临时工,都具有姓名,员工编号,所属部门,薪资。但临时工干的活就是一些较为粗重的,内部员工干的活都是一些较为轻松的。
package com.cyx.inheritance.staffs;public class Staffs {protected String name;protected int number;protected String department;protected double salary;public Staffs(String name,int number,String department,double salary){this.name = name;this.number = number;this.department = department;this.salary = salary;}protected void work(){System.out.println("工作");}}package com.cyx.inheritance.staffs;public class InternalStaffs extends Staffs{public InternalStaffs(String name,int number,String department,double salary){super(name, number, department, salary);}@Overrideprotected void work() {System.out.println(department+"部门的"+"编号为"+number+"的"+name+"干的是一些轻松的活"+",工资为每月"+salary);}
}package com.cyx.inheritance.staffs;public class OuterStaffs extends Staffs{public OuterStaffs(String name,int number,String department,double salary){super(name, number, department, salary);}@Overrideprotected void work() {System.out.println(department+"部门的"+"编号为"+number+"的"+name+"干的是一些粗重的活"+",工资为每月"+salary);}
}package com.cyx.inheritance.staffs;public class StaffsTest {public static void main(String[] args) {Staffs s1 = new InternalStaffs("张三",1001,"开发部",10000.3);s1.work();Staffs s2 = new OuterStaffs("李四",2001,"搬运部",5000.45);s2.work();}
}
编译结果为:
第三节 final修饰符
1.应用范围
final修饰符应该使用在类、变量以及方法上
2.final修饰类
来自官方的说明
Note that you can also declare an entire class final. A class that is declared final cannot be subclassed. This is particularly useful, for example, when creating an immutable class like the String class.
注意,你也可以声明这个类的final。声明为final的类不能被子类化。例如,当创建不可变类(如String类)时,这特别有用。
如果一个类被final修饰,表示这个类是最终的类,因此这个类不能够再被继承,因为继承就是对类进行扩展。
示例
package com.cyx.inheritance._final;public final class FinalClass {public void show(){System.out.println("这是最终类里面的方法");}
}
3.final修饰方法
You can declare some or all of a class's methods final. You use the final keyword in a method declaration to indicate that the method cannot be overridden by subclasses. The Object class does this—a number of its methods are final.
你可以将类的某些或者所有方法声明为final。在方法声明中使用final关键字表示该方法不能被子类覆盖。Object类就是这样做的-它的许多方法都是最终的。
示例
package com.cyx.inheritance._final;public class FinalMethod {public final void show(){System.out.println("这是一个最终方法,不能被重写");}
}
4.final修饰变量
final修饰变量的时候,变量必须在对象构建时完成初始化。final修饰的变量称为常量。
示例
final修饰的变量一定要在对象创建时完成赋值操作,final修饰的变量称之为常量,不可被更改。
赋值有两种常见方法:
或者
final修饰的变量称之为常量,不可被更改。
static和final修饰的变量就是静态常量。
package com.cyx.inheritance._final;public class FinalVariable {//final修饰的变量一定要在对象创建时完成赋值操作,final修饰的变量称之为常量,不可被更改private final int number;//static和final修饰的变量就是静态常量public static final String COUNTRY = "中国";public FinalVariable(){this.number = 10;}public void change(){
// this.number = 11; //因为number是一个常量,不能再被更改,因此会报编译错误}
}
思考如下代码的执行过程
package com.cyx.inheritance.test;public class Father {static {System.out.println("父类静态代码块执行");}public Father(){super();System.out.println("父类构造方法执行");}
}
package com.cyx.inheritance.test;public class Child extends Father{static {System.out.println("子类静态代码块执行");}public Child(){super();System.out.println("子类构造方法执行");}
}
package com.cyx.inheritance.test;public class Test {public static void main(String[] args) {new Child();}
}
最终编译结果为:
【解析】
构建Child对象时,发现Child是Father的子类,而Father又是Object的子类,因此JVM会首先加载Object类,然后加载Father类,最后加载Child类。而静态代码是在类第一次加载时执行,而且只会执行一次。因此Father类中的静态代码块先执行,然后再执行Child类中的静态代码块,最后才执行new Child(); 的代码。执行new Child();的内容时,编译器会自动调用父类的无参构造方法(即super()),所以先打印Father类中无参构造方法的内容,再打印Child类中构造方法的内容。所以编译结果如上图所示。