Java 6.0标准版(Mustang)包含了大量使Java开发更为容易的特性。在本文中,我们将讨论通过部分新特性来帮助你实现如下功能:

  · 设置文件和目录许可权

  · 获取分区上自由空间和可用空间数

  · 把Component对象添加到JTabbedPane的选项卡上

  · 在你的Java基础类/Swing(JFC/Swing)应用程序中使用流行的SwingWorker类

  因此,如果JSR 270专家组同意采纳这些特征,那么在Mustang的下一个发行版本中你就会看到这些特征。

  注意:为了运行本文中的源码,你必须下载并安装Mustang的最新版本。

  一、 设置文件和目录权限

  现在,从Mustang build 31开始,你可以在本地文件系统中设置一个文件的可读、可写和可执行标志。这项功能已经被添加到java.io.File类中,并通过使用下列方法来实现:

public boolean setReadable(boolean readable, boolean ownerOnly)
public boolean setReadable(boolean readable)
public boolean setWritable(boolean writable, boolean ownerOnly)
public boolean setWritable(boolean writable)
public boolean setExecutable(boolean executable, boolean ownerOnly)
public boolean setExecutable(boolean executable)

  如果你曾某种UNIX系统上工作过,那么你应该对这些方法非常熟悉-其实它们实现了chmod命令的一些功能。这些方法试图设置由现在的File对象所描述的文件或目录的适当权限。如果把第二个可选参数设置为true,那么该权限将仅应用于当前所有者标志。否则,这些方法将应用到所有用户。注意,如果底层文件系统没法区分该所有者和其他所有者的权限(在一些版本的Windows中就是这样),那么这一权限将应用到每一个人,而不管传递的是什么值。

  如果你是一个使用NT文件系统的Windows用户,那么你应该读一下这个文档,它解释了如何使用各种不同的选项来控制不同用户的文件存取权限问题。

  如你所想,如果用户没有权限来改变这个抽象路径名的存取权限,那么第一个方法就会失败(也就是说,返回false);而且,这些方法也会抛出一个java.lang.SecurityException异常-如果存在一个Java安全管理器并且它的checkRead()/checkWrite()/checkExecute()方法不允许存取该文件的话。

  下表1显示了在多种文件系统上运行这些命令的典型结果,以及这些命令在不同目标操作系统上的可用性。

  表1.在常用OS文件系统上的java.io.File权限操作

命令在Windows XP系统上的返回值在Linux系统上的返回值在solaris系统上的返回值
setReadable(true)true True(等价于chmod+r)True(等价于chmod+r)
setReadable(false)False(在Windows中文件可读性不能被设置为False)True(等价于chmod-r)True(等价于chmod-r)
setWritable(true)True(切换Windows的只读文件属性)True(等价于chmod+w)True(等价于chmod+w)
setWritable(false) true(切换Windows的只读文件属性)True(等价于chmod-w)True(等价于chmod-w)
setExecutable(true)trueTrue(等价于chmod+x)True(等价于chmod+x)
setExecutable(false)false(在Windows中文件可执行属性不能被设置为False)True(等价于chmod-x)True(等价于chmod-x)

  决定是否文件是可读,可写或可执行的方法与这个平台的前一个版本-Java 2平台,标准版(J2SE)5.0-保持一致。

public boolean canRead();
public boolean canWrite();
public boolean canExecute();

  二、 取得硬盘分配空间

  除了允许你设置文件和目录权限外,Mustang还为你提供了三个新方法来决定当前磁盘分区中的可用空间数,这是由一个java.io.File对象来描述的:

public long getTotalSpace();
public long getFreeSpace();
public long getUsableSpace();

  每一个这些方法返回要求的由java.io.File所描述的分区的字节大小,否则,如果从File对象中无法取得一个分区则返回值为0L。

  借助于getFreeSpace()和getUsableSpace()方法,未分配字节的返回数是(根据有关文档):"这仅是一种提示而不是保证-有可能使用大多数或所有这些字节;但紧跟这个调用之后的未分配的字节数很可能是准确的,当然也有可能因某些外部I/O操作(包括在该虚拟机外面所作的系统调用)而导致不准确。"

  那么,在这个两个方法之间有什么区别呢?getFreeSpace()方法返回分区的自由空间数量的一个即时数。而getUsableSpace()方法还包含了另外一些功能来检查写许可和其它操作系统限制,这将返回一个可用空间数的更好的估计值。如果你想决定在写向一个文件之前是否你有足够的磁盘空间,那么,典型情况下getUsableSpace()将给你一个更精确的估计值。注意,如果安装了一个安全管理器并且它不允许对于RuntimePermission("getFileSystemAttributes")进行调用,那么这两个方法都将抛出一个SecurityException异常。


 三、 使用Component描述JTabbedPane中的选项卡

  这是Swing的JtabbedPane中的一处微妙但是很有价值的改进。在以前的JtabbedPane中,你被限制仅用一个字符串,一个图标(或二者的结合)来描述一个选项卡。另外,如果你想的话,你还可以把一个提示小窗加到该选项卡上去。从Mustang的build 39开始,现在有可能使用一个Component来描述JtabbedPane中的一个选项卡。尽管这可能带来一系列的问题,但是,这种特性的最常用的方式是:添加一个Close按钮-它将从JTabbedPane中删除该选项卡。

  Sun程序员和Swing工程师Alexander Potochkin在他的最近的一个有关这个主题的博客日志中指出,这三个新方法已经被添加到JTabbedPane。

  你可以使用下列方法把Component设置为一个选项卡:

public void setTabComponentAt(int index, Component component)

  你可以使用下列方法得到这个组件:

public Component getTabComponentAt(int index)

  你可以使用下列这个方法来测试是否有组件被应用于这个JtabbedPane中:

public int indexOfTabComponent(Component tabComponent)

  下面是一个选项卡面板示例源代码-它允许你从一个JTabbedPane中动态地添加和删除选项卡。注意,在这个例子中我们创建了一个Jpanel,它包含两个组件:一个位于面板左边(BorderLayout.WEST)的JLabel和一个位于面板右边(BorderLayout.EAST)的带有一个ImageIcon的按钮。这里所用的图形是一个10x10像素大小的gif文件-它包含了一个小X。为了确保按钮的尺寸小一些,我们把它的尺寸重置为图标的宽度和高度各自加上2个像素。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TabbedPaneExample implements ActionListener {
 private JFrame frame;
 private JTabbedPane tabbedPane;
 private JButton addTabButton;
 private ImageIcon closeXIcon;
 private Dimension closeButtonSize;
 private int tabCounter = 0;
 public TabbedPaneExample() {
  //创建选项卡面板
  tabbedPane = new JTabbedPane();
  //创建一个按钮-用户可用来添加一个选项卡到选项卡面板
  addTabButton = new JButton("Add Tab");
  addTabButton.addActionListener(this);
  //创建一个框架来包含这个选项卡面板
  frame = new JFrame();
  //创建一个图像图标'X'以实现在每一个选项卡上的关闭功能。加载的gif是一个10x10图形(非黑色部分是透明的)
  closeXIcon = new ImageIcon("C:/CloseX.gif");
  //创建一个Dimension用来调整close按钮的大小
  closeButtonSize = new Dimension(
   closeXIcon.getIconWidth()+2,
   closeXIcon.getIconHeight()+2);
   //所选项卡面板添加到图形中央,把"Add Tab"按钮置于南面。然后包装它,调整其大小并显示它。
  frame.add(tabbedPane, BorderLayout.CENTER);
  frame.add(addTabButton, BorderLayout.SOUTH);
  frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  frame.pack();
  frame.setMinimumSize(new Dimension(300, 300));
  frame.setVisible(true);
 }
 public void actionPerformed(ActionEvent e) {
  final JPanel content = new JPanel();
  //创建一个描述该选项卡的面板并确保它是透明的
  JPanel tab = new JPanel();
  tab.setOpaque(false);
  //为该选项卡创建一个标签和一个Close按钮。一定要
  //把它的尺寸设置为几乎该图标的大小,并且
  //创建一个行为听取器-它将定位该选项卡并且从选项卡面板上删除它
  JLabel tabLabel = new JLabel("Tab " + (++tabCounter));
  JButton tabCloseButton = new JButton(closeXIcon);
  tabCloseButton.setPreferredSize(closeButtonSize);
  tabCloseButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent e) {
    int closeTabNumber = tabbedPane.indexOfComponent(content);
    tabbedPane.removeTabAt(closeTabNumber);
   }
  });
  tab.add(tabLabel, BorderLayout.WEST);
  tab.add(tabCloseButton, BorderLayout.EAST);
  //把该选项卡添加到选项卡面板。注意,
  //第一个参数(它正常是一个描述选项卡标题的String
  //),为null.
  tabbedPane.addTab(null, content);
  //不是在选项卡上使用String/Icon的结合,
  //而是使用我们的面板。
  tabbedPane.setTabComponentAt(tabbedPane.getTabCount()-1, tab);
 }
 public static void main(String[] args) {
  TabbedPaneExample main = new TabbedPaneExample();
 }
}

  结果显示于图1中。

图1.一个把多个JComponent用作选项卡的JTabbedPane

  注意,Alexander Potochkin的博客中提供了另外一种不同的方法,它子类化JButton-重载paintComponent()并且画出它自己的("X")。如果你不想使用你的代码发布一个gif文件,那么使用这种更为复杂的方法是非常有用的。


四、 SwingWorker现在包含到Mustang中

  大多数Swing程序员知道,无论什么时候编写事件驱动的代码,例如当一个按钮按下时调用ActionListener,都需要快速处理事件。你永远不需要花费比你必须处理事件驱动的线程更多的时间,否则,你的Swing GUI将成为不可响应并且不能有效地重绘它自己。在事件调度线程中实现一项较大的任务经常意味着你要从事件调度线程中"剔除"一个独立工作者线程并且让该线程运行于后台。这样以来,当使用Swing编写一个多线程的应用程序时,程序员需要牢记下面两条规则:

  · 如果把耗时的任务安排到一个调度线程中,那么这有可能导致应用程序具有不可响应性。因此,这样的任务应该用一个工作者线程来专门实现。

  · 对Swing组件的更新应该仅安排给一个事件调度线程来完成。

  因为这意味着,至少有两个线程在同时运行,因此创建一个处理线程间通讯的类是很有帮助的。有一个消息是,最新的Mustang发行版本中加入了对SwingWorker类(它是前一段时间Swing程序员使用的一种流行的解决方案)的支持。

  下面是来自于Javadoc的SwingWorker的正式声明。

public abstract class SwingWorker<T,V> extends Object
implements RunnableFuture<T>

  注意,这里的声明包含了两个泛型类型变量:T和V。如果你还不熟悉泛型类型变量的话,那么你可以先读一下有关泛型的基础知识,这是在J2SE 5.0中引入的一种特性。下面是定义:

  · T:由这个SwingWorker的doInBackground()和get()方法返回的结果类型

  · V:被这个SwingWorker的publish()和process()方法用来执行中间结果的类型

  一会之后,我们再讨论这些方法。然而,首先,让我们介绍一下SwingWorker中所使用的线程架构。这里援引Javadoc的描述:在一个SwingWorker的生命周期中共包含三个线程:

  · 当前线程:execute()方法。它调度SwingWorker在一个工作者线程上的执行并且立即返回。你可以使用两个get()方法之一来等待SwingWorker完成。

  · 工作者线程:这个线程上调用doInBackground()方法。这正是所有后台活动发生的地方。为了通知PropertyChangeListeners关于绑定属性的变化,你可以使用firePropertyChange和getPropertyChangeSupport()方法。默认情况下,有两个绑定属性可用-state和progress。

  · 事件调度线程:所有的Swing相关的活动都发生在这种线程中。SwingWorker调用process()和done()方法并且通知这个线程上的任何PropertyChangeListeners。

  典型地,你在其上实例化SwingWorker子类的当前线程是事件调度线程。这个SwingWorker通常响应下列一些事件:

  1. execute()方法被调用或运行于这个事件调度线程上。

  2. SwingWorker通知任何PropertyChangeListeners其状态已经变为SwingWorker.StateValue.STARTED。

  3. doInBackground()方法在工作者线程上执行。

  4. 一旦doInBackground()方法完成,即在当前线程上执行done()方法。

  5. SwingWorker通知任何PropertyChangeListeners其状态已经变为SwingWorker.StateValue.DONE。

  当在doInBackground()方法中时,你可以设置一个整型的progress属性-它使用下列方法指示工作者线程的当前进度:

protected void setProgress(int progress);

  一旦调用这个方法,SwingWorker就向所有的已经登记的听者激发一个属性事件来通知它们已经得到更新的进度值。

  你可以设置或添加工作者线程的最后结果-通过使用下面两个方法之一:

protected void process(V... chunks);
protected void publish(V... chunks);

  第二个方法,publish(),将使用一些中间类型V的一些可变个数的对象并且把它们发送到process()方法中进行处理。典型情况下,你将从doInBackground()线程中调用process()方法。这个process()方法应该总是被重载以接收输入的V参数并且把这些中间对象以某种形式连接成一个T类型。当然,至于process()方法如何实现这一任务要依赖于参数类型-它在你的SwingWorker子类中指定。

  同时,在当前线程中,你可以调用两个get()方法之一来检索工作者线程的结果。第一个get()方法,在工作者线程完成其任务之前将会无限地阻塞。第二个方法在检索已经被处理的结果宽之前将阻塞一段指定的时间。

public T get();
public T get(long timeout,TimeUnit unit);

  如果你希望取消这个工作者线程,那么在它完成执行之前,你可以从当前线程中调用cancel()方法。

public final boolean cancel(boolean mayInterruptIfRunning)

  这里的mayInterruptIfRunning参数指定,在试图停止这项任务时是否执行该任务的线程应该被中断。注意,调用cancel()方法将失败-如果该任务已经完成,如果该任务已经被取消或如果它因某些理由可能无法被取消。然而,如果该方法调用返回true并且当调用cancel()方法时这项任务还没有开始,那么SwingWorker永远不应该执行。

  五、 结论

  尽管本文中介绍的这些特征基本上相互独立,但是它们的确代表Mustang开发团队希望满足Java开发社区提出的一小部分实现请求。特别是,当创建Swing应用程序时,学习使用SwingWorker类是必须的-它可以把程序员从复杂的GUI线程问题中解脱出来。记住,和往常一样,这些特征在最终成为Java SE 6的最后发行版本之前要征得JSR 270专家组的同意。