继承的本质:复用已存在的方法和域
一、概念:
继承是java面向对象编程的基石,继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
public class Person {
public void run() {
System.out.println("任何人都可以跑。。。");
}
}
class Student extends Person{
private void eat() {
System.out.println("学生正在吃。。。。");
}
public static void main(String[] args) {
Student student = new Student();
student.run();
student.eat();
}
}
二、明确方法重载和方法重写1.方法重载
2.方法覆盖
在有些时候,子类当中需要调用父类当中的方法,但是父类当中的方法对子类来说并不一定适用。这个时候我们就需要使用到方法的覆盖。例如
public class Person {
public void run() {
System.out.println("任何人都可以跑。。。");
}
}
class Student extends Person{
public void run() {
System.out.println("这个学生在跑。。。");
}
private void eat() {
System.out.println("学生正在吃。。。。");
}
public static void main(String[] args) {
Student student = new Student();
student.run();
student.eat();
}
}
我们可以看到子类和父类当中都有run方法,而我们实际调用的却是子类自己的run方法,这就是方法的覆盖,子类通过重写和父类一样的方法实现方法的覆盖。
//方法的覆盖
//子类和父类当中都有相同的方法,
//子类通过重写父类方法实现方法的覆盖
public void getRun() {
System.out.println("所有小猫杜能跑。。。。");
}
//一个方法的签名是由方法名称以及参数列表所组成
//方法的重载是指在一个类当中有相同的方法名不同的参数列表
public void getRun(int s) {
System.out.println("所有小猫杜能跑。。。。"+s);
}
public static void main(String[] args) {
Cat cat = new Cat();
cat.getRun();
}
重载和重写的区别
重载: 发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序
不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写: 发生在父子类中,方法名、参数列表必须相同,返回值范围小于等于父
类,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类;如果父类
方法访问修饰符为 private 则子类就不能重写该方法。
三、super关键字
super 关键字有两个用途:一是调用超类的方法,二是调用超类的构造器。调用构造器语句的时候只能作为另一个构造器的第一条语句出现。
四、继承层次
java只支持单继承
继承并不限于一个层次。
public class Person {
public void run() {
System.out.println("任何人都可以跑。。。");
}
}
class Student extends Person{
public void run() {
super.run();
System.out.println("这个学生在跑。。。");
}
private void eat() {
System.out.println("学生正在吃。。。。");
}
public static void main(String[] args) {
Student student = new Student();
student.run();
student.eat();
}
}
class XiaoMing extends Student{
}
class XiaoHong extends Student{
}
由一个公共超类派生出来的所有类的集合被称为继承层次,在继承层次当中,从某个特定的类到其祖先的路径被称为继承链,但是一定要注意java是单继承的。
Person的继承层次
五、类的加载顺序
以下代码的输出结果是什么?
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");
}
}
先来想一下这段代码具体的执行过程,在执行开始,先要寻找到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
====----
所有的静态的类的初始化都交给类的第一个对象去执行
以下是测试用例
/**
* @ClassName TRRest
* @Description TODO
* @Author heaboy@heaboy.com
* @Version 1.0.0
*/
public class InitializeDemo {
private static int k = 1;
private static InitializeDemo t1 = new InitializeDemo("t1");
private static InitializeDemo t2 = new InitializeDemo("t2");
private static int i = print("i");
private static int n = 99;
{
print("构造块");
j=100;
}
public InitializeDemo(String str) {
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
static {
print("静态块");
n=100;
}
private int j = print("j");
public static int print(String str) {
System.out.println((k++) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
}