第4周作业-面向对象设计与继承
1.本周学习总结
2.书面作业
1.注释的应用
使用类的注释与方法的注释为前面编写的类与方法进行注释,并在Eclipse中查看。(截图)
对类的注释:
所得到的效果:
对方法的注释:
所得到的效果:
PS:我还知道一种将注释生成doc帮助文档的方法:
我们打开该目录,会发现新生成一个名为HTML的文件夹:
进入HTML文件夹后,我们打开Person.html,会发现我们写的注释都在里边啦:
2.面向对象设计(大作业1,非常重要)
2.1 将在网上商城购物或者在班级博客进行学习这一过程,描述成一个故事。(不得少于50字)
这几天电脑用的有点多,眼睛有些干涩,所以决定上某宝买瓶眼药水滴一滴。我打开了APP,输入了用户名和密码后进入了购物界面,商品分类多种多样,我进入了医药保健类找到了眼药水,将它加入了购物车。又上食品类买些零食,进入购物车进行结算,觉得零食可能不够吃,就增加了一些数量,最后剁了手。
2.2 通过这个故事我们能发现谁在用这个系统,系统中包含的类及其属性方法,类与类之间的关系。尝试找到这些类与属性,并使用思维导图描述类、属性、方法及类与类之间的关系。
2.3 尝试使用Java代码实现故事中描述的这一过程(不必很完善),将来要在这个基础上逐渐完善、扩展成一个完整的面向对象的系统。(可选:加分)
参考资料:
UML类图
面向对象案例-借款者姓名地址.zip
因为不清楚函数类里面具体该写些什么,所以只是大体作出了一个小框架,我知道肯定是不完善的,所以只是将自己想到的一些东西化成了代码:
3.ManagerTest.zip代码分析
分析ManagerTest.zip中的代码,回答几个问题:
3.1 在本例中哪里体现了使用继承实现代码复用?回答时要具体到哪个方法、哪个属性。
public Manager(String n, double s, int year, int month, int day)
{
super(n, s, year, month, day);
bonus = 0;
}
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
Manager类中的Manager的构造函数和getSlaray()方法,其中构造函数的对象Manager与Employee具有共有的属性name,salary,hireday,所以可以直接复用父类的代码;getSlaray()方法因为与父类中的实现方法完全相同,所以直接复用了父类的代码。
3.2 Employee类及其子类Manager都有getSalary方法,那怎么区分这两个方法呢?
public double getSalary()
{
double baseSalary = super.getSalary();
return baseSalary + bonus;
}
如子类中的代码所示,如果是以super.getSalary()调用的getSlaray()方法就是父类Employee类的getSalary方法,假如我们将super去掉:
public double getSalary()
{
double baseSalary = getSalary();
return baseSalary + bonus;
}
那么这里的getSalary()方法就是对子类的getSalary()自身进行递归。
所以,我认为区分这两个方法的关键就在于super关键字。
3.3 文件第26行e.getSalary(),到底是调用Manager类的getSalary方法还是Employee类的getSalary方法。
文件第26行e.getSalary()调用的是Manager类的getSalary方法。由代码中不难看出:
public class ManagerTest {
public static void main(String[] args) {
// construct a Manager object
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
boss.setBonus(5000);
Employee[] staff = new Employee[3];
// fill the staff array with Manager and Employee objects
staff[0] = boss;
staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
// print out information about all Employee objects
for (Employee e : staff)
System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
}
}
因为staff被创建为Employee类,所以e.getSalary()调用的自然是父类Employee类的getSalary(),但是,如果当e引用Manager对象的时候,e.getSalary()调用的是Manger类中的getSalary方法。
3.4 Manager类的构造函数使用super调用父类的构造函数实现了代码复用,你觉得这样的有什么好处?为什么不把父类构造函数中的相关代码复制粘贴到Manager的构造函数中,这样看起来不是更直观吗?
最大的好处就是能够减少我们代码的数量,避免我们重复地敲相同的代码,代码复用可以大大提高软件开发的效率。为什么不把父类构造函数中的相关代码复制粘贴到Manager的构造函数中呢?复制粘贴的话的确可以起到相同的效果,但如果我们需要对父类的构造函数进行修改时,这会造成不必要的麻烦,这是你就需要对每一处你使用父类的代码的地方进行修改,得不偿失,拖慢了进度,大大降低了效率,还可能会产生遗漏。
4.Object类
4.1 编写一个Fruit类及属性String name,如没有extends自任何类。使用System.out.println(new Fruit());是调用Fruit的什么方法呢?该方法的代码是从哪来的?尝试分析这些代码实现了什么功能?
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(new Fruit());
}
}
class Fruit{
private String name;
}
运行结果:Fruit@2a139a55
会输出这种结果是因为系统调用了Fruit.toString方法,而该方法的代码来自于Object类,因为当自己写的类没有是写入自己的方法时,系统默认自己写的类继承自Object类。我们进入查看println的源代码,会发现如下代码段:
该代码将输入的Object类的对象的值转换成了字符串,然后输出,并且换行。
4.2 如果为Fruit类添加了toString()方法,那么使用System.out.println(new Fruit());调用了新增的toString方法。那么其父类中的toString方法的代码就没有了吗?如果同时想要复用其父类的toString方法,要怎么操作?(使用代码演示)
如果子类重写toString方法,则会得到:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(new Fruit());
}
}
class Fruit{
private String name;
@Override
public String toString() {
return "Fruit [name=" + name + "]";
}
}
运行结果:Fruit [name=null]
父类的方法并不会消失,只是我们自己定义的子类中重写了toString方法,系统当然会优先调用子类中有的toString方法,没有被调用不意味着父类的方法就没有了。
如果要同时复用父类的代码可以对代码进行如下修改:
public class Main {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(new Fruit());
}
}
class Fruit{
private String name;
@Override
public String toString() {
System.out.println(super.toString());
return "Fruit [name=" + name + "]";
}
}
运行结果:Fruit@2a139a55
Fruit [name=null]
4.3 Fruit类还继承了Object类的eqauls方法。尝试分析其功能?自己编写一个equals方法覆盖父类的相应方法,功能为当两个Fruit对象name相同时(忽略大小写),那么返回true。(使用代码证明你自己覆盖的eqauls方法是正确的)
我们打开Object类的eqauls方法的源代码:
public boolean equals(Object obj) {
return (this == obj);
}
很显然,如果我们调用Object类的equals方法,必然需要先new两个对象,这样必然得到的答案是false,因为虽然对象名字相同,但他们是不同的对象。这样 必然会让人联想到String类中也有个equals方法, 使用这个方法时,那我们得到的答案就是true。
Fruit[] fruits = new Fruit[2];
fruits[0] = new Fruit("apple") ;
fruits[1] = new Fruit("apple");
System.out.println(fruits[0].equals(fruits[1]));
运行结果为false;
String str1 = new String("apple");
String str2 = "apple";
System.out.println(str1.equals(str2));
运行结果为true。
为什么同样是equals方法,得到的答案却是相反的呢?Object类的equals方法的本质其实是和“==”一样的,都是比较两个对象引用是否指向同一个对象(即两个对象是否为同一对象);根据代码,str1和str2都指向了内存中同一个有apple这个字符串的字符串池,那么答案自然是true了。
接下来就重写equals方法:
public boolean equals (Object object){
if (this == object)
return true;
if(!(object instanceof Fruit))
return false;
Fruit fruit = (Fruit )object;
if(this.name == null)
{
if (fruit.name != null)
return false;
}
else if(!this.name.equalsIgnoreCase(fruit.name))
return false;
return true ;
}
这段重写代码是自己写的,后来我发现编译器也会自己生成equals方法,并且还能附带重新编写哈希函数:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Fruit other = (Fruit) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
代码的具体实现原理:如果这个输入的类的对象不是我们所要的Fruit类,直接返回false;如果name为空,而输入的name不为空,也返回false;为了忽略大小写,我使用了String类里的equalsIgnoreCase方法,只要是Object类中判定错误的,我就返回true。
我的两个对象分别是:
这样的运行结果自然就是理想中的true啦。
4.4 在4.3的基础上使用ArrayList fruitList存储多个fruit,要求如果fruitList中已有的fruit就不再添加,没有的就添加进去。请编写相关测试代码。并分析ArrayList的contatins方法是如何实现其功能的?
运行结果:
进入查看contains的源代码:
我们会发现,contains能够实现还是用到了equals方法,如果传入的为空,而数组也为空,那么就返回其索引;如果不为空,则使用equals方法进行比较,如果相同,也返回索引。否则均返回-1。5.代码阅读:PersonTest.java(abstract、多态)
5.1 画出类的继承关系
5.2 读懂main函数,将自己推测的出代码运行结果与真正运行结果进行比较。尝试分析原因
main函数创建了4个Person以其子类Employee、Student以及Employee的子类Programmer、Manager为类型的对象,然后按照对象中年龄变量进行升序排序,并使用各自类中的toString方法中的输出形式输出。因为子类中的toString方法均为eclipse自动生成,所以输出的格式大致相同。而程序的运行结果也与我猜测的相同:
Manager [bonus=12000.3, toString()=Employee [salary=90000.1, toString()=Person [name=Clark, adress=GE, phonenumber=111, email=111@mail.com, age=10, gender=mail]]]
Student [status=1, toString()=Person [name=wang, adress=110, phonenumber=15959, email=15959@163.com, age=18, gender=male]]
Employee [salary=1000.0, toString()=Person [name=zhang, adress=136, phonenumber=1360, email=1360@mail.com, age=21, gender=female]]
Programmer [allowance=50000.0, toString()=Employee [salary=100000.0, toString()=Person [name=Gates, adress=usa, phonenumber=911, email=911@com, age=59, gender=male]]]
5.3 子类中里面使用了super构造函数,作用是什么?如果将子类中的super构造函数去掉,行不行?
调用父类的构造函数
不行,如果去掉子类中的super构造函数,编译就会出错,错误是:Implicit super constructor Employee() is undefined. Must explicitly invoke another constructor意思是隐式的super构造函数employee()是未定义的。必须显式调用另一构造函数。因为没有super,编译器会默认调用父类Employee的无参构造函数,而父类Employee并没有这个无参构造,所以会出错。
5.4 PersonTest.java中的代码哪里体现了多态?你觉得多态有什么好处?多态和继承有什么关系吗?
参考文件:PersonTest.java
for (Person person : peoples) {
System.out.println(person);
}
比如这段代码,我们输出person,但是person是一个Person类,而该类是一个抽象类,当我们要输出它的时候呢,系统会自动识别它的对象具体是哪一个类,进而调用具体的类的方法。
多态到目前为止在编程过程中我感受到的最大的好处就是对已存在代码具有可替换性,在抽象类中只需要定义一个方法,在具体的类中就可以用相同的方法名去做具体的实现,用网上别人博客里的话说是“提高了代码的扩展性”。
继承是子类继承了父类所有的方法和属性;多态,是父类可以有子类的对象,但是对于子类特有的方法,父类不能够去调用它,但是在编译的时候,编译器会自动去识别父类具体引用的是哪一个子类的对象。
PS:写这道题的真实感受是,在用的时候会不自觉地用出来,但是去理解理论的时候感觉自己就写不清楚了,所以里面存在的问题希望老师能够帮我指出来,在改正的同时能加深理解。
3.使用码云管理Java代码
本周Commit历史截图
4.PTA实验
实验总结
本次实验的题目猛一看还真的蛮多的,确实第一眼看到的时候吓了一跳,有点害怕。不过真的做起来呢,函数题的部分还是挺快的,不过麻烦还是有一些,编程题也是遇到了不少的麻烦。
这次编函数题,发现自己很不适应这种就编一个函数的题目,理解题意就花去很多时间,然后还是一知半解,去请教宿舍学霸才明白个大概。编函数题还有一个我不适应的地方就是我编完了这个函数但是没有办法去通过运行来验证我的答案是否正确。也正因为没有办法运行,所以函数题我都在notepad++上敲,notepad++在写的过程中不像MyEclipse那样会指出你编程过程中的错误,导致我提交的时候出现数次编译错误的现象,后面一行一行检查的时候才发现是自己其中一行代码有括号不匹配的情形,当然这也是自己不够认真检查,下次一定会注意。
编程题是对上一次实验的代码可以说是进行一番升级改造吧,但是问题依旧还是很多。第一题是对上次实验中的形状进行改写,增加了一个Shape的抽象类,并让Circle类和Rectangle类继承Shape类。这题有很多代码可以沿用上次实验题的,自己写的并不算多,所以难度并不大。对于sumAllArea和sumAllPerimeter方法放在哪个类中更合适的问题,我在编程过程中进行过尝试,一开始是将它们放在抽象类中,结果发现并没有办法得到答案(当然也不排除我实现的方法逻辑不对),之后我把它们加在了main方法的后边就ok了。还有就是自己的一个疏忽,一开始提交答案错误,可是样例输出又都对,我往函数上找,最后我发现了问题的所在:
public Rectangle(int width,int length) {
// TODO Auto-generated constructor stub
this.length = length;
this.width = width;
}
题目规定第一个参数是width,第二个参数是length,可是我把两个参数写反了,样例输出里恰好两个输入都是 1,所以答案依旧是对的。但是当两个参数不一样的时候,答案自然就不再正确。
第二题实验在代码上很多都可以用编译器自动生成,所以难度也不大。我遇到的最大的问题在于比较输入是否已经在person2数组内这一块。起初将person2创建成PersonOverride类型的长度为n2的数组结果发现无法得到实际用到的长度,随后改为使用ArrayList,之后发现使用了ArrayList需要用到contains来比较,这让我有些疑惑,既然用的是contains,那写equals方法还有什么用呢?我查看了contains的源代码发现其中使用了equals方法,所以equals方法还是要写的。最后还是输出的问题(不知道为啥,自己每次在输出都会出问题……),person2中数据的输出无法使用toString输出,需要用person2.get(i)才行,主要还是对ArrayList还不够熟悉。
第三题主要是对继承覆盖的练习,感觉上虽然看过去这题内容是最多的,但是我做起来觉得反而是最快的,过程中也没有遇到什么像前两题那样的问题,只是一些方法中的限制条件的考虑上还没有那么完善。