欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

黑马程序员 --- 面试

程序员文章站 2022-07-12 15:59:03
...
-------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 而不是显式地创建线程。
黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试

Executors类的方法:


黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试



黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试



黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试




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


黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试



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

各个类对象间的关系:


黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试



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培训期待与您交流!-------
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 6.7 KB
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 24.1 KB
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 22.1 KB
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 43.4 KB
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 17.5 KB
  • 黑马程序员 ---  面试
            
    
    博客分类: 学习日志 javaoop交通编程面试
  • 大小: 56 KB