`
tboss
  • 浏览: 45706 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
最近访客 更多访客>>
社区版块
存档分类
最新评论

你不知道的5个Java有用的工具

    博客分类:
  • Java
阅读更多

编者按:本文为 Neward & Associates 的主管Ted Neward 撰写的5件你不知道的事情系列的最后一篇(至少目前是这样)。Ted Neward 在Neward & Associates负责有关 Java、.NET、XML 服务和其他平台的咨询、指导、培训和推介。他现在居住在华盛顿州西雅图附近。以下为本文的译文:

很多年前,当我还是高中生的时候,我曾考虑以小说作家作为我的职业追求,我订阅了一本 Writer's Digest 杂志。我记得其中有篇专栏文章,是关于 “太小而难以保存的线头”,专栏作者描述厨房储物抽屉中放满了无法分类的玩意儿。这句话我一直铭记在心,它正好用来描述本文的内容,本Java 平台就充满了这样的 “线头” — 有用的命令行工具和库,大多数 Java 开发人员甚至都不知道,更别提使用了。其中很多无法划分到之前的 5 件事 系列 的编程分类中,但不管怎样,尝试一下:有些说不定会在您编程的厨房抽屉中占得一席之地。

1. StAX

在千禧年左右,当 XML 第一次出现在很多 Java 开发人员面前时,有两种基本的解析 XML 文件的方法。SAX 解析器实际是由程序员对事件调用一系列回调方法的大型状态机。DOM 解析器将整个 XML 文档加入内存,并切割成离散的对象,它们连接在一起形成一个树。该树描述了文档的整个 XML Infoset 表示法。这两个解析器都有缺点:SAX 太低级,无法使用,DOM 代价太大,尤其对于大的 XML 文件 — 整个树成了一个庞然大物。

幸运的是,Java 开发人员找到第三种方法来解析 XML 文件,通过对文档建模成 “节点”,它们可以从文档流中一次取出一个,检查,然后处理或丢弃。这些 “节点” 的 “流” 提供了 SAX 和 DOM 的中间地带,名为 “Streaming API for XML”,或者叫做StAX。(此缩写用于区分新的 API 与原来的 SAX 解析器,它与此同名。)StAX 解析器后来包装到了 JDK 中,在 javax.xml.stream 包。

使用 StAX 相当简单:实例化 XMLEventReader,将它指向一个格式良好的 XML 文件,然后一次 “拉出” 一个节点(通常用 while 循环),查看。例如,在清单 1 中,列举出了 Ant 构造脚本中的所有目标:

清单 1. 只是让 StAX 指向目标

  1.       
  2. import java.io.*;  
  3. import javax.xml.namespace.QName;  
  4. import javax.xml.stream.*;  
  5. import javax.xml.stream.events.*;  
  6. import javax.xml.stream.util.*;  
  7.  
  8. public class Targets  
  9. {  
  10.     public static void main(String[] args)  
  11.         throws Exception  
  12.     {  
  13.         for (String arg : args)  
  14.         {  
  15.             XMLEventReader xsr =   
  16.                 XMLInputFactory.newInstance()  
  17.                     .createXMLEventReader(new FileReader(arg));  
  18.             while (xsr.hasNext())  
  19.             {  
  20.                 XMLEvent evt = xsr.nextEvent();  
  21.                 switch (evt.getEventType())  
  22.                 {  
  23.                     case XMLEvent.START_ELEMENT:  
  24.                     {  
  25.                         StartElement se = evt.asStartElement();  
  26.                         if (se.getName().getLocalPart().equals("target"))  
  27.                         {  
  28.                             Attribute targetName =   
  29.                                 se.getAttributeByName(new QName("name"));  
  30.                             // Found a target!  
  31.                             System.out.println(targetName.getValue());  
  32.                         }  
  33.                         break;  
  34.                     }  
  35.                     // Ignore everything else  
  36.                 }  
  37.             }  
  38.         }  
  39.     }  
  40. }  
  41.  

StAX 解析器不会替换所有的 SAX 和 DOM 代码。但肯定会让某些任务容易些。尤其对完成不需要知道 XML 文档整个树结构的任务相当方便。

请注意,如果事件对象级别太高,无法使用,StAX 也有一个低级 API 在 XMLStreamReader 中。尽管也许没有阅读器有用,StAX 还有一个 XMLEventWriter,同样,还有一个 XMLStreamWriter 类用于 XML 输出。

 

2. ServiceLoader

Java 开发人员经常希望将使用和创建组件的内容区分开来。这通常是通过创建一个描述组件动作的接口,并使用某种中介创建组件实例来完成的。很多开发人员使用 Spring 框架来完成,但还有其他的方法,它比 Spring 容器更轻量级。

java.util 的 ServiceLoader 类能读取隐藏在 JAR 文件中的配置文件,并找到接口的实现,然后使这些实现成为可选择的列表。例如,如果您需要一个私仆(personal-servant)组件来完成任务,您可以使用清单 2 中的代码来实现:

清单 2. IPersonalServant

  1.       
  2. public interface IPersonalServant  
  3. {  
  4.     // Process a file of commands to the servant  
  5.     public void process(java.io.File f)  
  6.         throws java.io.IOException;  
  7.     public boolean can(String command);  
  8. }  

can() 方法可让您确定所提供的私仆实现是否满足需求。清单 3 中的 ServiceLoader 的 IPersonalServant 列表基本上满足需求:

清单 3. IPersonalServant 行吗?

  1.       
  2. import java.io.*;  
  3. import java.util.*;  
  4.  
  5. public class Servant  
  6. {  
  7.     public static void main(String[] args)  
  8.         throws IOException  
  9.     {  
  10.         ServiceLoader<IPersonalServant> servantLoader =   
  11.             ServiceLoader.load(IPersonalServant.class);  
  12.  
  13.         IPersonalServant i = null;  
  14.         for (IPersonalServant ii : servantLoader)  
  15.             if (ii.can("fetch tea"))  
  16.                 i = ii;  
  17.  
  18.         if (i == null)  
  19.             throw new IllegalArgumentException("No suitable servant found");  
  20.           
  21.         for (String arg : args)  
  22.         {  
  23.             i.process(new File(arg));  
  24.         }  
  25.     }  
  26. }  
  27.  

假设有此接口的实现,如清单 4:

清单 4. Jeeves 实现了 IPersonalServant

  1. import java.io.*;  
  2.  
  3. public class Jeeves  
  4.     implements IPersonalServant  
  5. {  
  6.     public void process(File f)  
  7.     {  
  8.         System.out.println("Very good, sir.");  
  9.     }  
  10.     public boolean can(String cmd)  
  11.     {  
  12.         if (cmd.equals("fetch tea"))  
  13.             return true;  
  14.         else  
  15.             return false;  
  16.     }  
  17. }  
  18.  

剩下的就是配置包含实现的 JAR 文件,让 ServiceLoader 能识别 — 这可能会非常棘手。JDK 想要 JAR 文件有一个 META-INF/services 目录,它包含一个文本文件,其文件名与接口类名完全匹配 — 本例中是 META-INF/services/IPersonalServant。接口类名的内容是实现的名称,每行一个,如清单 5:

清单 5. META-INF/services/IPersonalServant

  1.       
  2. Jeeves   # comments are OK  

幸运的是,Ant 构建系统(自 1.7.0 以来)包含一个对 jar 任务的服务标签,让这相对容易,见清单 6:

清单 6. Ant 构建的 IPersonalServant

  1.  
  2. <target name="serviceloader" depends="build"> 
  3.     <jar destfile="misc.jar" basedir="./classes"> 
  4.         <service type="IPersonalServant"> 
  5.             <provider classname="Jeeves" /> 
  6.         </service> 
  7.     </jar> 
  8. </target> 

这里,很容易调用 IPersonalServant,让它执行命令。然而,解析和执行这些命令可能会非常棘手。这又是另一个 “小线头”。

 

3. Scanner

有无数 Java 工具能帮助您构建解析器,很多函数语言已成功构建解析器函数库(解析器选择器)。但如果要解析的是逗号分隔值文件,或空格分隔文本文件,又怎么办呢?大多数工具用在此处就过于隆重了,而 String.split() 又不够。(对于正则表达式,请记住一句老话:“ 您有一个问题,用正则表达式解决。那您就有两个问题了。”)

Java 平台的 Scanner 类会是这些类中您最好的选择。以轻量级文本解析器为目标,Scanner 提供了一个相对简单的 API,用于提取结构化文本,并放入强类型的部分。想象一下,如果您愿意,一组类似 DSL 的命令(源自 Terry Pratchett Discworld 小说)排列在文本文件中,如清单 7:

清单 7. Igor 的任务

  1.       
  2. fetch 1 head  
  3. fetch 3 eye  
  4. fetch 1 foot  
  5. attach foot to head  
  6. attach eye to head  
  7. admire  

您,或者是本例中称为 Igor的私仆,能轻松使用 Scanner 解析这组违法命令,如清单 8 所示:

清单 8. Igor 的任务,由 Scanner 解析

  1.       
  2. import java.io.*;  
  3. import java.util.*;  
  4.  
  5. public class Igor  
  6.     implements IPersonalServant  
  7. {  
  8.     public boolean can(String cmd)  
  9.     {  
  10.         if (cmd.equals("fetch body parts"))  
  11.             return true;  
  12.         if (cmd.equals("attach body parts"))  
  13.             return true;  
  14.         else  
  15.             return false;  
  16.     }  
  17.     public void process(File commandFile)  
  18.         throws FileNotFoundException  
  19.     {  
  20.         Scanner scanner = new Scanner(commandFile);  
  21.         // Commands come in a verb/number/noun or verb form  
  22.         while (scanner.hasNext())  
  23.         {  
  24.             String verb = scanner.next();  
  25.             if (verb.equals("fetch"))  
  26.             {  
  27.                 int num = scanner.nextInt();  
  28.                 String type = scanner.next();  
  29.                 fetch (num, type);  
  30.             }  
  31.             else if (verb.equals("attach"))  
  32.             {  
  33.                 String item = scanner.next();  
  34.                 String to = scanner.next();  
  35.                 String target = scanner.next();  
  36.                 attach(item, target);  
  37.             }  
  38.             else if (verb.equals("admire"))  
  39.             {  
  40.                 admire();  
  41.             }  
  42.             else  
  43.             {  
  44.                 System.out.println("I don't know how to "   
  45.                     + verb + ", marthter.");  
  46.             }  
  47.         }  
  48.     }  
  49.       
  50.     public void fetch(int number, String type)  
  51.     {  
  52.         if (parts.get(type) == null)  
  53.         {  
  54.             System.out.println("Fetching " + number + " "   
  55.                 + type + (number > 1 ? "s" : "") + ", marthter!");  
  56.             parts.put(type, number);  
  57.         }  
  58.         else  
  59.         {  
  60.             System.out.println("Fetching " + number + " more "   
  61.                 + type + (number > 1 ? "s" : "") + ", marthter!");  
  62.             Integer currentTotal = parts.get(type);  
  63.             parts.put(type, currentTotal + number);  
  64.         }  
  65.         System.out.println("We now have " + parts.toString());  
  66.     }  
  67.       
  68.     public void attach(String item, String target)  
  69.     {  
  70.         System.out.println("Attaching the " + item + " to the " +  
  71.             target + ", marthter!");  
  72.     }  
  73.       
  74.     public void admire()  
  75.     {  
  76.         System.out.println("It'th quite the creathion, marthter");  
  77.     }  
  78.       
  79.     private Map<String, Integer> parts = new HashMap<String, Integer>();  
  80. }  
  81.  

假设 Igor 已在 ServantLoader 中注册,可以很方便地将 can() 调用改得更实用,并重用前面的 Servant 代码,如清单 9 所示:

清单 9. Igor 做了什么

  1. import java.io.*;  
  2. import java.util.*;  
  3.  
  4. public class Servant  
  5. {  
  6.     public static void main(String[] args)  
  7.         throws IOException  
  8.     {  
  9.         ServiceLoader<IPersonalServant> servantLoader =   
  10.             ServiceLoader.load(IPersonalServant.class);  
  11.  
  12.         IPersonalServant i = null;  
  13.         for (IPersonalServant ii : servantLoader)  
  14.             if (ii.can("fetch body parts"))  
  15.                 i = ii;  
  16.  
  17.         if (i == null)  
  18.             throw new IllegalArgumentException("No suitable servant found");  
  19.           
  20.         for (String arg : args)  
  21.         {  
  22.             i.process(new File(arg));  
  23.         }  
  24.     }  
  25. }  
  26.  

真正 DSL 实现显然不会仅仅打印到标准输出流。我把追踪哪些部分、跟随哪些部分的细节留待给您(当然,还有忠诚的 Igor)。

 

4. Timer

java.util.Timer 和 TimerTask 类提供了方便、相对简单的方法可在定期或一次性延迟的基础上执行任务:

清单 10. 稍后执行

  1. import java.util.*;  
  2.  
  3. public class Later  
  4. {  
  5.     public static void main(String[] args)  
  6.     {  
  7.         Timer t = new Timer("TimerThread");  
  8.         t.schedule(new TimerTask() {  
  9.             public void run() {  
  10.                 System.out.println("This is later");  
  11.                 System.exit(0);  
  12.             }  
  13.         }, 1 * 1000);  
  14.         System.out.println("Exiting main()");  
  15.     }  
  16. }  
  17.  

Timer 有许多 schedule() 重载,它们提示某一任务是一次性还是重复的,并且有一个启动的 TimerTask 实例。TimerTask 实际上是一个 Runnable(事实上,它实现了它),但还有另外两个方法:cancel() 用来取消任务,scheduledExecutionTime() 用来返回任务何时启动的近似值。

请注意 Timer 却创建了一个非守护线程在后台启动任务,因此在清单 10 中我需要调用 System.exit() 来取消任务。在长时间运行的程序中,最好创建一个 Timer 守护线程(使用带有指示守护线程状态的参数的构造函数),从而它不会让 VM 活动。

这个类没什么神奇的,但它确实能帮助我们对后台启动的程序的目的了解得更清楚。它还能节省一些 Thread 代码,并作为轻量级 ScheduledExecutorService(对于还没准备好了解整个 java.util.concurrent 包的人来说)。

 

5. JavaSound

尽管在服务器端应用程序中不常出现,但 sound 对管理员有着有用的 “被动” 意义 — 它是恶作剧的好材料。尽管它很晚才出现在 Java 平台中,JavaSound API 最终还是加入了核心运行时库,封装在 javax.sound * 包 — 其中一个包是 MIDI 文件,另一个是音频文件示例(如普遍的 .WAV 文件格式)。

JavaSound 的 “hello world” 是播放一个片段,如清单 11 所示:

清单 11. 再放一遍,Sam

  1. public static void playClip(String audioFile)  
  2. {  
  3.     try  
  4.     {  
  5.         AudioInputStream inputStream =  
  6.             AudioSystem.getAudioInputStream(  
  7.                 this.getClass().getResourceAsStream(audioFile));  
  8.         DataLine.Info info =   
  9.             new DataLine.Info( Clip.class, audioInputStream.getFormat() );  
  10.         Clip clip = (Clip) AudioSystem.getLine(info);  
  11.         clip.addLineListener(new LineListener() {  
  12.                 public void update(LineEvent e) {  
  13.                     if (e.getType() == LineEvent.Type.STOP) {  
  14.                         synchronized(clip) {  
  15.                             clip.notify();  
  16.                         }  
  17.                     }  
  18.                 }  
  19.             });  
  20.         clip.open(audioInputStream);          
  21.           
  22.         clip.setFramePosition(0);  
  23.  
  24.         clip.start();  
  25.         synchronized (clip) {  
  26.             clip.wait();  
  27.         }  
  28.         clip.drain();  
  29.         clip.close();  
  30.     }  
  31.     catch (Exception ex)  
  32.     {  
  33.         ex.printStackTrace();  
  34.     }  
  35. }  
  36.  

大多数还是相当简单(至少 JavaSound 一样简单)。第一步是创建一个文件的 AudioInputStream 来播放。为了让此方法尽量与上下文无关,我们从加载类的 ClassLoader 中抓取文件作为 InputStream。(AudioSystem 还需要一个 File 或 String,如果提前知道声音文件的具体路径。)一旦完成, DataLine.Info 对象就提供给 AudioSystem,得到一个 Clip,这是播放音频片段最简单的方法。(其他方法提供了对片段更多的控制 — 例如获取一个 SourceDataLine — 但对于 “播放” 来说,过于复杂)。

这里应该和对 AudioInputStream 调用 open() 一样简单。(“应该” 的意思是如果您没遇到下节描述的错误。)调用 start() 开始播放,drain() 等待播放完成,close() 释放音频线路。播放是在单独的线程进行,因此调用 stop() 将会停止播放,然后调用 start() 将会从播放暂停的地方重新开始;使用 setFramePosition(0) 重新定位到开始。

没声音?

JDK 5 发行版中有个讨厌的小错误:在有些平台上,对于一些短的音频片段,代码看上去运行正常,但就是 ... 没声音。显然媒体播放器在应该出现的位置之前触发了 STOP 事件。(见 参考资料 一节中错误页的链接。)

这个错误 “无法修复”,但解决方法相当简单:注册一个 LineListener 来监听 STOP 事件,当触发时,调用片段对象的 notifyAll()。然后在 “调用者” 代码中,通过调用 wait() 等待片段完成(还调用 notifyAll())。在没出现错误的平台上,这些错误是多余的,在 Windows® 及有些 Linux® 版本上,会让程序员 “开心” 或 “愤怒”。

结束语

现在您都了解了,厨房里的工具。我知道很多人已清楚了解我此处介绍的工具,而我的职业经验告诉我,很多人将从这篇介绍文章,或者说是对长期遗忘在凌乱的抽屉中的小工具的提示中受益。

我在此系列中做个简短的中断,让别人能加入分享他们各自领域的专业经验。但别担心,我还会回来的,无论是本系列还是探索其他领域的新的 5 件事。在那之前,我鼓励您一直探索 Java 平台,去发现那些能让编程更高效的宝石。

分享到:
评论

相关推荐

    Java反编译小工具 简单实用

    首先,我们要知道的是,Java程序在编译后会生成字节码文件,即.class文件,这些文件包含了机器可执行的指令,但并不直接包含源代码。Java反编译工具就是用来将.class文件转换回接近原始的Java源代码,以便于我们阅读...

    java反射机制工具类

    不过,当需要实现动态性、解耦或元编程时,Java反射机制是一个不可或缺的工具。在设计和实现`BeanHelper`类时,应考虑如何有效利用缓存策略,以平衡性能与代码复杂度。同时,应确保工具类的使用方式安全,避免不必要...

    java反射工具类 ReflectionUtils

    Java反射工具类ReflectionUtils是Java开发中常用的一个辅助类,它可以帮助开发者在运行时检查类、接口、字段和方法的信息,以及动态调用方法和访问字段。反射在Java编程中扮演着重要的角色,特别是在框架开发、插件...

    一个java应用程序的单元测试的遗产不知道record-n-run测试工具.zip

    本资源“一个java应用程序的单元测试的遗产不知道record-n-run测试工具.zip”似乎包含了一个名为“jrunr”的工具,这可能是一个专门用于Java单元测试的Record-and-Rerun测试框架。 Record-and-Run测试工具的主要...

    最近5年133个Java面试问题列表

    - SOLID设计原则是面向对象设计中的五个基本原则的缩写,即单一职责原则(Single Responsibility Principle)、开放封闭原则(Open/Closed Principle)、里氏替换原则(Liskov Substitution Principle)、接口隔离...

    java反编译工具jd-gui

    Java反编译工具JD-GUI是一款非常实用的Java开发者工具,它可以帮助我们查看Java字节码(.class文件)的源代码,即使原始的源代码不可用或丢失。这个工具对于学习、调试和理解第三方库的内部工作原理非常有用。在Java...

    Json解析(JavaAPI工具)

    5. **流式API**: Gson提供了一个流式API (`JsonReader` 和 `JsonWriter`),用于处理JSON流,这在处理大型JSON数据时非常有用。 6. **GsonBuilder**: 可以通过`GsonBuilder`来配置Gson的行为,比如开启或关闭字段...

    c/c++ 与java互通 AES加密解密,算法ECB/PKCS5PADDING

    工作原因c和java都得熟悉,因此把java端和c/c++实现都给大家了,注意java端要明确指明字符集为GBK,因为各版本jdk默认字符集并不一致,key采用16位,你知道的。压缩包里有两个工程,一个vc6.0一个myeclipse,都拿去...

    java2exe(java打包为exe程序)

    这对于那些希望简化部署过程,或者面向不熟悉Java环境的终端用户的开发者来说非常有用。 在Java程序开发完成后,通常需要依赖Java虚拟机(JVM)来运行,这意味着目标机器上必须安装了Java运行环境。然而,并非所有...

    java几个常用的jar包

    `java-unrar-0.3.jar`使得Java开发者能够在不依赖外部非Java组件的情况下处理RAR文件,这在跨平台的Java应用中尤其有用。通过这个库,开发者可以访问RAR文件中的文件列表、提取文件、验证RAR头等。 4. **javolution...

    java.util.concurrent_您不知道的5件事

    ### Java.util.concurrent_您不知道的5件事 #### 1. Semaphore(信号量) - **定义与作用**:`Semaphore` 类是一种控制多个线程访问共享资源的机制,它通过内部维护一个整数计数器(许可的数量)以及一组等待线程...

    重温 Thinking in Java 5 - The Class object

    通过类对象,我们可以创建类的实例,即使我们不知道具体的类信息。例如,`newInstance()`方法可以用来无参数地创建类的新实例。此外,反射还允许我们动态调用方法和修改字段值,这在处理插件系统、框架开发或单元...

    关于 Java Collections API 您不知道的 5 件事,第 2 部分

    Java Collections API 是Java编程语言中不可或缺的一部分,它提供了丰富的接口和类来操作各种集合,如List、Set、Map等。这篇博客的第二部分将深入探讨关于Collections API的一些不那么为人所知的知识点,旨在帮助...

    JAVA反编译工具Jad及Eclipse插件JadClipse配置

    Java反编译是将已编译的字节码文件(.class)转换回源代码的过程,这对于理解已加密或无源代码的库尤其有用。在这个过程中,Jad和JadClipse扮演了重要角色。 Jad 是一款知名的Java反编译器,全称为Java Decompiler...

    java东方标准大唐电信

    在Java编程语言中,“可变参数”是一个强大的特性,它允许函数接收数量不固定的参数。这一特性使得开发者在编写函数时不必预先知道参数的具体个数,大大增强了代码的灵活性。下面我们将深入探讨可变参数的细节。 1....

    java语言开发jar包_jar包_java_

    5. **类路径管理**:在Java命令行中,可以使用 `-classpath` 或 `-cp` 参数指定JAR包的位置,使得JVM知道在哪里寻找所需的类。 6. **运行时合并**:通过`-jar`选项,可以指定主类并直接运行JAR文件,如`java -jar ...

    27天成为Java大神

    你需要知道如何定义类,创建对象,以及理解封装、继承和多态这三个面向对象的特性。 3. **异常处理**:Java提供了异常处理机制,通过try-catch-finally语句块来捕获和处理运行时错误,确保程序的健壮性。 4. **...

    java笔试试题整理

    5. **多线程**:Java内置对多线程的支持,你需要理解Thread类和Runnable接口,以及同步机制(synchronized关键字,volatile变量,Lock接口,Semaphore等)。 6. **I/O流**:Java的I/O流用于读写文件和网络通信。...

    构造java探测class反序列化gadget1

    1. 黑盒安全审计:在不知道目标系统详细信息的情况下,快速判断是否存在特定的类,从而缩小潜在漏洞范围。 2. 漏洞验证:在确认某个Java反序列化漏洞是否存在时,可以先使用探测gadget进行测试,避免误报。 总的来...

Global site tag (gtag.js) - Google Analytics