1. 实验:利用IDE的debug功能给例6.4和例6.5的new语句设置断点,使用单步调试(step into/step over)跟踪子类对象实例化(初始化)的执行顺序,并总结该过程。
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; }
}
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;
}
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());
}
}
程序从main函数开始,执行“p1=new SonAddClass(2,3,5);”时,调用SonAddClass的含有三个参数的构造函数,然后首先调用父类AddClass的含有三个参数的构造函数,在父类的构造函数的执行过程中又套娃式的分别先调用含有两个参数和含有一个参数的重载的构造函数。
“SonAddClass p2=new SonAddClass(10,20);
SonAddClass p3=new SonAddClass(1);”
这两句的执行过程和上一句类似。
“System.out.println(“a+b+c=”+p1.Add());”
这一句在执行时先执行p1.Add(),再将“a+b+c=”输出。
运行结果:
class Pare
{int i;
Pare(){i=6;}
};
class Construct extends Pare
{ Construct(){}
Construct(int num){}
public static void main(String[] args){
Construct ct = new Construct(9);
System.out.println(ct.i);
}
}
程序从main函数开始,执行“Construct ct=new Construct(9);”时,首先调用Construct类的带参的构造函数,然后带参构造函数调用Construct类的不带参构造函数,然后在Construct类的不带参构造函数中隐式调用父类的不带参构造函数。
“System.out.println(ct.i);
System.out.println(ct.getSuper());”
这两句分别输出了ct的属性i和从父类继承下来的属性i。
运行结果:6
2. 如何实现两个对象之间互发消息,请举例说明。
运用类的组合,把一个类的引用作为另一个类的属性
class FighterPlane {
private String name;
private int missileNum;
private A a;
public void setA(A _a){
if (_a !=null) { a = _a ;}
}
public A getA(){
if (a !=null)
{ return a;}
else return null;
}
public void fire(){ ……}
public FighterPlane(String _name,int _missileNum){ ……}
class A {
FighterPlane fp;
public A(FighterPlane fpp){
this.fp = fpp;
fpp.setA(this);//将当前对象传给FighterPlane
}
public void invoke(){
//A中对象发送消息给FighterPlane的对象
fp.fire();
}
}
public class Run{
public static void main(String[] args)
{
FighterPlane ftp = new FighterPlane("su35",10);
A a = new A(ftp);
a.invoke(); }
}
3. 谈谈组合与继承的区别以及两者的使用场景(即什么时候宜用组合 ?什么时候宜用继承?)
1.区别:
组合:指将已存在的类作为一个新建类的成员变量类型来使用,可以调用已存在类的函数,但两个类之间无上下级关系。
继承:子类继承父类,即继承了父类的所有属性和方法,两个类之间有上下级关系。
2.使用场合:
组合:两个类之间无父子关系,只是一个类想使用另一个类的某个或某些方法函数时,则无需将类的属性和方法全部继承下来。
继承:两个类之间存在父子关系,一个类是在另一个类的基础上衍生的,需要包含另一个类的全部属性和方法。
4. Java中的运行时多态的含义是什么?有什么作用?请举例说明。
多态分为两种情况:编译时多态和运行时多态。
编译时多态就是我们所说的方法重载(overload)。、
运行时多态则是指方法重写(override) 可理解为一个接口,多个方法。
要实现运行时多态需要做以下两件事情:
方法重写(子类继承父类并重写父类中已有的或抽象的方法);
对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
使用接口类和抽象类是实现多态的常用方法
即有 3 个必要条件:继承,重写,向上转型。
作用:提高代码的使用率,使我们编写的代码简洁方便。
package homework;//homework包里
abstract public 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();
}
package homework;
import java.awt.*;
public 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);
}
}
package homework;
import java.awt.*;
public class Triangle extends Shapes{
public Triangle(int baseA,int baseB,int baseC) {
super(baseA,baseB,baseC,0);
m=(baseA+baseB+baseC)/2.0;
}
public double getArea() {
return(Math.sqrt(m*(m-k)*(m-x)*(m-y)));
}
public double getPerimeter() {
return (x+y+k);
}
}
package homework;
import java.awt.*;
public class Circle extends Shapes{
public Circle(int x,int y,int width) {
super(x,y,width,width/2.0);
}
public double getArea() {
return m*m*Math.PI;
}
public double getPerimeter() {
return 2*Math.PI*m;
}
}
package homework;
import java.awt.*;
import java.applet.*;
public class RunShape extends Applet {
Rect rect=new Rect(5,15,25,25);
Triangle tri =new Triangle(5,5,8);
Circle cir=new Circle(13,90,25);
private void drawArea(Graphics g,Shapes s,int a,int b) {
g.drawString(s.getClass().getName()+" Area"+s.getArea(),a,b);
}
private void drawPerimeter(Graphics g,Shapes s,int a,int b) {
g.drawString(s.getClass().getName()+"Perimeter"+s.getPerimeter(),a,b);
}
public void paint(Graphics g) {
g.drawRect(rect.x, rect.y, rect.k, (int)rect.m);
drawArea(g,rect,50,35);
drawPerimeter(g,rect,50,55);
drawArea(g,tri,50,75);
drawPerimeter(g,tri,50,95);
g.drawOval(cir.x-(int)cir.k/2,cir.y-(int)cir.k/2,cir.k,cir.k);
drawArea(g,cir,50,115);
drawPerimeter(g,cir,50,135);
}
}
运行时多态发生在RunShape
类中,父类Shapes
对象引用调用子类Rect
、Triangle
和Circle
的对象。
5. 使用接口改写例6.8中的程序。
import java.awt.*;
import java.applet.*;
interface Shapes//接口
{
abstract public double getArea();
abstract public double getPerimeter();
}
class Rect implements Shapes
{
protected int x,y,k;
protected double m;
public double getArea()
{ return(k*m); }
public double getPerimeter()
{ return(2*k+2*m);}
public Rect(int x,int y,int width,int height)
{
this.x=x;
this.y=y;
this.k=width;
this.m=height;
}
}
class Triangle implements Shapes
{
protected int x,y,k;
protected double m;
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)
{
this.x=baseA;
this.y=baseB;
this.k=baseC;
this.m=0;
//m充当了周长的一半
m= (baseA+ baseB+ baseC)/2.0;
}
}
class Circle implements Shapes
{
protected int x,y,k;
protected double m;
public double getArea()
//Math是java.lang包中的类,PI是静态其属性,其值为Π
{return(m* m *Math.PI);}
public double getPerimeter()
{return(2*Math.PI* m);}
public Circle(int x,int y,int width)
{
//m充当了半径的角色,k充当了直径
this.x=x;
this.y=y;
this.k=width;
this.m=width/2.0;
}
}
public class RunShape extends Applet
{
Rect rect=new Rect(5,15,25,25);
Triangle tri=new Triangle(5,5,8);
Circle cir =new Circle(13,90,25);
public void paint(Graphics g)
{
//绘制矩形,输出矩形的面积和周长
g.drawRect(rect.x,rect.y,rect.k,(int)rect.m);
g.drawString("Rect Area:"+rect.getArea(),50,35);
g.drawString("Rect Perimeter:"+rect.getPerimeter(),50,55);
//输出三角形的面积和周长
g.drawString("Triangle Area:"+tri.getArea(),50,75);
g.drawString("Triangle Perimeter:"+tri.getPerimeter(),50,95);
//绘制圆,输出圆的面积和周长
g.drawOval(cir.x-(int)cir.k/2,cir.y-(int)cir.k/2,cir.k,cir.k);
g.drawString("Circle Area:"+cir.getArea(),50,115);
g.drawString("Circle Perimeter:"+cir. getPerimeter(),50,135);
}
}
抽象类和接口的代码很相似,
不同的是用抽象类时可以把每个子类的相似属性也统一提取出放到抽象类中,而接口中只能放抽象出的方法。
因为接口中的属性是静态常量,会导致所有的实现类的对象的属性值相同,所以只能在实现类中定义相关属性。
两者相比感觉抽象类的抽象程度更高,接口的扩展性更强。
6. 自定义一个类,覆写equals方法,以满足自身业务需求
例子:
例如有一个钥匙类,id表示能打开的房门号,一个房门可能有多把备用钥匙,因此,我们覆写equals
方法,要求钥匙的房门号相同就返回True。
public class Key {
private int id;
public Key(int id) {
this.id = id;
}
public int getId() {
return id;
}
@Override
public boolean equals(Object o) {
return this.getId()==((Key)o).getId();
}
public static void main(String[] args){
Key key1 = new Key(1);
Key key2 = new Key(1);
System.out.println(key1.equals(key2));
}
}
只要两个对象的id相同,就会返回True,并不需要引用指向同一个对象。
7. 举例说明运算符instanceof的使用场景。
instanceof:二元运算符。
用法:boolean result = object instanceof class 判断对象是否为特定类的实例
在程序的运行多态中,即用父类或接口的引用去引用子类、实现类的对象时,我们想知道父类或接口的引用所引用的对象是否是某一个具体类的对象时,我们就可以用instanceof,避免向下转型抛出运行时异常。
也可以用来判断两类是否存在父类与子类的关系
Shape s = new Rect(10,30);//父类声明引用子类实例,向上转型
s = new Triangle(5,5,8);
s = new Circle(12.5);
java.lang.ClassCastException: Circle cannot be cast to Triangle
//Triangle triangle =(Triangle)s //向下转型抛出运行时异常
//triangle.drawTri();
if(s instanceof Rect){ //避免向下转型抛出运行时异常
Rect rectangle = (Rect)s;
rectangle.drawRect();
} else if (s instanceof Triangle){
Triangle triangle =(Triangle)s;
triangle.drawTri();
} else if (s instanceof Circle){
Circle circle = (Circle) s;
circle.drawCir();
}
8. 谈谈抽象类与接口的异同以及两者的使用场景。
使用场景:
抽象类:抽象类在被继承时体现的是is-a关系。一般是有继承关系,但父类和子类以及各个子类之间对某些方法的实现又不同。
接口:接口在被实现时体现的是can-do关系。一般是把多个类的相同功能抽象出来,类之间没有继承关系。