Blog03
一、前言
在PTA题目集7~9的题目中,增加了类的数量,一方面需要我们加强对类与类之间关系的理解,一方面需要我们花更多时间来理解代码之间的逻辑性,首先我来总结一下本次题目中的知识点,题目集7中主要考察的知识点是类的继承、多态性使用方法以及接口的应用,类的继承在前面几次题目集中已经出现过,我就不再过多的阐述,多态性的方法也与继承息息相关,新的知识就是接口的出现,我认为接口可以理解为一种特殊的类,里面全部是由全局常量和公共的抽象方法所组成。接口是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)接口的出现可以很好的解决java中一个类无法继承多个类的问题,但是需要这个类来实现接口,这里就会引入一个关键字即为实现——interface,在题目集8和题目集9中,重点考察的知识是考核类的封装性及类间关系设计,关于类间的关系设计需要我们花费一些时间进行思考,如何通过调整类与类之间的关系来以一种简单易懂的方式来达到题目的要求是我们思考的最终目的。这三次题目集就题量而言并不是很多,但是每道题的代码量都较大,需要花费大量时间去完成,就题目难度而言的话我认为都比较难,但是只要你肯花时间去理解的话还是能够做出来的。
二、设计与分析
1、题目集07中的7-1
首先我给出此题的题目要求,有需要的朋友可以打开链接https://images.ptausercontent.com/f52db597-70a8-4cf0-8e8c-5caf8f081cfd.pdf
对于此题我们需要先分析一下此题的类图:

从这个类图中我们可以很清晰的看出来此题需要我们使用继承与接口,值得注意的是这里面的Shape类是一个抽象类,在做题时需要注意一下,类图中的一些比较基础的属性、构造器和方法我就不再过多的解释,此题的难点在于在做题过程中会用到与链表和Collections类相关的知识,在做此题时我自己也对这部分的知识做了一个知识点的归纳,如下:
首先是与Arraylist有关的知识点:
ArrayList就是动态数组,用MSDN中的说法,就是Array的复杂版本,它提供了如下一些好处:
动态的增加和减少元素
实现了Collection和List接口
灵活的设置数组的大小
最简单的例子:
ArrayList List = new ArrayList();
for( int i=0;i <10;i++ ) //给数组增加10个Int元素
List.Add(i);
//..程序做一些处理
List.RemoveAt(5);//将第6个元素移除
for( int i=0;i <3;i++ ) //再增加3个元素
List.Add(i+20);
Int32[] values = (Int32[])List.ToArray(typeof(Int32));//返回ArrayList包含的数组
这是一个简单的例子,虽然没有包含ArrayList所有的方法,但是可以反映出ArrayList最常用的用法
1)构造器
ArrayList提供了三个构造器:
public ArrayList();
默认的构造器,将会以默认(16)的大小来初始化内部的数组
public ArrayList(ICollection);
用一个ICollection对象来构造,并将该集合的元素添加到ArrayList
public ArrayList(int);
用指定的大小来初始化内部的数组
2)IsSynchronized属性和ArrayList.Synchronized方法
IsSynchronized属性指示当前的ArrayList实例是否支持线程同步,而ArrayList.Synchronized静态方法则会返回一个ArrayList的线程同步的封装。
如果使用非线程同步的实例,那么在多线程访问的时候,需要自己手动调用lock来保持线程同步,例如:
ArrayList list = new ArrayList();
//...
lock( list.SyncRoot ) //当ArrayList为非线程包装的时候,SyncRoot属性其实就是它自己,但是为了满足ICollection的SyncRoot定义,这里还是使用SyncRoot来保持源代码的规范性
{
list.Add( “Add a Item” );
}
如果使用ArrayList.Synchronized方法返回的实例,那么就不用考虑线程同步的问题,这个实例本身就是线程安全的,实际上ArrayList内部实现了一个保证线程同步的内部类,ArrayList.Synchronized返回的就是这个类的实例,它里面的每个属性都是用了lock关键字来保证线程同步。
3)Count属性和Capacity属性
Count属性是目前ArrayList包含的元素的数量,这个属性是只读的。
Capacity属性是目前ArrayList能够包含的最大数量,可以手动的设置这个属性,但是当设置为小于Count值的时候会引发一个异常。
4)Add、AddRange、Remove、RemoveAt、RemoveRange、Insert、InsertRange
这几个方法比较类似
Add方法用于添加一个元素到当前列表的末尾
AddRange方法用于添加一批元素到当前列表的末尾
Remove方法用于删除一个元素,通过元素本身的引用来删除
RemoveAt方法用于删除一个元素,通过索引值来删除
RemoveRange用于删除一批元素,通过指定开始的索引和删除的数量来删除
Insert用于添加一个元素到指定位置,列表后面的元素依次往后移动
InsertRange用于从指定位置开始添加一批元素,列表后面的元素依次往后移动
另外,还有几个类似的方法:
Clear方法用于清除现有所有的元素
Contains方法用来查找某个对象在不在列表之中
其他的我就不一一累赘了,大家可以查看MSDN,上面讲的更仔细
5)TrimSize方法
这个方法用于将ArrayList固定到实际元素的大小,当动态数组元素确定不在添加的时候,可以调用这个方法来释放空余的内存。
6)ToArray方法
这个方法把ArrayList的元素Copy到一个新的数组中。
在此题已经给出的代码中还给出了与Collection有关的头文件,接下来再说说Collection类中一些方法的使用:
我在这里直接给出一种比较常用的方法,sort方法,我直接用代码来进行说明:

在有了以上知识的基础下我们再来分析此题,这道题目的难点是创建一个Card类的链表,然后将用户输入的信息储存在这个链表中,而这个Card类就是你自己定义的一个类,我们需要按照用户输入的数字来判断应该是何种类型的卡片进入到你的这个链表中,下面我直接给出代码:
class DealCardList {
ArrayList<Card> cardList=new ArrayList<Card>();
public DealCardList() {
super();
}
DealCardList(ArrayList<Integer> list){
for(int i=0;i<list.size();i++)
{
if(list.get(i)==1)
{
double r=Main.input.nextDouble();
Circle circle=new Circle(r);
Card card=new Card(circle);
card.getShape().setShapename("Circle");
cardList.add(card);
}
if(list.get(i)==2) {
double a=Main.input.nextDouble();
double b=Main.input.nextDouble();
Rectangle rectangle=new Rectangle(a,b);
Card card=new Card(rectangle);
card.getShape().setShapename("Rectangle");
cardList.add(card);
}
if(list.get(i)==3) {
double a=Main.input.nextDouble();
double b=Main.input.nextDouble();
double c=Main.input.nextDouble();
Triangle triangle=new Triangle(a,b,c);
Card card=new Card(triangle);
card.getShape().setShapename("Triangle");
cardList.add(card);
}
if(list.get(i)==4) {
double a=Main.input.nextDouble();
double b=Main.input.nextDouble();
double c=Main.input.nextDouble();
Trapezoid traperzoid=new Trapezoid(a,b,c);
Card card=new Card(traperzoid);
card.getShape().setShapename("Trapezoid");
cardList.add(card);
}
}
}
public boolean validate() {
for(int i=0;i<cardList.size();i++)
{if(!cardList.get(i).getShape().validate())
return false;}
return true;
}
public void cardSort() {
for(int j=0;j<cardList.size();j++)
for(int i=j+1;i<cardList.size();i++)
{
if(cardList.get(j).getShape().getArea()<cardList.get(i).getShape().getArea())
Collections.swap(cardList, j, i);
}
}
public double getAllArea() {
double area=0;
for(int i=0;i<cardList.size();i++)
area=area+cardList.get(i).getShape().getArea();
return area;
}
public void showResult() {
System.out.println("The original list:");
for(int i=0;i<cardList.size();i++)
System.out.print(cardList.get(i).getShape().getShapename()+":"+String.format("%.2f",cardList.get(i).getShape().getArea())+" ");
System.out.println();
System.out.println("The sorted list:");
cardSort();
for(int i=0;i<cardList.size();i++)
System.out.print(cardList.get(i).getShape().getShapename()+":"+String.format("%.2f",cardList.get(i).getShape().getArea())+" ");
System.out.println();
System.out.println("Sum of area:"+String.format("%.2f",getAllArea()));
}
}
再明白了上面这部分代码如何写了后,这道题的难度就不是很大了。
最后我给出这道题目的复杂度

2、题目集07中的7-2
这道题是上面那道题的变式题,其实并没有发生根本性的变化,首先我给出题目的链接:https://images.ptausercontent.com/848bbef1-2b5f-4c6a-8597-83fd7ca0c01c.pdf
此题与上面那道题的区别在于增加了分组输出并要求在组类将面积排序的要求,这一部分我们可以直接在链表中进行操作,即需要改动的就是链表中一些方法的添加与输出方法的修改,我直接给出代码:
class DealCardList {
ArrayList<Card> arrayList = new ArrayList<Card>();
ArrayList<Card> circleOfAreaArrayList = new ArrayList<Card>();
ArrayList<Card> rectangleOfAreaArrayList = new ArrayList<Card>();
ArrayList<Card> triangleOfAreaArrayList = new ArrayList<Card>();
ArrayList<Card> trapezoidOfAreaArrayList = new ArrayList<Card>();
public DealCardList() {
}
public DealCardList(ArrayList<Integer> List) {
for (int i = 0; i < List.size(); i++) {
switch (List.get(i)) {
case 1:
double radius = Main.input.nextDouble();
Shape circle = new Circle(radius);
Card card1 = new Card(circle);
card1.getShape().setShapeName("Circle");
arrayList.add(card1);
circleOfAreaArrayList.add(card1);
break;
case 2:
double width = Main.input.nextDouble();
double length = Main.input.nextDouble();
Shape rectangle = new Rectangle(width, length);
Card card2 = new Card(rectangle);
card2.getShape().setShapeName("Rectangle");
arrayList.add(card2);
rectangleOfAreaArrayList.add(card2);
break;
case 3:
double side1 = Main.input.nextDouble();
double side2 = Main.input.nextDouble();
double side3 = Main.input.nextDouble();
Shape triangle = new Triangle(side1, side2, side3);
Card card3 = new Card(triangle);
card3.getShape().setShapeName("Triangle");
arrayList.add(card3);
triangleOfAreaArrayList.add(card3);
break;
case 4:
double topSide = Main.input.nextDouble();
double bottomSide = Main.input.nextDouble();
double height = Main.input.nextDouble();
Shape trapezoid = new Trapezoid(topSide, bottomSide, height);
Card card4 = new Card(trapezoid);
card4.getShape().setShapeName("Trapezoid");
arrayList.add(card4);
trapezoidOfAreaArrayList.add(card4);
break;
default:
System.out.println("Wrong Format");
}
}
}
public boolean validate() {
boolean f = true;
for (int i = 0; i < arrayList.size(); i++) {
if (arrayList.get(i).getShape().validate()) {
f = false;
break;
}
}
if (arrayList.size() == 0) {
f= false;
}
return f;
}
public double getAllCircleArea() {
double temp = 0;
for (int i = 0; i < circleOfAreaArrayList.size(); i++) {
temp += circleOfAreaArrayList.get(i).getShape().getArea();
}
return temp;
}
public double getAllRectangleArea() {
double temp1 = 0;
for (int j = 0; j < rectangleOfAreaArrayList.size(); j++) {
temp1 += rectangleOfAreaArrayList.get(j).getShape().getArea();
}
return temp1;
}
public double getAllTriangleArea() {
double temp2 = 0;
for (int k = 0; k < triangleOfAreaArrayList.size(); k++) {
temp2 += triangleOfAreaArrayList.get(k).getShape().getArea();
}
return temp2;
}
public double getAllTrapezoidArea() {
double temp3 = 0;
for (int m = 0; m < trapezoidOfAreaArrayList.size(); m++) {
temp3 += trapezoidOfAreaArrayList.get(m).getShape().getArea();
}
return temp3;
}
public void showResult() {
System.out.println("The original list:");
System.out.print("[");
for (Card card : arrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.println("\nThe Separated List:");
System.out.print("[");
for (Card card : circleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : rectangleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : triangleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : trapezoidOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
Collections.sort(circleOfAreaArrayList);
Collections.sort(triangleOfAreaArrayList);
Collections.sort(rectangleOfAreaArrayList);
Collections.sort(trapezoidOfAreaArrayList);
System.out.println("\nThe Separated sorted List:");
System.out.print("[");
for (Card card : circleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : rectangleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : triangleOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.print("[");
for (Card card : trapezoidOfAreaArrayList) {
System.out.print(card.getShape().toString());
}
System.out.print("]");
System.out.println();
double area1 = getAllCircleArea();
double area2 = getAllRectangleArea();
double area3 = getAllTriangleArea();
double area4 = getAllTrapezoidArea();
ArrayList<Double> areas = new ArrayList<>();
areas.add(area1);
areas.add(area2);
areas.add(area3);
areas.add(area4);
Collections.sort( areas);
System.out.printf("The max area:%.2f",areas.get(3) );
}
}
在上面这段代码中我们可以清晰的看出我们需要创建更多的链表来解决这道题目。
最后我给出这道题目的复杂度:


三、题目集8和题目集9两道ATM机仿真题目的设计思路分析总结
这两道题目之间都有一定的联系,难度都较大。
1、题目集8的ATM机仿真题目
完成这道题需要有两个部分的完成,一部分是类的设计,一部分是算法的设计,从题目要求中我们可以看到我们需要设计的算法其实就只有存款与取款,总体来说比较简单,难点在于我们必须要很清楚这几个类之间的关系,我首先给出我写完后的代码的截图,方便大家理解:
;;
从上面的类图中就可以看出此题对于类的设计要求是比较高的,做此题时我们首先需要将题目中所给的数据全部初始化,接下来按照题目要求将类一个一个的写出,类的一些基本属性其实不太难,比较困难的点在于一些技巧的使用与对于单一职责的拿捏,这里我主要强调一下对与迭代器的使用与HashSet的用法,这两个方法在此题中主要用于判断卡号是否存在与ATM id是否存在,我先给出代码:

上述代码就是用于判断校验卡号是否存在,这部分代码我们就用了迭代器,通过迭代器我们可以将用户输入的卡号与我们存入的卡号一一匹配,若没有能够与之匹配的卡号,那么就会报错,这种写法不但简洁而且清晰明了,再判断ATM id是否存在时所用的方法与之基本相同,将Card换做ATM即可,其余的判断也与之相似,在这里我就不再过多的阐述,最后我想重点声明一下我对于这个类的设计是如何思考的:ATM机编号,银行又有银行名称和银联。而Card,Account,Bank,银联也具有如此关系。这里的关系不是那么复杂。搞清这些关系后,对于题目中给的用户的数据,需要一个单独的类进行初始化。形如 cardList.add(new Card(new Account(new User("杨过"),new Bank(new ChinaUnionPay("中国银联"),"中国建设银行"),和 atmList.add(new ATM(new Bank(new ChinaUnionPay("中国银联"),"中国工商银行"),"06"));到这数据存好之后,就是一系列的算法问题。由题意可知,需要通过用户的卡号对这张卡的一系列信息进行查找,然后对该账户进行存取款的操作和判断是否非法输入以及够不够钱的问题。如此一来,便得到上述类图所对应的代码。
2、题目集9的ATM机仿真题目
题目集9的题目实际上就是题目集8的变式,在题目集8的基础上增加了几个客户同时有了贷记卡,还可以跨行取款,但其实此题的主要框架与上面那道题类似,只要稍做添加即可,接下来我给出代码中的修改部分,我将账户类变为了抽象类,另外加了一个贷记卡类,让这个类继承账户类,从而达到可以使用的贷记卡的目的,但是由于贷记卡还可以实现更多的功能,我们需要在抽象账户类增加更多的属性,我给出截图:

还有就是在贷记卡与借记卡在存取款的规则是不同的,因为跨行时需要手续费,而贷记卡可以贷款,我给出相关代码及注释:
if (!isCreditAccount()) {
double commission = 0;
// 如果为跨行业务,则收取跨行费用
if (this.isCrossBank()) {
commission += this.getCrossBankCommission(amount);
}
// 取款超出余额
if (amount + commission > account.getBalance()) {
System.out.println("Sorry,your account balance is insufficient.");
return false;
}
}
// 如果为信用卡
if (isCreditAccount()) {
double commission = 0; // 手续费总额
double overdrawAmount = 0; // 透支金额
double balance = 0; // 当前卡上余额
balance = account.getBalance(); // 获取当前卡余额
// 有余额,则透支金额为取款金额与余额的差值
if (balance >= 0) {
overdrawAmount = amount - account.getBalance();
} else {// 余额为负值(已经透支),则透支金额为取款金额
overdrawAmount = amount;
}
// 收取跨行业务费用
if (this.isCrossBank()) {
commission += this.getCrossBankCommission(amount);
}
// 收取透支费用
if (overdrawAmount > 0) {
commission += this.getOverdrawCommission(overdrawAmount);
}
// 如果取款金额与手续费金额之和大于卡片余额与可透支最大金额之和,无法取款
if (commission + amount > account.getBalance() + account.getOverdrawAmount()) {
System.out.println("Sorry,your account balance is insufficient.");
return false;
}
}
return true;
}
最后我给出我代码的复杂度:

从复杂度可以看出在Max Depth和Max Complexity还较高,代码还有改进的空间。
四、采坑心得
1、![]()
在题目集的7的7-1中我们最后还需要输出每张卡片所对应的类型,而我开始在做时混淆了get与set的用法,导致我并没有将卡片的类型传入题目中,要牢记get是获取,set是传入一个值。
2、![]()
在这个链表里面装的应该是Bank类型的值,而我没有注意,搞错了类与类之间的关系,将尖括号里面的Bank写成了ATM,这是没有搞清楚类与类之间的关系造成的。
五、改进建议
1、随着代码行数的增多,让我越来越意识到打代码时格式规范性的必要,在我写代码时常常忽视这点,我认为若写代码时只是一味的按自己意愿乱写,不仅会增加代码错误的概率,同时会加大你在检查代码时的难度,尤其是括号的使用,在这方面我还有很大的进步空间。
2、关于代码的复杂度问题,好的代码的复杂度应该较低,且效率较高,例如需要写一大串的if-else嵌套语句时,可以用switch语句进行替代,提高代码的效率。
3、在设计类的时候应该考虑到单一职责,这样的代码才是好代码。
六、总结
对于这次题目集我总结了关于迭代器的用法,如下:
先看一段程序例子:
String s[] = {"hello","world"};
List str = new ArrayList();
for(int i = 0; i < 2; i++){
str.add(new String(s[i]));
}
Iterator e = str.iterator();
while(e.hasNext()){
System.out.println((String)e.next());
}
下面让我们对着API了解一下这个Iterator :
iterator重载了collection,在Java集合框架中Iterator用来替代Enumeration,Iterator允许调用者使用好的语义在迭代中从底层集合中删除元素。 该接口是Java集合框架成员。
Method Summary
boolean hasNext()
Returns true if the iteration has more elements. 如果迭代中还有元素返回true。
Object next()
Returns the next element in the iteration. 返回迭代中的下一个元素。
void remove()
Removes from the underlying collection the last element returned by the iterator (optional operation). 迭代器从底层集合中删除刚返回的元素(可选操作)。
Method Detail
hasNext
public boolean hasNext()
如果迭代中还有元素返回true。(换言之,如果next返回一个元素而不是抛出异常的话,返回true。) ,
如果迭代中还有元素返回true
next
public Object next()
返回迭代中的下一个元素。如果迭代中没有元素时抛出。
remove
public void remove()
迭代器从底层集合中删除刚返回的元素(可选操作)。该方法只能在每次调用next后调用一次。
如果底层集合已被修改而迭代器正在进程中(除了调用本方法),则迭代器的行为不能确定。
浙公网安备 33010602011771号