课堂作业06
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了课堂作业06相关的知识,希望对你有一定的参考价值。
1. 继承条件下的构造方法调用
(1)源代码
package demo1;
//一级级的调用,构造时先调用基类的构造函数;
//super
//构造函数调用必须是构造函数中的第一个语句
class Gradeparent
{
public Gradeparent()//默认构造函数
{
System.out.println("Gradeparent Created");
}
public Gradeparent(String string)//重载
{
System.out.println("Gradeparent Created.String"+string);
}
}
class Parent extends Gradeparent
{
public Parent()
{
//super(" hello ");
System.out.println("Parent Created");
}
}
class Child extends Parent
{
public Child()
{
System.out.println("Child Created");
}
}
public class TestInherits {
public static void main(String args[])
{
Child c=new Child();
}
}
(2)设计思想
先定义一个Gradeparent类,再定义一个Parent,直接继承Gradeparent,定义一个Child类,直接继承Parent类,为了看调用顺序,所以在每个构造函数中都有输出语句,用于判别是哪个先输出的,先调用的哪个;如果类中有无参构造函数和有参构造函数,默认的是无参构造函数,要是想调用父类的构造函数,可以用super。
(3)程序结果截图
第一个是没有用super,第二个用到了super,super()括号中有参数,所以就调用了有参数的基类构造方法。
(4)结论
父类与子类之间构造方法的调用关系修改Parent构造方法的代码,显式调用GrandParent的另一个构造函数,注意这句调用代码是否是第一句,影响重大!
通过 super 调用基类构造方法,必须是子类构造方法中的第一个语句。继承基类的方法是加extends关键词。
2. 为什么子类的构造方法在运行之前,必须调用父类的构造方法?能不能反过来?为什么不能反过来
构造函数的作用就是初始化,就像亲属关系中,只有有了父亲,才会有孩子,不能反过来。
3. 何为“不可变的类”?
创建“不可变的类”的对象后,此对象的属性不可改,而且也无法从此类派生出新子类。String就是一个典型的例子。不可变的“类”可以方便和安全地用于多线程环境中,访问它们可以不用加锁,因而能提供较高的性能。
(1)源程序
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();
}
}
(2)设计思想
定义不允许继承的类,类中有两个参数,一个无参数一个有参数,进行方法的重载,类中有两个实例属性,还有get函数,得到实例属性,equals方法判断对象是否是相等
4.为什么会输出这样的结果
(1)源程序
package demo1;
public class ExplorationJDKSource {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new A());
}
}
class A{}
(2)截图
(3)结果分析
前面示例中,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. 方法覆盖
(1)源代码
public class Shape {
public static void main(String args[])
{
Rectangle a=new Rectangle(3,4);
System.out.print("面积"+a.getArea());
}
/*void showArea()//抽象方法,求面积并且显示
{
}*/
}
interface DiagArea//定义接口类
{
double getDiagonal();//求对角线长度方法
double getArea();//求面积
}
class Rectangle implements DiagArea//矩形类,实现接口
{
int a,b;//表示长和宽
Rectangle()
{
a=0;b=0;
}
Rectangle(int a1,int b1)
{
a=a1;b=b1;
}
public double getDiagonal()//覆盖,求对角线长
{
return Math.sqrt(a*a+b*b);
}
public double getArea()//覆盖,求面积
{
return a*b;
}
}
class Square extends Rectangle//正方形继承矩形类
{
int x;//正方形的边
//构造函数,进行初始化
Square()
{
x=0;
}
Square(int aa)
{
a=aa;
}
public double getDiagonal()//求对角线
{
return Math.sqrt(a*a*2);
}
public double getArea()//求面积
{
return a*a;
}
void display()//显示边长、面积、对角线长
{
System.out.println("正方形的边长、面积、对角线分别是:");
System.out.println(x+" "+getArea()+" "+getDiagonal());
}
}
//class Circle
(2)设计思想
定义一个接口,里面有一些函数,实现接口,在里面实现覆盖,接着在主方法中定义变量,调用覆盖后的结果。
(3)截图
(4)编译错误,注意事项
覆盖方法的允许访问范围不能小于原方法。覆盖方法所抛出的异常不能比原方法更多。声明为final方法不允许覆盖。例如,Object的getClass()方法不能覆盖。不能覆盖静态方法,覆盖时要用public。
构造函数(constructor)是一种特殊的方法 。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中 。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。构造函数的功能主要用于在类的对象创建时定义初始化的状态。
构造一个对象,先调用其构造方法,来初始化其成员函数和成员变量。
子类拥有父的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。
不能反过来调用也是这个原因,因为父类根本不知道子类有神魔变量而且这样一来子类也得不到初始化的父类变量,导致程序运行出错!
1.源代码:
public class ExplorationJDKSource {
public static void main(String[] args) {
System.out.println(new A());
}
}
class A{}
2.结果截图:
3.结果分析:
前面示例中,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();
3.下列语句哪一个将引起编译错误?为什么?哪一个会引起运行时错误?为什么?
m=d;
d=m;
d=(Dog)m;
d=c;
c=(Cat)m;
先进行自我判断,得出结论后,运行TestCast.java实例代码,看看你的判断是否正确
编译错误
d=m;d=c;
不正确 子类对象可以直接赋给基类变量。
基类对象要赋给子类对象变量,必须执行类型转换,
其语法是:子类对象变量=(子类名)基类对象名;
运行错误c=(Cat)m
不正确 转换混乱。如果类型转换失败Java会抛出以下这种异常:ClassCastException
4.下边的程序运行结果是什么? 2. 你如何解释会得到这样的输出? 3. 计算机是不会出错的,之所以得 到这样的运行结果也是有原因的, 那么从这些运行结果中,你能总 结出Java的哪些语法特性?
public class ParentChildTest {
public static void main(String[] args) {
Parent parent=new Parent();
parent.printValue();
Child child=new Child();
child.printValue();
parent=child;
parent.printValue();
parent.myValue++;
parent.printValue();
((Child)parent).myValue++;
parent.printValue();
}
}
class Parent{
public int myValue=100;
public void printValue() {
System.out.println("Parent.printValue(),myValue="+myValue);
}
}
class Child extends Parent{
public int myValue=200;
public void printValue() {
System.out.println("Child.printValue(),myValue="+myValue);
}
}
1)
Parent.printValue(),myValue=100
Child.printValue(),myValue=200
Child.printValue(),myValue=200
Child.printValue(),myValue=200
3)
当子类与父类拥有一样的方法,并且让一个父类变量引用一个子类对象时,到底调用哪个方法,由对象自己的“真实”类型所决定,这就是说:对象是子类型的,它就调用子类型的方法,是父类型的,它就调用父类型的方法。如果子类与父类有相同的字段,则子类中的字段会代替或隐藏父类的字段,子类方法中访问的是子类中的字段(而不是父类中的字段)。如果子类方法确实想访问父类中被隐藏的同名字段,可以用super关键字来访问它。
如果子类被当作父类使用,则通过子类访问的字段是父类的!
5.为什么子类的构造方法在运行之前,必须调用父类的构造方法?能不能反过来?为什么不能反过来?
原因:构造函数用来在创建对象时初始化对象,与new运算符一起使用在创建对象的语句时。子类拥有父类的成员变量和成员方法,如果不调用,则从父类继承而来的成员变量和成员方法得不到正确的初始化。不可以反过来调用,父类不知道子类有什么变量,导致子类得不到正确的初始化,程序出错。
6. 多态含义和用途
让我们看一个开发场景:
某动物园有一饲养员小李,
每天需要给他所负责饲养的狮子、猴子和鸽子喂食。
请用一个程序来模拟他喂食的过程。
①三种动物对应三个类,每个类定义一个eat()方法,表示吃饲养员给它们的食物。
再设计一个Feeder类代表饲养员,其name字段保存饲养员名字,三个方法分别代表喂养三种不同的动物,其参数分别引用三种动物对象。
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("我要减肥,所以每天只吃一点大米。");
}
}
这种编程方式有什么不合理的地方?
每次喂食都要创建一次类。重复步骤多。
①引入继承
定义一个抽象基类Animal,其中定义一个抽象方法eat(),三个子类实现这个抽象方法。
Feeder类的三个喂养方法现在可以合并为一个feedAnimal()方法,注意它接收一个类型为Animal参数,而不是三个具体的动物类型。
依据多态特性,此方法将可以接收任何一个派生自Animal类的子类对象
56
public class Zoo
{
public static void main(String args[])
{
Feeder f = new Feeder("小李");
//饲养员小李喂养一只狮子
f.feedAnimal(new Lion());
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++)
{
f.feedAnimal(new Monkey());
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++)
{
f.feedAnimal(new Pigeon());
}
}
}
class Feeder
{
public String name;
Feeder(String name)
{
this.name = name;
}
public void feedAnimal(Animal an)
{
an.eat();
}
}
abstract class Animal
{
public abstract void eat();
}
class Lion extends Animal
{
public void eat()
{
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal
{
public void eat()
{
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal
{
public void eat()
{
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}
①进一步优化喂养一群动物
package zoo3;
public class Zoo
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Animal[] ans = new Animal[16];
//饲养员小李喂养一只狮子
ans[0] = new Lion();
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++) {
ans[1 + i] = new Monkey();
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++) {
ans[11 + i] = new Pigeon();
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
public void feedAnimals(Animal[] ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}
④第二次重构之后,Feeder类的feedAnimals()方法接收的是一个Animal数组,这有一个限制,就是只能创建固定个数的数组,无法动态地增减动物个数。
想想以下场景:
(1)动物园新进了一些动物
(2)某动物生病不幸死亡
(3)……
我们的代码能否应付以上的场景?
import java.util.Vector;
public class Zoo {
public static void main(String args[]) {
Feeder f = new Feeder("小李");
Vector<Animal> ans = new Vector<Animal>();
//饲养员小李喂养一只狮子
ans.add(new Lion());
//饲养员小李喂养十只猴子
for (int i = 0; i < 10; i++) {
ans.add(new Monkey());
}
//饲养员小李喂养5只鸽子
for (int i = 0; i < 5; i++) {
ans.add(new Pigeon());
}
f.feedAnimals(ans);
}
}
class Feeder {
public String name;
Feeder(String name) {
this.name = name;
}
//Vector<T>是JDK中提供的一个对象集合,可以随时向其中加入或移除对象
public void feedAnimals(Vector<Animal> ans) {
for (Animal an : ans) {
an.eat();
}
}
}
abstract class Animal {
public abstract void eat();
}
class Lion extends Animal {
public void eat() {
System.out.println("我不吃肉谁敢吃肉!");
}
}
class Monkey extends Animal {
public void eat() {
System.out.println("我什么都吃,尤其喜欢香蕉。");
}
}
class Pigeon extends Animal {
public void eat() {
System.out.println("我要减肥,所以每天只吃一点大米。");
}
}
总结:
多态编程有两种主要形式:
(1)继承多态:示例程序使用的方法
(2)接口多态:使用接口代替抽象基类。
使用多态最大的好处是:
当你要修改程序并扩充系统时,你需要修改的地方较少,对其它部分代码的影响较小!千万不要小看这两个“较”字!程序规模越大,其优势就越突出。
以上是关于课堂作业06的主要内容,如果未能解决你的问题,请参考以下文章