《电影院售票系统》项目笔记
笔记说明:
1、旨在记录开发过程中遇到的问题和解决方案。
2、笔记分为三个部分:第一,设计模块及实现功能;第二,出现的问题及解决方案;第三,代码优化
3、项目需求明细及全码,见博客Day036《电影院售票系统》项目全码
第一部分:设计模块
第二部分:问题及解决
问题一:
1、原代码
private static final Logger logger = Logger.getLogger(TheaterSystem.class);
private static ArrayList<Film> filmInfoList = null;
//省略
public void showInfo(){
System.out.println("**********************欢迎进入电影票选购系统**********************");
System.out.println("序号\t电影名称\t英文名称\t导演\t演员\t电影类型\t价格\t时间");
for(int i=0;i<infoList.size();i++){
System.out.println((i+1)+"\t"+infoList.get(i).getName()+"\t"+infoList.get(i).getPoster()+"\t"
+infoList.get(i).getDirector()+"\t"+infoList.get(i).getActor()+"\t"+infoList.get(i).getType()
+"\t"+infoList.get(i).getPrice()+"\t"+infoList.get(i).getItem());
}
System.out.println("下面为影院的座位结构图:");
System.out.println("\t\t\t屏幕");
for(int i=0;i<5;i++){
for(int j=0;j<7;j++){
String seat = (i+1)+"-"+(j+1);
System.out.print(seat+"\t");
seatList0.add(seat);
}
System.out.println();
}
//省略
}
2、问题及原因
在声明集合时并未生成集合,导致后面在集合里添加元素时,出现空指针报错。
3、解决方法
在一开始声明的时候就调用ArrayList的无参构造,创建结合对象。
private static ArrayList<String> seatList0 = new ArrayList<String>();
问题二:
1、原代码:
while(true){
System.out.println("请输入您需要的座位号:");
givenSeat = sc.next();
if(!infoList.get(indexOfThis).getGivenSeatList().contains(givenSeat)){
infoList.get(indexOfThis).givenSeatList.add(givenSeat);
int index2 = seatList.indexOf(givenSeat);
seatList.set(index2, "#");//将已售出座位标记位“#”
break;
}else{
System.out.println("您选择的座位号已售出或输入有误,请重新选择。");
}
}
2、异常:
请输入您需要的座位号:
11
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(ArrayList.java:422)
at java.util.ArrayList.set(ArrayList.java:450)
at cn.nj.Day0621.theatersystem.TheaterSystem.select(TheaterSystem.java:323)
at cn.nj.Day0621.theatersystem.TheaterSystem.main(TheaterSystem.java:70)
3、分析及解决:
原因:举例,如果输入11,那么也是不包含在getGivenSeatList( )中的,但是也不在seatList( )中。即需要首先判断输入的是否为一个合法值,如果然后再判断该座位是否已售出。上述代码跳过了第一层判断。
改进代码如下:
//再进行选座位和查重;注意也要对输入座位格式错误进行处理
while(true){
System.out.println("请输入您需要的座位号:");
givenSeat = sc.next();
if(seatList0.contains(givenSeat)){//注意:先判断是否是seatList0中的元素,再继续往下判断。如先是否为givenSeatList再seatList,如果输入11,那么也是不包含在getGivenSeatList()中的,但是也不在seatList中
if(!infoList.get(indexOfThis).getGivenSeatList().contains(givenSeat)){
infoList.get(indexOfThis).getGivenSeatList().add(givenSeat);
int index2 = infoList.get(indexOfThis).getSeatList().indexOf(givenSeat);
infoList.get(indexOfThis).getSeatList().set(index2, "#");//将已售出座位标记位“#”
logger.debug(seatList0.get(0));
break;
}else{
System.out.println("您选择的座位号已售出,请重新选择。");
}
}else{
System.out.println("您的输入有误,请重新输入。");
}
}
问题三:
1、演示情形:
请输入电影名称:
七武士
电影《七武士》的放映时间为:
场次1: 09:00
场次2: 13:00
请选择观影场次(输入序号):
1
2019-06-21 22:47:32 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 用户选择的时间为:09:00
请输入您要购买的票的类型(输入序号):
1.普通票 2.学生票 3.电影券
1
2019-06-21 22:47:34 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 票类型:普通票,折扣为:10折
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
1-1 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入您需要的座位号:
1-1
2019-06-21 22:47:38 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] #
2019-06-21 22:47:38 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 本次购票的折扣后价格为:60.0
是否继续购票:Y/N
y
请输入电影名称:
七武士
电影《七武士》的放映时间为:
场次1: 09:00
场次2: 13:00
请选择观影场次(输入序号):
1
2019-06-21 22:47:49 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 用户选择的时间为:09:00
请输入您要购买的票的类型(输入序号):
1.普通票 2.学生票 3.电影券
1
2019-06-21 22:48:11 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 票类型:普通票,折扣为:10折
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
# 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入您需要的座位号:
1-2
2019-06-21 22:48:15 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] #
2019-06-21 22:48:15 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 本次购票的折扣后价格为:60.0
是否继续购票:Y/N
y
请输入电影名称:
老无所依
电影《老无所依》的放映时间为:
场次1: 11:00
场次2: 15:00
请选择观影场次(输入序号):
1
2019-06-21 22:48:38 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 用户选择的时间为:11:00
请输入您要购买的票的类型(输入序号):
1.普通票 2.学生票 3.电影券
1
2019-06-21 22:48:40 [cn.nj.Day0621.theatersystem.TheaterSystem]-[DEBUG] 票类型:普通票,折扣为:10折
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
# # 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入您需要的座位号:
2、问题:
不同的场次,其座位情况竟相互影响。显然,这是不可以发生的。并且,也影响到了本应为独立不变的seatList0( )集合。
3、分析及解决:
(1)首先要规避不规范
Film类中把seatList( )的修饰符定义为了public,改为private
原代码:
import java.util.ArrayList;
public class Film {
private String name = null;
private String poster = null;
private String director = null;
private String actor = null;
private String type = null;
private double price = 0;
private String item = null;
public ArrayList<String> seatList0 = new ArrayList<String>();
public ArrayList<String> givenSeatList = new ArrayList<String>();
//略
}
/**
* 第二步,显示界面
*/
public void showInfo(){
System.out.println("**********************欢迎进入电影票选购系统**********************");
System.out.println("序号\t电影名称\t英文名称\t导演\t演员\t电影类型\t价格\t时间");
for(int i=0;i<infoList.size();i++){
System.out.println((i+1)+"\t"+infoList.get(i).getName()+"\t"+infoList.get(i).getPoster()+"\t"
+infoList.get(i).getDirector()+"\t"+infoList.get(i).getActor()+"\t"+infoList.get(i).getType()
+"\t"+infoList.get(i).getPrice()+"\t"+infoList.get(i).getItem());
}
System.out.println("下面为影院的座位结构图:");
System.out.println("\t\t\t屏幕");
for(int i=0;i<5;i++){
for(int j=0;j<7;j++){
String seat = (i+1)+"-"+(j+1);
System.out.print(seat+"\t");
seatList0.add(seat);
}
System.out.println();
}
// 用seatList0()始化每一个Film对象的seatList()
for(Film film:infoList){
film.setSeatList(seatList0());
}
(2)根本原因:
对于private static ArrayList seatList0 = new ArrayList( );
在初始化每个Film对象的seatList( )属性时,直接用的setSeattList(seatList0),实际上就造成了每个对象的seatList( )集合,指向的都是同一个地址。因此,每当一个对象对其seatList( )进行操作,都是直接操作于所有对象的seatList( )集合。
解决办法:每个对象的seatList()单独初始化
改后代码:
for(int k=0;k<items.getLength();k++){
if(items.item(k).getNodeName().equals("Item")){//含义:如果这个子元素的元素名是Item
logger.debug(items.item(k).getTextContent());
item = items.item(k).getTextContent();
Film film = new Film(name,poster,director,actor,type,price,item,new ArrayList<String>(),new ArrayList<String>());
infoList.add(film);
}
}
//### 将seatList0()中的元素单独赋值到到每一个Film对象的seatList( )集合
for(Film film:infoList){
for(String seat:seatList0){
film.getSeatList().add(seat);
}
}
问题四:
1、原码
if(infoList.get(i).getItem()==givenItem){
//略
}
2、问题
这是一个基本知识,但是在使用时很容易不经意间被错用,字符串是否相同的判断,应为equals(),而非“==“等于判断”
if(infoList.get(i).getItem().equals(givenItem)){
//略
}
问题五
1、原代码
//类成员变量部分
private static boolean buyAgain = false;//在本轮已购票后,询问是否继续购票
//类成员方法部分
/**
* 是否继续购票
*/
public void continueBuyTic(){
Scanner sc = new Scanner(System.in);
System.out.println("是否继续购票:继续请输入“Y”,不再购票输入其他任意字符。");
String choice = sc.next();
if(choice.equalsIgnoreCase("y")){
//给一个标志
buyAgain = true;
}else{
buyAgain = false;//####这里非常重要,一定要注意每次的条件要给足。否则出现:买过一次输入一次Y后,buyAgain就一直是true。后面不论选择是否购票,都会从头循环
}
}
问题:buyAgin在再一次购票前未重置,买过一次输入一次Y后,buyAgain就一直是true。导致后面不论选择是否购票,都会从头循环。
如下所示:
是否继续购票:继续请输入“Y”,不再购票输入其他任意字符。
y
请输入电影名称:
老无所依
电影《老无所依》的放映时间为:
场次1: 11:00 场次2: 15:00
请选择观影场次(输入序号):
1
请输入您要购买的票的类型(输入序号):
1.普通票 2.学生票 3.电影券
2
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
# 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入您需要的座位号:
1-2
是否继续购票:继续请输入“Y”,不再购票输入其他任意字符。
n
请输入电影名称:
2、解决如下
每买一张单票前,重置buyAgain为false
public void continueBuyTic(){
Scanner sc = new Scanner(System.in);
System.out.println("是否继续购票:继续请输入“Y”,不再购票输入其他任意字符。");
String choice = sc.next();
if(choice.equalsIgnoreCase("y")){
//给一个标志
buyAgain = true;
}else{
buyAgain = false;//####这里非常重要,一定要注意每次的条件要给足。否则出现:买过一次输入一次Y后,buyAgain就一直是true。后面不论选择是否购票,都会从头循环
}
}
第四部分:代码优化
优化一:
1、描述
String[] freeTicketNumbers1 = {"193004028","193004139","193004957","193004192","193004090",
"193004991","193006002","193006048","193006993"};//这是赠送票(免费电影券)的票号信息。
需求:实现某字符串是否在该数组内的快速判断。
方法一:改为集合。这种方法更为合意,方便后续进行扩展。
方法二:仍为数组,用for遍历
** 2、优化**
优选,并一次性添加多个字符:首先建立信息库-即存放免费券号的数组,然后通过遍历的方式添加到集合中。这也做也非常有利于后续直接在类的方法外部,实现增删券号。
代码如下:
//类的成员变量部分
private String[] freeTicInfo = {"193004028","193004139","193004957","193004192","193004991","193006002"};
//类的成员方法部分
public void initFreeTicket(){
for(int i=0;i<freeTicInfo.length;i++){
freeTickets.add(freeTicInfo[i]);
}
}
优化二:
1、描述
找的该对象的方式
一般而言,仅仅靠场次还不够,要求电影名和场次均匹配,因为不同的电影其场次可能一样。但是,本项目中的情景是一个影厅,故仅仅靠场次找到该对象可行。
如果有多个影厅,不同影厅间相同的放映时间,也很简单,在Film类中再增加一个room属性即可,电影名、影厅、场次,三者共同组成每一个Film对象的唯一性标识。
优化三:
1、需求
显示每个场次的余下的座位
实现效果如下:
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
1-1 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
原代码如下:
if(infoList.get(i).getItem().equals(givenItem)){
indexOfThis = i;//### 获取到了该对象在infoList中对应的下标
//先显示该场次的座位
ArrayList<String> seatTemp = infoList.get(indexOfThis).getSeatList();
for(int j=0;j<7;j++){
System.out.print(seatTemp.get(j)+"\t");
}
System.out.println("");
for(int j=7;j<14;j++){
System.out.print(seatTemp.get(j)+"\t");
}
System.out.println("");
for(int j=14;j<21;j++){
System.out.print(seatTemp.get(j)+"\t");
}
System.out.println("");
for(int j=21;j<28;j++){
System.out.print(seatTemp.get(j)+"\t");
}
System.out.println("");
for(int j=28;j<35;j++){
System.out.print(seatTemp.get(j)+"\t");
}
System.out.println("");
2、优化
代码如下:
if(infoList.get(i).getItem().equals(givenItem)){
indexOfThis = i;//### 获取到了该对象在infoList中对应的下标
//先显示该场次的座位
ArrayList<String> seatTemp = infoList.get(indexOfThis).getSeatList();
for(int j=1;j<=5;j++){
for(int k=(7*(j-1)+1);k<=7*j;k++){
System.out.print(seatTemp.get(k-1)+"\t");
}
System.out.println("");
}
优化四:
1、原代码
System.out.println("销售信息恢复如下(展示给后台):");
FileInputStream fis = null;
ObjectInputStream ois = null;
try {
fis = new FileInputStream(path);
ois = new ObjectInputStream(fis);
//注意强转
ArrayList<Ticket> ticket = (ArrayList<Ticket>)ois.readObject();
System.out.println(ticket);
如无影票售出,则会打印空集合,如下:
销售信息恢复如下(展示给后台):
[]
2、优化
try {
fis = new FileInputStream(path);
ois = new ObjectInputStream(fis);
//注意强转
ArrayList<Ticket> ticket = (ArrayList<Ticket>)ois.readObject();
if(ticket.size()==0){//不能用ticket.get(0)==null,因为ticket.get(0)并不存在
System.out.println("无影票售出,暂无销售信息。");
}else{
System.out.println(ticket);
}
如无销售信息,会直接给予告知,效果如下:
销售信息恢复如下(展示给后台):
无影票售出,暂无销售信息。
优化五
多处对用户输入异常的处理,如:
(1)进行场次选择时:输入选择场次列表以外的数字或任意字符
原代码:
if(isHere){//用户选择在该影院观影
//****操作第二步:输入电影播放时间****
System.out.println("电影《"+givenName+"》的放映时间为:");
int itemOrder = 0;
ArrayList<String> itemOrders = new ArrayList<String>();
for(Film film:infoList){
if(film.getName().equals(givenName)){
price = film.getPrice();//这里顺带把原价提出来。
itemOrders.add(film.getItem());
System.out.print("场次"+(itemOrder+1)+": "+film.getItem()+"\t");
itemOrder++;
}
}
System.out.println("\t请选择观影场次(输入序号):");
int index = sc.nextInt()-1;
givenItem = itemOrders.get(index);
如输入字符串,则出现出现如下报错:
电影《七武士》的放映时间为:
场次1: 09:00 场次2: 13:00
请选择观影场次(输入序号):
aa
Exception in thread "main" java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at cn.nj.Day0621.theatersystem.TheaterSystem.select(TheaterSystem.java:247)
at cn.nj.Day0621.theatersystem.TheaterSystem.main(TheaterSystem.java:77)
优化思路:
接受字符串,如该字符串是场次序号对应的字符串,则转为int型,获得场次信息,继续正常执行接下来的操作;如该字符串不是是场次序号对应的字符串,则重新输入,直到输入正确。
优化后代码:
//****操作第二步:输入电影播放时间****
System.out.println("电影《"+givenName+"》的放映时间为:");
int itemOrder = 0;
ArrayList<String> itemOrders = new ArrayList<String>();
for(Film film:infoList){
if(film.getName().equals(givenName)){
price = film.getPrice();//这里顺带把原价提出来。
itemOrders.add(film.getItem());
System.out.print("场次"+(itemOrder+1)+": "+film.getItem()+"\t");
itemOrder++;
}
}
System.out.println("");
boolean itemCorrect = false;
while(true){//这里对场次输入错误进行处理,(1)输入数字在序号以外(2)输入的字符串
System.out.println("请选择观影场次(输入序号):");
String indexString = sc.next();
for(int i=0;i<itemOrders.size();i++){
if(indexString.equals(String.valueOf(i+1))){
int index = Integer.parseInt(indexString)-1;
givenItem = itemOrders.get(index);
itemCorrect = true;
break;
}
}
if(itemCorrect){
break;
}
System.out.println("输入有误,请重新输入。");
}
(2)进行票的类型选择时:输入选择数字以外,尤其是输入任意字符或字符串时。
优化方式同(1)。
通过(1)和(2),总结一点如下:
设计技巧之:
当需要对输入的信息进行判断时,不要仅仅局限于正确输入的数据类型,最好是先以字符串形式进行接收,然后借助包装类实现目标数据类型与数据类型的相互转换进行判断和获得正确值。
优化六:
原代码问题:如用户购票一次后继续购票过程中,没有要看的电影了,这时候选择退出,没有为用户显示已购票信息。
即如下效果:
屏幕
1-1 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入电影名称:
七武士
电影《七武士》的放映时间为:
场次1: 09:00 场次2: 13:00
请选择观影场次(输入序号):
1
请输入您要购买的票的类型(输入序号):
1.普通票 2.学生票 3.电影券
1
该场次现有可供选择的座位如下(标“#”座位已售出):
屏幕
1-1 1-2 1-3 1-4 1-5 1-6 1-7
2-1 2-2 2-3 2-4 2-5 2-6 2-7
3-1 3-2 3-3 3-4 3-5 3-6 3-7
4-1 4-2 4-3 4-4 4-5 4-6 4-7
5-1 5-2 5-3 5-4 5-5 5-6 5-7
请输入您需要的座位号:
1-1
是否继续购票:Y/N
y
请输入电影名称:
h
抱歉,暂时没有该的电影。
继续购票请输入1,如退出系统输入1以外的任意键。
0
我们将不断引进更多优秀电影,为您呈现最佳视觉盛宴!
是否进入管理员系统:Y/N
y
请输入管理员名称:
adminGeorge
请输入管理员密码:
193004
销售信息恢复如下:
[Ticket [name=七武士, type=普通票, item=09:00, seat=1-1, price=60.0]]
优化思路:
原思路中,当用户选择继续购票时,这一条件被作为接下来所有购票用户操作的触发条件,并且显示用户已购票信息这一操作,也被放在了这一整个操作块中。
对于开头提到的情形,可以把显示用户已购票信息这一操作,放在永华是否继续购票的触发条件所包含的块以外。只要不是0张票,就给予购票信息显示,即将票数座位显示用户已购票信息的触发条件。
优化代码如下:
//****操作第九步(无需用户进行任何输入):显示购票信息****
//对应三种情形:(1)用户在首次选择影片时,未找到心仪影片,直接退出。buyCount=0
// (2)用户购买影片,并且按提示选择继续购票,之后未找到心仪影片而在这时选择退出。buyCount>=1
// (3)用户在被询问是否继续购票时选择退出。buyCount>=1。该情形与(2),设置一样的显示信息。
if(buyCount==0){
System.out.println("我们将不断引进更多优秀电影,欢迎您下次再来!");
}else{
int order = 1;
int length = ticketList.size();
System.out.println("您已购票的信息如下:");
System.out.println("总计购票:"+ticketList.size()+"张");
for(int j=1;j<=buyCount;j++){
Ticket ticTem = ticketList.get(length-j);
System.out.println(order+"、 "+"电影《"+ticTem.getName()+"》,"+ticTem.getType()+",放映开始时间为"
+ticTem.getItem()+",座位号为"+ticTem.getSeat()+",价格为"+ticTem.getPrice()+"元。");
order++;
}
System.out.println("祝您观影愉快!");
}
优化六
原代码:每次对运行TheaterSystem.java,均会重新生成已售票信心,造成之前存储的销售消息丢失,这显然不符合实际的应用场景。
现做改进,要求达到两点:
(1)每次运行TheaterSystem.java后,该轮售票信息已追加方式添加到原序列化的销售信息文档中。
(2)对于之前运行TheaterSystem.java时,已售出的座位,进行处理使之不再能够被售出。
实现思路:
运行售票系统后,后台首先获取已售出座位的信息,并对这些座位进行排除后,以余下的座位提供给观众。
具体实现:
第一部分:首先按照之前整体运行一次,得到ticketList的首轮信息(主要考虑到重新定义各个集合,这一工作的复杂性,故在现有代码基础上做扩展处理。而不在其内部进行大量改动),做到所有集合的初始化→然后,进行二次运行。
第二部分:在二次及后续运行中,首先读取之前保存的销售信息,对本轮的集合进行赋值,实现对已售出座位进行排除后再给客户进行选择的功能。这个时候能体会得到,有些功能还是单独定义为一个方法/模块比较方便后续使用。
补充知识点:
对象序列化不能像普通文件一样直接追加对象。因为你Java默 认的对象序列化是每次写入对象都会写入一点头aced 0005(占4个字节),然后每次读取都读完头然后在读内容。追加的情况就是当判断文件存在时,把那个4个字节的头aced 0005截取掉,然后再把对象写入到文件。这样就实现了对象序列化的追加。
即通过如下方式并不能向序列化文档中追加内容:
fos = new FileOutputStream(path,true);//以追加的方式
oos = new ObjectOutputStream(fos);