本质
复用已存在的方法和域
概念
继承是java面向对象编程的基石,继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
extends关键字
Java使用extends关键字来实现继承;
创建Person类
public class Person { private String name; public Person(String aname){ this.name=aname; } public void run() { System.out.println("任何人都可以跑。。。"); } }
创建Student类,继承Person
public class Student extends Person{ private String ID; public Student(String aname,String aID){ super(aname); //调用父类的构造方法 this.ID=aID; } private void eat() { System.out.println("学生正在吃。。。。"); } public static void main(String[] args) { Student student = new Student("AGi"); student.run(); student.eat(); } }
由以上代码我们可以看出:关键字extends表明正在构造的新类Student派生于一个已经存在的类Person,已存在的类称为超类或者父类;子类称为子类。我们还可以看出子类可以调用父类的方法,而且子类可以拥有自己的方法,功能比超类更丰富。(子类可以调用父类的方法,但是父类不能调用子类的方法)
方法重载与方法重写
方法重载:在一个类里面出现多个方法名字相同,参数不同便会产生方法重载。
public void method() { System.out.println("1"); } public void method(String a) { System.out.println("1"); } public void method(int a) { System.out.println("1"); } public void method(int a,int b) { System.out.println("1"); }
方法重写:在有些时候,子类当中需要调用父类当中的方法,但是父类当中的方法对子类来说并不一定适用。这个时候我们就需要使用到方法的覆盖。在子类中重写父类方法。
//父类 public class Person { public void run() { System.out.println("任何人都可以跑。。。"); } } //子类 public class Student extends Person { public void run() { System.out.println("学生都可以跑"); } }
我们可以看到子类和父类当中都有run方法,而我们实际调用的却是子类自己的run方法,这就是方法的覆盖,子类通过重写和父类一样的方法实现方法的覆盖。
重载和重写的区别是什么?
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类方法访问修饰符为 private 则子类就不能重写该方法。
super关键字
super 关键字有两个用途:一是调用超类的方法,二是调用超类的构造器。调用构造器语句的时候只能作为另一个构造器的第一条语句出现。
继承层次
Java只支持单继承,但继承层不限于一层。子类也可以被继承。final修饰的类不能被继承。
//父类 public class Person { public void run() { System.out.println("任何人都可以跑。。。"); } } //子类 public class Student extends Person { public void run() { System.out.println("学生都可以跑"); } } //Student的子类 public final class Monitor extends Student{ }//final修饰的类不能被继承
由一个公共超类派生出来的所有类的集合被称为继承层次,在继承层次当中,从某个特定的类到其祖先的路径被称为继承链,但是一定要注意java是单继承的。
类的加载顺序
public class Test extends Base{ static{ System.out.println("test static"); } public Test(){ System.out.println("test constructor"); } public static void main(String[] args) { new Test(); } } class Base{ static{ System.out.println("base static"); } public Base(){ System.out.println("base constructor"); } } /* base static test static base constructor test constructor*/
先来想一下这段代码具体的执行过程,在执行开始,先要寻找到main方法,因为main方法是程序的入口,但是在执行main方法之前,必须先加载Test类,而在加载Test类的时候发现Test类继承自Base类,因此会转去先加载Base类,在加载Base类的时候,发现有static块,便执行了static块。在Base类加载完成之后,便继续加载Test类,然后发现Test类中也有static块,便执行static块。在加载完所需的类之后,便开始执行main方法。在main方法中执行new Test()的时候会先调用父类的构造器,然后再调用自身的构造器。因此,便出现了上面的输出结果。
public class Demo { public Demo(String aa){ System.out.println("===="+aa); } static { System.out.println("11"); } public static Demo demo = new Demo("+++"); static { System.out.println("22"); } } class Test{ public static void main(String[] args) { Demo demo = new Demo("----"); } } /* 11 ====+++ 22 ====---- */
所有的静态的类的初始化都交给类的第一个对象去执行