JAVA课程学习第五课:面向对象(下)

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

例6.4的代码如下(示例):

public class AddClass {
    public int x = 0,y = 0,z = 0;
    AddClass(int x){
        this.x = x;
    }
    AddClass(int x,int y){
        this(x);
        this.y = y;
    }
    AddClass(int x,int y,int z){
        this(x,y);
        this.z = z;
    }
    public int add(){
        return x+y+z;
    }
}
//以上为第一个类AddClass

--------------------分界线------------------
public class SonAddClass extends AddClass {
    int a = 0, b = 0, c= 0;
    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);
        a = x+4;
        b = y+4;
        c = z+4;
    }
    public int add(){
        System.out.println("super:x+y+z= "+super.add());
        return a+b+c;
    }
}

//以上是第二个类的内容 AddClass 

------------------------分界线----------------------
//这是第三个类的内容,测试类。

public class JavaDemo {
    public static void main(String[] args){
        SonAddClass p1 = new SonAddClass(2,3,5);//设置断点行
        SonAddClass p2 = new SonAddClass(10,20);
        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());
    }
}

以下为对例6.4的分析:
设置断点并且开始Debugger后,我们单步调试,进入类SonAddClass的构造函数,这里根据参数个数,进入第三个构造函数,即为SonAddClass(int x,int y,int z)
这个构造函数中,显示调用了父类的构造函数(利用super关键字)
super(x,y,z);进入父类AddClass的构造函数体,同样的根据参数,进入AddClass(int x,int y,int z)在这个函数体里,使用了关键字this来调用同类的其它构造方法,进入AddClass(int x,int y) ,同上,又进入了函数AddClass(int x)
但是进入这个最终函数后,下一步并没有直接执行this.x = x语句,而是先对数据成员进行了初始化,public int x = 0,y = 0,z = 0;初始化完成后,进入构造函数,执行
this.x = x调用完成后,退回到AddClass(int x,int y)
中,执行this.y = y;,后再退回到函数AddClass(int x,int y,int z)
函数,执行this.z = z;至此,父类的构造函数调用完毕,回退到子类的构造函数.
SonAddClass(int x,int y,int z) super(x,y,z);同样的,下一步是执行语句int a = 0, b = 0, c= 0;对变量进行初始化,再进行接下来的赋值:a = x+4; b = y+4; c = z+4;至此,一个子类对象正式实例化完毕。
后两行new语句同理,

 SonAddClass p2 = new SonAddClass(10,20);
 SonAddClass p3 = new SonAddClass(1);

例6.6代码如下(示例):

public class Pare {
    int i = 3;
    Pare(){
        System.out.println("super");
    }
}
//以上为第一个类 
----------------------------分界线-------------------

public class Construct extends Pare{
    int i = 8;
    Construct(){
        System.out.println("this");
    }
    Construct(int num){
        this();
    }
    int getSuper(){
        return super.i;
    }
}
//以上为第二个类
-------------------------分界线-------------------
public class javaDemo {
    public static void main(String[] args){
        Construct ct = new Construct(9);
        System.out.println(ct.i);
        System.out.println(ct.getSuper());
    }
}
//以上为测试类。

例子6.6分析过程与6.4类似。区别在于例6.6没有显示的调用父类的构造函数,但是执行过程中,程序还是调用了父类的构造函数,并进行了父类数据成员的初始化。

综上,子类对象实例化过程如下:

  1. 为对象分配内存空间,并且对成员变量 进行默认的初始化。
  2. 绑定构造方法,将new中的参数传递给构造方法的形式参数。
  3. 调用this或者super语句(二者必居其一,但不能同时存在)。
  4. 进行显示初始化操作。
  5. 执行当前构造方法的方法体中的程序代码

具体流程如下图所示:
在这里插入图片描述

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

实现两个对象之间互发消息,那么采用组合的设计方式,在两个对象之间建立联系;在两个类内都有对方的对象引用作为数据成员,并真正的调用过程中,二者的对象引用都互相指向对方的对象。
代码如下(示例):

//车类
public class Car {
    private String name;
    private double price;
    private  People people;

    public Car(String name,double price){
        this.name = name;
        this.price = price;
    }
    public void setPeople(People people){
        this.people = people;
    }
    public People getPeople(){
        return this.people;
    }
    public String getInfo(){
        return"汽车型号: "+this.name+", 汽车价格: "+this.price;
    }
}
--------------------------------分界线------------------------
//第二个类

public class People {
    private  String name;
    private int age;
    private Car car;

    public People(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void setCar(Car car){
        this.car= car;
    }

    public Car getCar() {
        return car;
    }
    public String getInfo(){
        return "姓名: "+this.name+", 年龄: "+this.age;
    }
}
-------------------------------------分界线-----------------
//测试类

public class JavaDemo {
    public static void main(String args[]){
        People  people1 = new People("张三",29);
        Car car = new Car("奔驰G50",1588800.00);
        people1.setCar(car);
        car.setPeople(people1);
        System.out.println(people1.getCar().getInfo());
        System.out.println(car.getPeople().getInfo());
    }
}

以上程序中,车类对象中有人类对象的引用作为其数据成员,同样的,人类对象中也会有车类对象的引用作为数据成员,当通过代码

 people1.setCar(car);
 car.setPeople(people1);

建立二者之间的联系后,下一步就可以通过类之间定义的方法来互发消息,即为:

  System.out.println(people1.getCar().getInfo());
  System.out.println(car.getPeople().getInfo());
三、谈谈组合与继承的区别以及两者的使用场景(即什么时候宜用组合?什么时候宜用继承?)。
  1. 组合:通过对象内部的属性引用来实现。对象之间的耦合性较为松散。
  2. 继承:从已有的类派生出新的类。子类可以重用父类中的结构,也可以根据子类的功能需要进行结构扩充。所以,子类的定义范围往往比父类描述的范围更小,因此可以定义一个通用类,然后可根据需要将其扩展为其它多个特定类。
  3. 继承的目的在于避免重复,易于维护,易于理解;它就像对房间进行拓展成为一栋楼,前面的零部件它都具备,但是如果没有房间,大楼是无法构建的,具有结构和功能上的关联。而组合像房间里面的窗户、墙壁、地板、桌子、椅子等,他们之间并不存在结构上的相似性,只是功能上组合可以发挥更大的作用,但是单独之间可以独立运行的,并不影响。显而易见,在不具有结构和功能上的相似性时,使用继承可以减少代码重复率,易于维护;在结构实现不同、功能“可叠加”时,使用组合无疑是优于继承的。
四、Java中的运行时多态的含义是什么?有什么作用?请举例说明

运行时多态指的是重载,在运行时根据输入参数动态选择不同的成员方法执行,体现了一个类本身的多态性,使代码具有良好的拓展性。
例6.6代码如下(示例):

public class javaDemo {
    public static void main(String args[]){
        int resultA = sum(10,20);
        System.out.println("结果:"+resultA);
        int resultB = sum(10,20,30);
        System.out.println("结果:"+resultB);
        int resultC = sum(10.0,20.0);
        System.out.println("结果:"+resultC);
    }
    public static int sum(int a,int b){
        return a+b;
    }
    public static int sum(int a,int b,int c){
        return a+b+c;
    }
    public static int sum(double a,double b){
        return (int)(a+b);
    }
}

关于重载,更多的请看我的上一篇文章。java课程学习第4课

五、使用接口改写例6.8中的程序。

例6.8即为对抽象类的具体实现

在这里插入图片描述而与抽象类不同的是,接口中没有域变量,只有常量,因此在接口中我们只能定义求面积与周长的方法,而且必须是抽象方法,否则会报错。
接口定义如下(示例):

//接口定义的关键字为 interface 而不是class
public interface Shape {
    abstract public double getArea();
    abstract public double getPerimeter();
}

三个子类定义如下(示例):

public class Rect implements Shape {
    public int height;
    public int width;
    Rect(int x,int y){
        this.height = x;
        this.width =y;
    }
    @Override
    public double getArea() {
        return height*width;
    }
    @Override
    public double getPerimeter() {
        return (width+height)*2;
    }
}
---------上面是Rect---------------------下面是Triangle------------------
public class Triangle implements Shape{
    public int k,x,y;

    public Triangle(int baseA,int baseB,int baseC){
        this.k = baseA;
        this.x= baseB;
        this.y = baseC;
    }
    @Override
    public double getArea(){
        double m= (k+x+y)/2.0;
        return(Math.sqrt(m*( m-k)*( m-x)*(m-y)));
    }
    @Override
    public double getPerimeter(){
        return(k+x+y);
    }
}
----------上面是Triangle-----------------------下面是Circle--------------------
public class Circle implements Shape{
    public int r;
    public Circle(int r){
        this.r = r;
    }
    @Override
    public double getArea() {
        return (r*r*Math.PI);
    }
    @Override
    public double getPerimeter() {
        return (r*2*Math.PI);
    }
}
------------上面是Circle-----------------下面是测试类----------------------
public class javaDemo {
    public static void main(String[] args){
        Rect rect = new Rect(25,25);
        System.out.println("矩形的面积: "+rect.getArea());
        System.out.println("矩形的周长: "+rect.getPerimeter());

        Triangle tri = new Triangle(5,5,8);
        System.out.println("三角形的面积: "+rect.getArea());
        System.out.println("三角形的周长: "+rect.getPerimeter());

        Circle cir = new Circle(25/2.0);
        System.out.println("圆形的面积: "+rect.getArea());
        System.out.println("圆形的周长: "+rect.getPerimeter());
    }
}

子类继承抽象类,用的是关键字extends;
子类实现接口,用的是关键字 implements;
子类将接口中的抽象方法各自实现后,便可调用方法来求得面积与周长。

六、抽象类与接口
抽象类

抽象类与抽象方法都使用abstract关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。

抽象类与普通类的最大的区别是抽象类不能被实例化,只能被继承。

接口中 字段 默认为 public static final
接口中 方法 默认为 public abstract

接口

接口是抽象类的延伸。
Java8之前

  • 接口可以看成是一个完全抽象的类,不能有任何的方法实现;
  • 如果一个接口想要增加新的方法,那么要修改所有实现了该接口的类,让他们都实现新增的方法。但是,从Java8开始,接口也可以有默认的方法实现,原因是:不支持默认方法的接口的维护成本太高。

接口的成员(字段+方法)默认都是public,并且不允许定义为private,这样就能定义某些复用的代码又不会把方法暴露出去。

接口的字段默认为是staticfinal的。

抽象类与接口的比较
  • 从设计层面上看,抽象类提供了一种IS-A关系,需要满足里氏替换原则,即子类对象必须能够替换掉所有父类对象;但是接口更像是一种LIKE-A关系,他只是提供一种方法的实现契约,并不要求接口和实现接口的类具有IS-A关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类
  • 接口的字段只能是staticfinal类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是public的,而抽象类的成员可以有多种访问权限。
使用选择
使用接口
  1. 需要让许多不相关的类实现一个方法,
  2. 需要使用多重继承
使用抽象类
  1. 需要在几个相关的类中共享代码
  2. 需要能控制继承来的成员的访问权限,而不是都为public
  3. 需要继承非静态和非常量字段

在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从Java8开始,接口也可以有默认的方法实现,使得修改接口的成本也变得很低。

七、简述运算符instanceof的使用场景。

对象向下转型存在有安全隐患,为了保证转换的安全性,可以在转换前通过instance of 关键字进行对象所属类型的判断。
用法:对象引用 instanceof 类 (a instanceof A)
instanceof 比较的结果有三种:true,false,语法错误(编译无法通过)

  1. 如果 a 为类A实例或者A子类的实例,则返回true,
  2. 如果 a 为类A父类的实例,则返回false,
  3. 如果a为null,则返回的内容是false。
  4. 如果 a对象和类A没有任何关系,则编译不会通过;

代码如下(示例):

class  Uncle{}
class Pare{}
class Pare1 extends Pare{}
class Pare2 extends Pare1{}
public class javaDemo {
    public static void main(String[] args){
        Uncle  u1 = new Uncle();
        Pare  p = new Pare();
        Pare1 p1 = new Pare1();
        Pare2 p2 = new Pare2();
        if(p instanceof Pare){
            System.out.println("p instanceof Pare");
        }
        if(!(p1 instanceof Pare)){
            System.out.println("p1 not instanceof Pare");
        }
        else{
            System.out.println("p1 instanceof Pare");
        }
        if(p1 instanceof Pare2){
            System.out.println("p1 instanceof Pare2");
        }
        else{
            System.out.println("p1 not instanceof Pare2");
        }
        if(null instanceof String){
            System.out.println("null instanceof String");
        }
        else{
            System.out.println("null not  instanceof String");
        }
    }
}

程序输出结果:

p instanceof Pare
p1 instanceof Pare
p1 not instanceof Pare2
null not  instanceof String

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值