文章目录
一、实验:利用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没有显示的调用父类的构造函数,但是执行过程中,程序还是调用了父类的构造函数,并进行了父类数据成员的初始化。
综上,子类对象实例化过程如下:
- 为对象分配内存空间,并且对成员变量 进行默认的初始化。
- 绑定构造方法,将new中的参数传递给构造方法的形式参数。
- 调用this或者super语句(二者必居其一,但不能同时存在)。
- 进行显示初始化操作。
- 执行当前构造方法的方法体中的程序代码
具体流程如下图所示:
二、如何实现两个对象之间互发消息,请举例说明。
实现两个对象之间互发消息,那么采用组合的设计方式,在两个对象之间建立联系;在两个类内都有对方的对象引用作为数据成员,并真正的调用过程中,二者的对象引用都互相指向对方的对象。
代码如下(示例):
//车类
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());
三、谈谈组合与继承的区别以及两者的使用场景(即什么时候宜用组合?什么时候宜用继承?)。
- 组合:通过对象内部的属性引用来实现。对象之间的耦合性较为松散。
- 继承:从已有的类派生出新的类。子类可以重用父类中的结构,也可以根据子类的功能需要进行结构扩充。所以,子类的定义范围往往比父类描述的范围更小,因此可以定义一个通用类,然后可根据需要将其扩展为其它多个特定类。
- 继承的目的在于避免重复,易于维护,易于理解;它就像对房间进行拓展成为一栋楼,前面的零部件它都具备,但是如果没有房间,大楼是无法构建的,具有结构和功能上的关联。而组合像房间里面的窗户、墙壁、地板、桌子、椅子等,他们之间并不存在结构上的相似性,只是功能上组合可以发挥更大的作用,但是单独之间可以独立运行的,并不影响。显而易见,在不具有结构和功能上的相似性时,使用继承可以减少代码重复率,易于维护;在结构实现不同、功能“可叠加”时,使用组合无疑是优于继承的。
四、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
,这样就能定义某些复用的代码又不会把方法暴露出去。
接口的字段默认为是static
和final
的。
抽象类与接口的比较
- 从设计层面上看,抽象类提供了一种IS-A关系,需要满足里氏替换原则,即子类对象必须能够替换掉所有父类对象;但是接口更像是一种LIKE-A关系,他只是提供一种方法的实现契约,并不要求接口和实现接口的类具有IS-A关系。
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类
- 接口的字段只能是
static
和final
类型的,而抽象类的字段没有这种限制。 - 接口的成员只能是
public
的,而抽象类的成员可以有多种访问权限。
使用选择
使用接口
- 需要让许多不相关的类实现一个方法,
- 需要使用多重继承
使用抽象类
- 需要在几个相关的类中共享代码
- 需要能控制继承来的成员的访问权限,而不是都为
public
- 需要继承非静态和非常量字段
在很多情况下,接口优先于抽象类,因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从Java8
开始,接口也可以有默认的方法实现,使得修改接口的成本也变得很低。
七、简述运算符instanceof的使用场景。
对象向下转型存在有安全隐患,为了保证转换的安全性,可以在转换前通过instance of 关键字进行对象所属类型的判断。
用法:对象引用 instanceof 类 (a instanceof A)
instanceof 比较的结果有三种:true,false,语法错误(编译无法通过)
- 如果 a 为类A实例或者A子类的实例,则返回true,
- 如果 a 为类A父类的实例,则返回false,
- 如果a为null,则返回的内容是false。
- 如果 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