1. 面向对象的特性之二——继承性
一、继承的好处
- 减少代码的冗余,提高代码的复用性
- 便于功能的扩展
- 为多态性的使用提供前提
二、继承性的格式:class A extends B{}
- A:子类、派生类、subclass
- B:父类、超类、基类、superclass
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别地,父类中声明为private的属性或方法,子类继承父类以后,仍然获取了父类中私有的结构。只因为有封装性的影响,使得子类不能直接调用父类的结构而已。
- 子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。
三、Java中关于继承性的规定
- 一个类可以被多个子类继承
- 一个类只能有一个父类:java中类的单继承
- 子父类是相对的概念。子类直接继承的的父类称为直接父类,间接继承的父类称为间接父类。
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
四、如果没有显式的声明一个类的父类,则此类继承于java.lang.Object类。所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类(任何类都是Object类的子类)。意味着,所有的java类具有java.lang.Object类声明的功能。
public class ExtendsTest{
public static void main(String[] args){
Person p = new Person();
p.eat();
Student s = new Student();
s.eat();
}
}
class Person{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
class Student entends Person{
// String name;
// int age;
String major;
public Person(){
}
public Person(String name,int age,String){
this.name = name;
this.age = age;
}
// public void eat(){
// System.out.println("吃饭");
// }
// public void sleep(){
// System.out.println("睡觉");
// }
public void study(){
System.out.println("学习");
}
}
练习1:
package extendstest;
public class ManKind {
private int sex;//性别
private int salary;//薪资
public void setSex(int sex) {
this.sex = sex;
}
public int getSex(){
return sex;
}
public void setSalary(int salary){
this.salary = salary;
}
public int getSalary(){
return salary;
}
public void manOrWoman(){
if(sex == 1){
System.out.println("man");
}else if(sex == 0){
System.out.println("woman");
}else{
System.out.println("请输入0或1");
}
}
public void employeed(){
if(salary == 0){
System.out.println("no job");
}else{
System.out.println("job");
}
}
}
package extendstest;
public class Kids extends ManKind{
int yearOld;
public Kids(int yearOld){
this.yearOld = yearOld;
}
public void printAge(){
System.out.println("I am " + yearOld + " years old");
}
}
package extendstest;
public class KidsTest {
public static void main(String[] args) {
Kids someKids = new Kids(12);
someKids.printAge();
someKids.setSex(1);
someKids.setSalary(10000);
someKids.manOrWoman();
someKids.employeed();
}
}
2. 方法的重写(override/overwrite)
1、重写:在子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作。在程序执行时,子类的方法将覆盖父类的方法。
2、应用:重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参的方法时,实际执行的是子类重写的父类的方法。
3、重写的规定:从以下几个方面说明
方法的声明:
权限修饰符 返回值类型 方法名(形参列表) throws 异常的类型{
方法体;
}
- 约定俗成:子类中的叫重写的方法,父类中的叫被重写的方法;
- 子类重写的方法的方法名和形参列表与父类中被重写的方法的方法名和形参列表相同;
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符;
①特殊情况:子类不能重写父类中声明为private权限的方法 - 返回值类型:
①父类被重写的方法的返回值类型是void,则子类中重写的方法的返回值类型只能是void;
②若父类被重写方法的返回值类型是A 类型(比如Object),则子类重写的方法的返回值类型可以是A类或A的子类(String或Object)
③若父类被重写方法的返回值类型是基本数据类型(比如double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。 - 子类重写的方法抛出的异常类型不大于被重写的方法抛出的异常类型
- 子类和父类中同名同参数的方法要么都声明为非static的(此时才需要考虑重写),要么都声明为static的(但此时已不是方法的重写)
public class Person{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("人可以吃饭");
}
public void walk(int distance){
System.out.println("走路,走的距离是:" + distance + "公里");
}
}
public class Student extends Person{
String major;
public Student(){
}
public Student(String major){
this.major = major;
}
public void study(){
System.out.println("学习,专业是" + major);
public void eat(){//重写父类中的eat方法
System.out.println("学生应该多吃有营养的食物");
}
}
}
public class PersinTest{
public static void main(){
Student s = new Student("计算机科学与技术");
s.eat();
s.walk(10);
s.study();
}
}
3.区分方法的重写和重载
-
二者的概念:
重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
重写:在子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作。 -
重载和重写的具体规则
重载:与方法的权限修饰符、返回值类型、形参变量名、方法体都无关,只看参数列表,且参数列表(参数个数或参数类型)必须不同。
重写:见上面重写的规定 -
重载:不表现为多态性
重写:表现为多态性 -
重载在编译期就确定了所要调用的方法,而重写只有在运行期才能确定具体的方法。
4.super的使用
super:理解为:父类的
super可以用来调用:属性、方法、构造器
super的使用:
- 我们可以在子类的方法或构造器中,通过使用 “super.属性” 或 “super.方法” 的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略 “super.”
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式使用 “super.属性”的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们要想在子类中调用父类中被重写的方法时,则必须显式使用 “super.方法” 的方式,表明调用的是父类中被重写的方法。
- super调用构造器
① 我们可以在子类的构造器中显式的使用 “super(形参列表)” 的方式,调用父类中声明的指定的构造器
② “super(形参列表)”的使用,必须声明在子类构造器的首行!
③ 我们在类的构造器中,针对于 “this(形参列表)” 或 “super(形参列表)” 只能二选一,不能同时出现(因为都必须声明在子类构造器的首行)
④ 在构造器的首行,没有显式的声明 “this(形参列表)” 或 “super(形参列表)” ,则默认调用的是父类中空参的构造器(super())
⑤ 在类的多个构造器中,至少有一个类的构造器中使用了 “super(形参列表)” ,调用父类中的构造器。
子类对象实例化的全过程(帮助理解为什么继承)
-
从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。 -
从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。 -
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
package supertest;
public class Person {
String name;
int age;
int id = 1001;//身份证号
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("人可以吃饭");
}
public void walk(int distance){
System.out.println("走路,走的距离是:" + distance + "公里");
}
}
package supertest;
public class Student extends Person{
String major;
int id = 1002;//学号(和父类的id同名)
public Student(){
}
public Student(String major){
this.major = major;
}
public Student(String name,int age,String major){
//super调用父类构造器
super(name,age);
this.major = major;
}
public void study() {
System.out.println("学习,专业是" + major);
}
public void eat(){//对父类中的该方法进行重写
System.out.println("学生应该多吃有营养的食物");
}
public void show(){
this.eat();//或eat(); 表示调用自己的eat方法
super.eat();//调用父类的eat方法
//因为walk方法没有重写,所以不管哪种写法最终调的都是父类的walk方法
this.walk(20);//或super.walk();或this.walk();
System.out.println("name = " + name + " age = " + age);
System.out.println("身份证号是 " + super.id);
System.out.println("学号是" + id);//或this.id均可
}
}
package supertest;
public class SuperTest {
public static void main(String[] args) {
Student s = new Student("计算机科学与技术");
Student s = new Student("Tom","21","计算机科学与技术");
s.show();
}
}