动手动脑4(03-继承与多态)
10.21(03-继承与多态)
1.运行 TestInherits.java 示例,观察输出,注意总结父类与子类之间构造方法的调用关系修改Parent构造方法的代码,显式调用GrandParent的另一个构造函数,注意这句调用代码是否是第一句,影响重大!
TestInherits.java
class Grandparent 
{
    public Grandparent()
 	{
        	System.out.println("GrandParent Created.");
	
}
    public Grandparent(String string) 
	{
        	System.out.println("GrandParent Created.String:" + string);
	
 }
}
class Parent extends Grandparent
{
    public Parent()
	 {
        	//super("Hello.Grandparent.");
        	System.out.println("Parent Created");
	
       // super("Hello.Grandparent.");
	  }
}
class Child extends Parent 
{
    public Child()
	 {
	
        System.out.println("Child Created");
	  }
}
运行结果:
将两个super注释掉

将第二个注释掉

将第一个注释掉
 
结论: 通过 super 调用基类构造方法,必须是子类构造方法中的第一个语句。
2.思索:为什么子类的构造方法在运行之前,必须调用父类的构造方法?能不能反过来?为什么不能反过来?(提示: 构造函数的主要作用是什么? 从这个方面去想!)
构造函数是一种特殊的构造方法,在创建对象的时候初始化对象,构造一个对象,先调用其构造方法,而子类拥有父类的成员变量,如果不先调用父类的构造方法,则子类的成员变量也不能正确的初始化,不能反过来是因为,子类继承父类会由多得成员变量,而反过来,父类压根不知道子类有什么成员变量,构造方法就会出错,因此如果反过来,也是错误的。
3.不可变的“类”有何用?
可以方便和安全地用于多线程环境中,
访问它们可以不用加锁,因而能提供较高的性能。
不可变类的实例:Address.java
JDK中的实例:String
Address.java
public final class Address
{
	private final String detail;
	private final String postCode;
	//在构造方法里初始化两个实例属性
	public Address()
	{
		this.detail = "";
		this.postCode = "";
	}
	public Address(String detail , String postCode)
	{
		this.detail = detail;
		this.postCode = postCode;
	}
	//仅为两个实例属性提供getter方法
	public String getDetail()
	{
		 return this.detail;
	}
	public String getPostCode()
	{
		 return this.postCode;
	}
	//重写equals方法,判断两个对象是否相等。
	public boolean equals(Object obj)
	{
		if (obj instanceof Address)
		{
			Address ad = (Address)obj;
			if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode()))
			{
				return true;
			}
		}
		return false;
	}
	public int hashCode()
	{
		return detail.hashCode() + postCode.hashCode();
	}
}
4.参看ExplorationJDKSource.java示例 此示例中定义了一个类A,它没有任何成员: class A { }
示例直接输出这个类所创建的对象 public static void main(String[] args) { System.out.println(new A()); }
我们得到了一个奇特的运行结果: A@1c5f743
请按照以下步骤进行技术探险: (1)使用javap –c命令反汇编ExplorationJDKSource.class; (2)阅读字节码指令,弄明白println()那条语句到底调用了什么? (3)依据第(2)得到的结论,使用Eclipse打开JDK源码,查看真正被执行的代码是什么
激动啊,我终于发现了……

main方法实际上调用的是: public void println(Object x),这一方法内部调用了String类的valueOf方法。
valueOf方法内部又调用Object.toString方法:
public String toString() {
return getClass().getName() +"@" + Integer.toHexString(hashCode());
}
hashCode方法是本地方法,由JVM设计者实现: public native int hashCode();
5.来看一段代码(示例Fruit.java ):

注意最后一句,一个字串和一个对象“相加”,得到以下结果:

Fruit类覆盖了Object类的toString方法。
结论: 在“+”运算中,当任何一个对象与一个String对象,连接时,会隐式地调用其toString()方法,默认情况下,此方法返回“类名 @ + hashCode”。为了返回有意义的信息,子类可以重写toString()方法。
6.请自行编写代码测试以下特性(动手动脑): 在子类中,若要调用父类中被覆盖的方法,可以使用super关键字。
package practice;
public class UseSuperInherits
{
public static void main(String[] args)
{
Child c=new Child();
c.printValue();
}
}
class Parent
{
public int value=100;
public void printValue()
{
System.out.println("parent.value="+value);
}
}
class Child extends Parent
{
public int value=200;
public void printValue()
{
System.out.println("child.value="+value);
super.printValue();
}
}
运行结果:

7.怎样判断对象是否可以转换?
可以使用instanceof运算符判断一个对象是否可以转换为指定的类型:
Object obj="Hello";
if(obj instanceof String)
System.out.println("obj对象可以被转换为字符串");
参看实例: TestInstanceof.java
TestInstanceof.java
public class TestInstanceof
{
	public static void main(String[] args) 
	{
		//声明hello时使用Object类,则hello的编译类型是Object,Object是所有类的父类
		//但hello变量的实际类型是String
		Object hello = "Hello";
		//String是Object类的子类,所以返回true。
		System.out.println("字符串是否是Object类的实例:" + (hello instanceof Object));
		//返回true。
		System.out.println("字符串是否是String类的实例:" + (hello instanceof String));
		//返回false。
		System.out.println("字符串是否是Math类的实例:" + (hello instanceof Math));
		//String实现了Comparable接口,所以返回true。
		System.out.println("字符串是否是Comparable接口的实例:" + (hello instanceof Comparable));
		String a = "Hello";
		//String类既不是Math类,也不是Math类的父类,所以下面代码编译无法通过
		//System.out.println("字符串是否是Math类的实例:" + (a instanceof Math));
	}
}
8.下列语句哪一个将引起编译错误?为什么?哪一个会引起运行时错误?为什么?
m=d;
d=m;
d=(Dog)m;
d=c;
c=(Cat)m;
先进行自我判断,得出结论后,运行TestCast.java实例代码,看看你的判断是否正确。
TestCast.java
class Mammal{}
class Dog extends Mammal {}
class Cat extends Mammal{}
public class TestCast
{
	public static void main(String args[])
	{
		Mammal m;
		Dog d=new Dog();
		Cat c=new Cat();
		m=d;
		//d=m;
		d=(Dog)m;
		//d=c;
		//c=(Cat)m;
	}
}
m=d; ture
d=m; false
d=(Dog)m; true
d=c; false
c=(Cat)m; true
运行结果:

d=m,d=c运行时将会报错。因为m是父类对象,d是子类对象。将父类对象转化成子类对象,必须进行强制转换。而d和c是两个互不相干的类对象,所以不能将d赋值给c.
9.运行以下测试代码

回答问题:
1. 左边的程序运行结果是什么?
2. 你如何解释会得到这样的输出?
3. 计算机是不会出错的,之所以得 到这样的运行结果也是有原因的, 那么从这些运行结果中,你能总 结出Java的哪些语法特性?
请务必动脑总结,然后修改或编写一些代码进行测试,验证自己的想法,最后再看 后面的PPT给出的结论。
运行结果:

运行结果原因:
第一行与第二行:分别创建子类和父类的对象,并调用各自的方法。
第三行:将子类对象child赋值给父类对象parent,父类对象parent的属性值不变,只是将父类的同名方法覆盖,所以当父类对象parent只能调用子类的printValue()方法,又因为子类方法访问的是子类中的字段而不是父类,所以输出子类对象parent的myValue属性值200。
第四行:parent.myValue++是将父类对象parent的属性myValue++,变为101,但是父类对象parent调用方法时调用的还是子类的printValue()方法,子类方法访问的还是子类中的字段,所以输出子类对象child的myValue属性值200。
第五行:把父类对象parent强制类型转换成子类Child类型,此时对象parent的为子类对象,拥有子类的属性和方法,因此((Child)parent).myValue++后,parent的myValue的属性值变为201,输出结果201。
结论:
(1)子类对象可以赋值给父类的对象。父类进行子类强制转换可以赋值给子类的对象。
(2)子类能覆盖父类,但是父类中的变量的值是不改变的,访问父类中的变量时可用super来访问,反之则一直被子类覆盖。父类被覆盖时,对父类中的变量进行操作时,父类中的变量改变,但输出时仍输出覆盖父类的子类的变量。
(3)(child)Parent.myValue++,这时改变的将是覆盖父类的子类。
总结:子类父类拥有同名的方法时
(1)当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,这就是说:对象是子类型的,它就调用子类型的方法,是父类型的,它就调用父类型的方法。
这个特性实际上就是面向对象“多态”特性的具体表现。
(2)如果子类与父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。如果子类被当作父类使用,则通过子类访问的字段是父类的!
牢记:在实际开发中,要避免在子类中定义与父类同名 的字段。不要自找麻烦!——但考试除外,考试 中出这种题还是可以的。
10.

思索: 这种编程方式有什么不合理的地方吗?参看“动物园”示例版本一:Zoo1
Zoo1
public class Zoo 
{
	public static void main(String args[])
	{
		Feeder f = new Feeder("小李");
		// 饲养员小李喂养一只狮子
		f.feedLion(new Lion());
		// 饲养员小李喂养十只猴子
		for (int i = 0; i < 10; i++)
 		{
			f.feedMonkey(new Monkey());
		}
		
		// 饲养员小李喂养5只鸽子
		for (int i = 0; i < 5; i++)
 		{
			f.feedPigeon(new Pigeon());
		}
	
	}
}
class Feeder 
{
	public String name;
	public Feeder(String name)
	{
		this.name = name;
	}
	
	public void feedLion(Lion l)
	{
		l.eat();
	}
	
	public void feedPigeon(Pigeon p)
	{
		p.eat();
	}
	
	public void feedMonkey(Monkey m)
	{
		m.eat();
	}
}
class Lion
{
	public void eat() 
	{
		System.out.println("我不吃肉谁敢吃肉!");
	}
}
class Monkey 
{
	public void eat() 
	{
		System.out.println("我什么都吃,尤其喜欢香蕉。");
	}
}
class Pigeon 
{
	public void eat() 
	{
		System.out.println("我要减肥,所以每天只吃一点大米。");
	}
}
程序被写死了,无法更改数据
11.多态编程有两种主要形式:
(1)继承多态:示例程序使用的方法
(2)接口多态:使用接口代替抽象基类,这个任务留为作业。
现在我们可以回答前面提出的问题了:为什么要用多态?它有什么好处?
使用多态最大的好处是: 当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。

                
            
浙公网安备 33010602011771号