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

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

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

  • 相关阅读:
    软件的竞争力:性能 CQ
    2010.7.11 OA项目组一周工作报告 CQ
    2010.8.22 OA项目组一周工作报告 CQ
    2010.7.18 OA 项目组一周工作报告 CQ
    我的音乐 CQ
    2010.7.25 OA项目组一周工作报告 CQ
    JavaScript中的关于this
    递归函数的应用
    es6中的对象的可计算的属性名
    undefined 和 undeclared 的区别
  • 原文地址:https://www.cnblogs.com/wangdayang/p/13855197.html
Copyright © 2020-2023  润新知