面向对象之继承

   以下例进行此文描述

   将学生(student)和工人(worker)的共性描述提取出来,单独进行描述,只要让学生和工人与单独描述的这个类有关系,就可以了。

class Person {
    String name;
    int age;
}
class Student extends Person {
    
    void study() {
        System.out.println("good study");
    }
}
class Worker extends Person {
    
    void work() {
        System.out.println("good work");
    }
}
public class ExtendsDemo {

    public static void main(String[] args) {
        Student s = new Student();
    }

}

   通过上例可得继承的好处:

  1. 提高了代码的复用性
  2. 让类与类之间产生了关系。有了这个关系,才有了多态的特性

   注意:千万不要为了获取其他类的功能,简化代码而继承,必须是类与类之间有所属关系才可以继承,所属关系是:is a

   记住:先有父类,再有子类。

   Java语言中,java只支持单继承,不支持多继承。因为多继承容易带来安全隐患,当多个父类中定义了相同功能时,当功能内容不同时,子类对象不确定要运行哪一个。

   若Java支持多继承:

class A {
    void show() {
        System.out.println("a");
    }
 } 
 
 class B {
    void show() {
        System.out.println("b");
    }
 }
 
 class C extends A, B {
    C c = new C();
    c.show(); //此时该打印谁呢?
 }

   但是java保留这种机制,并用另一种体现形式来完成表示——(多实现)

   java支持多层继承,也就是一个继承体系。

   如何使用一个继承体系中的功能呢?

   想要使用体系,先查阅体系父类的描述,因为父类中定义的是该体系中的共性功能,通过了解共性功能,就可以知道该体系的基本功能,那么这个体系就可以基本使用了,那么在具体调用时,要创建最子类的对象,为什么呢?一是因为有可能父类不能创建对象(如抽象类),二是创建子类对象可以使用更多的功能,包括基本的也包括特有的。

   简而言之:查阅父类功能,创建子类对象使用功能

   聚集(聚合、组合):has a

   聚合:(举例)球员与球队的关系

   组合:事物的联系程度更紧密,(举例)人的心脏和手。

   子父类出现后,类成员的特点:

   类中成员:

  1. 变量
  2. 函数
  3. 构造函数

   ①变量

   如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量用this,子类要访问父类中的同名变量用super,super的使用和this的使用几乎一致,this代表的是本类对象的引用,super代表的是父类对象的引用。

class Fu {
    int num = 4;
}
class Zi extends Fu {
    //int num = 5;
    void show() {
        System.out.println(this.num);//this和super此时同时指向子类引用
    }
}

   ②子父类中的函数

   当子类出现和父类一模一样的函数时,当子类对象调用该函数时,会运行子类函数的内容,如同父类的函数被覆盖一样,这种情况是函数的另一种特性:重写(覆盖)

   当子类继承父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。

class Fu {
    void show() {
        System.out.println("fu show");
    }
    
    public void speak() {
        System.out.println("vb");
    }
}
class Zi extends Fu {
    
    public void speak() {
        System.out.println("java");
    }
    
    void show() {
        System.out.println("zi show");
    }
}

   还有利于扩展功能,如下例:

class Tel {
    
    void show() {
        System.out.println("number");
    }
    
}
class NewTel extends Tel {
    
    void show() {
//        System.out.println("number");
        super.show();
        System.out.println("name");
        System.out.println("picture");
    }
}

   覆盖:

  1. 子类覆盖父类,必须保证子类权限>=父类权限,才可以覆盖,否则编译失败
  2. 静态只能覆盖静态

   注意:

  1. 重载:只看同名函数的参数列表
  2. 重写:子父类方法要一模一样

   ③子父类中的构造函数

   在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句:super(),super():会访问父类中空参数的构造函数,而且子类中所有的构造函数默认第一行都是super()。

   为什么子类一定要访问父类中的构造函数?

   因为父类中的数据,子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的,所以子类在对象初始化时,要先访问一下父类中的构造函数。如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。

   注意:super语句一定定义在子类构造函数的第一行。

class Fu {// extends Object
    int num;
    Fu() {
        //super();
        num = 60;
        System.out.println("fu run");
    }
    Fu(int x) {
        System.out.println("fu run..."+x);
    }
    
}
class Zi extends Fu {
    
    Zi() {
        //super(); == this();
//        super(4);
        System.out.println("zi run");
    }
    Zi(int x) {
        this();//此构造函数没有super语句
        //super(); == this();
//        super(3);
        System.out.println("zi run..."+x);
    }
}

   子类的实例化过程:

   结论:子类的所有的构造函数,默认都会访问父类中空参数的构造函数。因为子类中每一个构造函数的第一行都有一个隐式super();当父类中没有空参数的构造函数时,子类必须手动通过super语句形式来指定要访问的父类中的构造函数;当然,子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少会有一个构造函数会访问父类中的构造函数。

   final:最终。作为一个修饰符

  1. 可以修饰类、函数、变量
  2. 被final修饰的类不可以被继承。为了避免被继承,被子类覆写功能
  3. 被final修饰的方法不可以被覆写
  4. 被final修饰的变量是一个常量,只能被赋值一次,既可以修饰成员变量,又可以修饰局部变量。当在描述事物时,一些数据的出现,值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便于阅读,而这个值不需要改变,所以用final修饰,作为常量,常量的书写规范,所有字母都大写,如果由多个单词组成,单词间通过_连接
  5. 内部类定义在类中的局部位置上时,只能访问该局部被fianl修饰的局部变量
class Demo {
    
    final int x = 3;
    //全局常量
    public static final double MY_PI = 3.14;//相当于加了一个锁
    
    final void show1() {}
    
    void show2() {
        final int y = 4;
        System.out.println(MY_PI);
    }
}
class SubDemo extends Demo {
//    void show1() {}
}

 

   当多个类中出现相同功能,但是功能主体不同,这时可以进行向上抽取,只抽取功能定义,而不抽取功能主体。

   抽象:看不懂

   抽象类的特点:

  1. 抽象方法一定定义在抽象类中
  2. 抽象方法和抽象类都必须被abstract关键字修饰
  3. 抽象类不可以用new创建对象,因为调用抽象方法没意义
  4. 抽象类中的抽象方法要被使用,必须由子类覆写其所有的抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类

   抽象类和一般类没有太大的不同,该如何描述事物就如何描述事物,只不过该事物出现了一些看不懂的东西。这些不确定的部分,也是该事物的功能,需要明确出现,但是无法定义主体,通过抽象方法表示。

  • 抽象类比一般类多了个抽象函数,就是在类中可以定义抽象方法,也可以不定义抽象方法
  • 抽象类不可以实例化

   特殊:抽象类可以不定义抽象方法,这样做仅仅是不让该类建立对象。

   练习1:

   假如我们在开发一个系统时需要对员工进行建模,员工包含3个属性:姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另外还有一个奖金属性。请使用继承的思想设计出员工类和经理类,要求类中提供必要的方法进行属性访问。

   分析:

  1. 员工类:name id pay
  2. 经理类:继承了员工,并有自己特有的bonus

   代码如下:

abstract class Employee {
    private String name;
    private String id;
    private double pay;
    
    Employee(String name, String id, double pay) {
        this.name = name;
        this.id = id;
        this.pay = pay;
    }
    
    public abstract void work();
}

class Pro extends Employee {
    Pro(String name, String id, double pay) {
        super(name, id, pay);
    }
    
    public void work() {
        System.out.println("pro work");
    }
}

class Manager extends Employee {
    private int bonus;
    
    Manager(String name, String id, double pay, int bonus) {
        super(name, id, pay);
        this.bonus = bonus;
    }
    
    public void work() {
        System.out.println("manager work");
    }
}

   练习2:获取一段程序运行的时间

   分析:原理:获取程序开始和结束的时间并相减即可。获取时间:System.currentTimeMillis();

abstract class GetTime {
    public final void getTime() {
        long start = System.currentTimeMillis();
        
        runCode();
        
        long end = System.currentTimeMillis();
        
        System.out.println("毫秒:"+(end - start));
    }
    
    public abstract void runCode();
}
class SubTime extends GetTime {
    
    public void runCode() {
        
        for(int x = 0; x < 4000; x++) {
            System.out.print(x);
        }
    }
}
public class TemplateDemo {

    public static void main(String[] args) {
        SubTime st = new SubTime();
        st.getTime();
    }

}

   当代码完成优化后,就可以解决这类问题。这种方式称为模板方法设计模式

   什么是模板方法设计模式

   在定义功能时,功能的一部分是确定的,但是有一部分是不确定的,而确定的部分在使用不确定的部分,那么这时就将不确定的部分暴露出去,由该类的子类去完成。(提高功能扩展性和代码复用性)

 

   接口(可扩展程序的功能)

   初期理解,可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。 

   接口定义时,格式特点:

  1. 接口中常见定义:常量,抽象方法
  2. 接口中的成员都有固定修饰符:常量:public static final;方法:public abstract。

   记住:接口中的成员都是public的。

   接口是不可以创建对象的,因为有抽象方法,需要被子类实现,子类对接口中的抽象方法全都覆盖后,子类才可以实例化,否则子类是一个抽象类。

   接口可以被多实现。也是对多继承不支持的转换形式。java支持多实现。

interface Inter {
    public static final int NUM = 3;
    public abstract void show();
}
interface InterA {
    public abstract void show();
}
class Demo1 {
    public void function() {
        
    }
}
class Test extends Demo1 implements Inter, InterA {
    public void show() {
        
    }
}

interface A {
    void methodA();
}
interface B extends A {
    void methodB();
}
interface C extends B { // interface C extends B, A
    void methodC();
}
class D implements C {
    public void methodA() {
        
    }
    public void methodB() {
        
    }
    public void methodC() {
    
    }
}

public class InterfaceDemo {

    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.NUM);
        System.out.println(Test.NUM);
        System.out.println(Inter.NUM);
        
//        t.NUM = 4;
    }

}

 

posted @ 2016-02-20 16:30  叶十一少  阅读(259)  评论(0编辑  收藏  举报