黑马程序员 --- 面试

[align=center]-------[url]http://www.itheima.com[/url]java培训、android培训期待与您交流!------- [/align]

这几天看了几个面试的视频,从中学到了不少面试经验。
一、面试题涉及的知识点
1、面向对象的分析与设计
谁拥有数据,谁就对外提供操作这些数据的方法。
对于现实生活中的现象来说,我们简单的理解只是我们自身的一种感觉。如果用面向对象的思想来解说这些现象,似乎有些不太一样。比如:我们每天的开关门。按照我们一般的理解那就是我们将门关上、开开。那么,用面向对象的思想怎么解释呢?
面向对象,主要的就是将现实事物解析为java语言中的对象,事物所具备的一些属性和方法等。在这里,人开、关门,人、门就是对象,而开、关门就是方法,这个方法应该定义在哪个对象上呢?根据“谁拥有数据,谁就对外提供操作这些数据的方法”的原理,开、关门的动作是门自身具有的功能,人只是给了门一个外力而已,所以开、关门的方法定义在类门上。
几个经典案例:
老师在黑板上画圆:
对象有,Person、Blackboard、Circle
并不是老师或黑板具有画圆的方法,而是圆自身具有的圆心和半径才能将圆画出来,所以,draw方法是圆具有的,故将draw方法定义在Circle中。
列车司机紧急刹车:只有车才具有刹车的功能,则将刹车功能定义在车上。
(1)球从一根绳子的一段移动到了另一端
通过名词提炼法,将对象提取出来,球(Ball)、绳子(Rope)。
球可以移动(即移动()方法),球在绳子上移动,说明绳子上有无数的点是球移动的位置,所以绳子具有确定球移动的某个位置的点的功能(即设置或获取球的为位置方法)。
代码只做分析。
package wangtingting;
import java.awt.Point;
public class Oop {
public static void main(String[] args){
//Rope ro = new Rope(1,3);
}
}
//创建绳子类
class Rope{
//定义绳子的起始点和终止点
private Point startPoint;
private Point endPoint;
//构造函数初始化
public Rope(Point startPoint,Point endPoint){
this.startPoint = startPoint;
this.endPoint = endPoint;
}
//生成set和get方法,设置、获取点
public Point getStartPoint() {
return startPoint;
}
public void setStartPoint(Point startPoint) {
this.startPoint = startPoint;
}

public Point nextPoint(Point currentPoint){
/*
* 通过两点一线的数学公式可以计算出当前点的下一个点,这个细节不属于设计阶段要考虑的问题,
* 如果当前点是终止点,则返回null,如果当前点不是线上的点,则抛出异常
* */
if(currentPoint == endPoint){
return null;
}
return currentPoint;
}
}
class Ball{
//定义操作球的绳子和当前点
private Rope rope;
private Point currentPoint;
public Ball(Rope rope,Point startPoint){
this.rope = rope;
this.currentPoint = startPoint;
}
public void move(){
currentPoint = rope.nextPoint(currentPoint);
System.out.println("小球移动了"+currentPoint);
}
}


(2)两块石头磨成一把石刀,石刀可以砍树,砍成木材,木材做成椅子
按照常理来说,石头和树并不能直接加工成石刀或木材(椅子),他们只是一种原料,将原料提供给加工厂让他们通过一些方法加工成石刀或木材(椅子)。
石刀和木材才具有砍树或做成椅子的功能。所以,石刀(间接的就是创造石刀的工厂)具有砍树的方法,木材(加工椅子的工厂)具有做成椅子的方法。
public class StoneknifeTest {  
public static void main(String[] args) {
//......
}
}
//创建加工chair的椅子加工厂类
class ChairFactory{
private String trees;
public ChairFactory(String trees) {
this.trees = trees;
}
public String creat(String trees){
return "好多的椅子啊";
}
}
//创建加工石头的类
class KnifeFactory{
private KnifeFactory kf;
private Stone stones;
private String stoneKnife;
public KnifeFactory(Stone stones){
this.stones = stones;
}
//创建生产石刀的方法
public StoneKnife creat(Stone firstStone,Stone secondStone){
StoneKnife sk = null;
//creat stoneKnife
//new StoneKnife(firstStone)+ " creat " + new StoneKnife(secondStone);
return sk;
}
}
//创建Stone类
class Stone{
private Stone stone;
public Stone(Stone stone){
this.stone = stone;
}
}
//创建StoneKnife类
class StoneKnife {
public StoneKnife() {}
//创建砍树的方法
public Tree cutTree(StoneKnife sk,String tree){
Tree trees = null;
//cutTree...
return trees;
}
}
//创建树木类
class Tree{
private Tree tree;
public Tree(Tree tree){
this.tree = tree;
}
}



2、面向接口编程
我们知道,系统的各种功能都是由很多不同的对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,我们设计人员来讲就不那么重要了。各个对象之间的协作关系则是系统设计的关键。面向接口编程就是指按照这种思想来编程。只要我们熟练的掌握了对象之间的协作关系,并符合各个对象间的协作关系就可以了。
在这里,父类的引用指向了子类对象,子类只要符合父类的一些规则或这种关系,就可以用子类来实现父类的一些功能。
private List<String> vechicles = new ArrayList<String>();
private List<Integer> queueNumbers = newArrayList<Integer>();
3、线程池
这是一个新的API文档,虽然没有学过,但查阅API文档,里面的原理还是一样的就是将线程的一些操作封装到了类或接口中,这样就更便捷。
线程池java.util.concurrent 包,在并发编程中很常用的实用工具类。
接口 Executor
所有已知子接口: ExecutorService, ScheduledExecutorService
所有已知实现类: AbstractExecutorService, ScheduledThreadPoolExecutor, ThreadPoolExecutor
执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。
通常使用 Executor 而不是显式地创建线程。
[img]http://dl2.iteye.com/upload/attachment/0085/1543/47d4474a-90d6-3e0f-9678-44581ebe6b13.png[/img]

Executors类的方法:


[img]http://dl2.iteye.com/upload/attachment/0085/1545/bbb379ed-5f1d-37cf-86d8-eb9516d4f3e1.png[/img]


[img]http://dl2.iteye.com/upload/attachment/0085/1539/87e7933f-93d6-382e-b53b-40f5cb860608.png[/img]


[img]http://dl2.iteye.com/upload/attachment/0085/1541/331fabed-5321-30dd-8c28-0ef379cf7795.png[/img]


参见
交通灯系统:Road.java、LampControler.java
银行业务调度系统:MainClass.java、ServiceWindow.java
匿名内部类
匿名内部类就是没有名字的内部类。
匿名内部类是内部类的简写格式
(2)定义匿名内部类的前提: 内部类必须是继承一个外部类或实现接口
(3)匿名内部类书写格式: new 父类或接口( ){定义子类内容}
(4)匿名内部类就是子类对象(带内容的对象)。
(5)匿名内部类中的方法最好不要超过3个。 写个小例子:
 abstract Demo{            
abstract void show();
}
class Outer{
int x = 3;
/* //创建内部类,并继承Demo
class Inner extends Demo{
void show (){
System.out.println("show::"+x);
} }
*/
public void function(){
//new Inner().show(); 外部类调用内部类方法:创建内部类对象,调用方法
/* 注释的部分可以简写成匿名内部类。 匿名内部类,顾名思义就是没有类名的内部类。 内部类没有类名那怎么调用方法呢? new Inner().show(); 我们可以分析一下,这句里的内部类类名Inner没有了,因为Inner继承了Demo,那么,可以创建Demo的子类对象来调用内部类方法,即沿袭父类的功能,来建立自己的特有内容,也就是覆盖父类的show方法, */ new Demo(){
//一下则是父类Demo的子类对象
void show(){ System.out.println("x=="+x); } }.show();
//Demo的子类对象调用show方法。
} }

在该面试题中,使用Runable接口的实例对象将要操作的线程的代码封装到匿名内部类中。参照3、
5、枚举
枚举是一个特殊的类,而且是一个不可被继承的final类,其中的元素都是类静态常量,它的出现可以将程序的错误在编译时期发现。
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器会报错。枚举可以让编译器在编译时既可以控制源程序中填写的非法值,普通的变量的方式在开发阶段无法实现这一目标。
用枚举类规定值,如WeekDay1类。以后用此类型定义的值只能是这个类中规定好的那些值,若不是这些值,编译器不会通过。
如果想在一个类中编写完每个枚举类和测试调用类,那么可将枚举类定义成调用类的内部类。
EnumTest.java
import java.util.Date;

public class EnumTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
//1、赋的值只能在类WeekDay中定义好的某个常量
WeekDay1 weekDay = WeekDay1.MON;
System.out.println(weekDay.nextDay());//SUN

//2、当定义一个WeekDay枚举类后,只能调用规定的值
WeekDay weekDay2 = WeekDay.FRI;
//自动将字符串打印
System.out.println(weekDay2);
System.out.println(weekDay2.name());
//排行
System.out.println(weekDay2.ordinal());
//静态方法,传递一个字符串,可将该字符串转换为对应的
System.out.println(WeekDay.valueOf("SUN").toString());
//返回数组
System.out.println(WeekDay.values().length);

//4、调用父类带参的构造方法
new Date(300){};
}
//2、定义枚举(枚举的基本应用)
public enum WeekDay{
//调用带参、无参的构造函数
SUN(1),MON(),TUE,WED,THI,FRI,SAT;
//3、构造方法必须定义在元素列表之后,若元素后还有内容,则用;
//构造方法必须私有修饰

//无参的构造函数
private WeekDay(){System.out.println("first");}
private WeekDay(int day){System.out.println("second");}
}

//4、实现带有抽象方法的枚举
public enum TrafficLamp{
//RED元素{}都是TrafficLamp的每个实例对象
RED(30){
public TrafficLamp nextLamp(){
return GREEN;
}
},
GREEN(45){
public TrafficLamp nextLamp(){
return YELLOW;
}
},
YELLOW(5){
public TrafficLamp nextLamp(){
return RED;
}
};
//返回值类型仍为该类类型
public abstract TrafficLamp nextLamp();
private int time;
//带参的构造函数
private TrafficLamp(int time){this.time = time;}
}
}


WeekDay1.java
public abstract class WeekDay1 {
private WeekDay1(){}
//常量 SUN是对象类型的值
public final static WeekDay1 SUN = new WeekDay1(){

//将抽象方法nextDay定义到每个SUN变量的内部
//这样就将大量的if else语句转移成了一个个独立的类
public WeekDay1 nextDay() {
return MON;
}
/*
* 采用抽象方法定义的nextDay可将大量的if else语句转换成了一个个独立的类。

public WeekDay1 nextDay() {
if(this == SUN){
return MON;
}else {
return SUN;
}
//return MON;
}
*/
//复写方法,将字符串打印
public String toString(){
return this == SUN?"SUN":"MON";
}

};
//实现枚举的抽象方法
public final static WeekDay1 MON = new WeekDay1(){
public WeekDay1 nextDay() {
// TODO Auto-generated method stub
return SUN;
}

};
//带有抽象方法的枚举
public abstract WeekDay1 nextDay();

/* public WeekDay nextDay(){
if(this == SUN){
return MON;
}else{
return SUN;
}
}
*/

public String toString(){
return this==SUN?"SUN":"MON";
}
}

参见Lamp.java、CustomerType.java
若枚举中只有一个成员时,可用单例模式实现(银行调度)。

6、单例设计模式
参见NumberMachine.java

二、具体的项目实现

1、交通灯管理系统:
模拟实现十字路口的交通灯管理系统逻辑,具体需求如下:
异步随机生成按照各个路线行驶的车辆。
例如:
由南向而来去往北向的车辆 ---- 直行车辆
由西向而来去往南向的车辆 ---- 右转车辆
由东向而来去往南向的车辆 ---- 左转车辆
。。。
信号灯忽略黄灯,只考虑红灯和绿灯。
应考虑左转车辆控制信号灯,右转车辆不受信号灯控制。
具体信号灯控制逻辑与现实生活中普通交通灯控制逻辑相同,不考虑特殊情况下的控制逻辑。
注:南北向车辆与东西向车辆交替放行,同方向等待车辆应先放行直行车辆而后放行左转车辆。
每辆车通过路口时间为1秒(提示:可通过线程Sleep的方式模拟)。
随机生成车辆时间间隔以及红绿灯交换时间间隔自定,可以设置。
不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。
由生活经验可知,车辆行驶的路线有:
直行路线:
从南向北,对应的由北向南;
从东向西,对应的由西向东;
转弯路线:
四个方向都可以左转、右转,即
S2N、S2W、S2E、
N2S、N2W、N2E、
W2E、W2S、W2N、
E2W、E2N、E2S、
图例:


[img]http://dl2.iteye.com/upload/attachment/0085/1549/c64f385d-fd93-39ed-83cf-ce5be482bd46.png[/img]


总共有12条路线,为了统一编程模型,可以假设每条路线都有一个红绿灯对其进行控制,右转弯的4条路线的控制灯可以假设称为常绿状态,另外,其他的8条线路是两两成对的,可以归为4组,所以,程序只需考虑图中标注了数字号的4条路线的控制灯的切换顺序,这4条路线相反方向的路线的控制灯跟随这4条路线切换,不必额外考虑。

面向对象分析:
主要的对象:红绿灯(Lamp)、红绿灯的控制系统(LampControler)、路线(Road)、车辆?
我们知道,当车辆在路上行驶时,如果看到红绿灯,那么他就要停下或行驶,但是,如果路上只有一辆车,那么该车辆就可以直接行驶,如果该车辆的前面还有车,则该车辆必须等前面的车辆行驶后再启动,所以,而是路线拥有判断这种情况的方法,即路线对象具有增加和减少车辆的方法。我们这里并不要体现车辆移动的过程,只是捕捉出车辆穿过路口的过程,也就是捕捉路上减少一辆车的过程,所以,这个车并不需要单独设计成为一个对象,用一个字符串表示就可以了。

每条路线上都会出现多辆车,路线上要随机增加新的车,在灯绿期间还要每秒钟减少一辆车。每条路线上随机增加新的车辆,增加到一个集合中保存。每条路线每隔一秒都会检查控制本路线的灯是否为绿,是则将本路线保存车的集合中的第一辆车移除,即表示车穿过了路口。
Road.java
package com.interview.wangtingting;


import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
* 每个Road对象代表一条路线,总共有12条路线,即系统中总共要产生12个Road实例对象。
* 每条路线上随机增加新的车辆,增加到一个集合中保存。
* 每条路线每隔一秒都会检查控制本路线的灯是否为绿,是则将本路线保存车的集合中的第一辆车移除,即表示车穿过了路口。
*
*/
public class Road {

//面向接口编程,将路上的车存储到一个List集合中,通过该集合的添加和删除操作模拟车上路和过路的场景
private List<String> vechicles = new ArrayList<String>();

//定义一变量,表示路线的名称
private String name =null;
//构造函数初始化,且一初始化就具有路线的名称
public Road(String name){
this.name = name;

//启动一个线程每隔一个随机的时间向vehicles集合中增加一辆车

/*
* 线程池java.util.concurrent 包,在并发编程中很常用的实用工具类。
* 创建一个单线程Executor,并返回新创建的单线程,用于将路上有车的情况封装到线程中
*
**/

ExecutorService pool = Executors.newSingleThreadExecutor();
//调用新建线程的execute方法(父类的),在某个时间执行某个指定的命令
pool.execute(new Runnable(){
//创建线程的子类对象后,复写父类Runnable接口的run方法,
public void run(){
for(int i=1;i<1000;i++){
try {
//线程随机休眠1-10秒的时间,
Thread.sleep((new Random().nextInt(10) + 1) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过集合的添加方法模拟车辆该路上有几辆车的情景
vechicles.add(Road.this.name + "_" + i);
}
}

});

//每隔一秒检查对应的灯是否为绿,是则放行一辆车
//创建一个线程池,并返回一个新创建的安排线程池。它可安排在给定延迟后运行命令或者定期地执行。
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
/*

* */
timer.scheduleAtFixedRate(
new Runnable(){
public void run(){
if(vechicles.size()>0){
//匿名内部类访问外部类的成员变量方式:类名.this.成员变量名
boolean lighted = Lamp.valueOf(Road.this.name).isLighted();
if(lighted){
//如果灯是绿灯,则放行一辆车,
System.out.println(vechicles.remove(0) + " is traversing !");
}
}

}
},
1,
1,
TimeUnit.SECONDS);

}
}


每条路线每隔一秒都会检查控制本路线的灯是否为绿,一个灯由绿变红时,应该将下一个方向的灯变绿。
设计一个Lamp类来表示一个交通灯,每个交通灯都维护一个状态:亮(绿)或不亮(红),每个交通灯要有变亮和变黑的方法,并且能返回自己的亮黑状态。
总共有12条路线,所以,系统中总共要产生12个交通灯。右拐弯的路线本来不受灯的控制,但是为了让程序采用统一的处理方式,故假设出有四个右拐弯的灯,只是这些灯为常亮状态,即永远不变黑。
除了右拐弯方向的其他8条路线的灯,它们是两两成对的,可以归为4组,所以,在编程处理时,只要从这4组中各取出一个灯,对这4个灯依次轮询变亮,与这4个灯方向对应的灯则随之一同变化,因此Lamp类中要有一个变量来记住自己相反方向的灯,在一个Lamp对象的变亮和变黑方法中,将对应方向的灯也变亮和变黑。每个灯变黑时,都伴随者下一个灯的变亮,Lamp类中还用一个变量来记住自己的下一个灯。
Lamp.java
package com.interview.wangtingting;

/**
* 每个Lamp元素代表一个方向上的灯,总共有12个方向,所有总共有12个Lamp元素。
* 有如下一些方向上的灯,每两个形成一组,一组灯同时变绿或变红,所以,
* 程序代码只需要控制每组灯中的一个灯即可:
* s2n,n2s
* s2w,n2e
* e2w,w2e
* e2s,w2n
* s2e,n2w
* e2n,w2s
* 上面最后两行的灯是虚拟的,由于从南向东和从西向北、以及它们的对应方向不受红绿灯的控制,
* 所以,可以假想它们总是绿灯。
*
*/

//新建枚举类
public enum Lamp {
//每个枚举元素各表示一个方向的控制灯
S2N("N2S","S2W",false),S2W("N2E","E2W",false),E2W("W2E","E2S",false),E2S("W2N","S2N",false),
//下面元素表示与上面的元素的相反方向的灯,它们的“相反方向灯”和“下一个灯”应忽略不计!
N2S(null,null,false),N2E(null,null,false),W2E(null,null,false),W2N(null,null,false),
//由南向东和由西向北等右拐弯的灯不受红绿灯的控制,所以,可以假想它们总是绿灯
S2E(null,null,true),E2N(null,null,true),N2W(null,null,true),W2S(null,null,true);

//枚举类的构造函数必须是私有修饰
private Lamp(String opposite,String next,boolean lighted){
this.opposite = opposite;
this.next = next;
this.lighted = lighted;
}


//定义红绿灯的状态
private boolean lighted;
//与当前灯同时为绿的对应方向
private String opposite;
//当前灯变红时下一个变绿的灯
private String next;
public boolean isLighted(){
return lighted;
}

// 某个灯变绿时,它对应方向的灯也要变绿
public void light(){
this.lighted = true;
if(opposite != null){
Lamp.valueOf(opposite).light();
}
System.out.println(name() + " lamp is green,下面总共应该有6个方向能看到汽车穿过!");

}

//某个灯变红时,对应方向的灯也要变红,并且下一个方向的灯要变绿,并返回下一个要变绿的灯
public Lamp blackOut(){
this.lighted = false;
if(opposite != null){
Lamp.valueOf(opposite).blackOut();
}

Lamp nextLamp= null;
if(next != null){
nextLamp = Lamp.valueOf(next);
System.out.println("绿灯从" + name() + "-------->切换为" + next);
nextLamp.light();
}
return nextLamp;
}
}



无论在程序的什么地方去获得某个方向的灯时,每次获得的都是同一个实例对象,所以Lamp类改用枚举来做显然具有很大的方便性,永远都只有代表12个方向的灯的实例对象。
设计一个LampController类,它定时让当前的绿灯变红。
LampController.java
package com.interview.wangtingting;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class LampController {
private Lamp currentLamp;

public LampController(){
//刚开始让由南向北的灯变绿;
currentLamp = Lamp.S2N;
currentLamp.light();

/*
* 创建一个调度线程池,调用该线程池的scheduleAtFixedRate(固定频率的)方法,
* 每隔10秒将当前绿灯变为红灯,并让下一个方向的灯变绿
* */
ScheduledExecutorService timer = Executors.newScheduledThreadPool(1);
timer.scheduleAtFixedRate(
new Runnable(){
public void run(){
System.out.println("来啊");
currentLamp = currentLamp.blackOut();
}
},
10,
10,
TimeUnit.SECONDS);
}
}


MainClass.java
package com.interview.wangtingting;

public class MainClass {
public static void main(String[] args) {

//产生12个方向的路线
String [] directions = new String[]{
"S2N","S2W","E2W","E2S","N2S","N2E","W2E","W2N","S2E","E2N","N2W","W2S"
};
for(int i=0;i<directions.length;i++){
new Road(directions[i]);
}

//产生整个交通灯系统
new LampController();
}

}



2、银行业务调度系统:
模拟实现银行业务调度系统逻辑,具体需求如下:
银行内有6个业务窗口,1 - 4号窗口为普通窗口,5号窗口为快速窗口,6号窗口为VIP窗口。
有三种对应类型的客户:VIP客户,普通客户,快速客户(办理如交水电费、电话费之类业务的客户)。
异步随机生成各种类型的客户,生成各类型用户的概率比例为:
VIP客户 :普通客户 :快速客户 = 1 :6 :3。
客户办理业务所需时间有最大值和最小值,在该范围内随机设定每个VIP客户以及普通客户办理业务所需的时间,快速客户办理业务所需时间为最小值(提示:办理业务的过程可通过线程Sleep的方式模拟)。
各类型客户在其对应窗口按顺序依次办理业务。
当VIP(6号)窗口和快速业务(5号)窗口没有客户等待办理业务的时候,这两个窗口可以处理普通客户的业务,而一旦有对应的客户等待办理业务的时候,则优先处理对应客户的业务。
随机生成客户时间间隔以及业务办理时间最大值和最小值自定,可以设置。
不要求实现GUI,只考虑系统逻辑实现,可通过Log方式展现程序运行结果。

经常去银行办业务的人对银行的工作流程应该非常熟悉。
在办业务前,要先在取号机器上根据自己办业务的类型(普通业务、VIP业务和快速业务)领取号码(在取号器内部会根据选取的类型按照既定的模式生成办业务的号码,且生成的号码顺序是每一类型业务的自然顺序),当不忙时当然可以直接办理业务,当很忙时,要等办业务的窗口叫号后才能办理业务。
办理业务的窗口可以分为普通窗口、快速窗口和VIP窗口,当快速窗口和VIP窗口空闲时,就会办理普通客户的业务。

各个类对象间的关系:


[img]http://dl2.iteye.com/upload/attachment/0085/1547/8167a2f0-b3c4-360c-8394-c8df55521f8e.jpg[/img]


NumberMachine.java

package com.interview.wangtingting;

public class NumberMachine {

//将NumberMachine类设计成单例。
//因为取号机器只有一个,号码都是通过该取号器根据不同办理业务的类型来生成的。

//创建私有的无参的构造函数,防止其他程序创建对象
private NumberMachine(){}
private static NumberMachine instance = new NumberMachine();
public static NumberMachine getInstance(){
return instance;
}

//定义三个成员变量分别指向三个NumberManager对象,分别表示普通、快速和VIP客户的号码管理器,
private NumberManager commonManager = new NumberManager();
private NumberManager expressManager = new NumberManager();
private NumberManager vipManager = new NumberManager();

//定义三个对应的方法来返回这三个NumberManager对象。
public NumberManager getCommonManager() {
return commonManager;
}
public NumberManager getExpressManager() {
return expressManager;
}
public NumberManager getVipManager() {
return vipManager;
}

}

NumberManager.java
package com.interview.wangtingting;

import java.util.ArrayList;
import java.util.List;

public class NumberManager {

//定义一个用于存储上一个客户号码的成员变量
private int lastNumber = 0;
//定义一个用于存储所有等待服务的客户号码的队列集合。
private List<Integer> queueNumbers = new ArrayList<Integer>();

//定义一个产生新号码的方法
public synchronized Integer generateNewNumber(){
queueNumbers.add(++lastNumber);
return lastNumber;
}
//定义一个获取马上要为之服务的号码的方法
//在这两个方法中同一批数据被不同的线程操作,所以,要进行同步
public synchronized Integer fetchNumber(){
if(queueNumbers.size()>0){
return (Integer)queueNumbers.remove(0);
}else{
return null;
}
}
}

ServiceWindow.java
package com.interview.wangtingting;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

/**
* 没有把VIP窗口和快速窗口做成子类,是因为实际业务中的普通窗口可以随时被设置为VIP窗口和快速窗口。
* */
public class ServiceWindow {
private static Logger logger = Logger.getLogger("cn.itcast.bankqueue");
private CustomerType type = CustomerType.COMMON;
private int number = 1;

public CustomerType getType() {
return type;
}

public void setType(CustomerType type) {
this.type = type;
}

public void setNumber(int number){
this.number = number;
}
//定义一个start方法,内部启动一个线程,根据服务窗口的类别分别循环调用三个不同的方法。
public void start(){
Executors.newSingleThreadExecutor().execute(
new Runnable(){
public void run(){
//下面这种写法的运行效率低,最好是把while放在case下面
while(true){
switch(type){
case COMMON:
commonService();
break;
case EXPRESS:
expressService();
break;
case VIP:
vipService();
break;
}
}
}
}
);
}


//定义三个方法分别对三种客户进行服务,

private void commonService(){
//定义窗口名称
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取普通任务!");
//获取办理业务的号码(通过NumberMachine获得实例对象再调用其方法getCommonManager、fetchNumber)
Integer serviceNumber = NumberMachine.getInstance().getCommonManager().fetchNumber();
//先判断号码是否为空
if(serviceNumber != null ){
//若不为null,打印窗口名称和号码
System.out.println(windowName + "开始为第" + serviceNumber + "号普通客户服务");
//获得服务的最长时间
int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;

try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号普通客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到普通任务,正在空闲一秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

private void expressService(){
Integer serviceNumber = NumberMachine.getInstance().getExpressManager().fetchNumber();
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取快速任务!");
if(serviceNumber !=null){
System.out.println(windowName + "开始为第" + serviceNumber + "号快速客户服务");
int serviceTime = Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号快速客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到快速任务!");
commonService();
}
}

private void vipService(){

Integer serviceNumber = NumberMachine.getInstance().getVipManager().fetchNumber();
String windowName = "第" + number + "号" + type + "窗口";
System.out.println(windowName + "开始获取VIP任务!");
if(serviceNumber !=null){
System.out.println(windowName + "开始为第" + serviceNumber + "号VIP客户服务");
int maxRandom = Constants.MAX_SERVICE_TIME - Constants.MIN_SERVICE_TIME;
int serviceTime = new Random().nextInt(maxRandom)+1 + Constants.MIN_SERVICE_TIME;
try {
Thread.sleep(serviceTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(windowName + "完成为第" + serviceNumber + "号VIP客户服务,总共耗时" + serviceTime/1000 + "秒");
}else{
System.out.println(windowName + "没有取到VIP任务!");
commonService();
}
}
}


CustomerType.java
package com.interview.wangtingting;

//创建枚举类,该系统中有三种类型的客户
public enum CustomerType {

//定义三个成员分别表示三种类型的客户。
COMMON,EXPRESS,VIP;
//复写toString方法,重写toString方法,返回类型的中文名称。
public String toString(){
String name = null;
switch(this){
case COMMON:
name = "普通";
break;
case EXPRESS:
name = "快速";
break;
case VIP:
name = name();
break;
}
return name;
}
}

Constants.java
package com.interview.wangtingting;

public class Constants {
public static int MAX_SERVICE_TIME = 10000; //10秒!
public static int MIN_SERVICE_TIME = 1000; //1秒!

/*每个普通窗口服务一个客户的平均时间为5秒,一共有4个这样的窗口,也就是说银行的所有普通窗口合起来
* 平均1.25秒内可以服务完一个普通客户,再加上快速窗口和VIP窗口也可以服务普通客户,所以,
* 1秒钟产生一个普通客户比较合理,*/
public static int COMMON_CUSTOMER_INTERVAL_TIME = 1;
}

MainClass.java
package com.interview.wangtingting;

//导入并发包
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

public class MainClass {

private static Logger logger = Logger.getLogger("com.interview.wangtingting");

public static void main(String[] args) {
//产生4个普通窗口
for(int i=1;i<5;i++){
ServiceWindow window = new ServiceWindow();
window.setNumber(i);
window.start();
}

//产生1个快速窗口
ServiceWindow expressWindow = new ServiceWindow();
expressWindow.setType(CustomerType.EXPRESS);
expressWindow.start();

//产生1个VIP窗口
ServiceWindow vipWindow = new ServiceWindow();
vipWindow.setType(CustomerType.VIP);
vipWindow.start();

//创建三个定时器,分别定时去创建新的普通客户号码、新的快速客户号码、新的VIP客户号码。

//普通客户拿号
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getCommonManager().generateNewNumber();
/**
* 采用logger方式,无法看到直观的运行效果,因为logger.log方法内部并不是直接把内容打印出出来,
* 而是交给内部的一个线程去处理,所以,打印出来的结果在时间顺序上看起来很混乱。
*/
//logger.info("第" + serviceNumber + "号普通客户正在等待服务!");
System.out.println("第" + serviceNumber + "号普通客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME,
TimeUnit.SECONDS);

//快速客户拿号
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getExpressManager().generateNewNumber();
System.out.println("第" + serviceNumber + "号快速客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 2,
TimeUnit.SECONDS);

//VIP客户拿号
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new Runnable(){
public void run(){
Integer serviceNumber = NumberMachine.getInstance().getVipManager().generateNewNumber();
System.out.println("第" + serviceNumber + "号VIP客户正在等待服务!");
}
},
0,
Constants.COMMON_CUSTOMER_INTERVAL_TIME * 6,
TimeUnit.SECONDS);
}

}


总结:虽然说学的东西当时会了,但真正做起项目来还是摸不着北。这就暴露出一些问题。我们当时学习的只是说每个知识点解剖来学,那些都是独立的,一旦应用在实际开发中还是想不起来用什么。这说明还是没有真正深入地理解每个知识点。没有实际开发经验。还是多做项目,在实际问题中应用每个知识点,这样才能将理论知识记得更牢。


[align=center]
-------[url]http://www.itheima.com[/url]java培训、android培训期待与您交流!------- [/align]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值