相关推荐: JPopupButton Swing开发之JEditorPane篇 有关GridBagLayout的用法 virtualbox:摄像头无显示:V4L2_CORE: Could not grab image (select timeout): Resource temporarily unavailable JavaCV入门指南:帧抓取器(FrameGrabber)的原理与应用 javaCV入门指南:调用FFmpeg原生API和JavaCV是如何封装了FFmpeg的音视频操作? SWT学习笔记(2)——转载倪大鹏的“SWT:AWT和SWING的强大竞争者” class VideoCapture之VideoCapture::grab
推荐群组: JSF
更多相关推荐
OO
Java 的乐趣与游戏:Java grab 包的技术提示
一些 Java SE 技术提示
作者:Jeff Friesen, JavaWorld.com, 01/02/07 翻译:suli原文地址:http://www.javaworld.com/javaworld/jw-01-2007/jw-0102-games.html 开发 Java 平台十年之久,我已经积累了一些使用 Java SE 的 grab 包加强游戏及其他 Java 平台开发的宝贵经验。 本期的 Java Fun and Games 将与您分享一些技术提示。 在文章的后半部分,将介绍如何将这些技术提示应用到一个网页抓图应用程序。
最简单的 API
不管计算机运行得有多快,我们却总是在等待某个任务的完成,比如,下载大个的文件、执行彻底搜索或者进行复杂的数学计算。 在这些费时的任务完成时,许多 Java 程序都会用一些花哨的方式提示用户,普遍方法是使用可以听得见的警告。
Java 提供了许多声音 API 可以用于创建有声警告。 可以使用 Java Speech API 告诉用户任务已经结束。 如果您希望任务完成时播放音效或音乐,Java Sound API 是一个不错的选择。 然而,因为 Java Speech 需要额外的分发文件,而 Java Sound 需要相当复杂的代码,您可能就希望使用 Audio Clip API 了。
Audio Clip API 基于 java.applet.AudioClip 和 java.applet.Applet 方法,例如: public static final AudioClip newAudioClip(URL url) 。 虽然此 API 比 Java Speech 和 Java Sound 更易于使用,但只用它来播放一段简单的声音也太过大材小用了。 对于这种简单的任务,还是考虑使用 Java 最简单的声音 API 吧。
最简单的声音 API 由 java.awt.Toolkit 的 public abstract void beep() 方法构成。 当调用此方法时,将发出简单的“哔跸”声。 为了展示 beep() 的用法,我创建了一个 CalcPi 应用程序,为 Pi 计数。 请看列表 1。
列表 1 CalcPi.java
// CalcPi.java
import java.awt.Toolkit;
import java.math.BigDecimal;
public class CalcPi
{
/* constants used in pi computation */
private static final BigDecimal ZERO = BigDecimal.valueOf (0);
private static final BigDecimal ONE = BigDecimal.valueOf (1);
private static final BigDecimal FOUR = BigDecimal.valueOf (4);
/* rounding mode to use during pi computation */
private static final int roundingMode = BigDecimal.ROUND_HALF_EVEN;
/* digits of precision after the decimal point */
private static int digits;
public static void main (String [] args)
{
if (args.length != 1)
{
System.err.println ("usage: java CalcPi digits");
return;
}
int digits = 0;
try
{
digits = Integer.parseInt (args [0]);
}
catch (NumberFormatException e)
{
System.err.println (args [0] + " is not a valid integer");
return;
}
System.out.println (computePi (digits));
Toolkit.getDefaultToolkit ().beep ();
}
/*
* Compute the value of pi to the specified number of
* digits after the decimal point. The value is
* computed using Machin's formula:
*
* pi/4 = 4*arctan(1/5) - arctan(1/239)
*
* and a power series expansion of arctan(x) to
* sufficient precision.
*/
public static BigDecimal computePi (int digits)
{
int scale = digits + 5;
BigDecimal arctan1_5 = arctan (5, scale);
BigDecimal arctan1_239 = arctan (239, scale);
BigDecimal pi = arctan1_5.multiply (FOUR).
subtract (arctan1_239).multiply (FOUR);
return pi.setScale (digits, BigDecimal.ROUND_HALF_UP);
}
/*
* Compute the value, in radians, of the arctangent of
* the inverse of the supplied integer to the specified
* number of digits after the decimal point. The value
* is computed using the power series expansion for the
* arc tangent:
*
* arctan(x) = x - (x^3)/3 + (x^5)/5 - (x^7)/7 +
* (x^9)/9 ...
*/
public static BigDecimal arctan (int inverseX, int scale)
{
BigDecimal result, numer, term;
BigDecimal invX = BigDecimal.valueOf (inverseX);
BigDecimal invX2 = BigDecimal.valueOf (inverseX * inverseX);
numer = ONE.divide (invX, scale, roundingMode);
result = numer;
int i = 1;
do
{
numer = numer.divide (invX2, scale, roundingMode);
int denom = 2 * i + 1;
term = numer.divide (BigDecimal.valueOf (denom), scale, roundingMode);
if ((i % 2) != 0)
result = result.subtract (term); else
result = result.add (term);
i++;
}
while (term.compareTo (ZERO) != 0);
return result;
}
}
列表 1 使用一种算法来计算 pi,该算法是早在 18 世纪初期由英国数学家 John Machin 发明的。 算法首先计算 pi/4 = 4*arctan(1/5)-arctan(1/239),然后将结果乘以 4 得出 pi 的值。 由于 arc (inverse) tangent 是使用一系列庞大的 term 来计算的, term 的数量越大得出的 pi 值越准确(小数点后显示的位数)
注意
列表 1 的大部分代码引用自 Sun 的 远程方法调用 教程的“创建一个客户端程序”部分。 此算法的实现依赖于 java.math.BigDecimal 和一个 arc-tangent 方法。 虽然 Java SE 5.0 等高级版本的 BigDecimal 包括常量 ZERO 和 ONE ,这些常量在 Java 1.4 中是不存在的。 同样,number-of-digits 命令行参数用于确定 arc-tangent 的数量和 pi 的精确度。 java CalcPi 0 3 java CalcPi 1 3.1 java CalcPi 2 3.14 java CalcPi 3 3.142 java CalcPi 4 3.1416 java CalcPi 5 3.14159 本文中更重要的部分是 Toolkit.getDefaultToolkit ().beep (); ,该语句用于在计算结束时发出“哗哗”的声音。 由于数字参数越大造成的计算时间越长,此声音可以让您知道 pi 的计算何时结束。 如果一声“哗”响不够用,可以按照如下方法创建其他的音效: Toolkit tk = Toolkit.getDefaultToolkit (); for (int i = 0; i < NUMBER_OF_BEEPS; i++) { tk.beep (); // On Windows platforms, beep() typically // plays a WAVE file. If beep() is called // before the WAVE sound finishes, the // second WAVE sound will not be heard. A // suitable delay solves this problem. // (I'm not sure if this problem occurs // on other platforms.) try { Thread.sleep (BEEP_DELAY); } catch (InterruptedException e) { } } 窗口居中 使您的 Java 程序看起来更加专业的一种方法就是使其对话框窗口(例如,一个 "about" 窗口)位于父窗口的中间。 可以使用 java.awt.Window 的 public void setLocationRelativeTo(Component c) 方法完成此任务,该方法将相对于组件参数 — null 来创建一个居于屏幕中间的窗口。 请看列表 2。 列表 2 AboutBox1.java // AboutBox1.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.border.*; public class AboutBox1 { public static void main (String [] args) { final JFrame frame = new JFrame ("AboutBox1"); frame.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE); JPanel panel = new JPanel () { { JButton btnWindow = new JButton ("Window center"); ActionListener l = new ActionListener () { public void actionPerformed (ActionEvent e) { new AboutBox (frame, "W").setVisible (true); } }; btnWindow.addActionListener (l); add (btnWindow); JButton btnScreen = new JButton ("Screen center"); l = new ActionListener () { public void actionPerformed (ActionEvent e) { new AboutBox (frame, "S").setVisible (true); } }; btnScreen.addActionListener (l); add (btnScreen); } }; frame.getContentPane ().add (panel); // frame.setLocationRelativeTo (null); frame.pack (); // frame.setLocationRelativeTo (null); frame.setVisible (true); } } class AboutBox extends JDialog { AboutBox (JFrame frame, String centerMode) { super (frame, "AboutBox", true /* modal */); final JButton btnOk = new JButton ("Ok"); btnOk.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { dispose (); } }); getContentPane ().add (new JPanel () {{ add (btnOk); }}); pack (); setLocationRelativeTo (centerMode.equals ("W") ? frame : null); } } Listing 2's AboutBox1 创建了一个 GUI,它的两个按钮建立了一个 "about" 对话框,该对话框通过 setLocationRelativeTo() 方法位于应用程序主窗口或屏幕的中心位置。 frame.pack (); 之前被注释掉的一行无法起到使主窗口居中的作用,因为主窗口的大小还没有确定。 但是,被注释掉的第二行起到了使窗口居中的作用。 getContentPane ().add (new JPanel () {{ add (btnOk); }}); 也许看起来有点奇怪,因为它嵌套了许多括号。 本质上该语句可以理解为,创建一个内部匿名类(该类扩展自 javax.swing.JPanel )的对象,通过由内层括号对标识的 object block initializer 为此对象添加一个按钮,然后将对象添加到对话框的 content pane 中。 添加阴影 如 果您想突出显示一个 "about" 对话框的标题文字,可以考虑以一定的偏移量和指定的颜色绘制背景文字以达到投放“阴影”的效果。 选择一个适当的颜色做为背景字的颜色,注意与前景文字和背景的颜色搭配。 然后使用反失真技术使边缘上的小锯齿变得平滑。 效果如图 1 所示:
图 1:阴影可以强调对话框的标题 图 1 展示了一个具有蓝色文字、黑色阴影和白色背景的 "about" 对话框。 该对话框是在 AboutBox2 程序的 AboutBox(JFrame frame, String centerMode) 构造函数内创建的。 由于该程序的代码与 AboutBox1.java 极为相似,所以我只给出其构造函数: AboutBox (JFrame frame, String centerMode) { super (frame, "AboutBox", true /* modal */); // Add a panel that presents some text to the dialog box's content pane. getContentPane ().add (new JPanel () { final static int SHADOW_OFFSET = 3; { // Establish the drawing panel's preferred // size. setPreferredSize (new Dimension (250, 100)); // Create a solid color border that both // surrounds and is part of the drawing // panel. Select the panel background // color that is appropriate to this look // and feel. Color c = UIManager.getColor ("Panel.background"); setBorder (new MatteBorder (5, 5, 5, 5, c)); } public void paintComponent (Graphics g) { // Prevent jagged text. ((Graphics2D) g).setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Because the border is part of the panel, // we need to make sure that we don't draw // over it. Insets insets = getInsets (); // Paint everything but the border white. g.setColor (Color.white); g.fillRect (insets.left, insets.top, getWidth ()-insets.left- insets.right, getHeight ()-insets.top- insets.bottom); // Select an appropriate text font and // obtain the dimensions of the text to be // drawn (for centering purposes). The // getStringBounds() method is used instead // of stringWidth() because antialiasing is // in effect -- and the documentation for // stringWidth() recommends use of this // method whenever the antialiasing or // fractional metrics hints are in effect. g.setFont (new Font ("Verdana", Font.BOLD, 32)); FontMetrics fm = g.getFontMetrics (); Rectangle2D r2d; r2d = fm.getStringBounds ("About Box", g); int width = (int)((Rectangle2D.Float) r2d) .width; int height = fm.getHeight (); // Draw shadow text that is almost // horizontally and vertically (the // baseline) centered within the panel. g.setColor (Color.black); g.drawString ("About Box", (getWidth ()-width)/2+ SHADOW_OFFSET, insets.top+(getHeight()- insets.bottom-insets.top)/2+ SHADOW_OFFSET); // Draw blue text that is horizontally and // vertically (the baseline) centered // within the panel. g.setColor (Color.blue); g.drawString ("About Box", (getWidth ()-width)/2, insets.top+(getHeight()- insets.bottom-insets.top)/2); } }, BorderLayout.NORTH); final JButton btnOk = new JButton ("Ok"); btnOk.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { dispose (); } }); getContentPane ().add (new JPanel () {{ add (btnOk); }}, BorderLayout.SOUTH); pack (); setLocationRelativeTo (centerMode.equals ("W") ? frame : null); } 除了向您介绍如何在 JPanel 子类组件的 public void paintComponent(Graphics g) 方法中呈现阴影以外,构造函数还揭示了一个技巧:使用 UIManager.getColor("Panel.background") 获取与对话框背景色匹配的组件边框的颜色(即现在的外观)。 超级链接和启动浏览器 许多程序都会在 "about" 对话框中呈现超级链接。单击超级链接时,程序将启动默认浏览器,并为用户打开应用程序的网站。我想 "about" 对话框中的超级链接是可以用类来描述的,所以我创建了一个 AboutBox3 应用程序来说明其可行性。请阅读以下代码: AboutBox (JFrame frame, String centerMode) { super (frame, "AboutBox", true /* modal */); // Create a pane that presents this dialog box's text. Surround the pane // with a 5-pixel empty border. Pane pane = new Pane (5); pane.setPreferredSize (new Dimension (250, 100)); // Create a title with a drop shadow for the pane. Font font = new Font ("Verdana", Font.BOLD, 32); Pane.TextNode tn = pane.new TextNode ("About Box", font, Color.blue, Pane.TextNode.CENTERX, Pane.TextNode.CENTERY, Color.black); pane.add (tn); // Create a link for the pane. font = new Font ("Verdana", Font.BOLD, 12); tn = pane.new TextNode ("Jeff Friesen", font, Color.blue, Pane.TextNode.CENTERX, 80, null, "http://www.javajeff.mb.ca", Color.red); pane.add (tn); // Add pane to the center region of the dialog box's content pane. getContentPane ().add (pane); // Create a button for disposing the dialog box. final JButton btnOk = new JButton ("Ok"); btnOk.addActionListener (new ActionListener () { public void actionPerformed (ActionEvent e) { dispose (); } }); // Add button via an intermediate panel that causes button to be laid // out at its preferred size to the south region of the dialog box's // content pane. getContentPane ().add (new JPanel () {{ add (btnOk); }}, BorderLayout.SOUTH); // Resize all components to their preferred sizes. pack (); // Center the dialog box with respect to the frame window or the screen.
setLocationRelativeTo (centerMode.equals ("W") ? frame : null); } AboutBox(JFrame frame, String centerMode) 构造函数创建了一个 Pane 组件,来描述一个用于绘制文字的区域。该组件的 Pane(int borderSize) 构造函数使用 borderSize 参数,来识别组件边框的大小(以像素为单位)-- 绘制区域的大小等于 Pane 的大小减去边框大小: Pane (int borderSize) { // Create a solid color border that both surrounds and is part of the // this component. Select the panel background color that is appropriate // to this look and feel. setBorder (new MatteBorder (borderSize, borderSize, borderSize, borderSize, UIManager.getColor ("Panel.background"))); } 该组件将文字存储为 Pane.TextNode 对象的数组列表。每个 TextNode 描述一个文字条目,并且创建自三个构造函数之一。最简单的构造函数是 TextNode(String text, Font font, Color color, int x, int y) ,它用于创建一个非超级链接且没有阴影的文字节点。使用的五个参数是: text 指定要绘制的文字 font 指定要使用的字体 font 指定要使用的文字颜色 x 指定第一个字符的起始列 y 指定每个字符基线所在的行 第二简单的构造函数是: TextNode(String text, Font font, Color color, int x, int y, Color shadowColor) .除了上述参数外,还需要为该函数指定 shadowColor ,即阴影的颜色。如果传递 null ,则不呈现阴影,这样一来该构造函数就与前一构造函数一样了。这两个构造函数都调用下面的第三个构造函数: TextNode (String text, Font font, Color color, int x, int y, Color shadowColor, String url, Color activeLinkColor) { this.text = text; this.font = font; this.color = color; this.x = x; this.y = y; this.shadowColor = shadowColor; this.url = url; this.activeLinkColor = activeLinkColor; if (url != null) { addMouseListener (new MouseAdapter () { public void mousePressed (MouseEvent e) { int mx = e.getX (); int my = e.getY (); if (mx >= TextNode.this.x && mx < TextNode.this.x+width && my > TextNode.this.y-height && my <= TextNode.this.y) { active = false; repaint (); } } public void mouseReleased (MouseEvent e) { int mx = e.getX (); int my = e.getY (); if (mx >= TextNode.this.x && mx < TextNode.this.x+width && my > TextNode.this.y-height && my <= TextNode.this.y) { active = true; repaint (); Launcher. launchBrowser (TextNode.this.url); } } }); addMouseMotionListener (new MouseMotionListener () { public void mouseMoved (MouseEvent e) { int mx = e.getX (); int my = e.getY (); if (mx >= TextNode.this.x && mx < TextNode.this.x+width && my > TextNode.this.y-height && my <= TextNode.this.y) { if (!active) { active = true; repaint (); } } else { if (active) { active = false; repaint (); } } } public void mouseDragged (MouseEvent e) { } }); } } 保存参数后,该构造函数会在 Pane 中注册一个鼠标监听器(假设 url 不为 null )。这些侦听器将判断鼠标指针是否位于超级链接文本上。如果是,则操纵 active 变量,将呈现该节点及其他节点,并启动浏览器。 class Launcher { static void launchBrowser (String url) { try { // Identify the operating system. String os = System.getProperty ("os.name"); // Launch browser with URL if Windows. Otherwise, just output the url // to the standard output device. if (os.startsWith ("Windows")) Runtime.getRuntime () .exec ("rundll32 url.dll,FileProtocolHandler " + url); else System.out.println (url); } catch (IOException e) { System.err.println ("unable to launch browser"); } } } Pane 的 public void paintComponent(Graphics g) 方法将在该组件及其文本节点呈现的时候被调用。该方法启用反失真技术(防止文字出现锯齿),获取组件的插入位置(所以文本不会落在边框上),清理绘制区域使其成为白色,并呈现数组列表存储的每个文字节点。 public void paintComponent (Graphics g) { // Prevent jagged text. ((Graphics2D) g).setRenderingHint (RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Because the border is part of the panel, we need to make sure that we // don't draw over it. Insets insets = getInsets (); // Paint everything but the border white. g.setColor (Color.white); g.fillRect (insets.left, insets.top, getWidth ()-insets.left-insets.right, getHeight ()-insets.top-insets.bottom); // Render all nodes. Iterator iter = nodes.iterator (); while (iter.hasNext ()) { TextNode tn = (TextNode) iter.next (); tn.render (g, insets); } } 每个文本节点都是由 TextNode 的 void render(Graphics g, Insets insets) 方法呈现的。该方法首先确定了字体,然后调用私有的 strDim() 方法来获取要绘制文字的规格尺寸等。然后呈现文本(可以选择阴影、超级链接等属性): void render (Graphics g, Insets insets) { g.setFont (font); Dimension d = strDim (g, text); width = (int) d.width; height = (int) d.height; // Always drop the drop shadow (if specified) first. if (shadowColor != null) { g.setColor (shadowColor); if (x == CENTERX) x = (getWidth ()-d.width)/2; if (y == CENTERY) y = insets.top+(getHeight ()-insets.bottom-insets.top)/2; // Draw the drop shadow. g.drawString (text, x+SHADOW_OFFSET, y+SHADOW_OFFSET); } // If the text is not a link, active can never be true -- the mouse // listeners are not installed. g.setColor ((active) ? activeLinkColor: color); // If a drop shadow was drawn, x and y will never equal CENTERX and // CENTERY (respectively). This is okay because x and y must contain // the same values as specified when drawing the drop shadow. if (x == CENTERX) x = (getWidth ()-d.width)/2; if (y == CENTERY) y = insets.top+(getHeight ()-insets.bottom-insets.top)/2; // Draw the text. g.drawString (text, x, y); } g.setColor ((active) ? activeLinkColor: color); 决定是否绘制有效的超级链接文本(使用由 activeLinkColor 指定的有效链接颜色),或者使用由 color 指定的颜色绘制无效超级链接文本(或非超级链接文本)。图 2 显示了此决定的结果: 图 2 当鼠标指针移动到文本上时,超级链接的文本变成了红色。单击可查看大图。 状态栏 许多应用程序都会呈现状态栏,以显示程序的名称和版本、相应于菜单的帮助文字、当前时间以及一些其他信息。由于状态栏如此有用,您可能认为 Java 包含了一个 javax.swing.JStatusBar 组件。然而,事实并非如此。幸运的是,创建自己的状态栏还算容易,如图 3 所示。
图 3. 状态栏中当前菜单项的帮助文字 图 3 显示了一个由 StatBar 应用程序创建的状态栏。该应用程序将一个 javax.swing.JLabel 组件和一个 javax.swing.event.MenuListener 结合在一起,然后使用 java.awt.event.MouseListener 显示相应于菜单的菜单项帮助文字 -- 或者在未选择菜单或菜单项时显示默认文字。请看列表 3。 列表 3. StatBar.java // StatBar.java import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; public class StatBar extends JFrame { // The status label serves as this application's status bar. A description // of the currently highlighted menu/item appears on the status bar. JLabel status; // The default text appears on the status bar at program startup, and when // no other menu/item text appears. String defaultStatusText = "Welcome to StatBar 1.0!"; // The MenuItem helper class conveniently organizes the menu items for each // of the File and Edit menus. This organization reduces the amount of // source code that appears in the StatBar() constructor, which hopefully // makes it easier to study the constructor, and facilitates adding extra // menu items in the future. class MenuItem { String label; // menu text ActionListener al; String desc; // menu description for status bar MenuItem (String label, ActionListener al, String desc) { this.label = label; this.al = al; this.desc = desc; } } // Construct StatBar's GUI and indirectly start AWT helper threads. public StatBar (String title) { // Pass application title to superclass, so that it appears on the title // bar. super (title); // When the user initiates a close operation from the System menu or by // clicking the tiny x window on a Microsoft Windows' window title bar, // terminate this application. setDefaultCloseOperation (EXIT_ON_CLOSE); // Construct the application's menu bar. JMenuBar mb = new JMenuBar (); // Create a menu listener shared by all menus on the menu bar. This menu // listener either displays default text or menu-specific text on the // status bar. MenuListener menul; menul = new MenuListener () { public void menuCanceled (MenuEvent e) { } public void menuDeselected (MenuEvent e) { status.setText (defaultStatusText); } public void menuSelected (MenuEvent e) { JMenu m = (JMenu) e.getSource (); status.setText (m.getActionCommand ()); } }; // Create a mouse listener shared by all menu items on all menus. This // mouse listener displays menu-item specific text on the status bar // whenever the mouse pointer enters the menu item. It displays default // text when the mouse pointer exits a menu item. MouseListener statusl = new MouseAdapter () { public void mouseEntered (MouseEvent e) { JMenuItem mi = (JMenuItem) e.getSource (); status.setText (mi.getActionCommand ()); } public void mouseExited (MouseEvent e) { status.setText (defaultStatusText); } }; // The first menu to appear on the menu bar is File. The user invokes // menu items on this menu to open, save, and print documents, and to // terminate the application. JMenu menuFile = new JMenu ("File"); menuFile.addMenuListener (menul); menuFile.setActionCommand ("Open document, save changes, print document " + "and terminate StatBar."); // Create a listener for each menu item on the File menu. ActionListener openl; openl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Open listener invoked."); } }; ActionListener saveasl; saveasl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Save as listener invoked."); } }; ActionListener savel; savel = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Save listener invoked."); } }; ActionListener printl; printl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Print listener invoked."); } }; ActionListener exitl; exitl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.exit (0); } }; // Identify menu items to be installed on the File menu. MenuItem [] itemsFile = { new MenuItem ("Open...", openl, "Open a document."), new MenuItem ("Save", savel, "Save changes to current document."), new MenuItem ("Save as...", saveasl, "Save current document to new " + "document."), new MenuItem ("Print...", printl, "Print current document."), new MenuItem (null, null, null), new MenuItem ("Exit", exitl, "Terminate StatBar.") }; // Install all of the previous menu items on the File menu. for (int i = 0; i < itemsFile.length; i++) { if (itemsFile [i].label == null) { menuFile.addSeparator (); continue; } JMenuItem mi = new JMenuItem (itemsFile [i].label); mi.addActionListener (itemsFile [i].al); mi.setActionCommand (itemsFile [i].desc); mi.addMouseListener (statusl); menuFile.add (mi); } // Add the file menu to the menu bar. mb.add (menuFile); // The second menu to appear on the menu bar is Edit. The user invokes // menu items on this menu to undo any changes and perform copy/cut/paste // operations on the current document. JMenu menuEdit = new JMenu ("Edit"); menuEdit.addMenuListener (menul); menuEdit.setActionCommand ("Perform various editing tasks and undo " + "changes."); // Create a listener for each menu item on the Edit menu. ActionListener undol; undol = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Undo listener invoked."); } }; ActionListener copyl; copyl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Copy listener invoked."); } }; ActionListener cutl; cutl = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Cut listener invoked."); } }; ActionListener pastel; pastel = new ActionListener () { public void actionPerformed (ActionEvent e) { System.out.println ("Paste listener invoked."); } }; // Identify menu items to be installed on the Edit menu. MenuItem [] itemsEdit = { new MenuItem ("Undo", undol, "Restore document."), new MenuItem (null, null, null), new MenuItem ("Copy", copyl, "Copy text to clipboard."), new MenuItem ("Cut", cutl, "Cut text to clipboard."), new MenuItem ("Paste", pastel, "Paste text from clipboard.") }; // Install all of the previous menu items on the Edit menu. for (int i = 0; i < itemsEdit.length; i++) { if (itemsEdit [i].label == null) { menuEdit.addSeparator (); continue; } JMenuItem mi = new JMenuItem (itemsEdit [i].label); mi.addActionListener (itemsEdit [i].al); mi.setActionCommand (itemsEdit [i].desc); mi.addMouseListener (statusl); menuEdit.add (mi); } // Add the edit menu to the menu bar. mb.add (menuEdit); // Install StatBar's menu bar. setJMenuBar (mb); // Create a status bar for displaying help text associated with the menus // and their items. status = new JLabel (defaultStatusText); status.setBorder (BorderFactory.createEtchedBorder ()); // Add the status bar to the bottom of the application's contentpane. getContentPane ().add (status, BorderLayout.SOUTH); // Establish a suitable initial size for displaying a document. setSize (450, 300); // Display GUI and start GUI processing. setVisible (true); } // Application entry point. public static void main (String [] args) { // Create the application's GUI and start the application. new StatBar ("StatBar"); } } 在使用默认状态栏文字创建了 status JLabel 之后, StatBar 刻画了一个边框 (通过 status.setBorder (BorderFactory.createEtchedBorder ()); )以使状态栏标签与 GUI 的其他部分区别开来。然后,标签被添加到框架窗口内容窗格的南侧区域,这是状态栏的常见位置。 注意 | 如果不希望状态栏显示任何默认文字,则需要至少输入一个空格来代替文字。如果使用空字符串 ( "" ) 来代替,则不会显示状态栏(虽然会显示其边框)。必须处理状态栏标签的首选字体大小,因为这涉及到当前的字体和那个不可缺少的字符。如果状态栏标签不包含任何字符(空字符串),其首选字体大小就会是 0,造成状态栏无法显示。 MenuItemListener 接口描述了一个 "File" 和 "Edit" 菜单的侦听程序。此接口的 public void menuSelected(MenuEvent e) 方法将在选择这些菜单时被调用,然后会显示菜单的帮助文字。选中一个菜单时,调用 public void menuDeselected(MenuEvent e) 显示文字。 MouseListener 接口描述每个菜单项的侦听程序。其 public void mouseEntered(MouseEvent e) 在鼠标进入一个菜单项时被调用。然后,菜单项的帮助文字会显示在状态栏中。鼠标指针移动到菜单项外时,调用 public void mouseExited(MouseEvent e) ,然后显示默认文字。 每个侦听程序都依赖于 javax.swing.JMenu 或 javax.swing.JMenuItem 的继承 public void setActionCommand(String command) 方法,此方法曾在指定每个菜单或菜单项的状态栏文字时被调用。文字是在侦听程序内部进行检索的,方法是调用相关的 public String getActionCommand() 方法。 抓图程序 几年前,我构建过一个基于命令行的 GetImages 应用程序 -- 请看列表 4 -- 来获取网页中的图像,并将它们存放到我的磁盘上。此应用程序只有一个用于连接到 HTML 文档的 URL 参数。解析 HTML 文档,将 标记的 src 属性值提取为可识别的图像文件,然后下载这些文件。 列表 4 GetImages.java // GetImages.java import java.io.*; import java.net.*; import java.util.regex.*; import javax.swing.text.*; import javax.swing.text.html.*; import javax.swing.text.html.parser.ParserDelegator; public class GetImages { public static void main (String [] args) { // Validate number of command-line arguments. if (args.length != 1) { System.err.println ("usage: java GetImages URL"); return; } // Create a Base URI from the solitary command-line argument. This URI // will be used in the handleSimpleTag() callback method to convert a // potentially relative URI in an tag's src attribute to an // absolute URI. final URI uriBase; try { uriBase = new URI (args [0]); } catch (URISyntaxException e) { System.err.println ("URI is improperly formed"); return; } // Convert the URI to a URL, so that the HTML document can be read and // parsed. URL url; try { url = new URL (args [0]); } catch (MalformedURLException e) { System.err.println ("URL is improperly formed"); return; } // Establish a callback whose handleSimpleTag() method is invoked for // each tag that does not have an end tag. The tag is an example. HTMLEditorKit.ParserCallback callback; callback = new HTMLEditorKit.ParserCallback () { public void handleSimpleTag (HTML.Tag tag, MutableAttributeSet aset, int pos) { // If an tag is encountered ... if (tag == HTML.Tag.IMG) { // Get the value of the src attribute. String src = (String) aset.getAttribute (HTML.Attribute.SRC); // Create a URI based on the src value, and then // resolve this potentially relative URI against // the document's base URI, to obtain an absolute // URI. URI uri = null; try { // Handle this situation: // // 1) http://www.javajeff.mb.ca // // There is no trailing forward slash. // // 2) common/logo.jpg // // There is no leading forward slash. // // 3) http://www.javajeff.mb.cacommon/logo.jpg // // The resolved URI is not valid. if (!uriBase.toString ().endsWith ("/") && !src.startsWith ("/")) src = "/" + src; uri = new URI (src); uri = uriBase.resolve (uri); System.out.println ("uri being " + "processed ... " + uri); } catch (URISyntaxException e) { System.err.println ("Bad URI"); return; } // Convert the URI to a URL so that its input // stream can be obtained. URL url = null; try { url = uri.toURL (); } catch (MalformedURLException e) { System.err.println ("Bad URL"); return; } // Open the URL's input stream. InputStream is; try { is = url.openStream (); } catch (IOException e) { System.err.println ("Unable to open input " + "stream"); return; } // Extract URL's file component and remove path // information -- only the filename and its // extension are wanted. String filename = url.getFile (); int i = filename.lastIndexOf ('/'); if (i != -1) filename = filename.substring (i+1); // Save image to file. saveImage (is, filename); } } }; // Read and parse HTML document. try { // Read HTML document via an input stream reader that assumes the // default character set for decoding bytes into characters. Reader reader = new InputStreamReader (url.openStream ()); // Establish a ParserDelegator whose parse() method causes the // document to be parsed. Various callback methods are called and // the document's character set is not ignored. The parse() method // throws a ChangedCharSetException if it encounters a tag // with a charset attribute that specifies a character set other // than the default. new ParserDelegator ().parse (reader, callback, false); } catch (ChangedCharSetException e) { // Reparse the entire file using the specified charset. A regexp // pattern is specified to extract the charset name. String csspec = e.getCharSetSpec (); Pattern p = Pattern.compile ("charset=\"?(.+)\"?\\s*;?", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher (csspec); String charset = m.find () ? m.group (1) : "ISO-8859-1"; // Read and parse HTML document using appropriate character set. try { // Read HTML document via an input stream reader that uses the // specified character set to decode bytes into characters. Reader reader; reader = new InputStreamReader (url.openStream (), charset); // This time, pass true to ignore the tag with its charset // attribute. new ParserDelegator ().parse (reader, callback, true); } catch (UnsupportedEncodingException e2) { System.err.println ("Invalid charset"); } catch (IOException e2) { System.err.println ("Input/Output problem"); e.printStackTrace (); } } catch (IOException e) { System.err.println ("Input/Output problem"); e.printStackTrace (); } } public static void saveImage (InputStream is, String filename) { FileOutputStream fos = null; try { fos = new FileOutputStream (filename); int bYte; while ((bYte = is.read ()) != -1) fos.write (bYte); } catch (IOException e) { System.err.println ("Unable to save stream to file"); } finally { if (fos != null) try { fos.close (); } catch (IOException e) { } } } } 列表 4 使用 javax.swing.text.html.parser.ParserDelegator 类创建一个 HTML 文档解析器,调用解析器对象的 public void parse(Reader r, HTMLEditorKit.ParserCallback cb, boolean ignoreCharSet) 实行解析。此方法使用了三个参数: r 标识一个用于读取 HTML 文档的 java.io.Reader 对象。 cb 标识一个处理所解析的标记及其属性的 javax.swing.text.html.HTMLEditorKit.ParserCallback 对象。 ignoreCharSet 标识是否忽略文档的 标记(如果存在)中的 charset 属性。 解析器在解析过程中调用了各种各样的 ParserCallback 方法。但只有一个方法是 GetImages 需要的:即 public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, int pos) 方法。这是一个为没有结束标记的标记(例如, )调用的方法,它使用了三个参数: t 通过 HTML.Tag 对象对标记进行标识。 a 通过 javax.swing.text.MutableAttributeSet 对象标识标记内的属性。 pos 标识当前解析到的位置 现在您已经清楚地认识到 GetImages 的工作原理,应该试着运行一下了。例如,您可以指定 java GetImages http://www.javajeff.mb.ca 将该网页上的图像下载到自己的 Web 站点的主页上。结果应该与以下类似(当前目录下出现了一些新图像文件): uri being processed ... http://www.javajeff.mb.ca/common/logo.jpg uri being processed ... http://www.javajeff.mb.ca/common/logo.gif uri being processed ... http://www.javajeff.mb.ca/na/images/wom.jpg 但是 GetImages 在命令行模式下再多么有用,也不及使用 GUI 抓取和查看图像来得方便。我的 IG 应用程序,结合 GetImages 源代码和前面的一些技巧以及一些 GUI 代码正是为此而生。图 4 展示了 GetImages 的 GUI,建议您查看本文的代码归档文件( 资源 )以获取源代码。
图 4 使用抓图程序的 GUI 方便地抓取和查看图像 结束语 此图像抓取程序将文章中介绍的技巧很好地结合在一起,但是还遗留了一些问题,关于这些问题的解决方法就当成家庭作业吧。问题 1:抓图程序下载图像,图像的标记需要指明相对 URL (例如, ),但是无法下载指明绝对 URL 的图像(例如, )。 问题 2:抓图程序无法下载动态生成 的 src 属性的图像。作为例子,以下 标记的 src 属性是我最近在 JavaWorld 主页上发现的: src="http://ad.doubleclick.net/ad/idg.us.nwf.jw_home/;abr=!ie;pos=top;sz=728x90;ptile=1;type=;ord=063423?" 。
作者简介 Jeff Friesen 是一位自由职业的软件开发者,擅长的领域是 C、C++ 和 Java 技术。资源 下载文章中使用的源代码: http://www.javaworld.com/javaworld/jw-01-2007/games/jw-01-games.zip 您可以使用 Java Fun and Games 中的 DevSquare 在线开发工具来生成和运行 Applet。请阅读此入门指南:
相关推荐: java的初始化 XFire 最新生火指南(上) 继承,构造器,初始化顺序 c++经典面试题 c++面试题 UML的9种图例解析 C++基础知识 Android 之 OO Principle(面向对象的原则) 推荐群组: EXT 更多相关推荐 OO 请教一下分离出导出类对象中的基类对象,不在导出类中添加方法可以实现吗? 相关推荐: java 多线程 生产者-消费问题 Spring service中ThreadLocal的应用? 应该有人需要这个吧——dhtmlxgrid 1.0 professional eclipse 插件与工具下载站点集合Eclipse插件网站分类 Eclipse插件列表 Android 应用和系统优化V1.2 BPEL入门记——Milestone2&3 44个JAVA代码质量管理工具 推荐群组: 读书空间 更多相关推荐 OO 用了code pro studio 觉得太好了。对于改进代码质量很好。最新的版本有个unit test edit ,但是对testng支持不是太好。 用4.6.1 版本,改进了audit 在代码改动时 标记不正常的毛病。 感觉是为了保持javacode的稳定性。这是它的好处。javabean不变,当schema变的时候改配置文件就可以了,不会影响java code。不然像jaxb就要重新generate java code了。 抛出异常的爱 写道 annotation 人造夹层 本来单层的java 又加入了一层 比html标签中加入<%%>还恶心 平白的增加出错机会...不禁要问为什么? jsp面页难到不是大家用过的最恶心的东西么 灵活么?好用么?美么?
唯一的好处就是大牛门又可以写出如C++般的魔术代码 我们常在页面用JSP2.0的jstl,觉得功能有一些不完善,但也挺好用的啊 也不要一棍子把jsp打死 Annotation用得好,可以减去很多重复的xml配置,举一个例子 属性名与数据库属性字段相同时,用Annotaion可以简洁很多 自己乱说一通,跑题了 public static List getClasses(String packageName, Class clazz) { List res = new ArrayList(); String pckgname = "test.package.test"; pckgname = packageName; String name = new String(pckgname); if (!name.startsWith("/")) { name = "/" + name; } name = name.replace('.', '/'); URL url = ClassTool.class.getResource(name); if (url == null) { return res; } File directory = new File(url.getFile()); if (directory.exists()) { String[] files = directory.list(); for (int i = 0; i < files.length; i++) { if (files[i].endsWith(".class")) { String classname = files[i].substring(0, files[i].length() - 6); try { String clsName = pckgname + "." + classname; logger.debug("clsName=" + clsName); Object o = Class.forName(clsName).newInstance(); // if (o instanceof Class) { // System.out.println(classname); // } if (o instanceof Object) { } res.add(pckgname + "." + classname); } catch (ClassNotFoundException cnfex) { logger.error("getClasses(String)" + cnfex, cnfex); } catch (InstantiationException iex) { } catch (IllegalAccessException iaex) { } } } } return res; } 也许用得到 相关推荐: db4o对象型数据库学习笔记 《DB4O系统应用》--之起步篇 关于db4o中在C/S模式下如何保证对全局共享数据的可靠并发访问 。 七夕表白代码.zip 15个nosql数据库 从类模型转换到数据库表结构的思考 关于数据建模(面向ER)和领域模型建模(面向OO)在企业应用中的作用的讨论 推荐群组: GlassFish 更多相关推荐 OO 关于对象数据库 DB4O 的一些BUG以及如何应对的方法 1、objectmanager 6.0不能正常显示中文而是框框,是字体设置不正确的原因,因为没有源程序,所以无法定位在哪里出了问题。 2、objectmanager 6.0需要对应db4o 6.1的版本 objectmanager 1.8需要对应db4o 5.5的版本 objectmanager 1.7需要对应db4o 5.2的版本 版本不对应会造成无法打开数据库文件,怎么就这么不兼容呢?连高版本的管理工具都无法打开旧的数据库。 3、com.db4o.eclipse_0.2.0默认只能打开db4o 5.2的版本的数据库 可以通过修改com.db4o.eclipse.plugin_0.2.0.jar包里的db4o的.jar文件,更换为不同的版本后来访问相应版本的数据库。 先解压 com.db4o.eclipse.plugin_0.2.0.jar 包,到 com.db4o.eclipse.plugin_0.2.0 目录下 方法一: 把 db4o-5.5-java1.2.jar 或 db4o-6.1-java1.2.jar 等jar文件改名为 db4o-5.0-java1.2.jar ,然后拷贝过来覆盖掉原来的 db4o-5.0-java1.2.jar 文件,重新打开 Eclipse 即可。 方法二: 拷贝 db4o-5.5-java1.2.jar 或 db4o-6.1-java1.2.jar 等jar文件 到com.db4o.eclipse.plugin_0.2.0 目录下, 然后进入 com.db4o.eclipse.plugin_0.2.0\META-INF 目录下,打开 MANIFEST.MF 文件,将里面的 db4o-5.0-java1.2.jar 替换成拷贝进来的相应版本的jar文件名,保存,重新打开 Eclipse 即可。 两种方式其实就是替换不同版本的jar文件而已。 4、在用工具打开时有问题时,还需要进入当前用户的目录下将.objectmanager.*之类的文件删除后,再运行工具来打开数据库。 比如: X:\Documents and Settings\YuLimin 目录下的 .objectmanager.yap之类的文件 相关推荐: Java 专业人士必备的书籍和网站列表 烂译列表 一些适合Java编程的书籍总结 Java程序员必看的 13 本 Java 书籍 Java程序员必读之热门书单 Java开发工程师必备技能 java 入门书籍(java7) JAVA零基础入门书籍推荐 推荐群组: 作书译书圈子 更多相关推荐 OO 对于 Java™ 语言开发人员来说,信息过量是一个真正的问题。每个新入行的程序员都要面临一个令人畏缩的挑战:要进入的行业是一个具有海量知识的行业。要了解的东西简直 太多了。对于有经验的老手来说,情况只有些微好转。知识量总在增大,仅仅跟上进度就是一个挑战。如果有一份专业人士必备的书籍和网站列表该有多好!本文就 是这个列表。它包含了每个专业的 Java 语言程序员在书架或浏览器书签中必备的最重要的书籍和网站。 这些都是您书架上必备的书和应该经 常使用的 Web 链接。时间是一项重要的资源,本文帮您回避那些分心的事情,把时间专注于最有益于您作为Java 语言程序员职业生涯的信息源。尽管有多少程序员就有多少他们最喜欢的参考资料,但本文收集的这些都是优中选优,来源于我书架上的私家珍藏和许多 Java 专家的推荐。 我考虑了两种组织这份参考资料列表的方法。我本可以通过主题领域来组织,这也许很有帮助,但主题列表很快就会变得不实用。相反,我选择了另一种方法:通过类型来组织,即书籍和 Web 站点。 总的来讲,有经验的老手们用 Web 站点来跟踪行业的走势。书籍、文章和论文有助于跟上潮流,但它们总体上更适合于基础学习。极富创造性的书籍偶尔会撼动一两个基础性的东西。这样的书也在本列表之列。 需 要提出的一点警告是,专注于 Java 语言的书籍和 Web 站点数量巨大。您钟爱的未必在这份列表里。那并不意味着它们不好。它们只是不在这份列表里而已。可能是因为我还不知道它们。也可能是因为我不认为它们能够 算得上是重要资源。不包含一些参考资料是一个评判问题,但如果不这样的话,您也许就要花几小时来拖动滚动条,还要花上成千上万美元来买书。如果您作为一个 专业的 Java 程序员,有一些常用的优秀参考资料,一定要让我知道这些资料。这份列表一直都在更新中,您提出的那些也许就会被收录进去。 书籍 每个程序员都会有一些由于经常被当作专业资料参阅而磨坏的书。下列书籍应该是 Java 语言程序员的书架上必备的。书很贵,所以我有意将这份列表弄得很短,仅限于重要书籍。 Thinking in Java (Bruce Eckel) Thinking in Java, 3rd edition (Bruce Eckel; Prentice Hall PTR,2002 年) Java 编程思想:第3版 (陈昊鹏 等译; 机械工业出版社,2005 年) Eckel 的书对于学习如何在 Java 语言环境中使用好面向对象技术极其实用。书中大量的代码样例解释了他所介绍的概念。文字出自一个并不认为 Java 技术总是正确答案的人,所以相当地实用。Eckel 具有多种语言的大量经验,还有用面向对象方式进行思考的扎实技能。本书将这些技能放到实用的 Java 语言环境中。他还在写一本新书,名为 Thinking in Enterprise Java 。 Effective Java (Joshua Bloch) Effective Java: Programming Language Guide (Joshua Bloch; Addison-Wesley,2001 年) Effective Java 中文版 (潘爱民 译; 机械工业出版社,2003 年) 本书是理解优秀 Java 程序设计原则的最佳书籍。大多数材料从其他的 “学习 Java ” 的书中根本找不到。例如,Bloch 书中关于覆盖 equals() 这一章是我读过的最好的参考资料之一。他也在书中包括了很实用的建议:用接口替代抽象类和灵活使用异常。Bloch 是 Sun 公司 Java 平台库的架构师,所以他透彻地了解这门语言。事实上,他编写了该语言中大量有用的库。本书必读! The Java Programming Language (Ken Arnold, James Gosling, David Holmes) The Java Programming Language (Ken Arnold,James Gosling,David Holmes; Addison-Wesley,2000 年) Java 编程语言(第 3 版) (虞万荣 等译,中国电力出版社,2003 年) 这也许是能弄到的最好的 Java 入门读物。它并不是一个标准规范,而是一本介绍每门语言特性的可读书籍。这本书在严谨性和教育性方面权衡得很好,能够让懂编程的人迅速被 Java 语言(和其丰富的类库)所吸引。 Concurrent Programming in Java: Design Principles and Patterns (Doug Lea) Concurrent Programming in Java: Design Principles and Patterns, 2nd edition (Doug Lea; Addison-Wesley,1999 年) Java 并发编程—设计原则与模式(第二版) (赵涌 等译,中国电力出版社,2004 年) 不是每个开发人员都需要如此细致地了解并发性,也不是每个工程师都能达到本书的水准,但却没有比本书更好的关于并发性编程的概述了。如果您对此感兴趣,请 从这里开始。Lea 是 SUNY 的一名专业程序员,他的和并发性有关的作品和想法都包含在了 JDK 5.0 规范(引自 JSR166)中,所以您大可放心,他所说的关于有效使用 Java 语言的建议是值得一听的。他是一个很善于沟通的人。 Expert One-On-One J2EE Design and Development (Rod Johnson) Expert One-On-One J2EE Design and Development (Rod Johnson) WROX: J2EE 设计开发编程指南 (魏海萍 译,电子工业出版社,2003 年) 对于刚接触 J2EE 的人来说,这是唯一的一本如实反映这项技术的书。本书收录了多年的成功经验和失败经验,不同于其他许多作者,Johnson 乐于将失败的经验公诸于众。J2EE 常常都被过度使用。Johnson 的书能帮您避免这一点。 Refactoring (Martin Fowler, Kent Beck, John Brant, William Opdyke, Don Roberts) Refactoring: Improving the Design of Existing Code (Martin Fowler,Kent Beck,John Brant,William Opdyke,Don Roberts; Addison-Wesley,1999 年) 重构:改善既有代码的设计(中文版) (侯捷 等译,中国电力出版社 ,2003 年) Fowler 写了几本现已出版的最流行的编程书,包括 Analysis Patterns 。他的关于 重构 的书是这一主题的基本书籍。重构代码是被程序员忽略的训练,但却是程序员最直观的想法。重构是在不改变代码结果的前提下改进现有代码的设计。这是保持代码 整洁的最佳方式,用这种方法设计的代码总是很容易修改。什么时候进行重构呢?当代码“散发出味道”时。Fowler 的书里满是 Java 语言代码的例子。许多 Java 语言集成开发环境(IDE)(包括了 IBM 的 Eclipse)都将 Fowler 的重构包含了进去,每一个都使用他的重构名命名,所以熟悉如 extract method 等重构方法还是很值得的。 Design Patterns (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides) Design Patterns: Elements of Reusable Object Oriented Software (Erich Gamma,Richard Helm,Ralph Johnson,John Vlissides; Addison-Wesley,1997 年) 设计模式:可复用面向对象软件的基础 (李英军 等译,机械工业出版社 ,2005 年) 这是一本在专业程序员圈子里更为有名的书,基于作者共同的绰号,这本书被认为是 “四人帮(GOF)之书”。模式是思考和解决普通编程问题时可以重用的方式。学习模式是一门学科。使用好模式(或知道什么时候 不 使用模式)是一项技能。忽略模式则是错误的。书中所有的例子都以 C++ 表示,但 Java 语言是从那里诞生的,让 Java 语言程序员由此联系到如何在 Java 语言中实现这些模式相对简单一些。熟悉模式并了解如何使用好模式使编程更加简单。这使得和其他程序员交流也更简单,因为在针对通用问题的通用解决方案中, 模式是描述解决方案中彼此协作的大量相关编程概念的快捷方式。一些更为通用的方式,如 工厂方法 则是普便存在的,甚至存在于 Java 语言本身。关于明智使用模式的这个主题,也可以阅读 Joshua Kerievsky 的 Refactoring to Patterns ,该书称可以让代码来告诉您何时实现模式。 Patterns of Enterprise Application Architecture (Martin Fowler) Patterns of Enterprise Application Architecture (Martin Fowler; Addison-Wesley,2002 年) 企业应用架构模式 (王怀民 等译,机械工业出版社 ,2004 年) 比起小型、一次性项目来说,企业开发当然代表了更大的挑战。那并不意味着企业开发带来的所有挑战都是新挑战。事实上有些时候,这项开发 已经 是以前完成过的了。Fowler 做了很多个这样的项目。他的书提到了一些通用解决方案,并提供了关于使用、折中和可选方案的指导。Fowler 在书中包含了一些熟悉的模式,如模型视图控制器(MVC),他也提供了一些您也许不了解的模式,如处理 Web 站点上特定页面请求或行为请求的 Page Controller 模式。正如您对待大多数模式一样,一旦您读过许多模式,您就会认为 “我已经知道那个模式了” 。也许是这样,但有一个用来引用模式的通用表达方式还是很有帮助的。在有多个组件(由不同人开发)的大型项目中,该类引用是一项很好的帮助。 UML Distilled (Martin Fowler) UML Distilled: A Brief Guide to the Standard Object Modeling Language (Martin Fowler; Addison-Wesley 2003 年) UML精粹:标准对象语言简明指南(第3版) (徐家福 译,清华大学出版社 ,2005 年) 对于专业的程序员来说,UML 是一门很重要的通用可视化沟通语言,但是它被过度使用和草率地滥用了。您无需对使用 UML 沟通了解太多。Martin 对 UML 的提炼为您提供了最核心的东西。事实上,前后的封页提供了常规基础上可能使用到的所有东西。该书中 UML 例子的代码都是 Java 代码。 Test-Driven Development: By Example (Kent Beck) Test-Driven Development: By Example (Kent Beck; Addison-Wesley 2002 年) 测试驱动开发(中文版) (崔凯 译,中国电力出版社 ,2004 年) 测试优先编程将使编程发生革命性变化,能助您成为更好的程序员。在写代码之前编写测试开始很难,但却是一项威力强大的技能。通过优先编写测试,可使代码更 加简单,并确保从一开始它就能工作(Beck 实践着他提倡的测试优先,与人合写了 JUnit,这是 Java 语言最流行的测试框架)。Beck 的书是权威的参考资料,扩展了的 Money 例子也用 Java 语言写成。Beck 详述了如何用测试优先进行 思考 (这也许是许多程序员首先遇到的障碍)。 The Pragmatic Programmer: From Journeyman to Master (Andy Hunt and Dave Thomas) The Pragmatic Programmer: From Journeyman to Master (Andrew Hunt 和 David Thomas; Addison-Wesley 1999 年) 程序员修炼之道——从小工到专家 (马维达 译,电子工业出版社 ,2004 年) 做一个纯粹的面向对象开发人员有其优势所在。在当今复杂的社会中,作为 Java 语言开发人员,为完成任务常要妥协。Hunt 和 Thomas 探讨了如何不将真正重要的东西妥协掉而完成任务。这不是一本关于 Java 语言的书,而是 Java 语言开发人员重要的思想读物。例如,我认为没从“要解决问题,而不是推卸责任”这句忠言中受益的程序员,不能像个自豪的艺术家一样在他的杰作上签上大名。 Peopleware: Productive Projects and Teams (Tom DeMarco and Timothy Lister) Peopleware: Productive Projects and Teams (Tom DeMarco,Timothy Lister; Dorset House,1999 年) 人件(第2版) (UMLChina 翻译组 译,清华大学出版社 ,2003 年) 这份列表中的其他所有书籍都至少和技术有些相关。这本书却不是。在所有技术行话和首字母缩略词的海洋中,有时软件开发人员和经理们会忘记:是 人 制造了软件。DeMarco 和 Lister 向我们提醒了这一事实,也向我们提醒了形成这一区别的原因。这不是一本关于一门特定编程语言的书籍,但却是每个 Java 语言程序员都应该读的书。关于 “累死程序员如何让经理们适得其反” 还有许多其他的好书,但这是最好的一本。 回页首 Web 站点 Web 站点的数目浩如烟海,如果您想要消化其中的内容,穷毕生之力也难以全部访问。包含 Java 语言某方面内容的详尽的网站列表会大得离谱。下列站点都是可靠、真实的。 Sun 的 Java 技术站点 Sun 的 Java 语言站点 这是 Sun 的 Java 语言主站。作为 Java 语言开发人员,您会发现自己频繁地访问此站点。下列链接特别重要,特别是对新入行的 Java 语言开发人员: New to Java Center New to Java Center New to Java Center 存放了许多循序渐进的 Java 技术资源链接。如果您刚接触这门语言,这是一个好的起点。 教程和代码库 Java Tutorial 这里有大名鼎鼎的 Java Tutorial,以及关于 Java 语言各个方面(例如 Collection)的其他教程。 IBM developerWorks IBM 的 developerWorks 推销自己也许有些厚脸皮,但 developerWorks 是一项巨大的资源,收录了大量 Java 语言工具和技术的教程和文章。其内容从初学者指南到学习这门语言到高级并发性技术。可以根据主题搜索内容,然后根据类型浏览。 Apache Software Foundation Apache Software Foundation Apache 站点是许多可重用库(通用领域)和工具的主页,这些库和工具帮助 Java 开发人员进行开发。这里的内容全都是开放源码,所以尽管下载想要的吧!许多极其流行的 Java 语言库和工具(如 Struts、Ant 和 Tomcat)都始于 Apache 项目。Jakarta 专区汇聚了大多数新兴的 Java 语言材料。 Eclipse.org Eclipse 有几个好的 Java 语言集成开发环境(IDE)。Eclipse(来自 IBM)是最新的 IDE 之一,它很快成为 Java 语言开发的首要 IDE。它完全是开源的,这意味着它是免费的。该站包含了学习如何有效使用 Eclipse 的各种参考资料。这里还有关于 Standard Widget Toolkit(SWT)的信息,SWT 是相对于 Swing 来说更加轻量级的选择。 Eclipse 插件中心和 Eclipse 插件 Eclipse 插件中心 和 Eclipse 插件 Eclipse 基于插件架构。事实上,插件是 Eclipse 的 Java 语言开发组件。但有差不多上千个插件,从 Web 开发的插件到在 Eclipse 环境中玩游戏的插件。这两个站点分类列出了大多数插件,可以进行搜索。它们是很棒的资源。如果您想在 Eclipse 开发环境中弄点新东西,幸运的话有某个插件可能已经实现,从这两个站点能找到想要的插件。这两个站点都允许评论插件,这样您就可以知道哪些插件好,哪些值 得一试。 JUnit.org JUnit.org Junit 是 Java 语言中一个基本的单元测试框架。该站点包含了 Junit 最新最棒的版本,外加大量有关测试(Java 语言或者其他语言的)各个层面上(针对桌面应用程序、Web 应用程序、J2EE 应用程序等)的其他资源。如果您想找测试资源,这里就是最佳起点。 TheServerSide.com TheServerSide.com 如 果您要(或将要)从事服务器端 Java 语言的开发,此站点是一处举足轻重的资源。您可以到这里找到有关 JBoss、J2EE、LDAP、Struts 和大量其他主题的文章,并且都是完全可检索的。这些文章不仅仅是简单描述 Java 语言的特征或者支持的库。它们更进一步地描述了库的新奇用法(如使用 Jakarta Velocity 作为规则引擎,而不是模板引擎)。它们也提供了有关 Java 语言现状的连续评论(当前的一篇文章是由 Tim Bray 所写的 Java is boring )。该站点更好的通用功能之一是对 Java 语言工具和产品(应用服务器等)的矩阵式比较。 Bruce Eckel's MindView, Inc. Bruce Eckel's MindView, Inc. Eckel 写了几本 “用 …… 进行思考” 的书,内容关于 Java 语言、Python 和 C++ ,当我学习 Java 语言时,他的 Thinking in Java 对我尤其有帮助。它很实用并切中要害,在“在 Java 语言环境中如何面向对象思考”方面具有卓识。您可以从此站点免费下载他所有书籍的电子版。他也写了许多好文章,并且他把这些文章的链接都放到了这里(包括 关于 Jython、Java 和 .NET 比较等内容的文章)。 ONJava.com ONJava.com O'Reilley 历年来出版了一些有关编程语言和工具的优秀书籍。他们的专注于 Java 语言的网站也不错。它有些有关各种 Java 语言工具(如 JDOM 和 Hibernate)、Java 平台(如 J2SE 和 J2EE)不同领域不同部分的文章。全部都可以被检索到。他们有优秀的文章和教程。该站点按主题排列。例如有 Java 和 XML、Java Security、Wireless Java 和 Java SysAdmin。该站点也有到 O'Reilley Learning Lab 的链接,在那里您能获得在线参考资料(Java 语言相关和其他的)。那些不是免费的,但是许多都面向大学认证。因此您可以以一种很方便的方式来学习技能,并得到一些认证。 java.net java.net 社区 java.net 社区有多个“社区”,有特定于主题的论坛和文章。例如 Java Desktop 社区有各类与 Java 语言桌面开发相关的资料。Java Patterns 社区作为一个门户,也许对提供 Java 语言的模式资源相当感兴趣。还有一个 Java User Groups (JUG) 社区,在那里能找到有关创建、加入和管理一个 JUG 的信息。 回页首 结束语 任 何 “好的”、“关键性的” 或者 “重要的” 参考资料列表都注定是不完整的,本文的列表也未能例外。 Java 语言的书籍数目众多,当然,万维网也很庞大。除本文所列的参考资料之外,还有很多用于学习 Java 语言的参考资料。但如果您拥有了这里所提到的所有书籍、网站、文章或者教程,您应当已经拥有了一个使您良好开端并助您登堂入室的实用宝库。 最 后,要成为一个能力日增和高效的 Java 语言开发人员,方法就是用它工作,动手来尝试。如果有一个教程详细介绍了所需创建的软件的每一部分,您很可能并没得到多少好处。有时,您可能得走自己的 路。在成功地尝试了一些新的东西之后,您可能想要写一篇文章、教程或者一本书来分享您所学到的。 相关推荐: JavaScript内核系列 第1章 前言及概述 web UI技术综述 flex与JAVA的SOCKET通信 12.Java内存模型 【游戏设计模式】之四 《游戏编程模式》全书内容提炼总结 应用集成与数据集成建设总体思路 中间件技术及其应用 推荐群组: springMVC 更多相关推荐 OO 本文来自 http://hi.baidu.com/yuxichu/blog1. Web开发技术的困境 1.1 JSF/ASP.net的缺陷 JSF/ASP.net在Web编程技术的发展中是一个了不起的进步,无数赞美的文章以及成功的案例足以说明这一点。但遗憾的是,JSF/ASP.net虽然实现了面向对象的Web编程,却在提供高效的Web应用系统方面做了倒退。在典型的JSF/ASP.net程序中,每一次Web事件的处理都需要将整个表单的数据发送到服务器端,由Web服务器重建该表单的服务器端视图,再调用相应的事件处理函数进行处理。这种处理方式利于实现代码与界面的分离和面向对象编程, 但却对系统性能造成了严重的影响,主要的缺陷有: 1. 所有的表单数据(包括表单的状态视图数据)都要发送到服务器上,整个页面需要刷新, 无法做到按需传递数据,在客户端与服务器之间频繁进行这种大量数据的传送将严重占用网络带宽; 2. 在当前客户机的性能越来越好的环境下,却将所有的业务逻辑都放在服务器端执行, 甚至简单的调整显示界面的代码都放到服务器端执行,这是对客户端资源的严重浪费, 简直可以用“暴殄天物”来形容; 3. 虽然经历了种种努力,在JSF/ASP.net编写的稍为复杂的Web程序中其界面描述部分(如JSF中的jsp文件和ASP.net中的aspx文件)中仍然做不到完全消除JavaScript代码(这些代码出现在或html标签的onEVENT属性中)。这样,JSF/ASP.net实际上没有完全实现界面与代码分离的目标。 4. 必须使用客户端脚本时,很难以直观的方法使 JavaScript代码也能以面向对象的方式进行编程。 虽然,某些JSF实现方案可以与AJAX结合起来使用,但这只在一定程度上解决了客户端与服务器之间数据交换的问题, 却使得整个编程模型更加晦涩难懂,而且AJAX并不是一个真正的面向对象编程模型。 1.2 一个常见的小“错误” 在分析了JSF/ASP.net技术的缺陷之后,为了便于描述本文即将提出的单一环境Web编程模型,在这里还得讲述一下我们很多人在刚学Web编程时都可能犯过的一个小“错误”──偶尔误解了代码的运行环境。在一个ASP/JSP文件中,其内容常常是一团各种内容的混杂体:各种HTML标签用于描述界面、客户端中包含着在浏览器中执行的代码、 <% %>之间则嵌着服务器端代码。这些内容混杂在一起,还经常必须通过服务器端代码来生成客户端代码。由于它们是如此的混乱,以致于很多人都犯过混淆代码运行环境的小“错误”,如例1-1所示。 例1-1 在ASP中显示错误消息(错误的形式) 在例1-1中,其意图是通过alert函数将服务器端的错误消息显示给用户, 却没有注意到无法在服务器端直接修改客户端变量的值。其正确形式应该是这样的: 例1-2 在ASP中显示错误消息(正确的形式) 但例1-2中的表示方式却不符合我们的思维习惯,如果错误描述中包含有单引号(')或回车换行符(\r\n), 就特别容易发生错误。当这种要求反复出现时,常常会给程序员带来沉重的负担。 在ASP/JSP程序中经常发生的这类“错误”说明,我们的大脑习惯于连续的思考同一个问题。在理想的情况下,一次业务或事件处理过程应该视为一个连续过程,而在传统的Web编程环境中,程序员必须将这个过程划分为多个阶段,其中一部分发生在客户端、而另一部分发生在服务器端,同时还得考虑客户端与服务器端之间的连接过程,于是一个连续的过程就被人为切割为多个片断,同时还得考虑各个片断的运行环境以及片断之间的影响。由于这类划分不是合乎逻辑的自然划分,从而对程序员的大脑提出了严重的挑战。当任务繁重时,人们很容易因为疏忽发生不易查觉的错误, 从而为系统的安全与稳定埋下严重的隐患。 2. 单一环境Web编程模型 2.1 概述 由于JSF/ASP.net在性能方面存在重大缺陷,AJAX则无法实现真正的面向对象编程(关于AJAX的不足之处已有很多文章予以描述,为免累赘本文不再重述),而在Web程序运行环境中,由于客户端与服务器的区分迫使程序员必须对一个业务处理过程进行人为的划分,同时这种划分导致各部分代码之间的存在相当微妙的影响,这就极易击中我们的思维盲区,因此本文顺应例1-2中描述的“小错误”,将错就错,提出单一环境Web编程模型。 该模型的基本思想是,为应用程序员营造一个虚拟的单一运行时环境,使得程序员可以将用户界面的事件处理逻辑编写为一个单一的处理过程, 不需区分哪些是客户端脚本,哪些是服务器端代码;然后由编译器将这个单一处理过程编译为适当的客户端脚本和服务器端代码, 并在适当的位置自动添加处理客户端脚本与服务器端代码之间交互的代码。 为便于理解,下面将以一个简单的示例来演示单一环境Web编程模型的实现机制。 该示例的功能为:点击页面上的一个按钮,则弹出一个模态对话框,向用户报告服务器上的当前时间。为便于与已有的Web编程技术进行比较, 该示例分别用ASP.net和单一环境Web编程模型描述一遍。 首先,以ASP.net描述,由两个文件组成:ServerTime.aspx和ServerTime.aspx.cs,分别如下: 例2-1 以ASP.net编写的显示服务器时间的用户界面部分 -----------------------------------ServerTime.aspx--------------------------------------- <%@ Page language="c#" Codebehind="ServerTime.aspx.cs" AutoEventWireup="false" Inherits="test.ServerTime" %> ServerTime 例2-2 以ASP.net编写的显示服务器时间的代码部分 -----------------------------------ServerTime.aspx.cs--------------------------------------- namespace test { public class ServerTime : System.Web.UI.Page { protected System.Web.UI.WebControls.Button BtnShowServerTime; override protected void OnInit(EventArgs e) { InitializeComponent(); base.OnInit(e); } private void InitializeComponent() { this.BtnShowServerTime.Click += new System.EventHandler(this.BtnShowServerTime_Click); } private void BtnShowServerTime_Click(object sender, System.EventArgs e) { //生成消息 String msg=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //生成在客户端显示消息的脚本,其中脚本内容在浏览器上执行 this.RegisterClientScriptBlock("X",""); } } } 接下来,以单一环境Web编程模型描述,同样也由两个文件组成:ServerTime.uw和ServerTime.uw.code,其中ServerTime.uw.code使用类C#语法编写, 其内容分别如下: 例3-1 以单一环境Web编程模型编写的显示服务器时间的用户界面部分 ------------------------ServerTime.uw------------------------------------------- ServerTime 例3-2 以单一环境Web编程模型编写的显示服务器时间的代码部分 ------------------------ServerTime.uw.code------------------------------------------- namespace test { public class ServerTime { //为表单Form1声明一个按钮对象 Button Form1.BtnShowServerTime; //注册对象的事件处理函数 public ServerTime() { Form1.BtnShowServerTime.onclick=BtnShowServerTime_Click; } private void BtnShowServerTime_Click() { //生成消息,该部分实际上在服务器上执行 string msg=DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //显示消息,该部分实际上在浏览器上执行 window.alert(msg); } } } 比较例2与例3中的事件处理过程,可以看到二者之间的明显差异: 1. 在ASP.net中,表单中的所有数据都必须提交给服务器,当表单中数据项特别多或状态视图(StateView)数据量很大时,这种多余的数据传输将多消耗大量网络资源;而在单一环境Web编程模型中,编译器通过分析代码,将其分离为服务器执行部分与客户端执行部分,通过类似于AJAX技术的方式将服务器端代码必需的数据提交给服务器,并获得服务器生成的消息,在这过程中,没有非必需数据的传输,可以极大的节省网络资源。 2. 在ASP.net中,客户端代码是通过字符串连接方式生成的,实际上编译只将其作为普通的字符串处理, 这样实际上就不能使用编译时的语法检查,失去了一个发现、修改错误的环节; 而在单一环境Web编程模型中,编译器可以对代码进行全面的语法检查。 3. 在ASP.net 中,显示消息的方式不符合人类的思维习惯, 该过程被人为划分为客户端与服务器两个阶段,并且这种个阶段的代码交织在一起,另一方面当消息格式复杂时,生成正确语法脚本的难度也大大增加,降低了系统的健壮性;而在单一环境Web编程模型中,消息以一种自然的方式(类似于桌面编程的方式)向用户显示, 消息的内容也不会影响代码的正确性。 4. 在单一环境Web编程模型中,界面描述部分是一个纯HTML页面,其中不含任何脚本代码, 实现了代码与界面的完全分离。 2.2 实现机制 通过上面示例的演示,本文设想单一环境Web编程模型的实现机制如下: 每一个Web表单由两部分构成,其中界面描述部分是一个纯HTML文件,该文件中通常不包含任何脚本代码(实际上允许嵌入脚本代码,但不鼓励这么做); 事件处理的代码部分全部位于代码文件中,该文件内容以类C#语法编写,并且将客户端与服务器的运行时环境视为一个整体,在代码中不用明确处理二者之间的差异。 为了更好的解释单一环境Web编程模型的实现机制,本文给出代码文件的设计示例, 后面的讨论基于这种文件格式进行。为了便于理解与对比,采用类C#语法描述,其基本结构如下: import JavaScript.js;//被引用的JavaScript.js文件 using PackageName;//被引用的服务器端的命名空间或包 namespace Namespace { class ClassName { //页面级变量声明段 client data_type1 client_variable; //客户端变量 server data_type2 server_variable; //服务器变量 media data_type3 media_variable; //中介变量 client Button FormName.BtnOK; //用户界面元素 //初始化函数 Init() { //完成显示界面对象的事件与处理逻辑的映射关系 FormName.BtnOK.onclick=BtnOK_OnClick; //映身一个点击事件处理逻辑 } //事件处理函数 void BtnOK_OnClick() { //完成访问数据库、调用其他服务器端程序、以及改变显示界面等工作 } //其他辅助函数 data_type function_name(data_type1 var1, … ) { } } } 1. 关键词import 导入一个脚本文件,其功能类似于< script language="JavaScript" src="path.js">,从而在该代码文件中可以直接调用该脚本文件中声明的函数。每一个import语句都被编译为一个脚本引用块,形如:。 2. 关键词using 允许在命名空间中使用类型,以便在命名空间中使用类型而不必指定命名空间, 其意义与C#中的using指令及JAVA中的import相同。 3. 关键词namespace 声明该类所属的命名空间或包,其意义与C#中的namespace及JAVA中的package相同。 4. 关键词class 声明一个类,其含义与C#及Java中的class类似。不同之处在于,此处声明的“类”可分为四种类型:一、类中没有服务器端变量与方法,且有客户端变量或方法,则该类为客户端类, 这种类型的class被编译为客户端脚本;二、类中没有客户端变量与方法,且有服务器端变量或方法,则该类为服务器端类, 这种类型的“类”被编译为服务器端类,如C#类或Java类等;三、类中既有服务器端变量或方法,也有客户端变量或方法,则该类属于混合类,这种类型的类中的一部分内容被编译为客户端脚本,另一部分内容被编译为Web服务,并且由编译器自动生成客户端脚本调用Web服务的代码以处理二者之间的交互。四、类中既没有服务器端变量或方法,也没有客户端变量或方法,则该类属于中性类, 这种类型的类被同时编译为客户端脚本和服务器端的代码。 5. 关键词client、server、media 这三个关键词均用于指定变量的性质,其中: client——声明一个仅用于客户端的变量,这类变量通常是显示界面的元素,也可能是ActiveX对象或简单数据类型变量,另外还包括默认的window、event、document、location、status等页面对象; server——声明一个仅用于服务器端的变量,这类变量通常是数据库连接对象、业务处理逻辑对象实例等, 也可能是简单数据类型变量,另外还包括默认的Request、Response、Session、Application、Server等服务器端对象; media——声明一个中介变量,这类变量用于在服务器与客户端变量之间交换信息, 其数据类型必须是简单数据类型,即char、integer、number、float、boolean、date-time等数据类型及其数组。 为了减轻程序员的负担,这三个关键词是可以省略的。 在这种情况下,编译器根据变量的数据类型确定其性质,其中: 数据类型为界面对象或ActiveX对象的变量为client变量; 简单数据类型及其数组为media变量;其他均为server变量。 6. 函数 类中的函数根据其引用的变量和调用的函数的性质可以分为四类:一、客户端函数,引用了客户端变量或调用了客户端函数,但没有引用服务器变量且没有调用任何服务器函数,这类函数被编译为JavaScript脚本函数;二、服务器函数,引用了服务器变量或调用了服务器函数,但没有引用客户端变量且没有调用任何客户端函数, 这类函数被调编译为服务器端代码表示的函数;三、中性函数,没有引用服务器变量且没有调用服务器函数,也没有引用客户端变量且没有调用任何客户端函数,这类函数被同时编译为等价的JavaScript脚本函数和服务器端代码。 四、混合函数,除前面三类以外的其他函数,这类函数被编译为一个客户端的JavaScript函数和一个或多个的Web服务方法, 并由编译器自动添加代码处理它们之间的交互。 6.1 初始化函数Init() 该函数属于混合函数,其主要功能是完成显示界面元素的事件与处理逻辑的映射关系, 被编译器编译为一段直接执行的客户端脚本,插入在编译后的页面末尾, 即 |