动手动脑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)接口多态:使用接口代替抽象基类,这个任务留为作业。

现在我们可以回答前面提出的问题了:为什么要用多态?它有什么好处?

使用多态最大的好处是: 当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“”字!程序规模越大,其优势就越突出。

原文地址:https://www.cnblogs.com/wangdayang/p/13855197.html