Java第6章作业:面向对象(下)

本章主要知识结构如下

  1. thissuper
  2. 构造方法的多态
  3. 抽象类与接口
  4. 引用
  5. 内部类

第一题:实验:利用IDE的debug功能给例6.4和例6.6的new语句设置断点,使用单步调试(step into/step over)跟踪子类对象实例化(初始化)的执行顺序,并总结该过程。

1、例6.4

代码与对象初始化的过程标注如下(注释里面的数字表示对象初始化过程中该句代码的执行顺序):

class AddClass {
    public int x = 0, y = 0, z = 0; // 5 当执行到构造函数的定义以后,便执行此句代码

    AddClass(int x) { // 4 执行到了AddClass类的第一个构造函数,接下来要执行非静态代码块,注意此时并未执行(this.x=x)这一句
        this.x = x; // 6 执行完非静态代码以后再执行构造函数内的实现
    }

    AddClass(int x, int y) {
        this(x); // 3
        this.y = y; // 7 步骤3已结束,执行此步
    }

    AddClass(int x, int y, int z) {
        this(x, y); // 2
        this.z = z; // 8 步骤2已结束,执行此步
    }

    public int add() {
        return x + y + z;
    }
}

public class SonAddClass extends AddClass {
    int a = 0, b = 0, c = 0; // 9 已经执行完父类的构造函数,接下来需要先执行本类的非静态代码,再执行本类构造函数内部的代码

    SonAddClass(int x) {
        super(x);
        a = x + 7;
    }

    SonAddClass(int x, int y) {
        super(x, y);
        a = x + 5;
        b = y + 5;
    }

    SonAddClass(int x, int y, int z) {
        super(x, y, z); // 1 首先执行此构造函数,然后调用父类的构造函数
        a = x + 4; // 10 执行本类构造函数内的代码,此时已经执行完本类的非静态代码
        b = y + 4; // 11
        c = z + 4; // 12
    }

    public int add() {
        System.out.println("super:x+y+z=" + super.add());
        return a + b + c;
    }

    public static void main(String[] args) {
        SonAddClass p1 = new SonAddClass(2, 3, 5); // 0 对此句对象初始化进行debug,下面以数字记录初始化过程
        SonAddClass p2 = new SonAddClass(10, 20); // 13 上面的new语句共执行了12个步骤,接下来便执行此句初始化过程
        SonAddClass p3 = new SonAddClass(1);
        System.out.println("a+b+c=" + p1.add());
        System.out.println("a+b=" + p2.add());
        System.out.println("a=" + p3.add());
    }
}
结果输出为:
super:x+y+z=10
a+b+c=22
super:x+y+z=30
a+b=40
super:x+y+z=1
a=8

对例6.4中对象初始化过程进行总结:

  1. 对象初始化时首先执行构造函数的声明处(不执行构造函数内部的定义代码),然后执行类内部的非静态代码,当执行完非静态代码定义以后再执行构造函数内的代码。
  2. 子类继承了父类的情况下,先执行子类构造函数,若子类构造函数中通过spuer关键字调用了父类的构造函数则执行对应的父类的构造函数,若子类构造函数中没有使用super显式调用父类的构造函数,则会默认调用父类的无参构造函数。
    以下代码可以证明这一点:
    class New{
       public New(){
          System.out.println("无参");
       }
       public New(String s){
          System.out.println("参数是"+s);
       }
    }
    
    class SubNew extends New{
       public SubNew(){
          System.out.println("子类的构造函数"); // 子类中没有显示调用父类构造函数,则会默认调用父类的无参构造函数
       }
    }
    
    public class Test{
       public static void main(String[] args){
          SubNew sn = new SubNew();
       }
    }
    输出为:
    无参
    子类的构造函数
    
2、例6.6

对例6.6进行探索的代码如下(注释里面的数字表示代码的执行顺序):

class Pare {
  int i = 3; // 5
  Pare() { // 4
    System.out.println("super"); // 6
  }
}

public class Construct extends Pare {
  int i = 8; // 7

  Construct() { // 3
    System.out.println("this"); // 8
  }
  Construct(int num) {
    this(); // 2
  } // 9

  int getSuper() {
    return super.i;
  };

  public static void main(String[] args) {
    Construct ct = new Construct(9); // 1 对此句进行探索
    System.out.println(ct.i); // 10
    System.out.println(ct.getSuper());
  }
}
输出为:
super
this
8
3

对例6.6进行总结

  1. 第一点与例6.4的总结的第一条相同:对象初始化时先执行到构造函数的声明处,再执行非静态代码,最后执行构造函数里面的代码;
  2. 当子类中的属性与父类同名时,若直接使用属性名调用属性则默认调用的是子类的属性,等同于this.属性名,若需要调用父类的属性则可以使用super.属性名

第二题:如何实现两个对象之间互发消息,请举例说明。

可以通过类的this关键字来实现对象之间互发消息。

举例来说要创建两个类(A, B),A类中含有B类的对象引用,B类中含有A类的对象引用,这样A类的对象便可以调用B类的对象引用,从而像B类对象发消息。

第三题:谈谈组合与继承的区别以及两者的使用场景(即什么时候宜用组合?什么时候宜用继承?)。

组合:是指在一个类中拥有另一个类的对象引用,这样便可以通过对象引用调用另一个类的公有属性和方法。

继承:子类继承父类时,便继承了父类所有的属性和方法(也包括private修饰的属性和方法,只是子类看不到private修饰的属性和方法)。

二者的使用场景:
继承通常是用来对父类的属性和方法进行完善,来创建出很具体的类;
在仅仅需要使用某个类的某些方法时,而不对该类的这些方法进行修改的情况下使用组合最方便。

第四题:Java中的运行时多态的含义是什么?有什么作用?请举例说明。

Java中多态有两种,编译时多态和运行时多态。

编译时多态:是指在编译时就可以确定要执行哪个方法,方法重载就属于编译时多态,此外对象引用指向了本类的对象(与父类的对象引用指向子类对象相对)时也属于编译时多态。

运行时多态:在运行时才可以确定执行哪个方法的多态即为运行时多态。

请看以下例子:

class Person{
	String type = "P";
	public String getName() {
		String name = "Person";
		return name;
	}
}
class Man extends Person{
	String type = "M";
	public String getName(){
		String name = "Man";
		return name;
	}
    public String newName(){
        return "newName";
    }
}

public class Test { 
	public static void main(String[] args) {
		Person p = new Man(); // 父类的对象引用指向了子类对象
		System.out.println(p.type);        //返回结果为P
		System.out.println(p.getName());   //返回结果为Man
 		System.out.println(((Man)p).newName()); // 
	}
 
}

当父类的对象引用指向了子类对象时(即上述代码Person p = new Man();),若子类对象对父类对象的某个方法进行了覆盖,则通过父类对象引用调用被子类覆盖的方法时会调用子类的方法,若子类没有对该方法进行覆盖则会调用父类的方法。

若子类中具有新的父类中没有的方法,则父类的对象引用无法调用子类中新的方法,若父类对象引用想要调用子类中新的方法(即上述main函数中的最后一句代码),需要进行强制类型转换,将父类对象引用转换为子类对象引用,即必须将Person对象引用p转为Man对象引用类型,因为newName方法是子类中特有的,父类没有这个方法。注意此处的转换格式!需要使用((Man)p)整体来调用newName方法

参考链接:Java 编译时多态和运行时多态

第五题:使用接口改写例6.8中的程序。

例6.8(未改写,使用了抽象类)代码如下:

abstract class Shapes {
    protected int x, y, k;
    protected double m;

    public Shapes(int x, int y, int k, double m) {
        this.x = x;
        this.y = y;
        this.k = k;
        this.m = m;
    }
    abstract public double getArea();
    abstract public double getPerimeter();
}

class Rect extends Shapes {
    public double getArea() {
        return (k * m);
    }
    public double getPerimeter() {
        return (2 * k + 2 * m);
    }
    public Rect(int x, int y, int width, int height) {
        super(x, y, width, height);
    }
}

class Triangle extends Shapes {
    public double getArea() {
        return (Math.sqrt(m * (m - k) * (m - x) * (m - y)));
    }
    public double getPerimeter() {
        return (k + x + y);
    }
    public Triangle(int baseA, int baseB, int baseC) {
        super(baseA, baseB, baseC, 0);
        // m充当了周长的一半
        m = (baseA + baseB + baseC) / 2.0;
    }
}

class Circle extends Shapes {
    public double getArea()
    {
        return (m * m * Math.PI); // Math是java.lang包中的类,PI是静态其属性,其值为Π
    }
    public double getPerimeter() {
        return (2 * Math.PI * m);
    }
    public Circle(int x, int y, int width) {
        // k充当了直径,m充当了半径的角色
        super(x, y, width, width / 2.0);
    }
}

public class RunShape{
    public static void show(Shapes s){
        System.out.println(s.getClass().getName()+" Area: "+s.getArea());
        System.out.println(s.getClass().getName()+" Perimeter: "+s.getPerimeter());
    }
    public static void main(String[] args){
        Shapes s1 = new Rect(5,15,25,25);
        Shapes s2 = new Triangle(5,5,8);
        Shapes s3 =new Circle(13,90,25);

        show(s1);
        show(s2);
        show(s3);

    }
  }

输出结果为:

Rect Area: 625.0
Rect Perimeter: 100.0
Triangle Area: 12.0
Triangle Perimeter: 18.0
Circle Area: 490.8738521234052
Circle Perimeter: 78.53981633974483

上述代码中涉及到了父类引用指向子类对象(即代码Shapes s1 = new Rect(5,15,25,25); Shapes s2 = new Triangle(5,5,8); Shapes s3 =new Circle(13,90,25);),这种引用是非常方便的,即所有父类引用可以引用子类对象,所有接口可以引用接口的实现类。

使用接口改写以后的代码如下:

  1. 首先在Shapes.java文件中写入以下代码:
    package myinterface;
    
    public interface Shapes {
        abstract public double getArea(int x, int y, int k, double m);
        abstract public double getPerimeter(int x, int y, int k, double m);
    }
    
    class Rect implements Shapes {
        public double getArea(int x, int y, int k, double m) {
            return (k * m);
        }
        public double getPerimeter(int x, int y, int k, double m) {
            return (2 * k + 2 * m);
        }
    }
    
    class Triangle implements Shapes {
        public double getArea(int x, int y, int k, double m) {
            m = (x + y + k) / 2.0;
            return (Math.sqrt(m * (m - k) * (m - x) * (m - y)));
        }
        public double getPerimeter(int x, int y, int k, double m) {
            return (k + x + y);
        }
    }
    
    class Circle implements Shapes {
        public double getArea(int x, int y, int k, double m) {
            m = k / 2.0;
            return (m * m * Math.PI); // Math是java.lang包中的类,PI是静态其属性,其值为Π
        }
        public double getPerimeter(int x, int y, int k, double m) {
            m = k / 2.0;
            return (2 * Math.PI * m);
        }
    }
    
  2. 然后在RunShape.java文件中写入如下代码:
    package myinterface;
    
    public class RunShapeIn{
        public static void show(Shapes s, int x, int y, int k, double m){
            System.out.println(s.getClass().getName()+" Area: "+s.getArea(x,y,k,m));
            System.out.println(s.getClass().getName()+" Perimeter: "+s.getPerimeter(x,y,k,m));
        }
        public static void main(String[] args){
            Shapes s1 = new Rect();
            Shapes s2 = new Triangle();
            Shapes s3 =new Circle();
    
            show(s1, 5,15,25,25);
            show(s2, 5,5,8,0);
            show(s3, 13,90,25,0);
        }
    }
    

改写以后输出结果为:

myinterface.Rect Area: 625.0
myinterface.Rect Perimeter: 100.0
myinterface.Triangle Area: 12.0
myinterface.Triangle Perimeter: 18.0
myinterface.Circle Area: 490.8738521234052
myinterface.Circle Perimeter: 78.53981633974483

可以看到,结果和改写之前是一样的,不同的地方在于类名前面加了包的名字。

第六题:简述运算符instanceof的使用场景。

先说下instanceof用法
instanceof是二元运算符,使用格式为A instanceof B,目的是判断对象A是不是类B的实例,AB的关系不同时一般有以下三种情况:

  1. A是本类对象,B是本类或父类:此时A instanceof B返回布尔值true
  2. A是父类对象,B是子类:此时A instanceof B返回布尔值false
  3. A是对象,B是与A类无关(没有继承关系)的类:A instanceof B不会编译通过,会如下错误:Incompatible conditional operand types A and B

使用场景
当同一个父类(一般是抽象类)被多个子类继承时,需要定义函数来对不同的子类进行不同的操作,此时需要判断该函数所接收的对象是哪个子类的实例,便可以对不同子类的实例进行不同的操作,防止报错,也是多态的体现。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值