第5章 面向对象编程-中

第5章、面向对象编程-中

目录

  1. 目录
  2. 一、面向对象的特征之二:继承性
  3. 二、方法的重写(override /overwrite)
  4. 三、super 关键字
  5. 四、子类对象实例化的全过程
  6. 五、面向对象特征之三:多态性(重难点)
  7. 六、Object 类的使用
  8. 七、包装类的使用
  9. 面试题
  10. 每日一考

一、面向对象的特征之二:继承性

  1. 继承的好处:
    • 减少代码冗余,提高代码复用性
    • 便于功能扩展
    • 多态性的使用提供前提
  2. 继承性格式:class A extends B{ ... }
    • A:子类、派生类、subclass
    • B:父类、超类、基类、superclass
    • 一旦子类A继承父类B后,字类A中就获取了父类B中声明的所有属性和方法。特别的,父类中声明为私有的属性和方法,子类继承父类后,仍然认为获取了父类中私有的结构,只是因为封装性的影响,使得字类不能直接调用父类的结构而已。(继承是能不能拿到的问题,封装是能不能使用的问题)
    • 子类继承父类后,还可以声明自己特有的属性或方法,实现功能的扩展
    • 子类与父类的关系,不同于数学上子集和集合的关系
    • extends:延展、扩展
  3. Java中关于继承性的规定
    • 一个类可被多个类继承
    • 一个类只能有一个父类:Java中类的单继承性
    • 子父类是相对的,支持多层继承(直接父类、间接父类)
      • 字类直接继承的父类:直接父类
      • 子类间接继承的父类:间接父类
    • 字类继承父类后,就获取了直接父类和所有间接父类中声明的属性和方法
  4. 如果没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。所有的类(除java.lang.Object类)都直接或间接的继承于java.lang.Object类,意味着所有的Java类都具有java.lang.Object类声明的功能

附:Eclipse的Debug,如何调试程序

  • System.out.println();
  • Eclipse的Debug调试

二、方法的重写(override / overwrite)

  1. 重写:字类继承父类后,可对父类中同名同参数的方法进行覆盖操作
  2. 应用:重写后,当创建子类对象后,通过子类对象调用父类中同名同参数的方法时,实际执行的是子类重写父类的方法
  3. 面试题:区分方法的重载与重写
    • 重载:除参数列表不同不同外,方法名、权限等相同
  4. 重写的规定
    • 方法的声明:权限修饰符 返回值类型 方法名(形参列表) { //方法体 }
    • 约定俗成:子类中的叫“重写的方法”;父类中的叫“被重写的方法”
    • 子类重写的方法的方法名和形参列表 与 父类被重写的方法的方法名和形参列表相同
    • 子类重写的方法的权限修饰符 不小于 父类被重写的方法的权限修饰符(子类重写方法的可见性要高于父类的被重写方法)
      • 子类不能重写父类中声明为private权限的方法(或者说 子类中与父类声明为private方法同名同参数的方法不能成为重写)
    • 返回值类型
      • 父类中被重写方法返回值类型为void,则子类重写方法返回值类型只能是void
      • 父类中被重写方法返回值类型为A类型,则子类重写方法返回值类型可以是A类型或A类型的子类
      • 父类中被重写方法返回值类型为基本数据类型,则子类重写方法返回值类型必须是相同的基本数据类型
    • 子类中重写的方法抛出的异常类型不大于父类中被重写的方法抛出的异常类型
    • 子类和父类中同名同参数的方法要么都声明为非static的(重写),要么都声明为static(不是重写,static方法不能覆盖 是随着类的加载而加载的)

三、super关键字

  1. super理解为:父类的
  2. super可用来显式调用父类中的属性、方法、构造器
  3. super的使用,调用父类的属性或方法
    • 可在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性或方法。但通常习惯省略“super.”
    • 特殊情况:当子类和父类中定义了同名的属性时,如想在子类中调用父类中声明的属性,则必须显式的使用“super.属性”的方式,表明调用的是父类中声明的属性。
    • 特殊情况:当子类重写了父类方法后,若想在子类方法中调用父类中被重写的方法时,则必须显式的“super.方法”的方式,表明调用的是父类中被重写的方法。
  4. super调用父类的构造器
    • 可在子类的构造器中显式的使用“super(形参列表)”的方式调用父类中生命的指定的构造器
    • “super(形参列表)”的使用必须声明在子类构造器的首行
    • 在类的构造器中,针对于"this(形参列表)"或“super(形参列表)”只能二选一,不能同时出现
    • 在构造器函数体首行,没有显式调用"this(形参列表)"和“super(形参列表)”,则默认调用的是父类中的空参构造器( 即super() )【一个类被继承时,父类中需要有空参构造器(定义有参构造器前需显式定义空参构造器,否则只有有参构造器时编译器将不会自动添加空参构造器),否则子类中显式定义的构造器会报错、或无显式定义构造器的子类定义处报错.若不想在父类中显式声明无参构造器,则可在子类中显式调用父类的有参构造器】【一个类中有n个构造器,则最多n-1个构造器使用this(形参列表),剩下的那个使用的是super(形参列表)】
    • 在类的多个构造器中,至少有一个类的构造器中使用了“super(形参列表) ”,调用父类的构造器。【子类中会有父类中的属性、方法,是因为调用了super(形参列表)】

四、子类对象实例化的全过程

  • 从结果看:子类继承父类后,就获取了父类中声明的属性或方法。创建子类的对象,在堆空间中,就会加载所有父类中声明的属性
  • 从过程看:当通过子类的构造器创建子类对象时,一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止,正因为加载过所有父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑调用
  • 明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终只是创建过一个对象,即为new的子类对象【构造器负责类中成员变量的初始化】

五、面向对象特征之三:多态性(重难点)

核心内容

  1. 多态性理解:一个事物的多种形态
  2. 多态性是什么:对象的多态性,父类的引用指向子类的对象(或子类对象赋给父类的引用)
  3. 多态性的使用:创建的对象调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法(虚拟方法调用(Virtual Method Invocation)
    • ClassA a = new ClassB(); //A为B的父类
    • 有了对象多态性后,在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。【总结:编译看左边,运行看右边】对象a无法调用子类ClassB中单独定义的方法
  4. 多态性使用前提
    • 类的继承关系
    • 方法的重写
  5. 对象的多态性只适用于重写方法,不适用于同名同类型的属性(属性不构成重写)
  6. 虚拟方法:
    • 子类中定义了与父 同名参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
    • 虚拟方法调用的过程也可称为动态绑定,虚方法动态绑定的子类重写的方法
    • 重载在方法调用之前,编译器就已经确定了所要调用的方法,称为“早绑定”或“静态绑定”;而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,称为“晚绑定”或“动态绑定

instanceof操作符

有了对象的多态性后,内存中实际上是加载了子类特有的属性和方法的,但由于变量声明为父类类型,导致编译时只能允许调用父类中生命的属性和方法,子类中特有的属性和方法不能调用。

  1. 如何调用子类特有的属性和方法
    • 强制类型转换(向下转型):使用强转时可能出现ClassCastException的异常(编译无报错,运行报错) 向上转型与向下转型
    • instanceof关键字(先判断后再强制类型转换)
      • a instanceof A; // 判断对象a是否是类型A的实例,如是则返回true,否则返回false
      • 为了避免向下转型时出现ClassCastException异常,在向下转型前先进性instanceof的判断,一旦返回true就可进行向下转型,否则不能进行向下转型
      • 已知a为A的实例对象,若a instanceof B也为true,则类型B为类型A的父类
  2. 笔试题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package ProgramExam;

    class Base{
    int count = 10;
    public void display(){
    System.out.println(this.count);
    }
    }

    class Sub extends Base{
    int count = 20;
    public void display(){
    System.out.println(this.count);
    }
    }

    public class FieldMethodTest {
    public static void main(String[] args) {
    Sub s = new Sub();
    System.out.println(s.count); //20
    s.display(); //20

    Base b = s;
    // 对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
    System.out.println(b == s); //true
    System.out.println(b.count);//10 声明为什么类型就去找那个类型所属的属性
    b.display();//20 重写的多态性
    }
    }
  • 若子类重写了父类方法,就意味着子类中定义的方法彻底覆盖了父类中的同名方法,系统将不可能把父类里的方法转移到子类中。编译看左边,运行看右边
  • 对于实例变量则不存在这种现象,即使子类中定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量。

六、Object类的使用

  1. Object类是所有Java类的根父类
  2. 若在类的声明中未使用extends关键字知名其父类,则默认父类为java.lang.Object类
  3. Object类中的功能(属性、方法)就具有通用性
    • 无属性
    • 方法:equals() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
  4. == 和equals()的区别:见《面试题》部分6.
  5. Object类中的toString()的使用
    • 当输出对象的引用时,实际上是调用当前对象的toString()方法(但当引用为Null时,直接输出引用为null,但调用toString()方法再输出则会出现NullPointerException异常)
      1
      2
      3
      public String toString() {
      return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
    • 像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用toString()方法时,返回“实体内容”信息
    • 自定义类也可重写toString()方法,当调用此方法时,返回对象的“实体内容”。
    • source --> Generate toString()自动生成toString()方法

七、包装类的使用

  1. java中的JUnit单元测试
    • 选中当前工程,右键选择-->build path - add libraries - JUnit 4 - 下一步
    • 创建Java类,进行单元测试
      • 此时的Java类要求:
        • 此类是public的
        • 此类提供公共的无参构造器
    • 此类的单元测试方法
      • 测试方法要求:方法的权限是public,没有返回值类型,没有形参
    • 此单元测试方法上需声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
    • 声明完成单元测试方法后,即可在方法体内测试相关代码
    • 写完方法体代码后,左键双击选中单元测试方法名,右键,run as - JUnit Test
    • 说明:
      • 若执行结果没有任何异常,绿条
      • 若执行结果出现异常,红条
  2. 包装类的使用
    • Java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征
    • 基本数据类型、包装类、String三者之间的相互转换【掌握】
      • 基本数据类型和包装类
        • 基本数据类型-->包装类:调用包装类的构造函数
        • 包装类-->基本数据类型:调用包装类的XxxValue()
        • JDK 5.0新特性:自动装箱 与 自动拆箱
          • 自动装箱
            1
            2
            int num = 10;
            Integer in1 = num;
          • 自动拆箱
            1
            int num2 = in1;
      • 基本数据类型和包装类 与 String之间的转换
        • 基本数据类型不能直接转换为String类型,但可通过连接运算(String s = num + "")实现转换;或调用String重载的valueOf(Xxx xxx)
        • String转换为基本数据类型及包装类:调用包装类的parseXxx()(int num = Integer.parseInt(str);)可能会报NumberFormatException异常
    • 包装类练习题10
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      package wrappertestpackage;

      import java.util.Scanner;
      import java.util.Vector;

      public class Practice10 {

      public static void main(String[] args) {

      // 1. 从键盘读入学生成绩
      Scanner scan = new Scanner(System.in);

      // 2. 创建Vector对象,存储学生成绩
      Vector v = new Vector();

      // 找出成绩最高分
      int maxScore = 0;

      for(;;){
      System.out.println("请输入学生成绩(以复数代表输入结束)");
      int score = scan.nextInt();
      if(score < 0){
      break;
      }else if(score > 100){
      System.out.println("输入数据非法,请重新输入。");
      continue;
      }

      //JDK5.0前
      // Integer inScore = new Integer(score);
      // v.addElement(inScore);

      // JDK5.0后
      v.addElement(score);//自动装箱

      if(maxScore < score){
      maxScore = score;
      }
      }

      System.out.println("最高成绩为:" + maxScore);

      for(int i=0; i<v.size(); i++){
      char level = 0;

      //JDK5.0前
      // Object obj = v.elementAt(i);
      // Integer inScore = (Integer)obj;
      // int score = inScore.intValue();

      // JDK5.0后
      int score = (int)v.elementAt(i);

      if(maxScore - score <= 10){
      level = 'A';
      }else if(maxScore - score <= 20){
      level = 'B';
      }else if(maxScore - score <= 30){
      level = 'C';
      }else{
      level = 'D';
      }

      System.out.println("student-" + i + " score is " + score + ", grade is " + level);
      }

      }

      }

面试题

  1. 区分方法的重写和重载
  • 二者的概念
  • 重载和重写的具体规则
  • 重载不表现为多态性,重写表现为多态性
  1. 谈谈你对多态性的理解

    • 实现代码的通用性
    • 举例:Object类中定义的public boolean equals(Object obj) {}。JDBC:使用Java操作程序(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
    • 抽象类和接口的使用体现了多态性(抽象类、接口不能实例化)
  2. 多态性是编译时行为,还是运行时行为?

  • 证明见InterviewTest.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    package ProgramExam;


    import java.util.Random;

    //面试题:多态是编译时行为还是运行时行为?
    //证明如下:
    class Animal {

    protected void eat() {
    System.out.println("animal eat food");
    }
    }

    class Cat extends Animal {

    protected void eat() {
    System.out.println("cat eat fish");
    }
    }

    class Dog extends Animal {

    public void eat() {
    System.out.println("Dog eat bone");

    }

    }

    class Sheep extends Animal {


    public void eat() {
    System.out.println("Sheep eat grass");

    }


    }

    public class InterviewTest {

    public static Animal getInstance(int key) {
    switch (key) {
    case 0:
    return new Cat ();
    case 1:
    return new Dog ();
    default:
    return new Sheep ();
    }

    }

    public static void main(String[] args) {
    int key = new Random().nextInt(3);

    System.out.println(key);

    Animal animal = getInstance(key);

    animal.eat();

    }

    }
  • 考察多态的笔试题目

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package ProgramExam;

    //考查多态的笔试题目:
    public class InterviewTest1 {

    public static void main(String[] args) {
    Base base = new Sub();
    base.add(1, 2, 3);

    // Sub s = (Sub)base;
    // s.add(1,2,3);
    }
    }

    class Base {
    public void add(int a, int... arr) {
    System.out.println("base");
    }
    }

    class Sub extends Base {

    public void add(int a, int[] arr) {
    System.out.println("sub_1");
    }

    // public void add(int a, int b, int c) {
    // System.out.println("sub_2");
    // }

    }
  1. 为什么super(形参列表)和this(形参列表)调用语句不能同时在一个构造器中出现?
  • 因为super(形参列表)和this(形参列表)的调用语句都得出现在首行
  1. 为什么super(形参列表)或this(形参列表)调用语句只能作为构造器的第一句出现?
  • 无论通过那个构造器创建子类对象,需要保证先初始化父类。目的:当子类继承父类后,继承父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化
  1. final、finally、finalize的区别?
  • final、finally为关键词,finalize为函数名
  • 功能差别
  1. == 和equals()的区别
  • == 运算符
    • 可用在基本数据类型变量和引用数据类型的变量中
    • 若比较的是基本数据类型变量,则比较两个变量保存的数据是否相等(不一定类型相同)
    • 若比较的是引用数据类型变量,则比较两个变量保存的地址是否相等(两个引用是否指向同一个实体 或 两个对象的地址是否相等)
    • 补充:==符号使用时,必须保证符号左右两边的变量类型一致(不一定相同)
  • equals()方法的使用
    • 是一个方法,不是运算符
    • 只适用于引用数据类型
    • Object类中equals()的定义
      1
      2
      3
      4
      public boolean equals(Object obj){
      return (this == obj)
      }
      //说明:Object类中定义的equals()和==的作用是相同的
    • 像String、Date、File、包装类等都重写了Object类的equals()方法。重写后,比较的不再是两个引用指向的地址是否相同,而是比较两个对象的“实体内容”是否相同
    • 通常情况下,自定义类使用equals()时也通常是比较两个对象的“实体内容”是否相同。那么就需要对Object类中的equals()进行重写 - 重写的原则:比较两个对象的实体 - 注意属性中的String对象比较时要调用equals()函数,不可使用==号 - source --> Generate hashCode() and equals()自动生成自定义类的equals()方法(实际开发中一般使用自动生成的) 重写equals()方法原则 【面试题】==和equals()方法的区别
  1. 包装类面试题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    package wrappertestpackage;

    import org.junit.Test;

    public class InterviewTest {

    @Test
    public void test1(){
    Object o1 = true? new Integer(1) : new Double(2.0);
    System.out.println(o1); // 1.0 [三元运算符的类型自动提升]
    }

    @Test
    public void test2(){
    Object o2;
    if(true){
    o2 = new Integer(1);
    }else{
    o2 = new Double(2.0);
    }
    System.out.println(o2); // 1
    }
    }
    @Test
    public void test3(){

    Integer i = new Integer(1);
    Integer j = new Integer(2);
    System.out.println(i == j);// false

    //Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],保存了从-128~127范围内的整数。
    //如果使用自动装箱的方式,给Integer赋值的范围在-128~127范围内时,可以直接使用数组内的元素,不用再去new对象了,
    //目的是提高效率。因此,

    Integer m = 1;
    Integer n = 1;
    System.out.println(m == n); // true
    //采用自动装箱且赋值整数在-128~127范围内时,引用是直接指向同一地址的

    Integer x = 128;
    Integer y = 128;
    System.out.println(x == y);// false
    //而当赋值整数超出这个范围时,自动装箱需要去new对象,因此两个引用的地址不相同

    }

每日一考

  1. 什么是多态性?什么是虚拟方法调用?
    • 对象的多态性:父类的引用指向子类的对象
    • 调用方法时,编译看左边,运行看右边。在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法
  2. 方法重写的具体规则有哪些?
    • 继承关系
    • 同名同参数的方法
    • 权限修饰符
    • 返回值类型
    • 抛出的异常
  3. super调用构造器,有哪些具体的注意点
    • 构造器中没有显式调用super(形参列表)和this(形参列表)时,构造器中会自动调用父类的空参构造器
  4. 如何实现向下转型?需要注意什么问题?如何解决此问题
    • 使用强转符(); 有可能出现ClassCastException异常;使用instanceof运算符
  5. ==和equals()有什么区别?
    • ==在比较基本数据类型变量时,比较变量存储的值;比较引用数据类型变量时,比较的是引用指向的地址是否相同;equals()为一个方法,只能用于引用数据类型,默认的equals()和==同样比较的是引用指向的地址,String、Date、包装类等类型的equals()进行了重写,比较的是两个对象的“实体内容”,自定义类通常比较的是“实体内容”,那么也需要重写equals()方法
    • 回答套路:1. 直接描述,直到无法描述;2. 不记得的知识点,描述使用情景(表示用过,而不是死记硬背)
  6. 写出8种基本数据类型及其对应的包装类
  7. 基本数据类型、包装类与String三者之间如何转换
    • 自动装箱、自动拆箱
    • 基本数据类型、包装类 --> String: valueOf(Xxx xxx)
    • String --> 基本数据类型、包装类:parseXxx(String s)