-
主要内容: ◆ Java事件处理机制 ◆ 常见事件及处理接口 ◆ Java绘图机制▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔▔ Java事件处理机制===============================================简介: Java事件处理机制是一种基于授权的事件处理机制。-----------------------------------------------------------------------------------------------------------------------事件处理中的主要概念: 事件源,能够产生事件的对象都可以成为事件源 . 监视器,负责监视事件源的对象,当事件源产生事件时,监视器将调用方法对事件进行处理。 事件源通过调用相应的方法将某个对象注册为自己的监视器。 对于文本框,这个方法是:addActionListener(监视器); java语言中监视器都是使用接口来实现的。 事件源注册监视器之后,相应的操作就会导致相应的事件的发生,并通知监视器,监视器就会作出相应的处理。 事件处理接口,接口中定义了处理事件的方法。 监视器负责处理事件源发生的事件。 监视器是一个对象,为了处理事件源发生的事件,监视器这个对象会自动调用接口中一个方法来处理事件。 Java规定:为了让监视器这个对象能对事件源发生的事件进行处理, 创建该监视器对象的类必须声明实现相应的接口,那么当事件源发生事件时,监视器就自动调用被类重写的某个接口方法。----------------------------------------------------------------------------------------------------------------------------事件处理步骤:1.写一个监视器类 class Pliceman implements XXXListener { 接口方法(XXXEvent e) { //....... } }2.创建事件源、监视器对象3.事件源注册监视器 事件源.addXXXListener(监视器);---------------------------------------------常见事件及处理接口=========================================================ActionEvent事件 ***------------------------------------------------------------------------------------- 1 ActionEvent事件源 : 文本框、按纽、菜单项、密码框和单选按纽都可以触发ActionEvent事件,即都可以成为ActionEvent事件的事件源。 2.注册监视器: ActionListener接口作为监视器 事件源.addActionListener(ActionListener listen) 将实现ActionListener接口的类的实例注册为事件源的监视器。 3.ActionListener接口:重写接口中的方法作为事件处理。 ActionListener接口在java.awt.event包中,该接口中只有一个方法: public void actionPerformed(ActinEvent e) ; 事件源触发ActionEvent事件后,监视器将发现触发的ActionEvent事件,然后调用接口中的这个方法对发生的事件作出处理。 ActionEvent类事先创建的事件对象就会传递给该方法的参数e。 4.ActionEvent类中的方法:下面的方法能够获取发生动作的事件源。 public Object getSource() --------- 调用该方法可以获取发生ActionEvent事件的事件源对象的引用。 public String getActionCommand() --------- 调用该方法可以获取发生ActionEvent事件时,和该事件相关的一个命令字符串。------------------------------------------------------------------------------------------------------------------------------带事件处理的GUI程序总结:通常有3各类:监视器类public class Pliceman implements ActionListener { //取数组的组件 //显示数据的组件 //设置取数据组件的方法 //设置显示数据组件的方法 //接口中的方法 public void actionPerformed(ActionEvent e) { //取得数据 //处理数据 //显示数据 }}窗口类pulic MainFrame extends JFrame { //组件 //监视器 public MainFrame() { init(); initEvent(); //设置窗口的属性 } void init() { //设置布局 //创建组件 //添加组件 } void initEvent() { //创建监视器 //设置监视器的属性 //事件源注册监视器 }}主类--------------------------------------------------------ItemEvent事件-----------------------------------------------------------------------------------------------------------1.ItemEvent事件源 : 选择框、下拉列表都可以触发ItemEvent事件。2.注册监视器:ItemListener接口作为监视器 事件源.addItemListener(ItemListener listen) 将实现ItemListener接口的类的实例注册为事件源的监视器。 3.ItemListener接口:重写方法作为事件处理 ItemListener接口在java.awt.event包中,该接口中只有一个方法 public void itemStateChanged(ItemEvent e) 事件源触发ItemEvent事件后,监视器将发现触发的ItemEvent事件, 然后调用接口中的itemStateChanged(ItemEvent e)方法对发生的事件作出处理。 ItemEvent类事先创建的事件对象就会传递给该方法的参数e。 4.ItemEvent类中的方法 : getSource()方法返回发生Itemevent事件的事件源外 getItemSelectable()方法返回发生Itemevent事件的事件源。 ---------------------------------------------------------------------------------------------------DocumentEvent事件---------------------------------------------------------------------------------------------------------------------1. DocumentEvent事件源 : 文本区所维护的文档能触发DocumentEvent事件 2. 注册监视器:DocumentListener作为监视器 事件源.addDucumentListener(DocumentListener listen) 将实现DocumentListener接口的类的实例注册为事件源的监视器。 3. DocumentListener接口:重写接口中的方法作为事件处理 DocumentListener接口在javax.swing.event包中,该接口中有三个方法: public void changedUpdate(DocumentEvent e) public void removeUpdate(DocumentEvent e) public void insertUpdate(DocumentEvent e)----------------------------------------------------------------------------------------------------------------------MouseEvent事件_1-----------------------------------------------------------------------------------------------------------------------1. 事件源: 任何组件上都可以发生鼠标事件,如:鼠标进入组件、退出组件、在组件上方单击鼠标、拖动鼠标等都触发鼠标事件, 即导致MouseEvent类自动创建一个事件对象。 2.使用MouseListener接口可以处理以下5种操作触发的鼠标事件 事件源.addMouseListener(MouseListener listener)。 在事件源上按下鼠标键、在事件源上释放鼠标键、在事件源上击鼠标键、鼠标进入事件源、鼠标退出事件源。 注册监视器:MouseListener接口作为监视器3. MouseListener接口中有如下方法:重写方法作为事件源的处理 mousePressed(MouseEvent) 负责处理在组件上按下鼠标键触发的鼠标事件 mouseReleased(MouseEvent) 负责处理在组件上释放鼠标键触发的鼠标事件 mouseEntered(MouseEvent) 负责处理鼠标进入组件触发的鼠标事件 mouseExited(MouseEvent) 负责处理鼠标离开组件触发的鼠标事件 mouseClicked(MouseEvent) 负责处理在组件上单击鼠标键触发的鼠标事件4. MouseEvent 中有下列几个重要的方法: getX() 获取鼠标指针在事件源坐标系中的x-坐标。 getY() 获取鼠标指针在事件源坐标系中的y-坐标。 getModifiers() 获取鼠标的左键或右键。 getClickCount() 获取鼠标被单击的次数。 getSource() 获取发生鼠标事件的事件源。5. MouseMotionListener接口 可以处理以下两种操作触发的鼠标事件, 在事件源上拖动鼠标、在事件源上移动鼠标。 事件源注册监视器的方法是: 事件源.addMouseMotionListener(MouseMotionListener listener)。 MouseMotionListener接口中有如下方法: mouseDragged(MouseEvent) 负责处理拖动鼠标触发的鼠标事件。 mouseMoved(MouseEvent) 负责处理移动鼠标触发的鼠标事件。------------------------------------------------------------------------------------------------------------------------------焦点事件-----------------------------------------------------------------------------------------------1.焦点事件源 :组件可以触发焦点事件。 2.注册监视器:组件可以使用 addFocusListener(FocusListener listener) 注册焦点事件监视器。 3.FocusListener接口 : 创建监视器的类必须要实现FocusListener接口,该接口有两个方法: public void focusGained(FocusEvent e) public void focusLost(FocusEvent e) 当发生FocusEvent事件时,监视器调用类实现的接口中的相应方法。 4.组件也可调用 public boolean requestFocusInWindow() 方法可以获得输入焦点。 --------------------------------------------------------------------------------------------------------------键盘事件--------------------------------------------------------------------------------------------------1. 事件源: 当一个组件处于激活状态时,敲击键盘上一个键就导致这个组件触发键盘事件。2.某个组件使用addKeyListener方法注册监视器。3.接口KeyListener中有如下方法: public void keyPressed(KeyEvent e), public void keyTyped(KeyEvent e), public void KeyReleased(KeyEvent e) 4.(KeyEvent 相关方法: public int getKeyCode()判断哪个键被按下、敲击或释放,返回一个键码值 。 getKeyChar()判断哪个键被按下、敲击或释放,返回键上的字符。-------------------------------------------------------------------------------------------------------窗口事件-------------------------------------------------------------------------------------WindowListener接口. (1) public void windowActivated(WindowEvent e) 当窗口从非激活状态到激活时,窗口的监视器调用该方法。(2) public void windowDeactivated(WindowEvent e) 当窗口激活状态到非激活状态时,窗口的监视器调用该方法。(3) public void windowClosing(WindowEvent e) 当窗口正在被关闭时,窗口的监视器调用该方法。(4) public void windowClosed(WindowEvent e) 当窗口关闭后,窗口的监视器调用该方法。(5) public void windowIconified(WindowEvent e) 当窗口图标化时,窗口的监视器调用该方法。(6) public void windowDeiconified(WindowEvent e) 当窗口撤消图标化时,窗口的监视器调用该方法。(7) public void windowOpened(WindowEvent e) 当窗口打开时,窗口的监视器调用该方法。--------------------------------------------------------------------------------------------------------适配器------------------------------------------------------------------------------------------------------------------------每个具有不止一个方法的AWT监听器接口都有一个实现了它的所有方法,但却不做任何工作的适配器类。适配器可以代替接口来处理事件,当Java提供处理事件的接口中多于一个方法时,Java相应地就提供一个适配器类,比如WindowAdapter类。适配器已经实现了相应的接口,例如WindowAdapter类实现了WindowListener接口。因此,可以使用WindowAdapte的子类创建的对象做监视器,在子类中重写所需要的接口方法即可。 WindowAdapter适配器 .ComponentAdapterContainerAdapterFocusAdapterKeyAdapterMouseAdapterMouseMotio**pterWindowAdapter===========================================匿名类实例或窗口做监视器匿名类的方便之处是匿名类的外嵌类的成员变量在匿名类中仍然有效,当发生事件时,监视器就比较容易操作事件源所在的外嵌类中的成员.当事件的处理比较简单、系统也不复杂时,使用匿名类做监视器是一个不错的选择。让事件源所在的类的实例作为监视器,能让事件的处理比较方便,这是因为,监视器可以方便的操作事件源所在的类中的其他成员。当事件的处理比较简单,系统也不复杂时,让事件源所在的类的实例作为监视器是一个不错的选择。===================================事件总结-------------------------------------------------------------------------------------------------------1.授权模式 Java的事件处理是基于授权模式,即事件源调用调用方法将某个对象注册为自己的监视器。2.接口回调 addXXXListener(XXXListener listener)方法中的参数是一个接口,listener可以引用任何实现了该接口的类所创建的对象, 当事件源发生事件时,接口listener立刻回调被类实现的接口中的某个方法。3.方法绑定 当事件源触发事件发生后,监视器准确知道去调用哪个方法。4.保持松耦合 当事件源触发事件发生后,系统知道某个方法会被执行,但无须关心到底是哪个对象去调用了这个方法, 因为任何实现接口的类的实例(做为监视器)都可以调用这个方法来处理事件。 ====================================================================================================Java绘图机制---------------------------------------------------------------Component类有一个方法public void paint(Graphics g),程序可以在其子类中重写这个方法。当程序运行时,java运行环境会用Graphicd2D(Graphics的一个子类)将参数g实例化,对象g就可以在重写paint方法的组件上内绘制图形、图像等 .Graphics2D的“画笔”分别使用draw和fill方法来绘制和填充一个图形。组件都是矩形形状,组件本身有一个默认的坐标系,组件的左上角的坐标值是(0,0)。java.awt.geom直线 Line2D.Double(double x1, double y1, double x2, double y2)矩形Rectangle2D.Double(double x, double y, double w, double h)圆角矩形RoundRectangle2D.Double(double x, double y, double w, double h);椭圆Ellipse2D.Double(double x, double y double w, double h);圆弧Arc2D.Double(double x, double y, double w, double h, double start, double extent, int type) x, y, w, h-----------椭圆位置大小 start, -------------圆弧其实位置,单位:度 extent-----------划过的度的弧。单位:度。正值:逆时针;负值:顺时针。 type ---------------Arc2D.OPEN Arc2D.CHORD Arc2D.PIE文本 g.drawString(s, x, y)二次曲线三次曲线多边形-----------------------------------------------------有时需要平移、缩放或旋转一个图形。可以使用AffineTransform类来实现对图形的这些操作。例如: AffineTransform trans=new AffineTransform(); 将图形沿顺时针或逆时针以(x,y)为轴点旋转number个弧度 :trans.rotate(60.0*3.1415927/180,100,100); translate(double a, double b) -----------------------移动 scale(double a, double b) ------------放缩rotate(double number, double x, double y)-------------------------旋转 x,y --------旋转圆心 number--------旋转弧度g_2d.setTransform(trans);-------------------------------------------------两个图形进行布尔运算运算之前,必须分别用这两个图形创建两个Area区域对象,例如: Area a1 = new Area(T1); Area a2 = new Area(T2);a1就是图形T1所围成的区域;a2就是T2所围成的区域。那么,a1调用add方法: a1.add(a2); ------------- 或之后,a1就变成a1和a2经过布尔“或”运算后的图形区域。可以用Graphics2D对象g来绘制或填充一个Area对象(区域): g.draw(a1); g.fill(a1); a1.intersect(a2); ---------与 a1.exclusiveOr(a2) ----------------异或 a1.subtract(a2) ------------差。---------------------------------------------------1.加载图像组件调用getToolkit()方法可以返回这个对象的引用。Tollkit类的对象调用方法 Image getImage(String fileNme)或 Image getImage(File file)。可以返回一个Image对象,该对象封装着参数file(或参数fileName)指定的图像文件。2.绘制图像 public boolean drawImage(Image img,int x,int y,ImageObserver observer); 参数img是被绘制的Image对象,x、y是要绘制指定图像的矩形的左上角所处的位置,observer是加载图像时的图像观察器 。-----------------------------------------------------------
-
StringString类代表字符串,这个是最基本的对字符串的类,这个也是使用比较多的类,这里就不再详细介绍了构造new String(String str)new String(StringBuilder str)new String(StringBuffer str)new String(byte[] bys,String charsetName) 通过使用指定的字符集解码指定的 byte 子数组,构造一个新的 String。常用方法str charAt(int index) 返回指定索引处的字符String concat(String str) 将指定字符串str连接到此字符串的结尾,返回连接成功后的字符,因此需要接受才能有效果boolean contains(CharSequence s) 判断此字符串是否包含指定的char值序列,这里的 CharSequence是一个接口,可以直接使用它的子类作为参数(String,StringBuffer,StringBuild)static String copyValueOf(char[] c) 将字符数组变成字符串并且返回static String copyValueOf(char[] c,int off,int count) 将截取的字符数组变成字符串并且返回,off是开始截取的下标,count是截取的个数boolean endWith(String s) 判断字符串是否是以s结尾boolean equals(Object o) 用于比较int indexOf(char c) 返回字符c在字符串中第一次出现的索引int indexOf(char c,int fromIndex) 从指定索引处开始搜索,查找第一次出现的索引int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。int indexOf(String str,int fromIndex) 返回指定子字符串在此字符串中第一次出现处的索引,从指定的索引开始。boolean isEmpty()int length()boolean matches(String regex) 是否匹配正则表达式trim() 返回字符串的副本,忽略前导空白和尾部空白。String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。String substring(int beginIndex, int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。
-
Java的Socket API提供了一个很方便的对象接口进行网络编程。本文用一个简单的TCP Echo Server做例子,演示了如何使用Java完成一个网络服务器。 用作例子的TCP Echo Server是按以下方式工作的: 当一个客户端通过TCP连接到服务器后,客户端可以通过这个连接发送数据到服务端,而服务端接收到数据后会把这些数据用同一个TCP连接发送回客户端。服务端会一直保持这个连接直到客户端关闭它为止。 因为服务器需要能同时处理多个客户端,我们先选用一个常见的多线程服务模型: 让一个Thread负责监听服务端口,当有新的连接建立的时候,这个监听的Thread会为这个连接创建一个新的Thread来处理它。这样,服务器可以接受多个连接,并让多个Thread来分别处理它们。 以下是相应的服务端程序: public class EchoServer implements Runnable { public void run() { try { ServerSocket svr = new ServerSocket(7); while (true) { Socket sock = svr.accept(); new Thread(new EchoSession(sock)).start(); } } catch (IOException ex) { throw new Exceptio**pter(ex); } } } 这段代码先创建了一个ServerSocket的对象并让其监听在TCP端口7上,然后在一个循环中用accept()方法接收新的连接,并创建处理这一连接的Thread。实际处理每个客户端连接的逻辑包含在EchoSession这个类里面。 在以上代码中使用了Exceptio**pter这个类,它的作用是把一个checked Exception包装成RuntimeException。详细的说明可以参考避免在Java中使用Checked Exception 一文。 以下是EchoSession的代码: public class EchoSession implements Runnable { public EchoSession(Socket s) { _sock = s; } public void run() { try { try { InputStream input = _sock.getInputStream(); OutputStream output = _sock.getOutputStream(); byte [] buf = new byte [128]; while (true) { int count = input.read(buf); if (count == -1) break; output.write(buf, 0 , count); } } finally { _sock.close(); } } catch (IOException ex) { throw new Exceptio**pter(ex); } } protected Socket _sock = null; } EchoSession接受一个Socket对象作为构造参数,在其run()方法中,它不停的从这个Socket对象的InputStream里面读数据并写回到该Socket的OutputStream中去,直到这个连接被客户端关闭为止(InputStream的read方法返回-1)。 EchoSession需要一个线程来执行,这容易让人联想到用Thread来作为EchoSession的父类。不过,这样做不够灵活,开销也比较大。而选择让EchoSession实现Runnable接口就灵活得多。在接下来的使用Thread Pool的Echo Server中可以看到这一点。以上已经是一个完整的TCP Echo Server,不过随着客户不停的连接和断开,这个服务器会不停的产生和消除线程,而这两个都是比较‘昂贵'的操作。为了避免这种消耗,可以考虑采用Thread Pool的机制。 使用在一个简单的Thread缓冲池的实现一文中Thread Pool的实现,可以对EchoServer作如下修改(EchoSession无需做修改): public class EchoServer implements Runnable { public void run() { try { ServerSocket svr = new ServerSocket(7); // 初始化Thread Pool SyncQueue queue = new SyncQueue(10); for (int i = 0; i < 10; i ++) { new Thread(new Worker(queue)).start(); } while (true) { Socket sock = svr.accept(); // 把任务放入Thread Pool queue.put(new EchoSession(sock)); } } catch (IOException ex) { throw new Exceptio**pter(ex); } } } 这里可以看出让EchoSession实现Runnable接口的灵活性,无需修改它就可以在Thread Pool里使用。 在这个例子里使用的Thread Pool比较简单,没有动态调整Thread数量的功能,所以这个Echo Server最多只能同时服务10个客户端。然而通过重载SyncQueue,我们可以很方便地加入这个功能以突破这个限制。在对网络服务器的性能以及并发度要求很高的时候,让每个客户端由一个专门的Thread来处理有可能不能满足我们的要求(想象一下同时有数千个客户端的情况)。这时可以考虑使用Java的NIO API来构建服务器架构,因为NIO中IO操作都是非阻塞的,我们只需要很少的Thread就可以充分地利用CPU来处理多个客户端的请求。
-
单例模式应该是设计模式中比较简单的一个,也是非常常见的,但是在多线程并发的环境下使用却是不那么简单了,今天给大家分享一个我在开发过程中遇到的单例模式的应用。首先我们先来看一下单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。单例模式的要素:1.私有的静态的实例对象2.私有的构造函数(保证在该类外部,无法通过new的方式来创建对象实例)3.公有的、静态的、访问该实例对象的方法单例模式分为懒汉形和饿汉式懒汉式:应用刚启动的时候,并不创建实例,当外部调用该类的实例或者该类实例方法的时候,才创建该类的实例。(时间换空间)优点:实例在被使用的时候才被创建,可以节省系统资源,体现了延迟加载的思想。缺点:由于系统刚启动时且未被外部调用时,实例没有创建;如果一时间有多个线程同时调用LazySingleton.getLazyInstance()方法很有可能会产生多个实例。例子:123456789101112publicclassSingletonClass{ //私有构造函数,保证类不能通过new创建privateSingletonClass(){}privatestaticSingletonClassinstance=null; publicstaticSingletonClassgetInstance(){if(instance==null){ //创建本类对象 instance=newSingletonClass();}returninstance;} }饿汉式:应用刚启动的时候,不管外部有没有调用该类的实例方法,该类的实例就已经创建好了。(空间换时间。)优点:写法简单,在多线程下也能保证单例实例的唯一性,不用同步,运行效率高。缺点:在外部没有使用到该类的时候,该类的实例就创建了,若该类实例的创建比较消耗系统资源,并且外部一直没有调用该实例,那么这部分的系统资源的消耗是没有意义的。例子:123456789101112131415161718192021222324252627282930313233publicclassSingleton{//首先自己在内部定义自己的一个实例,只供内部调用privatestaticfinalSingletoninstance=newSingleton();//私有构造函数 privateSingleton(){}//提供了静态方法,外部可以直接调用publicstaticSingletongetInstance(){returninstance;}}下面模拟单例模式在多线程下会出现的问题/***懒汉式单例类*/publicclassLazySingleton{//为了易于模拟多线程下,懒汉式出现的问题,我们在创建实例的构造函数里面使当前线程暂停了50毫秒privateLazySingleton(){try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("生成LazySingleton实例一次!");}privatestaticLazySingletonlazyInstance=null;publicstaticLazySingletongetLazyInstance(){if(lazyInstance==null){lazyInstance=newLazySingleton();}returnlazyInstance;}}测试代码:我们在测试代码里面新建了10个线程,让这10个线程同时调用LazySingleton.getLazyInstance()方法12345678910111213publicclassSingletonTest{publicstaticvoidmain(String[]args){ //创建十个线程调for(inti=0;i<10;i++){newThread(){@Overridepublicvoidrun(){LazySingleton.getLazyInstance();}}.start();}}}结果:生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!生成LazySingleton实例一次!可以看出单例模式懒汉式在多线程的并发下也会出现问题,分析一下:多个线程同时访问上面的懒汉式单例,现在有两个线程A和B同时访问LazySingleton.getLazyInstance()方法。假设A先得到CPU的时间切片,A执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,在还没有执行实例创建的时候此时CPU将执行时间分给了线程B,线程B执行到if(lazyInstance==null)时,由于lazyInstance之前并没有实例化,所以lazyInstance==null为true,线程B继续往下执行实例的创建过程,线程B创建完实例之后,返回。此时CPU将时间切片分给线程A,线程A接着开始执行实例的创建,实例创建完之后便返回。由此看线程A和线程B分别创建了一个实例(存在2个实例了),这就导致了单例的失效。解决办法:我们可以在getLazyInstance方法上加上synchronized使其同步,但是这样一来,会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式来实现呢?我们可以考虑使用"双重检查加锁"的方式来实现,就可以既实现线程安全,又能够使性能不受到很大的影响。我们看看具体解决代码123456789101112131415161718192021222324publicclassLazySingleton{privateLazySingleton(){try{Thread.sleep(50);}catch(InterruptedExceptione){e.printStackTrace();}System.out.println("生成LazySingleton实例一次!");}privatestaticLazySingletonlazyInstance=null;publicstaticLazySingletongetLazyInstance(){ //先检查实例是否存在,如果不存在才进入下面的同步块if(lazyInstance==null){ //同步块,线程安全地创建实例 synchronized(LazySingleton.class){ //再次检查实例是否存在,如果不存在才真正地创建实例 if(lazyInstance==null){ lazyInstance=newLazySingleton(); } }}returnlazyInstance;}}这样我们就可以在多线程并发下安全应用单例模式中的懒汉模式。这种方法在代码上可能就不怎么美观,我们可以优雅的使用一个内部类来维护单例类的实例,下面看看代码12345678910111213publicclassGracefulSingleton{privateGracefulSingleton(){System.out.println("创建GracefulSingleton实例一次!");}//类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载privatestaticclassSingletonHoder{ //静态初始化器,由JVM来保证线程安全privatestaticGracefulSingletoninstance=newGracefulSingleton();}publicstaticGracefulSingletongetInstance(){returnSingletonHoder.instance;}}说一下我在实际开发中的场景:为了程序的高效率使用多线程并发,然而是循环调用,可能导致创建线程数过多,考虑采用线程池管理,这时候创建线程池仍然是处于循环调用中,也可能导致多个线程池,这时候就考虑使用单例模式。源代码:1234567891011121314151617181920publicclassThreadPoolFactoryUtil{privateExecutorServiceexecutorService; //在构造函数中创建线程池privateThreadPoolFactoryUtil(){//获取系统处理器个数,作为线程池数量intnThreads=Runtime.getRuntime().availableProcessors();executorService=Executors.newFixedThreadPool(nThreads);}//定义一个静态内部类,内部定义静态成员创建外部类实例privatestaticclassSingletonContainer{privatestaticThreadPoolFactoryUtilutil=newThreadPoolFactoryUtil();}//获取本类对象publicstaticThreadPoolFactoryUtilgetUtil(){returnSingletonContainer.util;}publicExecutorServicegetExecutorService(){returnexecutorService;}}涉及到一个静态内部类,我们看看静态内部类的特点:1、静态内部类无需依赖于外部类,它可以独立于外部对象而存在。2、静态内部类,多个外部类的对象可以共享同一个内部类的对象。3、使用静态内部类的好处是加强了代码的封装性以及提高了代码的可读性。4、普通内部类不能声明static的方法和变量,注意这里说的是变量,常量(也就是finalstatic修饰的属性)还是可以的,而静态内部类形似外部类,没有任何限制。可以直接被用外部类名+内部类名获得。
-
jar文件听说过吗,没有?或者陌生!好,没关系,这就是我们的第一站:打包发布。 为什么会有这个玩意呢,首先,这是jar的全称:JavaTM Archive (JAR) file,是的,就是java存档文件。这有点类似zip文件,想一想它是干什么的用的呢,压缩!?没错就是要压缩,将我们原先零散的东西放到一下,重新组织,所有这些目的只有一个:方便!好了,不用管他是怎么压缩的,我们的重点是哪些是我们要压缩的(输入),还有压缩成了什么(输出),进而将它发布(部署)。 那我们的输入(要压缩的东西)主要是class文件,还有辅助的资源(这其中可能有图片,jsp文件,html文件等等)。Jar技术在jdk1.1版本中就已存在,在1.2中又有了增强。接下来说说jar的好处吧,这是官方的描述:安全,快速下载,压缩,猎取包,版本化包,可携。 说了这么多,我们现在开始实施。 先打开命令提示符(win2000或在运行框里执行cmd命令,win98为DOS提示符),输入jar Chelp,然后回车(如果你盘上已经有了jdk1.1或以上版本),看到什么: 用法:jar {ctxu}[vfm0Mi] [jar-文件] [manifest-文件] [-C 目录] 文件名 ... 选项: -c 创建新的存档 -t 列出存档内容的列表 -x 展开存档中的命名的(或所有的〕文件 -u 更新已存在的存档 -v 生成详细输出到标准输出上 -f 指定存档文件名 -m 包含来自标明文件的标明信息 -0 只存储方式;未用zip压缩格式 -M 不产生所有项的清单(manifest〕文件 -i 为指定的jar文件产生索引信息 -C 改变到指定的目录,并且包含下列文件: 如果一个文件名是一个目录,它将被递归处理。 清单(manifest〕文件名和存档文件名都需要被指定,按'm' 和 'f'标志指定的相同顺序。 示例1:将两个class文件存档到一个名为 'classes.jar' 的存档文件中: jar cvf classes.jar Foo.class Bar.class 示例2:用一个存在的清单(manifest)文件 'mymanifest' 将 foo/ 目录下的所有文件存档到一个名为 'classes.jar' 的存档文件中: jar cvfm classes.jar mymanifest -C foo/ . 来个小例子试试看: 我们只有一个HelloWorld,如下: public class HelloWorld{ public static void main(String[ ] args){ System.out.println(“Hi, Hello World!”); } } 将这个java文件存到C盘跟目录下,ok,接下来, 在先前打开的命令提示符下(跳转到C盘提示符下),我们输入javac HelloWorld.java,然后继续输入:jar cvf hello.jar HelloWorld.class,回车后去你的C盘看看,多了什么,没错 hello.jar 。 基本的步骤我们现在都知道了,你可以自己去尝试一下随着jar后面的参数的不同,结果有什么变化。 紧接着我们看看如何运行我们的jar包。 在进入正题之前,你要先打开我们刚刚做好的jar包看看,多了什么呢,META-INF目录?再看看里面是什么,还有一个MANIFEST.MF文件是不是?用文本编辑器(我这里是UltraEdit)打开它看看: Manifest-Version: 1.0 Created-By: 1.4.2 (Sun Microsystems Inc.) 就是这样。这里我们对它进行修改,加一句:Main-Class: HelloWorld (在第三行)。这个就是我们之前写的那个类,也就是我们的入口类。也即, Manifest-Version: 1.0 Created-By: 1.4.2 (Sun Microsystems Inc.) Main-Class: HelloWorld 接下来,我们在命令提示符里执行: jar umf MANIFEST.MF app.jar (应该是hello.jar吧) 这样我们使用了我们自己的MANIFEST.MF文件对原来默认的进行了更新。你不妨可以再进去看看是不是添上了Main-Class: HelloWorld这一句。 (是吗,我怎么没试出来,提示java.io.FileNotFoundException:MANIFEST.MF(系统找不到指定的文件)怎么回事?) Ok,这个最后的一步了,来验证我们做的一切,在命令提示符中输入: java -jar hello.jar(执行) 出现了什么, Hi, Hello World! 我们再来看看jar文件在tomcat中发布,注意:在tomcat中我们就不能再用jar这种格式,而改war格式,它是专门用于web应用的,其实整个过程下来基本上和jar是类似的: 先准备我们要打包的资源。 找到存放tomcat的webapps目录,进到其中,新建一个文件夹,这里命名为hello,再进去新建WEB-INF文件夹,再进去新建classes文件夹,此时我们也将我们唯一的servlet,HelloWorld.java放到这里,在与classes目录同级下建立一文件web.xml。Ok,目前我们初步建立了一个简单的web应用。 这是HelloWorld.java: import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class HelloWorld extends HttpServlet { public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter out = res.getWriter(); out.println(""); out.println(""); out.println(""); out.println("Hello, World!"); out.println(""); } }//end here! 对它编译。下面是web.xml: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <servlet> <servlet-name>hello</servlet-name> <servlet-class>HelloWorld</servlet-class> </servlet> <servlet-mapping> <servlet-name>hello</servlet-name> <url-pattern>/HelloWorld</url-pattern> </servlet-mapping> </web-app> 在命令提示符下进到先前创制的hello目录下,执行 jar cvf hello.war * ,我们便得到hello.war。将它拷贝至webapps目录下,ok,来看最后一步,打开tomcat的目录conf中的server.xml,加入: <Context path="/hello" docBase="hello.war" debug="0" reloadable="true"/> 大功告成!运行它,启动tomcat,后在浏览器中输入http://localhost:8080/hello/HelloWorld,有了吗? 最后,如果你想用ant来完成以上的打包活动,下面就告诉你: 对于jar来说。在build.xml中, <target name="jar"> <jar destfile="${app_home}/hello.jar"> <fileset dir="${dest}" includes="**"/> <!--fileset dir="${dest}" includes="**/action.properties"/--> </jar> </target> 对于war, <war warfile="hello.war" webxml="./WEB-INF/web.xml"> <fileset dir="html"/> <lib dir="lib/"> <exclude name="oracle*.jar"/> </lib> <classes dir="build/servlets"> <include name="**/*.class"/> </classes> </war> 好了,就这么多,希望对你有点帮助。:) 补充: jar基本操作: 1. 创建jar文件 jar cf jar-file input-file(s) c---want to Create a JAR file. f---want the output to go to a file rather than to stdout. eg: 1)jar cf myjar.jar query_maintain_insert.htm 2)jar cvf myjar.jar query_maintain_insert.htm v---Produces verbose(详细的) output. 3)jar cvf myjar.jar query_maintain_insert.htm mydirectory 4)jar cv0f myjar.jar query_maintain_insert.htm mydirectory 0---don't want the JAR file to be compressed. 5)jar cmf MANIFEST.MF myjar.jar yahh.txt m---Used to include manifest information from an existing manifest file. 6)jar cMf MANIFEST.MF myjar.jar yahh.txt M---the default manifest file should not be produced. 7)jar cvf myjar.jar * *---create all contents in current directory. 2. 察看jar文件 jar tf jar-file t---want to view the Table of contents of the JAR file. eg: 1)jar vft yahh.jar v---Produces verbose(详细的) output. 3. 提取jar文件 jar xf jar-file [archived-file(s)] x---want to extract files from the JAR archive. eg: 1)jar xf yahh.jar yahh.txt(仅提取文件yahh.txt) 2)jar xf yahh.jar alex/yahhalex.txt(仅提取目录alex下的文件yahhalex.txt) 3)jar xf yahh.jar(提取该jar包中的所有文件或目录) 4. 修改Manifest文件 jar cmf manifest-addition jar-file input-file(s) m---Used to include manifest information from an existing manifest file. 5. 更新jar文件 jar uf jar-file input-file(s) u---want to update an existing JAR file.
-
对象:一切皆为对象。对象包括两部分内容:属性(名词形容词),行为(动词)。对象和对象之间是有关系的: 派生,关联,依赖。类:对同一类别的众多对象的一种抽象。类,还是用来生成对象的一种模板,对象是类的一种具体化的表现。面向对象的三大特性:封装,继承,多态。1234class 类名{访问修饰符 成员变量的定义;访问修饰符 成员函数(方法)的定义; }访问修改符:默认不写,private,public。private,私有。只能被当前class 类名{}中的代码访问到。出了这个类的{},访问不到了。public,公有。能被当前class 类名{}内访问。也能被其它位置的对象访问。默认不写,在当前class 类名{}中可以访问。当前包中的其它类中也可以访问。出了当前包,就访问不到了get方法和set方法1.get成员变量名()方法:当成员变量是private的时候,用来获取成员变量。1.set成员变量名()方法:当成员变量是private的时候,用来直接改变成员变量。类的实例化:用类来生成对象。类名 变量名 = new 类名();类名 变量名 = new 构造函数;构造函数:必须与类名相同。不能有返回类型。自动被执行。对象new出的来时间自动被执行。一般都是public。先执行成员变量的初始化,在执行成员函数。不写一般也会被系统默认创建。1234567891011121314151617public class gou { //成员变量 private String mingZi = "大黄" ; private String zhuRen; //成员函数(方法) public String getMingZi() { return mingZi; } public void setMingZi(String mingZi) { if(mingZi != zhuRen){ this.mingZi = mingZi; } } public void jiaoHuan(){ System.out.println(mingZi+":旺!旺!旺!"); }}123456public class home { public static void main(String[] args) { gou a = new gou(); a.setMingZi("小黄"); //用set方法修改名字。 System.out.println("小狗的名字是:"+a.getMingZi()); //用get方法获取名字。 a.jiaoHuan();最后显示小黄小狗的名字是:汪汪汪方法的重载:函数名相同,参数不同(数量,类型)。这两个函数就形成了重载。重载与函数返回类型号没有关系,重载只跟函数名和参数有关系。重载了的函数,会自动跟据调用的实参来决定调用哪个函数。除了构造函数可以重载,普通成员函数也可以重载
-
Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限, 下面看例子:复制代码代码如下: package org.thread.demo; class MyThread extends Thread{ private String name; public MyThread(String name) { super(); this.name = name; } public void run(){ for(int i=0;i<10;i++){ System.out.println("线程开始:"+this.name+",i="+i); } } } package org.thread.demo; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("线程a"); MyThread mt2=new MyThread("线程b"); mt1.run(); mt2.run(); } } 但是,此时结果很有规律,先第一个对象执行,然后第二个对象执行,并没有相互运行。在JDK的文档中可以发现,一旦调用start()方法,则会通过JVM找到run()方法。下面启动 start()方法启动线程:复制代码代码如下: package org.thread.demo; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("线程a"); MyThread mt2=new MyThread("线程b"); mt1.start(); mt2.start(); } }; 这样程序可以正常完成交互式运行。那么为啥非要使用start();方法启动多线程呢? 在JDK的安装路径下,src.zip是全部的java源程序,通过此代码找到Thread中的start()方法的定义,可以发现此方法中使用了private native void start0();其中native关键字表示可以调用操作系统的底层函数,那么这样的技术成为JNI技术(java Native Interface) ·Runnable接口 在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。复制代码代码如下: public interface Runnable{ public void run(); } 例子:复制代码代码如下: package org.runnable.demo; class MyThread implements Runnable{ private String name; public MyThread(String name) { this.name = name; } public void run(){ for(int i=0;i<100;i++){ System.out.println("线程开始:"+this.name+",i="+i); } } }; 但是在使用Runnable定义的子类中没有start()方法,只有Thread类中才有。此时观察Thread类,有一个构造方法:public Thread(Runnable targer)此构造方法接受Runnable的子类实例,也就是说可以通过Thread类来启动Runnable实现的多线程。(start()可以协调系统的资源):复制代码代码如下: package org.runnable.demo; import org.runnable.demo.MyThread; public class ThreadDemo01 { public static void main(String[] args) { MyThread mt1=new MyThread("线程a"); MyThread mt2=new MyThread("线程b"); new Thread(mt1).start(); new Thread(mt2).start(); } } · 两种实现方式的区别和联系: 在程序开发中只要是多线程肯定永远以实现Runnable接口为主,因为实现Runnable接口相比 继承Thread类有如下好处: ->避免点继承的局限,一个类可以继承多个接口。 ->适合于资源的共享 以卖票程序为例,通过Thread类完成:复制代码代码如下: package org.demo.dff; class MyThread extends Thread{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println("卖票:ticket"+this.ticket--); } } } }; 下面通过三个线程对象,同时卖票:复制代码代码如下: package org.demo.dff; public class ThreadTicket { public static void main(String[] args) { MyThread mt1=new MyThread(); MyThread mt2=new MyThread(); MyThread mt3=new MyThread(); mt1.start();//每个线程都各卖了10张,共卖了30张票 mt2.start();//但实际只有10张票,每个线程都卖自己的票 mt3.start();//没有达到资源共享 } } 如果用Runnable就可以实现资源共享,下面看例子:复制代码代码如下: package org.demo.runnable; class MyThread implements Runnable{ private int ticket=10; public void run(){ for(int i=0;i<20;i++){ if(this.ticket>0){ System.out.println("卖票:ticket"+this.ticket--); } } } } package org.demo.runnable; public class RunnableTicket { public static void main(String[] args) { MyThread mt=new MyThread(); new Thread(mt).start();//同一个mt,但是在Thread中就不可以,如果用同一 new Thread(mt).start();//个实例化对象mt,就会出现异常 new Thread(mt).start(); } };
-
一、选择排序(SelectSort)基本原理:对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录的位置进行交换;接着对不包括第一个记录以外的其他记录进行第二次比较,得到最小的记录并与第二个记录进行位置交换;重复该过程,直到进行比较的记录只有一个为止。1234567891011121314151617181920212223242526272829public class SelectSort { public static void selectSort(int[] array) { int i; int j; int temp; int flag; for (i = 0; i < array.length; i++) { temp = array[i]; flag = i; for (j = i + 1; j < array.length; j++) { if (array[j] < temp) { temp = array[j]; flag = j; } } if (flag != i) { array[flag] = array[i]; array[i] = temp; } } } public static void main(String[] args) { int[] a = { 5, 1, 9, 6, 7, 2, 8, 4, 3 }; selectSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}二、插入排序(InsertSort)基本原理:对于给定的一组数据,初始时假设第一个记录自成一个有序序列,其余记录为无序序列。接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中,直至最后一个记录插入到有序序列中为止。12345678910111213141516171819202122232425public class InsertSort { public static void insertSort(int[] a) { if (a != null) { for (int i = 1; i < a.length; i++) { int temp = a[i]; int j = i; if (a[j - 1] > temp) { while (j >= 1 && a[j - 1] > temp) { a[j] = a[j - 1]; j--; } } a[j] = temp; } } } public static void main(String[] args) { int[] a = { 5, 1, 7, 2, 8, 4, 3, 9, 6 }; // int[] a =null; insertSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}三、冒泡排序(BubbleSort)基本原理:对于给定的n个记录,从第一个记录开始依次对相邻的两个记录进行比较,当前面的记录大于后面的记录时,交换位置,进行一轮比较和换位后,n个记录中的最大记录将位于第n位;然后对前(n-1)个记录进行第二轮比较;重复该过程直到进行比较的记录只剩下一个为止。12345678910111213141516171819202122public class BubbleSort { public static void bubbleSort(int array[]) { int temp = 0; int n = array.length; for (int i = n - 1; i >= 0; i--) { for (int j = 0; j < i; j++) { if (array[j] > array[j + 1]) { temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; } } } } public static void main(String[] args) { int a[] = { 45, 1, 21, 17, 69, 99, 32 }; bubbleSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}四、归并排序(MergeSort)基本原理:利用递归与分治技术将数据序列划分成为越来越小的半子表,再对半子表排序,最后再用递归方法将排好序的半子表合并成为越来越大的有序序列。对于给定的一组记录(假设共有n个记录),首先将每两个相邻的长度为1的子序列进行归并,得到n/2(向上取整)个长度为2或1的有序子序列,再将其两两归并,反复执行此过程,直到得到一个有序序列。12345678910111213141516171819202122232425262728293031323334353637383940414243444546public class MergeSort { public static void merge(int array[], int p, int q, int r) { int i, j, k, n1, n2; n1 = q - p + 1; n2 = r - q; int[] L = new int[n1]; int[] R = new int[n2]; for (i = 0, k = p; i < n1; i++, k++) L[i] = array[k]; for (i = 0, k = q + 1; i < n2; i++, k++) R[i] = array[k]; for (k = p, i = 0, j = 0; i < n1 && j < n2; k++) { if (L[i] > R[j]) { array[k] = L[i]; i++; } else { array[k] = R[j]; j++; } } if (i < n1) { for (j = i; j < n1; j++, k++) array[k] = L[j]; } if (j < n2) { for (i = j; i < n2; i++, k++) { array[k] = R[i]; } } } public static void mergeSort(int array[], int p, int r) { if (p < r) { int q = (p + r) / 2; mergeSort(array, p, q); mergeSort(array, q + 1, r); merge(array, p, q, r); } } public static void main(String[] args) { int a[] = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; mergeSort(a, 0, a.length - 1); for (int j = 0; j < a.length; j++) { System.out.print(a[j] + " "); } }}五、快速排序(QuickSort)基本原理:对于一组给定的记录,通过一趟排序后,将原序列分为两部分,其中前一部分的所有记录均比后一部分的所有记录小,然后再依次对前后两部分的记录进行快速排序,递归该过程,直到序列中的所有记录均有序为止。12345678910111213141516171819202122232425262728293031323334public class QuickSort { public static void sort(int array[], int low, int high) { int i, j; int index; if (low >= high) return; i = low; j = high; index = array[i]; while (i < j) { while (i < j && index <= array[j]) j--; if (i < j) array[i++] = array[j]; while (i < j && index > array[i]) i++; if (i < j) array[j--] = array[i]; } array[i] = index; sort(array, low, i - 1); sort(array, i + 1, high); } public static void quickSort(int array[]) { sort(array, 0, array.length - 1); } public static void main(String[] args) { int a[] = { 5, 8, 4, 6, 7, 1, 3, 9, 2 }; quickSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}六、希尔排序(ShellSort)基本原理:先将待排序的数组元素分成多个子序列,使得每个子序列的元素个数相对减少,然后对各个子序列分别进行直接插入排序,待整个待排序序列"基本有序后",最后再对所有元素进行一次直接插入排序。123456789101112131415161718192021222324252627public class ShellSort { public static void shellSort(int[] a) { int len = a.length; int i, j; int h; int temp; for (h = len / 2; h > 0; h = h / 2) { for (i = h; i < len; i++) { temp = a[i]; for (j = i - h; j >= 0; j -= h) { if (temp < a[j]) { a[j + h] = a[j]; } else break; } a[j + h] = temp; } } } public static void main(String[] args) { int a[] = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; shellSort(a); for (int j = 0; j < a.length; j++) { System.out.print(a[j] + " "); } }}七、最小堆排序(MinHeapSort)基本原理:对于给定的n个记录,初始时把这些记录看作一颗顺序存储的二叉树,然后将其调整为一个小顶堆,然后将堆的最后一个元素与堆顶元素进行交换后,堆的最后一个元素即为最小记录;接着讲前(n-1)个元素重新调整为一个小顶堆,再将堆顶元素与当前堆的最后一个元素进行交换后得到次小的记录,重复该过程直到调整的堆中只剩一个元素时为止,该元素即为最大记录,此时可得到一个有序序列。123456789101112131415161718192021222324252627282930313233343536public class MinHeapSort { public static void adjustMinHeap(int[] a, int pos, int len) { int temp; int child; for (temp = a[pos]; 2 * pos + 1 <= len; pos = child) { child = 2 * pos + 1; if (child < len && a[child] > a[child + 1]) child++; if (a[child] < temp) a[pos] = a[child]; else break; } a[pos] = temp; } public static void myMinHeapSort(int[] array) { int i; int len = array.length; for (i = len / 2 - 1; i >= 0; i--) { adjustMinHeap(array, i, len - 1); } for (i = len - 1; i >= 0; i--) { int tmp = array[0]; array[0] = array[i]; array[i] = tmp; adjustMinHeap(array, 0, i - 1); } } public static void main(String[] args) { int[] a = { 5, 4, 9, 8, 7, 6, 0, 1, 3, 2 }; myMinHeapSort(a); for (int i = 0; i < a.length; i++) { System.out.print(a[i] + " "); } }}
-
同步和死锁:【问题引出】:比如说对于买票系统,有下面的代码:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:543210-1这里出现了-1,显然这个是错的。,应该票数不能为负值。如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,其他的线程必须等到这个线程结束之后才能继续执行。【使用线程同步解决问题】采用同步的话,可以使用同步代码块和同步方法两种来完成。【同步代码块】:语法格式:synchronized(同步对象){//需要同步的代码}但是一般都把当前对象this作为同步对象。比如对于上面的买票的问题,如下:复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for(int i=0;i<10;++i){synchronized (this) {if(count>0){try{Thread.sleep(1000);}catch(InterruptedException e){e.printStackTrace();}System.out.println(count--);}}}}public static void main(String[] args) {hello he=new hello();Thread h1=new Thread(he);Thread h2=new Thread(he);Thread h3=new Thread(he);h1.start();h2.start();h3.start();}private int count=5;}【运行结果】:(每一秒输出一个结果)54321【同步方法】也可以采用同步方法。语法格式为synchronized 方法返回类型 方法名(参数列表){// 其他代码}现在,我们采用同步方法解决上面的问题。复制代码代码如下:/*** @author Rollen-Holt* */class hello implements Runnable {public void run() {for (int i = 0; i < 10; ++i) {sale();}}public synchronized void sale() {if (count > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count--);}}public static void main(String[] args) {hello he = new hello();Thread h1 = new Thread(he);Thread h2 = new Thread(he);Thread h3 = new Thread(he);h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】(每秒输出一个)54321提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。此处列举经典的生产者和消费者问题。【生产者和消费者问题】先看一段有问题的代码。复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable{private Info info=null;Producer(Info info){this.info=info;}public void run(){boolean flag=false;for(int i=0;i<25;++i){if(flag){this.info.setName("Rollen");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(20);flag=false;}else{this.info.setName("chunGe");try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.info.setAge(100);flag=true;}}}}/*** 消费者类* */class Consumer implements Runnable{private Info info=null;public Consumer(Info info){this.info=info;}public void run(){for(int i=0;i<25;++i){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.info.getName()+"<---->"+this.info.getAge());}}}/*** 测试类* */class hello{public static void main(String[] args) {Info info=new Info();Producer pro=new Producer(info);Consumer con=new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<---->100chunGe<---->20chunGe<---->100Rollen<---->100chunGe<---->20Rollen<---->100Rollen<---->100Rollen<---->100chunGe<---->20chunGe<---->20chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20Rollen<---->100chunGe<---->20大家可以从结果中看到,名字和年龄并没有对于。那么如何解决呢?<!--[if !supportLists]-->1) <!--[endif]-->加入同步<!--[if !supportLists]-->2) <!--[endif]-->加入等待和唤醒先来看看加入同步会是如何。复制代码代码如下:class Info {public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public synchronized void set(String name, int age){this.name=name;try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}this.age=age;}public synchronized void get(){try{Thread.sleep(100);}catch (Exception e) {e.printStackTrace();}System.out.println(this.getName()+"<===>"+this.getAge());}private String name = "Rollen";private int age = 20;}/*** 生产者* */class Producer implements Runnable {private Info info = null;Producer(Info info) {this.info = info;}public void run() {boolean flag = false;for (int i = 0; i < 25; ++i) {if (flag) {this.info.set("Rollen", 20);flag = false;} else {this.info.set("ChunGe", 100);flag = true;}}}}/*** 消费者类* */class Consumer implements Runnable {private Info info = null;public Consumer(Info info) {this.info = info;}public void run() {for (int i = 0; i < 25; ++i) {try {Thread.sleep(100);} catch (Exception e) {e.printStackTrace();}this.info.get();}}}/*** 测试类* */class hello {public static void main(String[] args) {Info info = new Info();Producer pro = new Producer(info);Consumer con = new Consumer(info);new Thread(pro).start();new Thread(con).start();}}【运行结果】:Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100Rollen<===>20ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100ChunGe<===>100从运行结果来看,错乱的问题解决了,现在是Rollen 对应20,ChunGe对于100,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、,我们可以使用其中的等待和唤醒操作。
-
我们换为Runnable接口:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello implements Runnable {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello he=new hello();new Thread(he).start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1总结一下吧:实现Runnable接口比继承Thread类所具有的优势:1):适合多个相同的程序代码的线程去处理同一个资源2):可以避免java中的单继承的限制3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。所以,本人建议大家劲量实现接口。复制代码代码如下:/*** @author Rollen-Holt* 取得线程的名称* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();new Thread(he,"A").start();new Thread(he,"B").start();new Thread(he).start();}}【运行结果】:AAABThread-0Thread-0Thread-0说明如果我们没有指定名字的话,系统自动提供名字。提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。判断线程是否启动复制代码代码如下:/*** @author Rollen-Holt 判断线程是否启动* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he);System.out.println("线程启动之前---》" + demo.isAlive());demo.start();System.out.println("线程启动之后---》" + demo.isAlive());}} 【运行结果】线程启动之前---》false线程启动之后---》trueThread-0Thread-0Thread-0主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。线程的强制执行:复制代码代码如下:/*** @author Rollen-Holt 线程的强制执行* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {System.out.println(Thread.currentThread().getName());}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he,"线程");demo.start();for(int i=0;i<50;++i){if(i>10){try{demo.join(); //强制执行demo}catch (Exception e) {e.printStackTrace();}}System.out.println("main 线程执行-->"+i);}}}【运行的结果】:main 线程执行-->0main 线程执行-->1main 线程执行-->2main 线程执行-->3main 线程执行-->4main 线程执行-->5main 线程执行-->6main 线程执行-->7main 线程执行-->8main 线程执行-->9main 线程执行-->10线程线程线程main 线程执行-->11main 线程执行-->12main 线程执行-->13...线程的休眠:复制代码代码如下:/*** @author Rollen-Holt 线程的休眠* */class hello implements Runnable {public void run() {for (int i = 0; i < 3; i++) {try {Thread.sleep(2000);} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + i);}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();}}【运行结果】:(结果每隔2s输出一个)线程0线程1线程2线程的中断:复制代码代码如下:/*** @author Rollen-Holt 线程的中断* */class hello implements Runnable {public void run() {System.out.println("执行run方法");try {Thread.sleep(10000);System.out.println("线程完成休眠");} catch (Exception e) {System.out.println("休眠被打断");return; //返回到程序的调用处}System.out.println("线程正常终止");}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.start();try{Thread.sleep(2000);}catch (Exception e) {e.printStackTrace();}demo.interrupt(); //2s后中断线程}}【运行结果】:执行run方法休眠被打断在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。复制代码代码如下:/*** @author Rollen-Holt 后台线程* */class hello implements Runnable {public void run() {while (true) {System.out.println(Thread.currentThread().getName() + "在运行");}}public static void main(String[] args) {hello he = new hello();Thread demo = new Thread(he, "线程");demo.setDaemon(true);demo.start();}}虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。线程的优先级:复制代码代码如下:/*** @author Rollen-Holt 线程的优先级* */class hello implements Runnable {public void run() {for(int i=0;i<5;++i){System.out.println(Thread.currentThread().getName()+"运行"+i);}}public static void main(String[] args) {Thread h1=new Thread(new hello(),"A");Thread h2=new Thread(new hello(),"B");Thread h3=new Thread(new hello(),"C");h1.setPriority(8);h2.setPriority(2);h3.setPriority(6);h1.start();h2.start();h3.start();}}【运行结果】:A运行0A运行1A运行2A运行3A运行4B运行0C运行0C运行1C运行2C运行3C运行4B运行1B运行2B运行3B运行4。但是请读者不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源、另外,主线程的优先级是5.线程的礼让。在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。
-
java中的多线程在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。对于直接继承Thread的类来说,代码大致框架是:复制代码代码如下:class 类名 extends Thread{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}先看一个简单的例子:复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,直接调用run方法* */class hello extends Thread {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.run();h2.run();}private String name;}【运行结果】:A运行 0A运行 1A运行 2A运行 3A运行 4B运行 0B运行 1B运行 2B运行 3B运行 4我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。当我们把上面的主函数修改为如下所示的时候:复制代码代码如下:public static void main(String[] args) {hello h1=new hello("A");hello h2=new hello("B");h1.start();h2.start();}然后运行程序,输出的可能的结果如下:A运行 0B运行 0B运行 1B运行 2B运行 3B运行 4A运行 1A运行 2A运行 3A运行 4因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。那么:为什么我们不能直接调用run()方法呢?我的理解是:线程的运行需要本地操作系统的支持。如果你查看start的源代码的时候,会发现:复制代码代码如下:public synchronized void start() {/*** This method is not invoked for the main method thread or "system"* group threads created/set up by the VM. Any new functionality added* to this method in the future may have to also be added to the VM.** A zero status value corresponds to state "NEW".*/if (threadStatus != 0 || this != me)throw new IllegalThreadStateException();group.add(this);start0();if (stopBeforeStart) {stop0(throwableFromStop);}}private native void start0();注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。通过实现Runnable接口:大致框架是:复制代码代码如下:class 类名 implements Runnable{方法1;方法2;…public void run(){// other code…}属性1;属性2;…}来先看一个小例子吧:复制代码代码如下:/*** @author Rollen-Holt 实现Runnable接口* */class hello implements Runnable {public hello() {}public hello(String name) {this.name = name;}public void run() {for (int i = 0; i < 5; i++) {System.out.println(name + "运行 " + i);}}public static void main(String[] args) {hello h1=new hello("线程A");Thread demo= new Thread(h1);hello h2=new hello("线程B");Thread demo1=new Thread(h2);demo.start();demo1.start();}private String name;}【可能的运行结果】:线程A运行 0线程B运行 0线程B运行 1线程B运行 2线程B运行 3线程B运行 4线程A运行 1线程A运行 2线程A运行 3线程A运行 4关于选择继承Thread还是实现Runnable接口?其实Thread也是实现Runnable接口的:复制代码代码如下:class Thread implements Runnable {//…public void run() {if (target != null) {target.run();}}}其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:http://www.cnblogs.com/rollenholt/archive/2011/08/18/2144847.htmlThread和Runnable的区别:如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。复制代码代码如下:/*** @author Rollen-Holt 继承Thread类,不能资源共享* */class hello extends Thread {public void run() {for (int i = 0; i < 7; i++) {if (count > 0) {System.out.println("count= " + count--);}}}public static void main(String[] args) {hello h1 = new hello();hello h2 = new hello();hello h3 = new hello();h1.start();h2.start();h3.start();}private int count = 5;}【运行结果】:count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1count= 5count= 4count= 3count= 2count= 1大家可以想象,如果这个是一个买票系统的话,如果count表示的是车票的数量的话,说明并没有实现资源的共享
-
JAVA源码编译由三个过程组成:1、源码编译机制。2、类加载机制3、类执行机制我们这里主要介绍编译和类加载这两种机制。一、源码编译代码编译由JAVA源码编译器来完成。主要是将源码编译成字节码文件(class文件)。字节码文件格式主要分为两部分:常量池和方法字节码。二、类加载类的生命周期是从被加载到虚拟机内存中开始,到卸载出内存结束。过程共有七个阶段,其中到初始化之前的都是属于类加载的部分加载----验证----准备----解析-----初始化----使用-----卸载系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类,当运行某个java程序时,会启动一个java虚拟机进程,两次运行的java程序处于两个不同的JVM进程中,两个jvm之间并不会共享数据。1、加载阶段这个流程中的加载是类加载机制中的一个阶段,这两个概念不要混淆,这个阶段需要完成的事情有:1)通过一个类的全限定名来获取定义此类的二进制字节流。2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。3)在java堆中生成一个代表这个类的Class对象,作为访问方法区中这些数据的入口。由于第一点没有指明从哪里获取以及怎样获取类的二进制字节流,所以这一块区域留给我开发者很大的发挥空间。这个我在后面的类加载器中在进行介绍。2、准备阶段这个阶段正式为类变量(被static修饰的变量)分配内存并设置类变量初始值,这个内存分配是发生在方法区中。1、注意这里并没有对实例变量进行内存分配,实例变量将会在对象实例化时随着对象一起分配在JAVA堆中。2、这里设置的初始值,通常是指数据类型的零值。private static int a = 3; 这个类变量a在准备阶段后的值是0,将3赋值给变量a是发生在初始化阶段。3、初始化阶段初始化是类加载机制的最后一步,这个时候才正真开始执行类中定义的JAVA程序代码。在前面准备阶段,类变量已经赋过一次系统要求的初始值,在初始化阶段最重要的事情就是对类变量进行初始化,关注的重点是父子类之间各类资源初始化的顺序。java类中对类变量指定初始值有两种方式:1、声明类变量时指定初始值;2、使用静态初始化块为类变量指定初始值。初始化的时机1)创建类实例的时候,分别有:1、使用new关键字创建实例;2、通过反射创建实例;3、通过反序列化方式创建实例。12new Test();Class.forName(“com.mengdd.Test”);2)调用某个类的类方法(静态方法)1Test.doSomething();3)访问某个类或接口的类变量,或为该类变量赋值。 12int b=Test.a;Test.a=b;4)初始化某个类的子类。当初始化子类的时候,该子类的所有父类都会被初始化。5)直接使用java.exe命令来运行某个主类。除了上面几种方式会自动初始化一个类,其他访问类的方式都称不会触发类的初始化,称为被动引用。1、子类引用父类的静态变量,不会导致子类初始化。12345678910111213141516171819202122public class SupClass{ public static int a = 123; static { System.out.println("supclass init"); }}public class SubClass extends SupClass{ static { System.out.println("subclass init"); }}public class Test{ public static void main(String[] args) { System.out.println(SubClass.a); }}执行结果:supclass init1232、通过数组定义引用类,不会触发此类的初始化123456789101112131415public class SupClass{ public static int a = 123; static { System.out.println("supclass init"); }}public class Test{ public static void main(String[] args) { SupClass[] spc = new SupClass[10]; }}执行结果:3、引用常量时,不会触发该类的初始化123456789101112131415public class ConstClass{ public static final String A= "MIGU"; static { System.out.println("ConstCLass init"); }}public class TestMain{ public static void main(String[] args) { System.out.println(ConstClass.A); }}执行结果:MIGU用final修饰某个类变量时,它的值在编译时就已经确定好放**量池了,所以在访问该类变量时,等于直接从常量池中获取,并没有初始化该类。初始化的步骤1、如果该类还没有加载和连接,则程序先加载该类并连接。2、如果该类的直接父类没有加载,则先初始化其直接父类。3、如果类中有初始化语句,则系统依次执行这些初始化语句。
-
12345678910111213141516171819202122232425262728293031323334353637class Exc{ int a; int b;} public class Except { @SuppressWarnings("finally") static int compute (){ Exc e = new Exc(); e.a = 10; e.b = 10; int res = 0 ; try{ res = e.a / e.b; System.out.println("try ……"); return res + 1; }catch(NullPointerException e1){ System.out.println("NullPointerException occured"); }catch(ArithmeticException e1){ System.out.println("ArithmeticException occured"); }catch(Exception e3){ System.out.println("Exception occured"); }finally{ System.out.println("finnaly occured"); } System.out.println(res); return res+3; } public static void main(String[] args){ int b = compute(); System.out.println("mian b= "+b); }}输出:123try ……finnaly occuredmian b= 2结论: 如果没有异常, 则执行try 中的代码块,直到 try 中的 return,接着执行 finally 中的代码块,finally 执行完后 , 回到try 中执行 return 。退出函数。12345678910111213141516171819202122232425262728293031323334353637class Exc{ int a; int b;} public class Except { @SuppressWarnings("finally") static int compute (){ Exc e = new Exc();// e.a = 10;// e.b = 10; int res = 0 ; try{ res = e.a / e.b; System.out.println("try ……"); return res + 1; }catch(NullPointerException e1){ System.out.println("NullPointerException occured"); }catch(ArithmeticException e1){ System.out.println("ArithmeticException occured"); }catch(Exception e3){ System.out.println("Exception occured"); }finally{ System.out.println("finnaly occured"); } System.out.println(res); return res+3; } public static void main(String[] args){ int b = compute(); System.out.println("mian b= "+b); }}输出:1234ArithmeticException occuredfinnaly occured0mian b= 3结论: 如果try 中有异常, 则在异常语句处,跳转到catch 捕获的异常代码块, 执行完 catch 后,再执行 finally ,跳出 try{}catch{}finally{} ,继续向下执行,不会去执行try中 后面的语句。
-
数组对于每一门编程语言来说都是一个重要的数据结构之一 当然不同语言对数组的实现及处理也不尽相同java语言中提供的数组是用来存储固定大小的同类型元素你可以声明一个被数组变量 如果numbers[100]来代替直接声明100个独立变量 number()number1......number99声明数组: 首先必须声明数组的变量 菜鸟在程序中使用数组 俩种方法:data type[] arrayrefvar; 或 data type arrayrefvar[];例如:double [] mylist ; 或者 double mylist [];创建数组:java语言中经常使用new操作符来创建数组语法:arrrayRefVar =new dataType[arraySize];例如:mylist =new double[20]上面语句做了俩件事:1:使用dataType[ArraySize]创建了一个数组2:把新创建的数组的引用赋值给变量arrayRefVar;数组变量的声明,和创建数组可以用一条语句完成;double[] mylist=new double[5];或者还可以使用下列方法创建数组;double [] mylist={1,2,3,4,5};数组的元素是通过索引来访问的数组索引从0开始 所以索引值从0到arrayRefvar。length-1.处理数组: 数组的元素类型和数组的大小都是确定的 所以当处理数组元素的时候我们通常使用基本循环或者使用for each 循环例子:for-eachu想你换 是jdk 1.5引进的一种新的循环被称为 for-each 循环或者加强循环 它能在不适用下表的情况下遍历数组格式:for(type element:array){System.out.println(element);}把上述代码在执行一次:数组作为函数的参数传递:数组可以作为参数传递给方法例如:下面的例子就是一个打印int 数组中元素的方法for(int i=0;i<array.length;i++) System.out.println(array[i]+" ");调用方法打印出数字: printArray(new int[]{1,2,3,4,5,6,78,9,});数组可以作为函数值返回int []result =new int[list.length];for(int i=0,j=result.length-1<list.length;i++,j--){ result[j]=list[i];多维数组:多维数组可以堪称是数组的数组 比如二位数组就是一个特殊的一维数组 其每一个元素都是一个一维数组如: string str[] [] =new string[3][4]多位数组的动态初始化 以二维数组为例 type[] [] typename=new type[typeLength1][Lentgh2];type可以为基本数据类型和符合数据类型 typelength1 和typelength2必须为正整数 type length1 为行数 2为列数如:int a[] [] =new int [2][3] 所以二位数组a可以看为是俩行三列的二位数组从最高维开始分别为每一维分配空间 如:String s[][] = new String[2][];s[0] = new String[2];s[1] = new String[3];s[0][0] = new String("Good");s[0][1] = new String("Luck");s[1][0] = new String("to");s[1][1] = new String("you");s[1][2] = new String("!");解析:s[0]=new String[2]和s[1]=new string[3]是最高维分配引用空间 也就是最高维限制其能 保存数据的最长的长度 然后在为其每个数组元素单独分配空间 s0=new string("good")如何引用?对二位数组中的每个元素引用方式为 arrayname[index1][index2]采用下标法:如num[1][0];arraysjava util arrays类能发辫的操作数组 它能提供的所有方法都是静态的具有以下功能 给数组赋值 fill方法对数组排序 sort方法比较数组 equals方法查找数组元素:通过binary search方法能对排序号的数组进行二分查找法操作;到此我们数组的介绍就告一段落了 喜欢的小伙伴 点赞+收藏
-
swtich case 语句判断一个变量与一系列值中某个值是否相等 每个值称为一个分支语法:switch(expression){case value: 语句break;case value:语句break;default: 语句}switch case 语句有如下几个规则:switch 语句中的变量类型可以是byte short char int 从javase7开始 switch 支持字符串string类型 同时case标签必须为字符串常量swtich语句可以拥有多个case 语句 每个case后面必须要跟一个要比较的值和冒号case语句中的值的数据类型必须与变量的数据类型相同 而且只能是常量当变量的值与case语句的值相等时 那么case语句之后的语句开始执行 直到遇到break语句出现才会跳出switch语句当遇到break语句时 switch 语句种植 程序跳转到switch语句后面的语句执行 case语句不必要包含break语句 如果没有break语句出现 程序会继续执行下一条case语句 直到出现break语句为止switch语句可以包含一个default分支 该分支一般是switch语句的最后一个分支 在default没有case语句的值和变量的值相等情况下才会执行 defalut分支则不要break语句;switch case 执行时 一定先进行匹配 匹配成功返回当前case 的值 再根据是否有break 判断是否继续输出 或者跳出判断如果case语句中没有break语句时 jvm 并不会顺序输出每一个case对应的返回值而是继续匹配 匹配不成功则返回默认 case如果case语句块中没有break语句时 匹配成功后 从当前case开始后续所有case的值都会输出如果当前匹配成功的case语句块没有break语句 则从当前case开始后续所有case 的值都会输出 如果后续的case语句块有break语句则会跳出判断
上滑加载中
推荐直播
-
华为云码道Agent集成与鸿蒙实战2026/08/11 周二 19:00-21:00
王一男-华为云码道产品规划专家;李炎-华为云码道产品专家;彭江敏-华为云鸿蒙端云一体化开发专家
本次直播带你解读华为云码道7月份产品新特性、新功能。更有专家演示码道Agent Space × 钉钉机器集成实战,从0到1打通消息通道;码道鸿蒙端云一体化实战,快速搭建员工签到系统。
回顾中 -
华为云开发者AI素养直播课·第五期2026/09/04 周五 16:00-18:00
林华鼎-华为云AI开发者运营负责人;蒋春阳-华为云AI开发者案例开发专家
本期直播内容: AI工具体验营 · 第5-8课连讲。Agent-Team 多智能体协作完成毕业设计实践
回顾中 -
华为云开发者AI素养ClassRoom·第六期2026/09/08 周二 19:00-20:00
樊渊-2026华为软件挑战赛冠军
高手来了:看软挑高手解析二维排样问题—从工业难题到算法突破
回顾中
热门标签