第六章小结
1. 实验:利用IDE的debug功能给例6.4和例6.5的new语句设置断点,使用单步调试(step into/step over)跟踪子类对象实例化(初始化)的执行顺序,并总结该过程。
//例6.4
class AddClass {
private 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());
}
}
//运行结果:
/** super:x+y+z=10
a+b+c=22
super:x+y+z=30
a+b=40
super:x+y+z=1
a=8 */
Debug运行顺序:
首先从主函数进入后访问 SonAddClass p1=new SonAddClass (2,3,5); 随后进入SonAddClass 类中寻找与之相对应的构造方法,随后执行 super(x,y,z), 然后去到父类相应的函数 AddClass (int x,int y,int z),中,执行 this(x,y) 语句,然后再去到 AddClass (int x,int y) 中, 执行this(x) 语句,然后再去到AddClass (int x) 中,然后执行初始化字段语句private 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) 中 ,然后执行子类的初始化字段语句 int a=0,b=0,c=0; ,然后在执行 a=x+4; b=y+4; c=z+4; 语句,最后返回主函数中,剩余两个 new 语句,顺序类似。
//例6.5
class Pare {
int i=3;
Pare(){ i=6;System.out.println("call super()");}
}
class Construct extends Pare{
int i = 10;
Construct(){
System.out.println("execute Construct()");
}
Construct(int num){
this();
System.out.println("execute Construct(int)");
}
public static void main(String[] args){
Construct ct = new Construct(9);
System.out.println(ct.i);
}
}
//运行结果:
/**
call super()
execute Construct()
execute Construct(int)
10
*/
Debug运行顺序:
从主函数中进入,执行语句 Construct ct = new Construct(9); ,进入到 Construct ** 类的构造函数当中,执行函数 ** Construct(int num) ** ,然后进入父类执行方法Pare()** ( 注意:进入函数 Construct(int num)当中后,虽然函数体中并没有执行父类构造方法的显示调用语句,但是系统会自动的隐式调用父类的构造方法 Pare(),) ,然后执行父类的初始化字段语句,然后执行方法 **Pare() **,的剩余语句,然后放回到方法 Construct(int num) 中,执行子类的初始化字段,然后执行函数的剩余语句,然后在返回到主函数中。
子类对象实例化的过程:
- 为子类对象分配内存空间,对成员变量进行默认的初始化
- 绑定子类构造方法,将new中的参数传递给构造方法的形式参数
- 调用this或super语句(二者必居其一,不能同时存在), 对从父类继承来的实例变量进行初始化。
- 按定义顺序执行实例变量初始化操作。
- 执行子类构造方法的剩余代码
2. 如何实现两个对象之间互发消息,请举例说明。
- 运用类的组合可以实现两个对象之间互发消息,即一个类的引用作为另一个类的数据成员。
class FighterPlane
{
String name;
int missileNum;
public FighterPlane(String _name,int _missileNum){
name = _name;
missileNum = _missileNum;
}
public void fire(){
if (missileNum>0){
System.out.println("now fire a missile !");
missileNum -= 1;
}
else{
System.out.println("No missile left !");
}
}
}
class A
{
FighterPlane fp;
public A(FighterPlane fpp){
this.fp = fpp; //A对象中拥有了FighterPlane对象的引用
}
public void invoke(){
//A中对象发送消息给FighterPlane的对象
System.out.println(fp.name);
}
}
public class Run{
public static void main(String[] args)
{
FighterPlane ftp = new FighterPlane("su35",10);
//产生A对象,并将ftp对象引用作为参数传入
A a = new A(ftp);
//发送消息,产生调用关系
a.invoke();
}
}
- 内部类
class Outer
{
private String index="The String is in Outer Class";
public class Inner
{
String index="The String is in Inner Class";
void print()
{
String index="The String is in print Method";
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print()
{
Inner inner = new Inner();
inner.print();
}
Inner getInner()//注意:必须使用此方法创建内部类实例对象
{
return new Inner();
}
}
public class TestOuterAndInner
{
public static void main(String[] args)
{
Outer outer = new Outer(); //先产生外部类对象
//Outer.Inner inner = outer.getInner();//内部类前没有public时的访问方法
Outer.Inner inner = outer.new Inner(); //利用对象的new方法
inner.print();
}
}
//输出结果:
/**
The String is in print Method
The String is in Inner Class
The String is in Outer Class
*/
注意:
- 内部类可以直接访问外部类中的所有属性,包括修饰符为private 的属性或方法
- 内部类对象产生方法:
- 如果内部类的修饰符为public:则采用本例的代码形式 Outer.Inter inner = outer.new Inner() 来产生内部类的对象
- 如果内部类前的修饰付为private:则需要使用外部的类的特定接口方法来得到内部类的实例,例如:本例的getInner 方法
- 当static 修饰内部类时,内部类就相当于外部类的一个静态属性,内部类中就可以有 static 属性或方法。 static 内部类不能在使用外部类的非static 的属性或方法,产生对象时也有自己的特点。例如:按照本例的代码:Outer.Inner i = new Outer.Inner();
3. 谈谈组合与继承的区别以及两者的使用场景(即什么时候宜用组合 ?什么时候宜用继承?)。
区别:
- 组合:一个类(A)的引用作为另一个类(B)的数据成员,类B的对象可以通过其中类A的引用,来使用类中的属性和方法,但是类A并不具有类B中所拥有的属性和方法。
- 继承 :一个类(A)是另一个类(B)的子类,则称类A继承类B,其中类B有的方法,在类A中都有,并且类A(子类)可以覆写类B(父类)中的方法。
使用场景:
- 组合:
- 当某一个类要使用另一个类的某些方法时,但这两个类又没有相同的之处,这时使用组合会更比较方便
- 继承:
- 如果要重写现有类的方法,使用继承
- 如果定义了许多类,而这些类中又有较多的公共的属性和方法,则考虑写将这些类的属性和方法抽象出来,来实现继承。
4.Java中的运行时多态的含义是什么?有什么作用?请举例说明。
多态: 多态是指一个程序中同名的不同方法共存的情况,Java中提供两种多态的机制:重载(overloading)与覆写(overriding)
运行时多态: 当同一个引用变量(父类引用)指向不同的子类实例,然后访问引用变量成员方法,方法会有不同的表现。即一个接口,多个方法
- 父类的引用指向子类对象,调用的方法是子类的方法
class A {
void fun() {
System.out.println(“I am A”);
}
}
//A的子类B
class B extends A {
void fun() {
System.out.println(“I am B”);
}
}
//A的子类C
class C extends A {
void fun() {
System.out.println(“I am C”);
}
}
class Test {
public static void main(String[] args) {
//定义父类对象引用变量
A a;
//子类对象
B b = new B();
C c = new C();
//父类对象引用变量 引用 子类对象
a=b;
a.fun(); //输出 I am B
a=c;
a.fun(); // //输出 I am C
}
}
- 通过接口指向不同的对象来实现不同的方法
//定义接口InterA
interface InterA {
void fun();
}
//实现接口InterA的类B
class B implements InterA {
public void fun() {
System.out.println(“This is B”);
}
}
//实现接口InterA的类C
class C implements InterA {
public void fun() {
System.out.println(“This is C”);
}
}
class Test {
public static void main(String[] args) {
//定义接口
InterA a;
//接口类型变量引用实现接口的类的对象
a= new B();
a.fun();
a = new C();
a.fun();
}
}
5.使用接口改写例6.8中的程序。’
//例6.8
public abstract class Shape {
protected double x,y,z; //子类可见
//抽象方法
abstract public double getArea();
abstract public double getPerimeter();
//抽象类可以有构造方法,初始化从父类继承的实例变量
public Shape(double x,double y,double z){
this.x = x;
this.y = y;
this.z = z;
}
}
public class Rect extends Shape {
public Rect(double height, double width) { super(height,width, 0.0); }
public double getArea(){
return x * y;
}
public double getPerimeter(){
return (x + y)*2;
}
}
public class Circle extends Shape{
public Circle(double radius){
super(radius,0.0,0.0);
}
public double getArea(){
return( x * x *Math.PI);
}
public double getPerimeter(){
return(2*Math.PI* x);
}
}
public class Triangle extends Shape {
public Triangle(double lenX,double lenY,double lenZ){
super(lenX, lenY, lenZ);
}
public double getArea(){
double m= (x + y+ z)/2.0;
return Math.sqrt(m*( m-x)*( m-y)*(m-z));
}
public double getPerimeter(){
return x + y + z;
}
}
public class TestShape {
public static void main(String[] args) {
Rect rect = new Rect(25,25);
Triangle tri = new Triangle(5,5,8);
Circle cir = new Circle(12.5);
printArea(rect);
printPerimeter(rect);
printArea(tri);
printPerimeter(tri);
printArea(cir);
printPerimeter(cir);
}
public static void printArea(Shape s){
System.out.println(s.getClass()+":"+s.getArea());
}
public static void printPerimeter(Shape s){
System.out.println(s.getClass()+":"+s.getPerimeter());
}
}
使用接口改编
//例6.8
interface Shape {
double getArea();
double getPerimeter();
}
public class Rect implements Shape {
private double x,y;
public Rect(double height, double width) {
this.x = height;
this.y = width;
}
public double getArea(){
return x * y;
}
public double getPerimeter(){
return (x + y)*2;
}
}
public class Circle implements Shape{
private double x;
public Circle(double radius){
this.x = radius;
}
public double getArea(){
return( x * x *Math.PI);
}
public double getPerimeter(){
return(2*Math.PI* x);
}
}
public class Triangle implements Shape {
private double x,y,z;
public Triangle(double lenX,double lenY,double lenZ){
this.x = lenX;
this.y = lenY;
this.z = lenZ;
}
public double getArea(){
double m= (x + y+ z)/2.0;
return Math.sqrt(m*( m-x)*( m-y)*(m-z));
}
public double getPerimeter(){
return x + y + z;
}
}
public class TestShape {
public static void main(String[] args) {
Rect rect = new Rect(25,25);
Triangle tri = new Triangle(5,5,8);
Circle cir = new Circle(12.5);
printArea(rect);
printPerimeter(rect);
printArea(tri);
printPerimeter(tri);
printArea(cir);
printPerimeter(cir);
}
public static void printArea(Shape s){
System.out.println(s.getClass()+":"+s.getArea());
}
public static void printPerimeter(Shape s){
System.out.println(s.getClass()+":"+s.getPerimeter());
}
}
/**
运行结果:
class com.runoob.test.Rect:625.0
class com.runoob.test.Rect:100.0
class com.runoob.test.Triangle:12.0
class com.runoob.test.Triangle:18.0
class com.runoob.test.Circle:490.8738521234052
class com.runoob.test.Circle:78.53981633974483
*/
接口注意事项attenion:
- 接口定义用关键字interface,而不是用class,interface前的修饰符要么为public,要么为缺省。
- 在类中,用implements关键字来实现接口。一个类可以实现多个接口,在implements后用逗号隔开多个接口的名字。一个接口也可被多个类来实现。
- 接口具有继承性,可通过extends关键字声明接口的父接口列表。
- 接口定义的数据成员全是public final static(静态常量),即使没有修饰符。存储在该接口的静态存储区域内,使用接口名.字段或实现类.字段均可访问。
- 接口中没有构造方法;所有的抽象方法都是public abstract 方法(与抽象类有所不同)。即使没有修饰符,其效果完全等效。注:方法前不能修饰为final。
- 如果实现某接口的类不是abstract修饰的抽象类,则在类的定义部分必须实现接口的所有抽象方法,而且方法头部分应该与接口中的定义完全一致。
- 如果实现接口的类是abstract类,则它可以不实现该接口的所有方法。但对于抽象类的任何一个非抽象的子类而言,接口中的所有抽象方法都必须实现。
- 类在实现接口的抽象方法时,必须显式使用public修饰符,否则将被警告为缩小了接口中定义的方法的访问控制范围。
6.自定义一个类,覆写equals方法,以满足自身业务需求
覆写的要点:
- 首先判断要比较的对象是否为null,若是直接返回false,若不比较则会发生空指针异常
- 然后判断是否与自己相等(地址是否一样),若一样则直接返回true
- 接下来判断两个类是否是同类,若不是返回false
- 最后向下转型,比较两个类的内容是个相同
class Person{
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String toString(){
return this.name+"今年"+this.age+"岁";
}
public boolean equals(Object obj){//Object类可接受任何类
if(obj==null){//判断是否为空,若不判断则会出先空指针异常(NullPointerException)
return false;
}
//判断是否在与自身比较(通过比较地址),若是则直接返回true
if(this==obj){
return true;
}
//判断是否是同类
if(!(obj instanceof Person)){
return false;
}
//到达此处时必定是同类但不同地址的对象在比较
Person per=(Person)obj;//向下转型,比较属性值
return this.name. == per.name && this.age==per.age;//判定属性内容是否相等
}
}
class Student{}
public class Test{
public static void main(String[] args) {
Person per1=new Person("张三",18);
Person per2=new Person("张三",18);
Person per3=new Person("lisi",19);
Person per4=null;
Student stu=new Student();
System.out.println(per1.equals(per1));//true
System.out.println(per1.equals(stu));//false
System.out.println(per1.equals(per3));//false
System.out.println(per1.equals(per4));//false
}
}
7. 举例说明运算符instanceof的使用场景。
instanceof 关键字用于比较引用类型
格式: a instanceof A 其中 a 为对象的引用,A为类。
- 如果a 是 A的实例或 A 子类的实例,则返回true
- 如果a 是 A 父类的实例,则放回false
- 如果a 对象的类 和A 没有任何的关系,则编译不会通过
class Uncle{}
class Pare{}
class Pare1 extends Pare{}
class Pare2 extends Pare1{}
class Pare3 {
public static void main(String[] args){
Uncle u = 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 ( p2 instanceof Pare)
{System.out.println("p2 instanceof Pare");}
if ( p1 instanceof Pare1)
{System.out.println("p1 instanceof Pare1");}
if ( p2 instanceof Pare1)
{System.out.println("p2 instanceof Pare1");}
if ( p1 instanceof Pare2)
{System.out.println("p1 instanceof Pare2");}
else
{System.out.println("p1 not instanceof Pare2");}
/*if (p instanceof Uncle) //去掉注释后,由于类Uncle 和类 Pare 无任何的关系,所以会出现第三中情况,编译不通过
{System.out.println("p instanceof Uncle");}
else
{System.out.println("p not instanceof Uncle");}*/
if (null instanceof String)
{System.out.println("null instanceof String");}
else
{System.out.println("null not instanceof String");}
}
}
/**
运行结果:
p instanceof Pare
p1 instanceof Pare
p2 instanceof Pare
p1 instanceof Pare1
p1 not instanceof Pare2
null not instanceof String
*/
8.谈谈抽象类与接口的异同以及两者的使用场景。
抽象类: 用abstract 修饰的类车称为抽象类,用abstract 修饰的成员方法称为抽象方法
public abstract class AbstractCla {
private int m;
public abstract void a(); //抽象方法
public abstract void b(); //抽象方法
public void c(){
a(); //非抽象方法可以调用抽象方法
}
}
abstract class AbstractClb extends AbstractCla {
public void b() { } //仅实现抽象方法b()
}
class ConcreteCl extends AbstractClb {
@Override
public void a() { } //实现抽象方法a()
}
- 抽象类中可以有零个或多个抽象方法,也可以包含非抽象方法。只要有一个抽象方法,类前就必须有abstract 修饰
- 抽象类不能创建对象,创建对象由具体子类来实现,但可以有声明,声明能引用所有具体子类的对象。
- 对于抽象方法,在抽象类中只指定方法名及类型,而不写实现代码。抽象类必定要派生子类,若派生的子类是具体类,则具体子类中必须实现抽象类中定义的所有抽象方法(覆盖)
- 在抽象类中,非抽象方法可以调用抽象方法
- abstract 不能与private ,static、final 或 native 并列修饰同一个方法。
接口:
- 可以被引用调用的方法(public 方法或同包中的protected 方法 或 默认方法),相当于API 的概念
- 同“类” 概念地位相等的专有概念 interface ,interface 是方法说明的集合
interface 声明格式:
[public] interface 接口名 [extends 父接口列表] {
//静态常量数据成员声明
[punlic][static][final] 变量类型 变量名 = 常量值;
// 抽象方法声明
[public][abstract] 返回值类型 方法名(参数列表)[throws 异常列表];
// 缺省方法,Java8引入,可被实现类继承使用
[public] default 返回值类型 方法名(参数列表)[throws 异常列表] { 方法实现语句} ;
//静态方法 Java 8 引入,通过接口直接使用
[public] static 返回值类型 方法名(参数列表)[throws 异常列表] { 方法实现语句} ;
}
public interface InterfaceA {
public static final int ON = 1;
public abstract void a();
}
public interface InterfaceB {
public abstract void b();
}
//允许多继承
public interface InterfaceC extends InterfaceA, InterfaceB {
public abstract void c();
}
public class InterfaceImp implements InterfaceC{
@Override
public void a() {
System.out.println("call a()");
}
@Override
public void b() {
System.out.println("call b()");
}
@Override
public void c() {
System.out.println("call c()");
}
}
- 共同点:二者都可以具有抽象方法,都不能实例化,但都可以有自己的声明,并能引用子类或实现类对象
- 不同点:
特性 | 接口 | 抽象类 |
---|---|---|
组合 | 新的类可以组合对个接口 | 只能继承单一抽象类 |
状态 | 不能包含属性(除了静态属性,不支持对象状态 ) | 可以包含属性,非抽象方法可能引用这些属性 |
默认方法和抽象方法 | 不需要在子类中实现默认方法。默认方法可以引用其他接口的方法 | 必须在子类中实现抽象方法 |
构造器 | 没有构造器 | 可以有构造器 |
可见性 | 隐式public | 可以是protected 或 友元 |
抽象类在被继承时体现的是is - a 关系,接口在被实现时体现的是can-do关系
使用场景:
- 抽象类:
- 即想约束子类具有共同的行为(但不在乎其如何实现),又想拥有缺省的方法,又拥有实例变量
- 如:模板方法设计模式,模板方法使得子类可以在不改变算法结构的情况下,重现定义算法中某些步骤的具体实现
- 接口:
- 约束多个实现类具有共同的行为,但不在乎每个实现类如何具体实现
- 作为能够实现特定功能的标识存在,也可以是什么接口方法都没有的纯粹标识
- 实现类需要具备很多不同的功能,但各个功能之间可能没有任何联系
- 使用接口的引用调用用具体实现类中的实现的方法。