黑马程序员 --- 面试

------- http://www.itheima.comjava培训、android培训期待与您交流!-------


这几天看了几个面试的视频,从中学到了不少面试经验。
一、面试题涉及的知识点
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 而不是显式地创建线程。


Executors类的方法:















参见
交通灯系统: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、
图例:






总共有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窗口空闲时,就会办理普通客户的业务。

各个类对象间的关系:






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);
	}

}


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



------- http://www.itheima.comjava培训、android培训期待与您交流!-------

猜你喜欢

转载自wtt2312.iteye.com/blog/1850830