2005年08月14日

import java.io.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;

/**
*这个类提供一些 JDom 对象常用的方法。
* @author 刘海龙(xiaoleilong@mail.biti.edu.cn)
*/
public class JDomUtil {

/**
* 根据指定路径的XML文件建立JDom对象
* @param filePath XML文件的路径
* @return 返回建立的JDom对象,建立不成功返回null 。
*/
public static Document buildFromFile(String filePath) {
try {
SAXBuilder builder = new SAXBuilder();
Document anotherDocument = builder.build(new File(filePath));
return anotherDocument;
} catch(JDOMException e) {
e.printStackTrace();
} catch(NullPointerException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据XML 字符串 建立JDom对象
* @param xmlString XML格式的字符串
* @return 返回建立的JDom对象,建立不成功返回null 。
*/
public static Document buildFromXMLString(String xmlString) {
try {
SAXBuilder builder = new SAXBuilder();
Document anotherDocument = builder.build(new StringReader(xmlString));
return anotherDocument;
} catch(JDOMException e) {
e.printStackTrace();
} catch(NullPointerException e) {
e.printStackTrace();
}
return null;
}
/**
* 根据Dom对象建立JDom对象
* @param Dom org.w3c.dom.Document对象
* @return 返回建立的JDom对象,建立不成功返回null 。
*/
public static Document buildFromDom(org.w3c.dom.Document Dom)
throws JDOMException, IOException {
org.jdom.input.DOMBuilder builder = new org.jdom.input.DOMBuilder();
Document jdomDoc = builder.build(Dom);
return jdomDoc;
}

/**
*这个方法使用XMLOutputer将一个JDom对象输出到标准输出设备,使用 GBK 编码
* @param myDocument 将要被输出的JDom对象
*/
public static void outputToStdout(Document myDocument) {
outputToStdout(myDocument,"GBK");
}
/**
*这个方法使用XMLOutputer将一个JDom对象输出到标准输出设备
* @param myDocument 将要被输出的JDom对象
* @param encoding 输出使用的编码
*/
public static void outputToStdout(Document myDocument,String encoding) {
try {
XMLOutputter outputter = new XMLOutputter(" ", true,encoding);
outputter.output(myDocument, System.out);
} catch (java.io.IOException e) {
e.printStackTrace();
}
}
/**
* 这个方法将JDom对象转换字符串.
* @param document 将要被转换的JDom对象
*/
public static String outputToString(Document document){
return outputToString(document,"GBK");
}
/**
* 这个方法将JDom对象转换字符串.
* @param document 将要被转换的JDom对象
* @param encoding 输出字符串使用的编码
*/
public static String outputToString(Document document,String encoding){
ByteArrayOutputStream byteRep = new ByteArrayOutputStream();
XMLOutputter docWriter = new XMLOutputter(" ", true,encoding);
try{
docWriter.output(document, byteRep);
}catch(Exception e){

}

return byteRep.toString();
}
public static org.w3c.dom.Document outputToDom(org.jdom.Document jdomDoc)
throws JDOMException {
org.jdom.output.DOMOutputter outputter = new org.jdom.output.DOMOutputter();
return outputter.output(jdomDoc);
}
/**
* 这个方法使用XMLOutputter将JDom对象输出到文件
* @param myDocument 将要输出的JDom对象
* @param filePath 将要输出到的磁盘路径
*/
public static void outputToFile(Document myDocument,String filePath) {
outputToFile(myDocument,filePath,"GBK");
}
/**
* 这个方法使用XMLOutputter将JDom对象输出到文件
* @param myDocument 将要输出的JDom对象
* @param filePath 将要输出到的磁盘路径
* @param encoding 编码方式
*/
public static void outputToFile(Document myDocument,String filePath,String encoding) {
//setup this like outputDocument
try {
XMLOutputter outputter = new XMLOutputter(" ", true,encoding);

//output to a file
FileWriter writer = new FileWriter(filePath);
outputter.output(myDocument, writer);
writer.close();

} catch(java.io.IOException e) {
e.printStackTrace();
}
}
/**
* 这个方法将JDom对象通过样式单转换.
* @param myDocument 将要被转换的JDom对象
* @param xslFilePath XSL文件的磁盘路径
*/
public static void executeXSL(Document myDocument,String xslFilePath,StreamResult xmlResult) {
try {
TransformerFactory tFactory = TransformerFactory.newInstance();
// Make the input sources for the XML and XSLT documents
org.jdom.output.DOMOutputter outputter = new org.jdom.output.DOMOutputter();
org.w3c.dom.Document domDocument = outputter.output(myDocument);
javax.xml.transform.Source xmlSource = new javax.xml.transform.dom.DOMSource(domDocument);
StreamSource xsltSource = new StreamSource(new FileInputStream(xslFilePath));
//Get a XSLT transformer
Transformer transformer = tFactory.newTransformer(xsltSource);
//do the transform
transformer.transform(xmlSource, xmlResult);
} catch(FileNotFoundException e) {
e.printStackTrace();
} catch(TransformerConfigurationException e) {
e.printStackTrace();
} catch(TransformerException e) {
e.printStackTrace();
} catch(org.jdom.JDOMException e) {
e.printStackTrace();
}
}

//Main 函数,局部测试用。
public static void main(String argv[]) {

}
}

2005年07月25日

 关键字:Java, JSP, Servlet, template, 模板, Apache, Jakarta, Velocity
读者要求:了解 Java Servlet 基本概念

 Velocity 是一个基于 Java 的通用模板工具,来自于 jakarta.apache.org 。

Velocity 的介绍请参考 Velocity — Java Web 开发新技术。这里是它的一个应用示例。

这个例子参照了 PHP-Nuke 的结构, 即所有 HTTP 请求都以 http://www.some.com/xxx/Modules?name=xxx&arg1=xxx&bbb=xxx 的形式进行处理。例子中所有文件都是 .java 和 .html , 没有其他特殊的文件格式。除了 Modules.java 是 Java Servlet, 其余的 .java 文件都是普通的 Java Class.

所有 HTTP 请求都通过 Modules.java 处理。Modules.java 通过 Velocity 加载 Modules.htm。 Modules.htm 有页头,页脚,页左导航链接,页中内容几个部分。其中页头广告、页中内容是变化部分。页头广告由 Modules.java 处理,页中内容部分由 Modules.java dispatch 到子页面类处理。

1) Modules.java

     
 
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.velocity.*;
import org.apache.velocity.context.*;
import org.apache.velocity.exception.*;
import org.apache.velocity.servlet.*;
import commontools.*;

public class Modules
  extends VelocityServlet {
  public Template handleRequest(HttpServletRequest request,
                 HttpServletResponse response,
                 Context context) {
    //init
    response.setContentType("text/html; charset=UTF-8");
    response.setCharacterEncoding("utf-8");

    //prepare function page
    ProcessSubPage page = null;
    ProcessSubPage mainPage = new HomeSubPage();
    String requestFunctionName = (String) request.getParameter("name");
    boolean logined = false;

    String loginaccount = (String) request.getSession(true).getAttribute(
      "loginaccount");
    if (loginaccount != null) {
      logined = true;
    }

    //default page is mainpage
    page = mainPage;
    if (requestFunctionName == null||requestFunctionName.equalsIgnoreCase("home")) {
      page = mainPage;
    }

    //no login , can use these page
    else if (requestFunctionName.equalsIgnoreCase("login")) {
      page = new LoginProcessSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("ChangePassword")) {
      page = new ChangePasswordSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("ForgetPassword")) {
      page = new ForgetPassword();
    }
    else if (requestFunctionName.equalsIgnoreCase("about")) {
      page = new AboutSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase("contact")) {
      page = new ContactSubPage();
    }

    //for other page, need login first
    else if (logined == false) {
      page = new LoginProcessSubPage();
    }

    else if (requestFunctionName.equalsIgnoreCase("listProgram")) {
      page = new ListTransactionProgramSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "ViewProgramItem")) {
      page = new ViewTransactionProgramItemSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "UpdateProgramObjStatus")) {
      page = new UpdateTransactionProgramObjStatusSubPage();
    }
    else if (requestFunctionName.equalsIgnoreCase(
      "Search")) {
      page = new SearchSubPage();
    }

    //check if this is administrator
    else if (Utilities.isAdministratorLogined(request)) {
      //Utilities.debugPrintln("isAdministratorLogined : true");
      if (requestFunctionName.equalsIgnoreCase("usermanagement")) {
        page = new UserManagementSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "UploadFiles")) {
        page = new UploadFilesSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "DownloadFile")) {
        page = new DownloadFileSubPage();
      }
      else if (requestFunctionName.equalsIgnoreCase(
        "Report")) {
        page = new ReportSubPage();
      }

    }
    else {
      //no right to access.
      //Utilities.debugPrintln("isAdministratorLogined : false");
      page = null;
    }
    //Utilities.debugPrintln("page : " + page.getClass().getName());

    if(page != null){
      context.put("function_page",
            page.getHtml(this, request, response, context));
    }else{
      String msg = "Sorry, this module is for administrator only.
You are not administrator.
";       context.put("function_page",msg);     }          context.put("page_header",getPageHeaderHTML());     context.put("page_footer",getPageFooterHTML());     Template template = null;     try {       template = getTemplate("/templates/Modules.htm"); //good     }     catch (ResourceNotFoundException rnfe) {       Utilities.debugPrintln("ResourceNotFoundException 2");       rnfe.printStackTrace();     }     catch (ParseErrorException pee) {       Utilities.debugPrintln("ParseErrorException2 " + pee.getMessage());     }     catch (Exception e) {       Utilities.debugPrintln("Exception2 " + e.getMessage());     }     return template;   }   /**    * Loads the configuration information and returns that information as a Properties, e    * which will be used to initializ the Velocity runtime.    */   protected java.util.Properties loadConfiguration(ServletConfig config) throws     java.io.IOException, java.io.FileNotFoundException {     return Utilities.initServletEnvironment(this);   } }
 
     

2) ProcessSubPage.java , 比较简单,只定义了一个函数接口 getHtml

     
 
import javax.servlet.http.*;
import org.apache.velocity.context.*;
import org.apache.velocity.servlet.*;
import commontools.*;

public abstract class ProcessSubPage implements java.io.Serializable {
  public ProcessSubPage() {
  }

  public String getHtml(VelocityServlet servlet, HttpServletRequest request,
             HttpServletResponse response,
             Context context) {
    Utilities.debugPrintln(
      "you need to override this method in sub class of ProcessSubPage:"
      + this.getClass().getName());
    return "Sorry, this module not finish yet.";
  }

}
 
     

他的 .java 文件基本上是 ProcessSubPage 的子类和一些工具类。 ProcessSubPage 的子类基本上都是一样的流程, 用类似
context.put("page_footer",getPageFooterHTML());
的写法置换 .html 中的可变部分即可。如果没有可变部分,完全是静态网页,比如 AboutSubPage, 就更简单。

3) AboutSubPage.java

     
 
import org.apache.velocity.servlet.VelocityServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.velocity.context.Context;

public class AboutSubPage extends ProcessSubPage {
  public AboutSubPage() {
  }

  public String getHtml(VelocityServlet servlet, HttpServletRequest request,
             HttpServletResponse response, Context context) {
    //prepare data
    //context.put("xxx","xxxx");         
             
    Template template = null;
    String fileName = "About.htm";
    try {
      template = servlet.getTemplate(fileName);
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template " + fileName + " " + ex.getMessage();
    }
  }
}
 
     

其他 ProcessSubPage 的子类如上面基本类似,只不过会多了一些 context.put("xxx","xxxx") 的语句。

通过以上的例子,我们可以看到,使用 Velocity + Servlet , 所有的代码为: 1 个 java serverlet + m 个 java class + n 个 Html 文件

这里是用了集中处理,然后分发(dispatch)的机制。不用担心用户在没有登陆的情况下访问某些页面。用户验证,页眉页脚包含都只写一次,易于编写、修改和维护。代码比较简洁,并且很容易加上自己的页面缓冲功能。可以随意将某个页面的 html 在内存中保存起来,缓存几分钟,实现页面缓冲功能。成功、出错页面也可以用同样的代码封装成函数,通过参数将 Message/Title 传入即可。

因为 Java 代码与 Html 代码完全在不同的文件中,美工与java代码人员可以很好的分工,每个人修改自己熟悉的文件,基本上不需要花时间做协调工作。而用 JSP, 美工与java代码人员共同修改维护 .jsp 文件,麻烦多多,噩梦多多。而且这里没有用 xml ,说实话,懂 xml/xls 之类的人只占懂 Java 程序员中的几分之一,人员不好找。

因为所有 java 代码人员写的都是标准 Java 程序,可以用任何 Java 编辑器,调试器,因此开发速度也会大大提高。美工写的是标准 Html 文件,没有 xml, 对于他们也很熟悉,速度也很快。并且,当需要网站改版的时候,只要美工把 html 文件重新修饰排版即可,完全不用改动一句 java 代码。

爽死了!!

4) 工具类 Utilities.java

     
 
import java.io.*;
import java.sql.*;
import java.text.*;
import java.util.*;
import java.util.Date;
import javax.naming.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.velocity.*;
import org.apache.velocity.app.*;
import org.apache.velocity.context.Context;
import org.apache.velocity.servlet.*;

public class Utilities {
  private static Properties m_servletConfig = null;

  private Utilities() {
  }

  static {
    initJavaMail();
  }

  public static void debugPrintln(Object o) {
    String msg = "proj debug message at " + getNowTimeString() +
      " ------------- ";
    System.err.println(msg + o);
  }

  public static Properties initServletEnvironment(VelocityServlet v) {
    // init only once
    if (m_servletConfig != null) {
      return m_servletConfig;
    }

    //debugPrintln("initServletEnvironment....");

    try {
      /*
       * call the overridable method to allow the
       * derived classes a shot at altering the configuration
       * before initializing Runtime
       */
      Properties p = new Properties();

      ServletConfig config = v.getServletConfig();

      // Set the Velocity.FILE_RESOURCE_LOADED_PATH property
      // to the root directory of the context.
      String path = config.getServletContext().getRealPath("/");
      //debugPrintln("real path of / is : " + path);
      p.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, path);

      // Set the Velocity.RUNTIME_LOG property to be the file
      // velocity.log relative to the root directory
      // of the context.
      p.setProperty(Velocity.RUNTIME_LOG, path +
             "velocity.log");
// Return the Properties object.
//return p;

      Velocity.init(p);
      m_servletConfig = p;
      return p;
    }
    catch (Exception e) {
      debugPrintln(e.getMessage());
      //throw new ServletException("Error initializing Velocity: " + e);
    }
    return null;

    //this.getServletContext().getRealPath("/");
  }

  private static void initJavaMail() {
  }

  public static Connection getDatabaseConnection() {
    Connection con = null;
    try {
      InitialContext initCtx = new InitialContext();
      javax.naming.Context context = (javax.naming.Context) initCtx.
        lookup("java:comp/env");
      javax.sql.DataSource ds = (javax.sql.DataSource) context.lookup(
        "jdbc/TestDB");
      //Utilities.debugPrintln("ds = " + ds);
      con = ds.getConnection();
    }
    catch (Exception e) {
      Utilities.debugPrintln("Exception = " + e.getMessage());
      return null;
    }
    //Utilities.debugPrintln("con = " + con);
    return con;
  }

  public static java.sql.ResultSet excuteDbQuery(Connection con, String sql,
    Object[] parameters) {
    //Exception err = null;
    //Utilities.debugPrintln("excuteDbQuery" + parameters[0] + " ,sql=" + sql);

    try {
      java.sql.PreparedStatement ps = con.prepareStatement(sql);
      for (int i = 0; i < parameters.length; i++) {
        processParameter(ps, i + 1, parameters[i]);
      }
      return ps.executeQuery();
    }
    catch (Exception e) {
      //Utilities.debugPrintln(e.getMessage());
      e.printStackTrace();
    }
    return null;
  }

  public static void excuteDbUpdate(String sql, Object[] parameters) {
    Connection con = Utilities.getDatabaseConnection();
    excuteDbUpdate(con, sql, parameters);
    closeDbConnection(con);
  }

  public static void excuteDbUpdate(Connection con, String sql,
                   Object[] parameters) {
    Exception err = null;
    try {
      java.sql.PreparedStatement ps = con.prepareStatement(sql);
      for (int i = 0; i < parameters.length; i++) {
        processParameter(ps, i + 1, parameters[i]);
      }
      ps.execute();
    }
    catch (Exception e) {
      err = e;
      //Utilities.debugPrintln(err.getMessage());
      e.printStackTrace();
    }
  }

  private static void processParameter(java.sql.PreparedStatement ps,
                     int index, Object parameter) {
    try {
      if (parameter instanceof String) {
        ps.setString(index, (String) parameter);
      }
      else {
        ps.setObject(index, parameter);
      }
    }
    catch (Exception e) {
      //Utilities.debugPrintln(e.getMessage());
      e.printStackTrace();
    }
  }

  public static void closeDbConnection(java.sql.Connection con) {
    try {
      con.close();
    }
    catch (Exception e) {
      Utilities.debugPrintln(e.getMessage());
    }
  }

  public static String getResultPage(
    String title, String message, String jumpLink,
    VelocityServlet servlet, HttpServletRequest request,
    HttpServletResponse response, Context context) {

    Template template = null;

    context.put("MessageTitle", title);
    context.put("ResultMessage", message);
    context.put("JumpLink", jumpLink);

    try {
      template = servlet.getTemplate(
        "/templates/Message.htm");
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template Message.htm " + ex.getMessage();
    }

  }

  public static String mergeTemplate(String fileName, VelocityServlet servlet,
                    Context context) {
    Template template = null;

    try {
      template = servlet.getTemplate(fileName);
      StringWriter sw = new StringWriter();
      template.merge(context, sw);
      return sw.toString();
    }
    catch (Exception ex) {
      return "error get template " + fileName + " " + ex.getMessage();
    }
  }

}
 
     

 

注意:基于排版的需要,代码中使用了中文全角空格。如果要复制代码,请在复制后进行文字替换。


说明: 
前一段时间,看到网上一个网友贴出一个用 Java 跨平台的生成 Jpeg 图片缩影图的方法。最近整理了一下,形成了一个通用的类。并制作成一个 jar 包。以方便重用。并给出了使用示例。(作者: abnerchai ;联系: josserchai@yahoo.com ) 

一、源代码 
1、JpegTool.java 
// JpegTool.java 

package com.abner.jpegtool; 

import javax.imageio.ImageIO; 

import javax.imageio.IIOException; 

import java.awt.image.BufferedImage; 

import java.awt.Image; 

import java.io.File; 

import java.awt.image.AffineTransformOp; 

import java.awt.geom.AffineTransform; 

import com.abner.jpegtool.JpegToolException; 

import java.io.*; 

/** 

* 本类实现一个对 JPG/JPEG 图像文件进行缩影处理的方法 

* 即给定一个 JPG 文件,可以生成一个该 JPG 文件的缩影图像文件 (JPG 格式 ) 

* 提供三种生成缩影图像的方法: 

* 1 、设置缩影文件的宽度,根据设置的宽度和源图像文件的大小来确定新缩影文件的长度来生成缩影图像 

* 2 、设置缩影文件的长度,根据设置的长度和源图像文件的大小来确定新缩影文件的宽度来生成缩影图像 

* 3 、设置缩影文件相对于源图像文件的比例大小,根据源图像文件的大小及设置的比例来确定新缩影文件的大小来生成缩影图像 

* 新生成的缩影图像可以比原图像大,这时即是放大源图像。 

* @author abnerchai contact:josserchai@yahoo.com 

* @version 1.0 

* @exception JpegToolException 

*/ 

public class JpegTool  

// 对象是否己经初始化 

private boolean isInitFlag = false; 

// 定义源图片所在的带路径目录的文件名 

private String pic_big_pathfilename = null; 

// 生成小图片的带存放路径目录的文件名 

private String pic_small_pathfilename = null; 

// 定义生成小图片的宽度和高度,给其一个就可以了 

private int smallpicwidth = 0; 

private int smallpicheight = 0; 

// 定义小图片的相比原图片的比例 

private double picscale = 0; 

/** 

* 构造函数 

* @param 没有参数 

*/ 

public JpegTool(){ 

this.isInitFlag = false; 

{} 

/** 

* 私有函数,重置所有的参数 

* @param 没有参数 

* @return 没有返回参数 

*/ 

private void resetJpegToolParams() 

this.picscale = 0; 

this.smallpicwidth = 0; 

this.smallpicheight = 0; 

this.isInitFlag = false; 

{} 

/** 

* @param scale 设置缩影图像相对于源图像的大小比例如 0.5 

* @throws JpegToolException 

*/ 

public void SetScale(double scale) throws JpegToolException  

if(scale<=0){ 

throw new JpegToolException(" 缩放比例不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.picscale = scale; 

this.isInitFlag = true; 



/** 

* @param smallpicwidth 设置缩影图像的宽度 

* @throws JpegToolException 

*/ 

public void SetSmallWidth(int smallpicwidth) throws JpegToolException  

if(smallpicwidth<=0){ 

throw new JpegToolException(" 缩影图片的宽度不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.smallpicwidth = smallpicwidth; 

this.isInitFlag = true; 



/** 

* @param smallpicheight 设置缩影图像的高度 

* @throws JpegToolException 

*/ 

public void SetSmallHeight(int smallpicheight) throws JpegToolException  

if(smallpicheight<=0){ 

throw new JpegToolException(" 缩影图片的高度不能为 0 和负数! "); 

{} 

this.resetJpegToolParams(); 

this.smallpicheight = smallpicheight; 

this.isInitFlag = true; 



/** 

* 生成源图像的缩影图像 

* @param pic_big_pathfilename 源图像文件名,包含路径(如 windows 下 C:\pic.jpg ; Linux 下 /home/abner/pic/pic.jpg ) 

* @param pic_small_pathfilename 生成的缩影图像文件名,包含路径(如 windows 下 C:\pic_small.jpg ; Linux 下 /home/abner/pic/pic_small.jpg ) 

* @throws JpegToolException 

*/ 

public void doFinal(String pic_big_pathfilename,String pic_small_pathfilename) throws JpegToolException  

if(!this.isInitFlag){ 

throw new JpegToolException(" 对象参数没有初始化! "); 

{} 

if(pic_big_pathfilename==null || pic_small_pathfilename==null) 

throw new JpegToolException(" 包含文件名的路径为空! "); 

{} 

if((!pic_big_pathfilename.toLowerCase().endsWith("jpg")) && (!pic_big_pathfilename.toLowerCase().endsWith("jpeg"))) 

throw new JpegToolException(" 只能处理 JPG/JPEG 文件! "); 

{} 

if((!pic_small_pathfilename.toLowerCase().endsWith("jpg")) && (!pic_small_pathfilename.toLowerCase().endsWith("jpeg"))) 

throw new JpegToolException(" 只能处理 JPG/JPEG 文件! "); 

{} 

int smallw = 0; 

int smallh = 0; 

// 新建源图片和生成的小图片的文件对象 

File fi = new File(pic_big_pathfilename); 

File fo = new File(pic_small_pathfilename); 

// 生成图像变换对象 

AffineTransform transform = new AffineTransform(); 

// 通过缓冲读入源图片文件 

BufferedImage bsrc = null; 

try  

bsrc = ImageIO.read(fi); 

{}catch (IOException ex)  

throw new JpegToolException(" 读取源图像文件出错! "); 

{} 

int srcw = bsrc.getWidth();// 原图像的长度 

int srch = bsrc.getHeight();// 原图像的宽度 

double scale = (double)srcw/srch;// 图像的长宽比例 

if(this.smallpicwidth!=0)// 根据设定的宽度求出长度 

smallw = this.smallpicwidth;// 新生成的缩略图像的长度 

smallh = (smallw*srch)/srcw ;// 新生成的缩略图像的宽度 

{}else if(this.smallpicheight!=0)// 根据设定的长度求出宽度 

smallh = this.smallpicheight;// 新生成的缩略图像的长度 

smallw = (smallh*srcw)/srch;// 新生成的缩略图像的宽度 

{}else if(this.picscale!=0)// 根据设置的缩小比例设置图像的长和宽 

smallw = (int)((float)srcw*this.picscale); 

smallh = (int)((float)srch*this.picscale); 

{}else 

throw new JpegToolException(" 对象参数初始化不正确! "); 

{} 

System.out.println(" 源文件大小: "+srcw+"X"+srch); 

System.out.println(" 新文件大小: "+smallw+"X"+smallh); 

double sx = (double)smallw/srcw;// 小 / 大图像的宽度比例 

double sy = (double)smallh/srch;// 小 / 大图像的高度比例 

transform.setToScale(sx,sy);// 设置图像转换的比例 

// 生成图像转换操作对象 

AffineTransformOp ato = new AffineTransformOp(transform,null); 

// 生成缩小图像的缓冲对象 

BufferedImage bsmall = new BufferedImage(smallw,smallh,BufferedImage.TYPE_3BYTE_BGR); 

// 生成小图像 

ato.filter(bsrc,bsmall); 

// 输出小图像 

try  

ImageIO.write(bsmall, "jpeg", fo); 

{}catch (IOException ex1)  

throw new JpegToolException(" 写入缩略图像文件出错! "); 

{} 





2、JpegToolException.java 
// JpegToolException.java 

package com.abner.jpegtool; 

/** 

* <p>Description: JpegTool 使用的异常类 </p> 

* @author abnerchai 

* @version 1.0 

*/ 

public class JpegToolException extends Exception  

private String errMsg = ""; 

public JpegToolException(String errMsg) { 

this.errMsg = errMsg; 

{} 

public String getMsg() 

return "JpegToolException:"+this.errMsg; 

{} 



二、 jar包制作 
编译上面两个文件,形成两个 class 文件,均存放在目录:当前目录 comabnerjpegtool 目录下面。进入当前目录,执行: jar cvf jpegtool.jar com/* ;便在当前目录生成一个 jpegtool.jar 文件。发布这个 jar 包,以后便可以像使用其它 jar 包一样使用这个工具了,下面我们给出一个示例。 

三、使用示例 
为了方便,我们给出在 JSP 文件中使用该包的示例 JSP 代码,首先,设置你的类库路径,将 jpegtool.jar 文件放入你的类库路径中。然后设置你的应用服务器的类库路径,将 jpegtool.jar 放入你的应用服务器的类库路径中。然后测试下面的 JSP 代码: 

<!—test.jsp – > 

<%@page contentType="text/html; charset=GB2312" %> 

<%@page import = "com.abner.jpegtool.JpegTool"%> 

<html><head><title>test</title></head> 

<body bgcolor="#ffffff"> 

<% 

try 

JpegTool jpegTool = new JpegTool(); 

jpegTool.SetSmallWidth(100); 

jpegTool.doFinal("C:\big.jpeg","C:\small.jpeg"); 

{}catch(Exception e) 

e.printStackTrace(); 

{} 

%> 

</body> 

</html> 

对于在 Servlet 和 Java 程序中使用,类似于上面的 JSP 。 

注意:以上程序必须在支持 JDK1.4 的平台上运行。否则有些类库找不到。 
AbnerChai [原作]   

2005年07月24日

作者:肖菁

文章摘要

JFreeReport用于生成报表。JFreeReport的数据继承自Swing组件的TableModel接口,使用基于XML的报表格式定义文件对报表进行格式化。JFreeReport生成的报表可以分页预览、打印或者保存为多种格式的文件如pdf、Excel、html等。作者在本文中详细的介绍了如何定义报表格式定义文件、如何使用JFreeReport生成/预览报表和将报表转为其它各种格式。

关键词

JFreeReport  报表生成  预览

报表生成一直是企业信息化过程中重要的一环,也是目前比较难于实现的一环,今天作者给大家介绍一种新的报表生成组件――JFreeReport。JFreeReport是JFreeReport.Org基于LGPL授权协议提供的一组java包,用于生成各类报表,JFreeReport的数据继承自Swing组件的TableModel接口,使用基于XML的报表格式定义文件对报表进行格式化。JFreeReport生成的报表可以分页预览、打印,而且支持导出为多种格式的文件如pdf、Excel、CSV、html等。更重要的是,JFreeReport不仅支持基于C/S结构的系统,而且支持基于B/S结构的系统中的在线报表显示。更详细的关于JFreeReport的介绍请大家访问JFreeReport的官方网站JFree.org

1  环境准备

1.1  JFreeReport组件

请大家到http://prdownloads.sourceforge.net/jfreereport/jfreereport-0.8.4_7.zip?download下载JFreeReport组件,下载的是一个ZIP文件,然后将ZIP文件解压缩到c:\jfreereport(后面的章节中将使用%jfreereport_home%表示这个目录)目录下。

1.2  JFreeReport扩展组件

请大家到http://www.jfree.org/jfreereport/jfreereport-ext-0.8.4_7.zip下载JFreeReport扩展组件,他用于支持JFreeReport组件生成的报表的在线显示。请大载后解压缩到c:\jfreereport-ext目录下(后面的章节中将使用%jfreereport_ext_home%表示这个目录)

1.3  Ant工具

Apache公司提供的一个基于JAVA的自动化脚本引擎,请大家到http://ant.apache.org/下载ant的可执行文件,关于如何使用ant请大家查看ant的帮助文档或者http://ant.apache.org/网站上的在线帮助文档。示例中主要是用ant来负责编译java代码。

1.4  作者提供的代码

为了运行本文中作者提到的例子和相关资源文件,请大家下载作者提供的vivianjDemo.zip文件和中文转换工具gb2unicode.jar。然后解压缩到%jfreereport_home%\vivianjDemo(后面的章节中将使用%demo _home%表示这个目录)目录下。

2  JFreeReport生成报表的基本步骤

我们首先演示一个简单的例子,说明使用JFreeReport生成报表的一些必要的步骤。

2.1  实例说明

在这个例子中,我们将循环生成100条数据放入TableModel中,然后使用JFreeReport组件提供的预览功能在屏幕上显示生成的报表。

[注] 为了简化,这里仅仅是逐条显示数据,不作任何修饰和统计工作,所以也不使用报表格式定义文件。

2.2  代码编制

整个演示实例(HelloWorld.java)的代码和相关注释如下,如果你执行了1.3中规定的步骤,你可以在%demo _home%/src/org/vivianj/jfreereport/看到这个文件。

/**
 * HelloWorld.java
 */

package org.vivianj.jfreereport;

import java.awt.Color;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Point2D;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableModel;

import org.jfree.report.Boot;
import org.jfree.report.ElementAlignment;
import org.jfree.report.JFreeReport;
import org.jfree.report.ReportProcessingException;
import org.jfree.report.elementfactory.TextFieldElementFactory;
import org.jfree.report.modules.gui.base.PreviewDialog;
import org.jfree.ui.FloatDimension;

/**
 * 使用JFreeReport生成报表的简单例子,用于演示使用JFreeReport生成报表的一些基本步骤
 *
 * 本例子中,为了简化操作,报表定义是使用java直接编码
 *
 * @ 作者 : bookman
 */
public class HelloWorld
{
  /**
   * 处理窗口关闭事件
   */
  protected static class CloseHandler extends WindowAdapter
  {
    public void windowClosing(final WindowEvent event)
    {
      System.exit(0);
    }
  }

  /**
   * 创建和显示简单的报表
   */
  public HelloWorld()
  {
	// 获得创建报表需要用到的数据
    final TableModel data = createData();
	//获得报表要用到的报表定义内容
    final JFreeReport report = createReportDefinition();
	//将报表定义和数据结合
    report.setData(data);
    try
    {
	  //将生成的报表放到预览窗口中
      final PreviewDialog preview = new PreviewDialog(report);
      preview.addWindowListener(new CloseHandler());
      preview.pack();
	  //显示报表预览窗口
      preview.setVisible(true);
    }
    catch (ReportProcessingException e)
    {
      System.out.println(e);
    }

  }

  /**
   * 创建生成报表需要用到的数据
   *
   * @返回一个TableModel实例
   */
  private TableModel createData()
  {

    final Object[] columnNames = new String[]{"Column1", "Column2"};
    final DefaultTableModel result = new DefaultTableModel(columnNames, 100);
	int rownum = 0;
	int colnum = 0;
	for (;rownum < 100 ; rownum++)
	{
        result.setValueAt("say Hello " + rownum + "次", rownum, 0);
        result.setValueAt("say World " + rownum + "次" , rownum, 1);
	}

    return result;

  }

  /**
   * 创建一个报表定义
   *
   * @返回一个报表定义实例
   */
  private JFreeReport createReportDefinition()
  {

    final JFreeReport report = new JFreeReport();
    report.setName("A Very Simple Report");
	/**
	  * 定义要显示报表第一列的样式
	 */
    TextFieldElementFactory factory = new TextFieldElementFactory();
    factory.setName("T1");
    factory.setAbsolutePosition(new Point2D.Float(0, 0));
    factory.setMinimumSize(new FloatDimension(150, 20));
    factory.setColor(Color.black);
    factory.setHorizontalAlignment(ElementAlignment.LEFT);
    factory.setVerticalAlignment(ElementAlignment.MIDDLE);
    factory.setNullString("-");
    factory.setFieldname("Column1");
    report.getItemBand().addElement(factory.createElement());
	/**
	  * 定义要显示报表第二列的样式
	 */
    factory = new TextFieldElementFactory();
    factory.setName("T2");
    factory.setAbsolutePosition(new Point2D.Float(200, 0));
    factory.setMinimumSize(new FloatDimension(150, 20));
    factory.setColor(Color.black);
    factory.setHorizontalAlignment(ElementAlignment.LEFT);
    factory.setVerticalAlignment(ElementAlignment.MIDDLE);
    factory.setNullString("-");
    factory.setFieldname("Column2");
    report.getItemBand().addElement(factory.createElement());
	/**
	  * 返回一个报表定义的实例
	 */
    return report;

  }

  public static void main(final String[] args)
  {
    // 初始化JFreeReport
    Boot.start();

    //调用演示实例
    new HelloWorld();
  }

}

2.3  运行例子

如果你执行了1.3中规定的步骤,你可以进入命令行界面,然后进入%demo_home%目录下,修改setenv.cmd中的相关设置,执行serenv.cmd设置环境变量。执行java org.vivianj.jfreereport.HelloWorld查看运行结果。下面这个图片是作者执行后结果的屏幕截图:

大家可以看到,JFreeReport已经自动帮我们实现了分页。上面这个图片显示的是第一页的数据,你可以通过工具栏中的查看其它页面中的内容。

2.4  基本步骤解释

使用JFreeReport生成报表通常需要以下三个基本步骤:

  1. 生成可通过TableModel接口访问的数据,如本例中的createData方法完成的功能
  2. 生成一个JFreeReport实例,他定义了我们如何格式化显示数据,如本例中的createReportDefinition方法完成的功能
  3. 将数据和JFreeReport实例连接起来,并且将该JFreeReport实例传给PreviewDialog的一个实例显示给用户

3  使用JFreeReport生成复杂报表

3.1  报表定义文件

报表定义文件是JFreeReport生成复杂报表的重要文件,他就是一个XML文档,主要描述如何使用指定的格式生成复杂的报表,同时使用报表定义文件也可以在报表格式需要修改时只需要更新该报表定义文件,而不需要修改应用代码。

3.1.1  报表定义文件分类

JFreeReport中使用了两种基于XML的报表定义文件来保存报表定义信息:简单格式和扩展格式.很明显,简单格式不能够完全的描述JFreeReport支持的全部报表定义信息,但是他更易于上手使用。而扩展格式则能够对JFreeReport的报表定义提供完整的支持,但是扩展格式太详细了,不太容易使用。

关于这两种报表定义格式文件所支持的标签内容以及如何编写这两种格式的报表定义文件请大家参考%jfreereport_home%下的jfreereport-0.8.3-A4.pdf中的相关部分,该文件附录中还包括了这两种格式的报表定义文件的DTD文档供大家参考.当然大家也JFreeReport的例子中提供了多种形式的报表定义文件,基本上涵盖了常用的报表格式定义,大家可以参考这些例子编写自己的报表定义文件,。

3.2  代码编制

这个例子和2.2中的代码基本一致,只是报表定义内容不再由java编码实现,而是由报表定义文件提供,所以调用方面稍微有点不同,详细的代码如下,请大家注意其中加粗显示的部分:

/**
 * JFreeReport.java
 */

 package org.vivianj.jfreereport;

import java.io.File;
import java.text.MessageFormat;
import javax.swing.table.TableModel;
import javax.swing.JFrame;

import org.jfree.ui.RefineryUtilities;
import org.jfree.report.Boot;
import org.jfree.report.modules.gui.base.PreviewFrame;
import org.jfree.report.modules.parser.base.ReportGenerator;
import org.jfree.report.JFreeReport;

import org.vivianj.jfreereport.tablemodel.SampleData;

/**
 * 使用JFreeReport生成复杂报表的例子,
 * 用于演示使用JFreeReport生成复杂报表的一些基本步骤
 *
 * 本例子中,报表定义使用了报表定义文件,该文件是保存在c:\下的report3.xml文件
 * 本例中使用的报表定义使用了简单报表定义格式
 *
 * @ 作者 : bookman
 */
public class JFreeTest
{

	public JFreeTest(final String urlname, final TableModel data)
  {
	//创建对报表定义文件的引用
    final File in = new File(urlname);;
    if (in == null)
    {
      System.out.print(" in is null");
      return;
    }

    System.out.print("Processing Report: " + in);
    final ReportGenerator gen = ReportGenerator.getInstance();

    try
    {
      //从报表定义文件中获得要用到的报表定义内容
      final JFreeReport report1 = gen.parseReport(in);
      if (report1 == null)
      {
        System.out.print(" report1 is null");
        return;
      }
      //将报表定义和数据结合
      report1.setData(data);
	  //将生成的报表放到预览窗口中
      final PreviewFrame frame1 = new PreviewFrame(report1);
      frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame1.pack();
      RefineryUtilities.positionFrameRandomly(frame1);
      frame1.setVisible(true);
      frame1.requestFocus();
    }
    catch (Exception e)
    {
      System.out.print("report.definitionfailure-----------------------\r\n" + e);
    }
  }

  public static void main(String[] args)
	{
		Boot.start();

		final TableModel data3 = new SampleData();
		JFreeTest jft = new JFreeTest("c:\\report3.xml",data3);
	}
}

4  中文乱码问题

大家在参看报表定义文件的时候会发现,里面的报表头定义中有一些显示内容,如果你直接将他修改成中文,显示在报表上就会乱码,这是因为这些报表定义文件都是XML文档,他的encoding默认设置是ISO-8859-1,所以出现了中文乱码的问题,解决办法有两个:

1.最简单的方法就是将encoding的设置修改为GB2312

2.还有一个方法就是不修改encoding的设置,而是将这些中文内容使用他们的unicode码来代替。

[注] 作者提供了一个GUI的界面提供这种转化功能,你只需要输入中文就可以获得对应的unicode码,请大家下载作者提供的gb2unicode.jar,设置好java运行环境,然后执行java -jar gb2unicode.jar就可以了。

5  总结

报表问题是企业信息化中比较关注的一个焦点,也比较难于实现客户化,作者在本文中给大家介绍了一个新的报表生成包-JfreeReport,他可以根据XML文档中定义的报表格式,生成符合客户要求的报表。文章中给出了2个简单的例子,演示了如何使用JFreeReport生成报表的详细步骤,希望能够帮助大家熟悉JFreeReport的工作方式。同时文章中给出了使用JFreeReport过程中可能遇到的中文问题的解决办法。

其实JFreeReport还支持在JSP、Servlet中显示报表,只是还需要用到他的扩展组件,作者这里没有给出例子,大家可以参考下载的JFreeReport的扩展组件中的例子。

参考资料

JFreeReport 在线帮助文档 http://www.jfree.org/jfreereport/index.html

JFreeReport 的JavaDoc http://www.jfree.org/jfreereport/javadoc/index.html

关于作者

肖菁目前是湖南省长沙铁道学院科创计算机系统集成有限公司软件中心软件工程师,IBM developerworks/BEA dev2dev撰稿人,主要研究J2EE编程技术、Web Service技术以及他们在websphere、weblogic、apache平台上的实现,拥有IBM 的 Developing With Websphere Studio证书,个人网站:http://vivianj.go.nease.net/

Java Image功能一直都在增强,但是向磁盘写一个图像文件或者返回一个PNG或JPEG依然比较难实现。但是我们可以使用Java Advanced Imaging(JAI)API来解决这个问题。JAI可以从SUN的Java站点下载,它包含在JDK 1.4的javax.imageio包中。

Windows中提供一个installation.exe文件提供JAI的自动安装,而UNIX和Linux中则没有。虽然JAI可以运行在纯Java模式,但是同时也提供有Windows、Linux以及UNIX的本地库,这样可以提高运行速度。

下面的例子是安装在苹果的OS X上的JAI,所以我们选择纯Java模式。要安装JAI,你需要jai tar.gz文件中的一个。将三个重要的jar文件,mlibwrapper_jai.jar, jai_codec.jar, and jai_core.jar放到你的classpath中。我们推荐将他们放到你的JDK的jre/lib/ext目录下。

对于Java来说,JAI是一个奇怪的系统,不像其他的方面有很多方法需要了解,这里只有一个叫做JAI的高级类和一些辅助方法。这些方法的第一个参数是一个操作类型名字,所以我们将会看到这样的代码:

source = JAI.create("fileload", .. );
JAI.create("extrema", src, …);
JAI.create("histogram", src, …);

在这个系统中,你可以很容易的添加你自己的或者第三方的功能,对类型的要求比较松,所以利用它进行开发难度比较大。

将一个AWT图像转化成一个PNG文件需要下面的一小段代码:

import java.awt.Image;
import java.awt.image.renderable.ParameterBlock;

import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
…..
Image img = ….
OutputStream out =….
ParameterBlock pb = new ParameterBlock().add(img);
PlanarImage src = (PlanarImage) JAI.create("awtImage", pb);
JAI.create("encode", src, out, "PNG", null);

从上面的例子可以看出向create方法传递参数有两种方法。新方法是使用ParameterBlock包含所有的参数。我们使用这种方法实现了awtImage操作。老方法是则使用一个重载方法,现在已不提倡使用,但是我们可以使用这种方法实现解码操作。

上面的代码将把一个Image编码成为一个PNG并将其写入OutputStream中。要得到更多有关JAI的信息,我们强烈推荐你阅读指南。
2005年07月22日

  下面代码中用到的sourceImage是一个已经存在的Image对象

图像剪切

  对于一个已经存在的Image对象,要得到它的一个局部图像,可以使用下面的步骤:

//import java.awt.*;
//import java.awt.image.*;

Image croppedImage;
ImageFilter cropFilter;
CropFilter =new CropImageFilter(25,30,75,75); //四个参数分别为图像起点坐标和宽高,即CropImageFilter(int x,int y,int width,int height),详细情况请参考API
CroppedImage= Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(sourceImage.getSource(),cropFilter));


  如果是在Component的子类中使用,可以将上面的Toolkit.getDefaultToolkit().去掉。FilteredImageSource是一个ImageProducer对象。

图像缩放

  对于一个已经存在的Image对象,得到它的一个缩放的Image对象可以使用Image的getScaledInstance方法:

Image scaledImage=sourceImage. getScaledInstance(100,100, Image.SCALE_DEFAULT); //得到一个100X100的图像
Image doubledImage=sourceImage. getScaledInstance(sourceImage.getWidth(this)*2,sourceImage.getHeight(this)*2, Image.SCALE_DEFAULT); //得到一个放大两倍的图像,这个程序一般在一个swing的组件中使用,而类Jcomponent实现了图像观察者接口ImageObserver,所有可以使用this。
//其它情况请参考API



灰度变换

  下面的程序使用三种方法对一个彩色图像进行灰度变换,变换的效果都不一样。一般而言,灰度变换的算法是将象素的三个颜色分量使用R*0.3+G*0.59+B*0.11得到灰度值,然后将之赋值给红绿蓝,这样颜色取得的效果就是灰度的。另一种就是取红绿蓝三色中的最大值作为灰度值。java核心包也有一种算法,但是没有看源代码,不知道具体算法是什么样的,效果和上述不同。

/* GrayFilter.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.image.*;

public class GrayFilter extends RGBImageFilter {
int modelStyle;
public GrayFilter() {
modelStyle=GrayModel.CS_MAX;
canFilterIndexColorModel=true;
}
public GrayFilter(int style) {
modelStyle=style;
canFilterIndexColorModel=true;
}
public void setColorModel(ColorModel cm) {
if (modelStyle==GrayModel
else if (modelStyle==GrayModel
}
public int filterRGB(int x,int y,int pixel) {
return pixel;
}
}

/* GrayModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/


import java.awt.image.*;

public class GrayModel extends ColorModel {
public static final int CS_MAX=0;
public static final int CS_FLOAT=1;
ColorModel sourceModel;
int modelStyle;

public GrayModel(ColorModel sourceModel) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
modelStyle=0;
}

public GrayModel(ColorModel sourceModel,int style) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
modelStyle=style;
}

public void setGrayStyle(int style) {
modelStyle=style;
}

protected int getGrayLevel(int pixel) {
if (modelStyle==CS_MAX) {
return Math.max(sourceModel.getRed(pixel),Math.max(sourceModel.getGreen(pixel),sourceModel.getBlue(pixel)));
}
else if (modelStyle==CS_FLOAT){
return (int)(sourceModel.getRed(pixel)*0.3+sourceModel.getGreen(pixel)*0.59+sourceModel.getBlue(pixel)*0.11);
}
else {
return 0;
}
}

public int getAlpha(int pixel) {
return sourceModel.getAlpha(pixel);
}

public int getRed(int pixel) {
return getGrayLevel(pixel);
}

public int getGreen(int pixel) {
return getGrayLevel(pixel);
}

public int getBlue(int pixel) {
return getGrayLevel(pixel);
}

public int getRGB(int pixel) {
int gray=getGrayLevel(pixel);
return (getAlpha(pixel)<<24)+(gray<<16)+(gray<<8)+gray;
}
}


  如果你有自己的算法或者想取得特殊的效果,你可以修改类GrayModel的方法getGrayLevel()。

色彩变换

  根据上面的原理,我们也可以实现色彩变换,这样的效果就很多了。下面是一个反转变换的例子:

/* ReverseColorModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.image.*;

public class ReverseColorModel extends ColorModel {
ColorModel sourceModel;
public ReverseColorModel(ColorModel sourceModel) {
super(sourceModel.getPixelSize());
this.sourceModel=sourceModel;
}

public int getAlpha(int pixel) {
return sourceModel.getAlpha(pixel);
}

public int getRed(int pixel) {
return ~sourceModel.getRed(pixel);
}

public int getGreen(int pixel) {
return ~sourceModel.getGreen(pixel);
}

public int getBlue(int pixel) {
return ~sourceModel.getBlue(pixel);
}

public int getRGB(int pixel) {
return (getAlpha(pixel)<<24)+(getRed(pixel)<<16)+(getGreen(pixel)<<8)+getBlue(pixel);
}
}
/* ReverseColorModel.java*/
/*@author:cherami */
/*email:cherami@163.net*/


import java.awt.image.*;

public class ReverseFilter extends RGBImageFilter {
public ReverseFilter() {
canFilterIndexColorModel=true;
}

public void setColorModel(ColorModel cm) {
substituteColorModel(cm,new ReverseColorModel(cm));
}

public int filterRGB(int x,int y,int pixel) {
return pixel;
}
}


  要想取得自己的效果,需要修改ReverseColorModel.java中的三个方法,getRed、getGreen、getBlue。
  下面是上面的效果的一个总的演示程序。

/*GrayImage.java*/
/*@author:cherami */
/*email:cherami@163.net*/

import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import java.awt.color.*;

public class GrayImage extends JFrame{
Image source,gray,gray3,clip,bigimg;
BufferedImage bimg,gray2;
GrayFilter filter,filter2;
ImageIcon ii;
ImageFilter cropFilter;
int iw,ih;

public GrayImage() {
ii=new ImageIcon(\"images/11.gif\");
source=ii.getImage();
iw=source.getWidth(this);
ih=source.getHeight(this);
filter=new GrayFilter();
filter2=new GrayFilter(GrayModel.CS_FLOAT);
gray=createImage(new FilteredImageSource(source.getSource(),filter));
gray3=createImage(new FilteredImageSource(source.getSource(),filter2));
cropFilter=new CropImageFilter(5,5,iw-5,ih-5);
clip=createImage(new FilteredImageSource(source.getSource(),cropFilter));
bigimg=source.getScaledInstance(iw*2,ih*2,Image.SCALE_DEFAULT);
MediaTracker mt=new MediaTracker(this);
mt.addImage(gray,0);
try {
mt.waitForAll();
} catch (Exception e) {
}
}

public void paint(Graphics g) {
Graphics2D g2=(Graphics2D)g;
bimg=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB);
Graphics2D srcG = bimg.createGraphics();
RenderingHints rhs = g2.getRenderingHints();
srcG.setRenderingHints(rhs);
srcG.drawImage(source, 0, 0, null);
ColorSpace graySpace=ColorSpace.getInstance(ColorSpace.CS_GRAY);
ColorConvertOp op=new ColorConvertOp(graySpace,rhs);
gray2=new BufferedImage(iw, ih, BufferedImage.TYPE_INT_RGB);
op.filter(bimg,gray2);
g2.drawImage(source,40,40,this);
g2.drawImage(gray,80,40,this);
g2.drawImage(gray2,120,40,this);
g2.drawImage(gray3,160,40,this);
g2.drawImage(clip,40,80,this);
g2.drawImage(bigimg,80,80,this);
}

public void update(Graphics g) {
paint(g);
}

public static void main(String args[]) {
GrayImage m=new GrayImage();
m.setSize(400,400);
m.setVisible(true);
}
}

2005年07月16日
Command对象

一个SQL命令的描述文本可能是相当复杂的,有的命令中还要用到若干个参数。因此,用Conn.Execute方法来执行命令有时表达起来十分困难。Command对象就是克服这一困难的最好工具。使用Command对象可以预先把命令和参数储存在它的属性和参数集合中,最后执行语句Command.Execute即可。

一 Command对象的属性

建立Command对象的方法如下:
Set CommandName = Server.CreqateObject("ADODB.command")
其中,CommandName是您给Command对象的命名。建立了Command对象以后,需要给它设置属性值。Command对象有七条属性,前两条是必须设置的。

1 ActiveConnection属性
Command对象是基于一个已建立的Connection对象的。本属性用来表示连接信息,它一般是一个连接对象的名称。语法
CommandName.ActiveConnection = 连接信息(如onnection对象名)
如:Comm.ActiveConnection=Conn 。这一步是不可缺少的。

2 CommandText属性
这一属性用来储存要执行的命令本身,它可以是一条SQL命令,数据库表名或储存过程的名。语法
CommandName.CommandText = 命令字符串
如:Comm.CommandText = "Insert into friends values(5,?,?,?,?)" 。这是一个插入命令,"?"部分由参数对象提供。

3 CommandType属性
这一属性用来储存命令的操作类型,类似于RecordSet的OPEN方法的Option参数。可以省略。其取值-1,1,2,3的含义如下表。

参数常数
 数值
 说明
 
adCmdUnknown
 -1
 类型不确定,这是缺省值
 
AdCmdText
 1
 一般命令。文本SQL命令
 
AdCmdTable
 2
 一个存在的表名称
 
AdCmdStoreProc
 3
 一个存在的过程名(Stored Procedure)
 

该属性可以读/写,例如:Comm.CommandType = adCmdText

4 CommandTimeOut属性
该属性用来确定Command命令执行的最长等待时间(秒),缺省值为30秒。该值设置为0表示无时间限制。在程序中如果使用表单输入,输入时由等待(不操作),就会发生“超时”而报错。

5 Name属性
该属性储存了Command对象的名称,名称是在创建时给出的,所以该属性是只读的。

6 State属性
该属性用来设置Command对象的状态。状态有两种,adStateClosed=0 表示关闭,adStateOpen=1 表示打开(缺省值)。

7 Prepared属性
Prepared属性是Boolean型的,用来设置Command对象的SQL命令是否预编译。如 Comm.Prepared = Ture,则对数据库进行操作前预编译SQL命令,这样可以提供执行速度,这对于需要多次执行的命令很有用。缺省值为false。

Command对象有两个方法Execute 和CreateParameter。Execute方法用来执行命令,但执行前往往需要用 CreateParameter 方法来创建参数。我们先介绍Execute方法。

二 Execute方法

Execute方法的功能是以Parameters集合中的参数来执行Command对象中储存的命令。Execute方法既是过程也是函数,所以有两种使用方法:

Set recordset = Command.Execute([RecordAffeted][,Parameters][,Option])
Command.Execute [RecordAffeted,Parameters,Option]

三个参数RecordAffeted,Parameters 和 Option都是可选项,RecordAffeted = 整数,表示执行命令时返回或影响的记录条数,缺省时无限制。Parameters选用时表示要传递参数集合Parameters中的所有参数。参数集合Parameters在下文叙述。Option 的可取值诶 -1,1,2,3,与上文CommandType属性中的含义一样。

例句:

Comm.Execute
Comm.Execute 20,Parameters,1
Set rs = Comm.Execute()
Set rs = Comm.Execute(20,Parameters,1)

三 Paremeter对象

Paremeter对象即参数对象,每个对象储存一个参数。Paremeter对象含有五个属性,它们分别是name 参数名(如字段名),type 参数类型(数据类型),direction 操作方向(输入,输出..),size=数据长度(字节数)和value=参数的值。其中,direction参数的可取值如下表。

参数常数 数值 说明 
adParemUnknown -1 参数类型不确定,这是缺省值 
AdParemInput 1 输入参数 
AdParemOutput 2 输出参数 
AdParemReturnvalue 3 返回值参数 

四 Paremeters集合(对象)

Paremeters集合是Command对象拥有的所有Paremeter对象的集合,Command对象一经创建,Paremeters集合就存在(一开始是空集合)。它只有一个属性Count=长整数表示已经定义的参数的个数,每个参数要用Command 对象的CreateParemeters()方法来创建。

Count属性的语法 N=comm.Parameters.Count

Paremeters集合用几个方法。Append方法把一个用CreateParemeters()方法创建的参数加入到参数集合中。delete方法输出一个指定的参数。Refresh方法用来重新取得Paremeters集合中的所有参数。Item方法用来取得Paremeters集合中的某个参数。它们的语法如下:

Comm.Paremeters.Append param
Comm.Paremeters.Delete index
Comm.Paremeters.Refresh
Set param = Comm.Paremeters.Item(index)
Set param = Comm.Paremeters(index)
Comm.Paremeters.Append param

其中,param是一个参数名,index是参数集的索引号,如数组的下标,从0 到 Parameters.Count-1。

五 CreateParemeters方法

Command对象的CreateParemeters()方法用来创建一个参数对象,语法如下:

Set 参数名 = CreateParemeters(Name,Type,Direction,Size,value)

其中Name,type等参数就是待创建的的诸属性,其含义参看上文Paremeter对象。使用时只要注意它们的次序。例如:
Set param = comm.CreateParameter("Name",129,AdParemInput,6,"李哓红")
注意,括号中的"Name"表示Param.name="Name", Name实际上是一个字段名。

六 Command对象应用示例

程序目标 应用Command对象数据库添加一个记录,数据值用Paremeter对象提供。

数据源friends,要执行的命令为:Insert Into friends values(5,?,?,?,?)
要添加的记录是(5 李哓红 女 1978.5.30 22)

代码

<%@ Language="vbscript" %>
<!– append.asp –>
<%
Const adChar=129   ’参数类型
Const adCmdText=1   ’命令类型
Const adParamInput=1 ’操作方向

Set Conn=Server.CreateObject("ADODB.Connection")
Conn.Open "dsn=friends"
Set Comm=Server.CreateObject("ADODB.Command")
Comm.ActiveConnection=Conn
Comm.CommandText = "Insert into friends values(5,?,?,?,?)"
Comm.CommandType = adCmdText

Set param = comm.CreateParameter("name",adChar,adParamInput,6,"李哓红")
Comm.Parameters.append param
Set param = comm.CreateParameter("sex",adChar,adParamInput,2,"女")
Comm.Parameters.append param
Set param = comm.CreateParameter("birthday",adChar,adParamInput,9,"1978.5.30")
Comm.Parameters.append param
Set param = comm.CreateParameter("age",adChar,adParamInput,2,"22")
Comm.Parameters.append param
Comm.Execute

‘记录添加后显示所有记录以观看Execute后的结果
Set rs=Conn.Execute("select* from friends",10,1)
Response.Write "<a href=’vbscript:history.back()’>返回</a><br>"
Response.Write "<table border=2>"
Response.Write "<tr>"
for i=0 to rs.fields.Count-1 ’输出表头
 Response.Write "<td>" &Ucase(rs(i).name) & "</td>"
Next
Do While not rs.Eof ’输出记录
 Response.Write "<tr>"
 for i=0 to rs.fields.Count-1 ’输出表行
  Response.Write "<td>" & rs(i).value & "</td>"
 Next
 Response.Write "</tr>"
 rs.MoveNext
Loop
rs.close
conn.close
%>

2005年07月12日


 
修改jiveHome目录中的jive_startup.xml文件,增加以下内容:
     <locale>
         <characterEncoding>UTF-8</characterEncoding>
         <country>CN</country>
         <timeZone>Asia/Beijing</timeZone>
         <language>zh</language>
     </locale>

然后按照附件修改SetCharacterEncodingFilter.java文件,重新编译后放在WEB-INF\classes目录中。

步骤1、安装mySQL的时候必须设置数据库编码为UTF8。
步骤2、按附件修改com.jivesoftware.util.SetCharacterEncodingFilter类,编译好后放回jivebase.jar中替换调原来的SetCharacterEncodingFilter.class文件,注意路径。
步骤3、将jiveforums.jar中的jive_forums_i18n_en.properties释放出来,将里面的英文资源翻译成中文并保存为jive_forums_i18n_zh.ori.properties,再用native2ascii -encoding GBK jive_forums_i18n_zh.ori.properties jive_forums_i18n_zh.properties生成jive_forums_i18n_zh.properties文件,将该文件复制至WEB-INF\classes目录里。
步骤4、进入jive forums的管理界面,在system–>jive properties里,增加以下属性
                  Property Name                 Property Value
                locale.characterEncoding               UTF-8     
                locale.country                           CN     
                locale.language                          zh     
                locale.timeZone                       Asia/Beijing

在settings–>local settings里,将字符集设定为UTF-8,并将local和timeZone设置好
步骤5、重启jive,


License的破解可反编译com.jivesoftware.base.License类,修改getExpiresDate函数,让它返回一个大时间(例如2999年12月31日),重新编译,放到WEB-INF/classes下(注意建相应的路径),重新启动jive即可。

 

2005年07月08日

JDK 1.5版本包含了Java语法方面的主要改进。

自从Java 1.0版本首次受到开发人员欢迎以来,Java语言的语法就没有发生过太大的变化。虽然1.1版本增加了内部类和匿名内部类,1.4版本增加了带有新的assert关键字的assertion(断定)功能,但Java语法和关键字仍然保持不变–像编译时常量一样处于静态。它将通过J2SE 1.5(代号Tiger)发生改变。

过去的J2SE版本主要关注新类和性能,而Tiger的目标则是通过使Java编程更易于理解、对开发人员更为友好、更安全来增强Java语言本身,同时最大限度地降低与现有程序的不兼容性。该语言中的变化包括generics(泛化)、autoboxing、一个增强的“for”循环、 typesafe enums(类型安全的枚举类型)、一个静态导入工具(static import facility)和varargs。

通过generics来改进类型检查

generics使你能够指定一个集合中使用的对象的实际类型,而不是像过去那样只是使用Object。generics也被称为“参数化类型”,因为在generics中,一个类的类型接受影响其行为的类型变量。

generics并不是一个新概念。C++中有模板,但是模板非常复杂并且会导致代码膨胀。C++编码人员能够仅使用C++模板,通过一些小的技巧来执行阶乘函数,然后看着编译器生成C++源代码来处理模板调用。Java开发人员已经从C++语言中学到了很多关于generics的知识,并经过了足够长时间的实践,知道如何正确使用它们。Tiger的当前计划是从健壮的Generic Java (GJ)方案演变而来的。GJ方案的口号是“使Java的类型简化、再简化。”

为了了解generics,让我们从一个不使用generics的例子开始。下面这段代码以小写字母打印了一个字符串集合:


//获得一个字符串集合
public void lowerCase(Collection c) {
  Iterator itr = c.iterator();
  while (itr.hasNext()) {
    String s = (String) itr.next();
    System.out.println(s.toLowerCase());
  }
}


这个方法不保证只接收字符串。编程人员负责记住传给这个方法什么类型的变量。Generics通过显式声明类型来解决这个问题。Generics证明并执行了关于集合包含什么东西的规则。如果类型不正确,编译器就会产生一个错误。在下面的改写代码中,注意Collection和Iterator是如何声明它们只接收字符串对象的:


public void lowerCase(
     Collection<String> c)  {
  Iterator<String> itr = c.iterator();
  while (itr.hasNext()) {
    System.out.println(
      itr.next().toLowerCase());
  }
}


现在,该代码包含了更强大的类型,但它仍然包含许多键盘类型。我们将在后面加以介绍。注意,你可以存储类型参数的任何子类型。接下来,我们将使用这个特性draw()一个形状集合。


// 获得孩子集合…
public void drawAll(Collection<Shape> c) {
  Iterator<Shape> itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}


尖括号中的值被称为类型变量。参数化类型能够支持任何数量的类型变量。例如,java.util.Map就支持两个类型变量–一个用于键类型,一个用于值类型。下面的例子使用了一个带有指向一列元素对象的字符串查找键的map:


public static void main(String[] args) {
  HashMap<String, List<Element>> map =
    new HashMap<String,
          List<Element>>();
  map.put("root",
    new ArrayList<Element>());
  map.put("servlet",
    new LinkedList<Element>());
}


这个类定义声明了它支持多少个类型变量。类型参数的数量必须精确地与所期望的相匹配。而且,类型变量一定不能是原始类型(primitive types)。


List<String, String> // takes one
List<int>            // 无效的,原始类型

即使在期望使用一个普通类型(raw type)的时候,你也可以使用一个参数化类型。当然,你也可以反过来做,但这么做会收到一条编译时警告:


public static void oldMethod(List list) {
  System.out.println(list.size());
}

public static void main(String[] args) {
  List<String> words =
    new ArrayList<String>();
  oldMethod(words);   // 没问题
}


这就实现了轻松的向后兼容:接受一个原始列表的老方法能够直接接受一个参数化List<String>。接受参数化List<String>的新方法也能够接受一个原始列表,但是因为原始列表不声明或执行相同的类型约束,所以这个动作会触发一个警告。可以保证的是:如果在编译时你没有得到名为unchecked(未检查)的警告,那么在运行时编译器生成的强制类型转换(cast)将不会失败。

有趣的是,参数化类型和普通类型被编译为相同的类型。没有专门的类来指定这一点,使用编译器技巧就可以完成这一切。instanceof检查可以证明这一点。


words instanceof List             // true
words instanceof ArrayList        //true
words instanceof ArrayList<String> // true


这个检查产生了一个问题:“如果它们是相同的类型,这种检查能起多大作用?”这是一条用墨水而不是用血写的约束。这段代码将产生一个编译错误,因为你不能向List<String>中添加新的Point:


List<String> list =
  new ArrayList<String>();
list.add(new Point());  // 编译错误

但是这段代码被编译了!


List<String> list =
  new ArrayList<String>();
((List)list).add(new Point());


它将参数化类型强制转换为一个普通类型,这个普通类型是合法的,避免了类型检查,但正如前面所解释的那样,却产生了一个调用未检查的警告:


warning: unchecked call to add(E) as a member of the raw type
 java.util.List
    ((List)list).add(new Point());
    ^


写一个参数化类型

Tiger提供了一个写参数化类型的新语法。下面显示的Holder类可以存放任意引用类型。这样的类很便于使用,例如,通过引用语义支持CORBA传递,而不需要生成单独的Holder类:


public class Holder<A> {
  private A value;
  Holder(A v) { value = v; }
  A get() { return value; }
  void set(A v) { value = v; }
}


使用一个参数化的Holder类型,你能够安全地得到和设置数据,而不需进行强制类型转换:


public static void main(String[] args) {
  Holder<String> holder =
    new Holder<String>("abc");
  String val = holder.get();  // "abc"
  holder.set("def");
}


“A”类型参数名可以是任何标准的变量名。它通常是一个单一的大写字母。你也可以声明类型参数必须能够扩展另一个类,如下所示:


// 也可以
public class Holder<C extends Child>


关于是否能够声明任何其他的类型参数仍然存在争议。你对generics了解的越深,你需要的特殊规则就越多,但是特殊规则越多,generics就会越复杂。

Tiger中设计用来保存线程局部变量(thread local variable)的核心类java.lang.ThreadLocal,将可能变得与下面这个 Holder类的作用类似:


public class ThreadLocal<T> {
  public T get();
  public void set(T value);
}


我们也将看见java.lang.Comparable的变化,允许类声明与它们相比较的类型:


public interface Comparable<T> {
  int compareTo(T o);
}
public final class String implements Comparable<String> {
  int compareTo(String anotherString);
}


Generics不仅仅用于集合,它们有更为广泛的用途。例如,虽然你不能基于参数化类型(因为它们与普通类型没有什么不同)进行捕捉(catch),但是你可以抛出(throw)一个参数化类型。换句话说,你可以动态地决定throws语句中抛出什么。

下面这段令人思维混乱的代码来自generics规范。该代码通过扩展Exception的类型参数E定义了一个 Action接口。Action类有一个抛出作为E 出现的任何类型的run()方法。然后,AccessController类定义一个接受Action<E>的静态exec()方法,并声明exec()抛出E。声明该方法自身是参数化的需要该方法标记(method signature)中的特殊<E extends Exception>。

现在,事情变得有点棘手了。main()方法调用在Action 实例(作为一个匿名内部类实现)中传递的AccessController.exec()方法。该内部类被参数化,以抛出一个 FileNotFoundException。main()方法有一个捕捉这一异常类型的catch语句。如果没有参数化类型,你将不能确切地知道run()会抛出什么。有了参数化类型,你能够实现一个泛化的Action类,其中run()方法可以任意实现,并可以抛出任意异常(Exception):


interface Action<E extends Exception> {
  void run() throws E;
}

class AccessController {
  public static <E extends Exception>
  void exec(Action<E> action) throws E {
    action.run();
  }
}

public class Main {
  public static void main(String[] args) {
    try {
      AccessController.exec(
      new Action<FileNotFoundException>() {
        public void run()
        throws FileNotFoundException {
          // someFile.delete();
        }
      });
    }
    catch (FileNotFoundException f) { }
  }
}


协变返回类型

下面进行一个随堂测验:下面的代码是否能够成功编译?


class Fruit implements Cloneable {
  Fruit copy() throws
    CloneNotSupportedException {
   return (Fruit)clone();
  }
}
class Apple extends Fruit
           implements Cloneable {
  Apple copy()
    throws CloneNotSupportedException {
   return (Apple)clone();
  }
}


答案:该代码在J2SE 1.4中不能编译,因为改写一个方法必须有相同的方法标记(包括返回类型)作为它改写的方法。然而,generics有一个叫做协变返回类型的特性,使上面的代码能够在Tiger中进行编译。该特性是极为有用的。

例如,在最新的JDOM代码中,有一个新的Child接口。Child有一个detach()方法,返回从其父对象分离的Child对象。在Child接口中,该方法当然返回Child:


public interface Child {
  Child detach();
  // etc
}


当Comment类实现detach()时,它总是返回一个Comment,但如果没有协变返回类型,该方法声明必须返回Child:


public class Comment {
  Child detach() {
    if (parent != null)
      parent.removeContent(this);
    return this;
  }
}


这意味着调用者一定不要将返回的类型再向下返回到一个Comment。协变返回类型允许Comment 中的detach()返回 Comment。只要Comment是Child的子类就行。除了能够返回Document的DocType和能够返回Element的EntityRef,该特性对立刻返回Parent 的Child.getParent()方法也能派上用场。协变返回类型将确定返回类型的责任从类的用户(通过强制类型转换确认)转交给类的创建者,只有创建者知道哪些类型彼此之间是真正多态的。 这使应用编程接口(API)的用户使用起来更容易,但却稍微增加了API设计者的负担。

Autoboxing

Java有一个带有原始类型和对象(引用)类型的分割类型系统。原始类型被认为是更轻便的,因为它们没有对象开销。例如,int[1024]只需要4K存储空间,以及用于数组自身的一个对象。然而,引用类型能够在不允许有原始类型的地方被传递,例如,传递到一个List。这一限制的标准工作场景是在诸如list.add(new Integer(1))的插入操作之前,将原始类型与其相应的引用类型封装(box或wrap)在一起,然后用诸如((Integer)list.get(0)).intValue()的方法取出(unbox)返回值。

新的 autoboxing特性使编译器能够根据需要隐式地从int转换为Integer,从char 转换为Character等等。auto-unboxing进行相反的操作。在下面的例子中,我不使用autoboxing计算一个字符串中的字符频率。我构造了一个应该将字符型映射为整型的Map,但是由于Java的分割类型系统,我不得不手动管理Character和Integer封箱转换(boxing conversions)。


public static void countOld(String s) {
  TreeMap m = new TreeMap();
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    Character c = new Character(chars[i]);
    Integer val = (Integer) m.get(c);
    if (val == null)
      val = new Integer(1);
    else
      val = new Integer(val.intValue()+1);
    m.put(c, val);
  }
  System.out.println(m);
}


Autoboxing使我们能够编写如下代码:


public static void countNew(String s) {
  TreeMap<Character, Integer> m =
    new TreeMap<Character, Integer>();
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    char c = chars[i];
    m.put(c, m.get(c) + 1);  // unbox
  }
  System.out.println(m);
}


这里,我重写了map,以使用generics,而且我让autoboxing给出了map能够直接存储和检索char 和int值。不幸的是,上面的代码有一个问题。如果m.get(c)返回空值(null),会发生什么情况呢?怎样取出null值?在抢鲜版(early access release)(参见下一步)中,取出一个空的Integer 会返回0。自抢鲜版起,专家组决定取出null值应该抛出一个NullPointerException。因此,put()方法需要被重写,如下所示:


m.put(c, Collections.getWithDefault(
  m, c) + 1);


新的Collections.getWithDefault()方法执行get()函数,在该方法中,如果值为空值,它将返回期望类型的默认值。对于一个int类型来说,则返回0。

虽然autoboxing有助于编写更好的代码,但我的建议是谨慎地使用它。封箱转换仍然会进行并仍然会创建许多包装对象(wrapper-object)实例。当进行计数时,采用将一个int与一个长度为1的的 int数组封装在一起的旧方法更好。然后,你可以将该数组存储在任何需要引用类型的地方,获取intarr[0]的值并使用intarr[0]++递增。你甚至不必再次调用put(),因为会在适当的位置产生增量。使用这一方法和其他一些方法,你能够更有效地进行计数。使用下面的算法,执行100万个字符的对时间会从650毫秒缩短为30毫秒:


 
public static void countFast(String s) {
  int[] counts = new int[256];
  char[] chars = s.toCharArray();
  for (int i=0; i < chars.length; i++) {
    int c = (int) chars[i];
    counts[c]++;  // no object creation
  }
  for (int i = 0; i < 256; i++) {
    if (counts[i] > 0) {
      System.out.println((char)i + ":"
           + counts[i]);
    }
  }
}


在C#中,我们可以看到一个类似但稍微不同的方法。C#有一个统一的类型系统,在这个系统中值类型和引用类型都扩展System.Object。但是,你不能直接看到这一点,因为C#为简单的值类型提供了别名和优化。int是System.Int32的一个别名,short是System.Int16的一个别名,double是System.Double的一个别名。在C#中,你能够调用“int i = 5; i.ToString();”,它是完全合法的。这是因为每个值类型都有一个在它被转换为引用类型时创建的相应隐藏引用类型(在值类型被转换为一个引用类型时创建的)。


int x = 9;
object o = x;  //创建了引用类型
int y = (int) o;


当基于一个不同的类型系统时,最终结果与我们在J2SE 1.5中看到的非常接近。

 

对于循环的增强

还记得前面的这个例子么?


public void drawAll(Collection<Shape> c) {
  Iterator<Shape> itr = c.iterator();
  while (itr.hasNext()) {
    itr.next().draw();
  }
}


你再也不用输入这么多的文字了!这里是Tiger版本中的新格式。


public void drawAll(Collection<Shape> c) {
  for (Shape s : c) {
    s.draw();
  }
}


你可以阅读这样一段代码“foreach Shape s in c”。我们注意到设计者非常聪明地避免添加任何新的关键字。考虑到很多人都用“in”来输入数据流,我们对此应该感到非常高兴。编译器将该新的语法自动扩展到其迭代表中。


for (Iterator<Shape> $i = c.iterator();
     $i.hasNext(); ) {
  Shape s = $i.next();
  s.draw();
}


你可以使用该语法来对普通(raw,非参数化的)类型进行迭代,但是编译器会输出一个警告,告诉你必须的类型转换可能会失败。你可以在任何数组和对象上使用“foreach”来实现新的接口java.lang.Iterable。


public interface Iterable<T> {
  SimpleIterator<T> iterator();
}
public interface SimpleIterator<T> {
  boolean hasNext();
  T next();
}


java.lang中的新的接口避免对java.util的任何语言依赖性。Java语言在java.lang之外必须没有依赖性。要注意通过next()方法来更巧妙地使用协变返回类型。需要说明的一点是,利用该“foreach”语法和SimpleIterator接口,就会丧失调用iterator.remove()的能力。如果你还需要该项能力,则必须你自己迭代该集合。

与C#对比一下,我们会看到相似的语法,但是C#使用“foreach”和“in”关键字,从最初版本开始它们就被作为保留字。


// C#
foreach (Color c in colors) {
  Console.WriteLine(c);
}


C#的“foreach”对任何集合(collection)或者数组以及任何可列举的实现都有效。我们再一次看到了在Java和C#之间的非常相似之处。

(类型安全的枚举类型)typesafe enum

enums 是定义具有某些命名的常量值的类型的一种方式。你在C,C++中已经见过它们,但是显然,它们曾经在Java中不用。现在,经过了八年之后,Java重又采用它们,并且大概比先前的任何语言都使用得更好。让我们首先来看看先前我们是如何解决enum问题的?不知道你有没有编写过如下代码?


class PlayingCard {
  public static final int SUIT_CLUBS
    = 0;
  public static final int SUIT_DIAMONDS
    = 1;
  public static final int SUIT_HEARTS
    = 2;
  public static final int SUIT_SPADES
    = 3;
  // …
}


这段代码很简单也很常见,但是它有问题。首先,它不是类型安全的(typesafe)。可以给一个方法传递文字“5”来获取一个suit,并且将被编译。同时,这些值用这些常量直接被编译成每个类。Java通过这些常量进行这种"内联"(inlining)来达到优化的目的,但其风险在于,如果对这些值重新排序并且只重新编译该类,则其他类将会错误地处理这些suits。 而且,该类型是非常原始的,它不能被扩展或者增强,同时如果你输出这些值中的一个,你只会得到一个含意模糊的整型量,而不是一个好记的有用名字。这种方法非常简单,这也正是我们为什么要这样做的原因,但是它并不是最好的方法。所以,也许需要尝试一下下面的这个方法:


class PlayingCard {
  class Suit {
     public static final Suit CLUBS
       = new Suit();
     public static final Suit DIAMONDS
       = new Suit();
     public static final Suit HEARTS
       = new Suit();
     public static final Suit SPADES
       = new Suit();
     protected Suit() { }
  }
}


它是类型安全的(typesafe)且更加具有可扩展性,并且,属于面向对象设计的类。然而,这样简单的一种方法并不支持序列化,没有合法值的列表,无法将这些值排序,并且,不能作为一个有意义的字符串来打印一个值。你当然可以添加这些特性,Josh Bloch在他的Effective Java一书中(第五章,第21条)为我们准确展示了如何解决这些问题。然而,你最终得到的是几页蹩脚的代码。

Java新的enum特性具有一个简单的单行语法:


class PlayingCard {
  public enum Suit { clubs,
        diamonds, hearts, spades }
}


被称之为enum(枚举)类型的该suit,对应于每个enum常量都有一个成员项。每个enum类型都是一个实际类,它可以自动扩展新类java.lang.Enum。编译器赋予enum类以有意义的String()、 hashCode(), 和equals() 方法, 并且自动提供Serializable(可序列化的)和Comparable(可比较的)能力。令人高兴地是enum类的声明是递归型的:


public class Enum<E extends Enum<E>>
     implements Comparable<E>,
               Serializable {


使用最新的enum类型可以提供很多好处:包括:比整型操作(int operations)更好的性能,编译时更好的类型安全性,不会被编译到客户端并且可以被重新命名和排序的常量,打印的值具有含意清晰的信息,能够在集合(collections)甚至switch中被使用,具有添加域(fields)和方法的能力,以及实现任意接口的能力。

每个enum具有一个字符串名字和一个整型顺序号值:


out.println(Suit.clubs);       // "clubs"
out.println(Suit.clubs.name);  // "clubs"
out.println(Suit.clubs.ordinal);    // 0
out.println(Suit.diamonds.ordinal); // 1
Suit.clubs == Suit.clubs     // true
Suit.clubs == Suit.diamonds  // false
Suit.clubs.compareTo(Suit.diamonds) // -1


enum可以拥有构造器和方法。甚至一个main()方法都是合法的。下面的例子将值赋给罗马数字:


public enum Roman {
  I(1), V(5), X(10), L(50), C(100),
  D(500), M(1000);

  private final int value;

  Roman(int value) { this.value = value; }

  public int value() { return value; }

  public static void main(String[] args) {
    System.out.println(Roman.I);
  }
}


非常奇怪的是,不能将序列数值赋给一个enum,比如说“enum Month{jan=1,feb=2,….}”。 然而,却可以给enum常量添加行为。比如说,在JDOM中,XMLOutputter支持数种空白处理方法。如果JDOM是参照J2SE1.5构建的,那么这些方法就可以用一个enum来定义,并且enum类型本身可以具有这种处理行为。不管这种编码模式是不是会被证明是有用的,我们都会逐渐了解它。肯定这是一个异常有趣的概念。


public abstract enum Whitespace {
  raw {
    String handle(String s) { return s; }
  },
  trim {
    String handle(String s) {
      return s.trim(); }
  },
  trimFullWhite {
    String handle(String s) {
      return s.trim().equals("") ? "":s; }
  };
  abstract String handle(String s);

  public static void main(String[] args) {
    String sample = " Test string ";
    for (Whitespace w : Whitespace.VALUES)
      System.out.println(w + ": ‘"
          + w.handle(sample) + "’");
}}


很少有公开的出版物谈及Java新的enum。enum常量名是不是都应该都大写?对于常量来说,这是一个标准,但是规范指出小写名称“于更好的字符串格式,一般应该避免使用定制的toString方法。”另外,名字和顺序号该是域还是方法?这是封装方法一再引起争论的问题。在向J2SE1.5添加关键字方面,该特性也落了一个不太好的名声。令人伤心的是,它还是一个通常被用作存储Enumerator(计数器)实例的词。如果你已经在你的代码中使用了“enum”,那么在你为J2SE 1.5的应用编译之前,必须修改它。现在,你已得到了充分的警示。

让我们看一下C#,所有的enum都扩展成System.Enum。每个enum都具有可以被赋值的整型(或者字节型或者其他类型)值。enum还拥有静态方法,以便从字符串常量来初始化enum,获取有效值列表,从而,可以看到某个值是不是被支持。通过使用[flags]属性来标记一个enum,你可以确保值支持位屏蔽,并且系统负责打印被屏蔽的值的有用输出:


// C#
[Flags]
public enum Credit : byte {
  Visa = 1, MC = 2, Discover = 4 }

Credit accepted = Credit.Visa | Credit.MC;

c.WriteLine(accepted);         // 3
c.WriteLine(accepted.Format());//"Visa|MC"


静态导入

静态导入使得我们可以将一套静态方法和域放入作用域(scope)。它是关于调用的一种缩写,可以忽略有效的类名。比如说,对Math.abs(x)的调用可以被简单地写成 abs(x)。为了静态地导入所有的静态域和方法,我们可以使用“import static java.lang.Math”,或者指定要导入的具体内容,而使用“import static java.lang.System.out”–在这里没有什么令人激动的新特性,只是缩写而已。它让你可以不用Math而来完成math(数字计算)。


import static java.lang.Math.*;
import static java.lang.System.out;

public class Test {
  public static void main(String[] args) {
    out.println(abs(-1) * PI);
  }
}


注意,“static”关键字的重用是为了避免任何新的关键词。语言越成熟,对于“static”关键词的使用就越多。如果在静态成员之间发生冲突的话,就会出现含混的编译错误,这一点跟类的导入一样。是否将java.lang.Math.* 作为固有的导入引发了一定的争论,不过在获知其将会触发含混的错误之后,这种争论不会再发生了。

Varargs

“varargs”表示“参数的变量”,存在于C语言中,并且支持通用的printf()和scanf()函数。 在Java中,我们通过编写一些接受Object[]、List、Properties(属性)的方法以及可以描述多个值的其它简单数据结构–比如说,Method.invoke(Object obj,Object[] args–来模拟这一特性。)。这要求调用程序将数据封装到这种单一的容器结构中。varargs允许调用程序传递值的任意列表,而编译器会为接收程序将其转化为数组。其语法就是在参数声明中的参数名之后添加“…”,以便使其成为vararg。它必须是最后一个参数–比如说,编写一个sum()函数以便将任意数量的整数相加:


out.println(sum(1, 2, 3));

public static int sum(int args…) {
  int sum = 0;
  for (int x : args) { sum += x; }
  return sum;
}


在抢鲜版本中,vararg符号使用方括号,就像sum(int[] args…)。然而,在之后的讨论中,根据James Gosling的提议,方括号被去掉了。在这里的例子中,我们不使用方括号,但是如果你需要在抢鲜版本中使用这些代码的话,就需要将方括号添加上去。借助autoboxing,可以通过接受一个Object args…,可以接受任何类型的参数,包括原始类型。这与printf()类型的一些方法一样,它们接受任何数量的所有类型的参数。实际上,该Tiger版本可以使用这一特性通过format方法(其行为与printf()一样)来提供一个Formattable(可格式化)的接口。这是我们在以后的文章中将要讨论的话题。目前,我们只编写简单的printf():


public static void printf(String fmt,
                        int args…) {
  int i = 0;
  for (char c : fmt.toCharArray()) {
    out.print(c == ‘%’ ? args[i++] : c);
  }
}

public static void main(String[] args) {
  printf("My values are % and %
",
         1, 2);
}

 
在Tiger版本中,你会发现采用一个新格式的invoke()函数: invoke(Object obj, Object args…). 这看上去更加自然。


结论

J2SE1.5版努力使Java的编程更加简便、安全和更加富有表现力。这些特性和谐完美的被结合在一起。如果你跟我一样,总是喜欢用老的“for”循环,你肯定希望你拥有Tiger。

然而,需要记住的是,该规范并没有最终完成,很多地方还需要修改。管理这些变化的专家小组(JSR-14,JSR-175以及JSR-201)会在2003年年末的beta版本发布之前,以及预期在2004年发布最终版发布之前,会做出很多修改。然而,Sun表达了对JavaOne的信心,认为总体上主要原则不会改变太多。如果你想体验一下,那么你可以从下面的站点获取抢鲜版本。 从中你会找到可能从任何一个预览版软件都会遇到的错误,但是也会看到很多激动人心的新特性。我强烈建议你尝试一下。
 

2005年07月03日
全面挖掘Java Excel API 使用方法
作者佚名 来源InterNet 加入时间:2005-1-28
使用Windows操作系统的朋友对Excel(电子表格)一定不会陌生,但是要使用Java语言来操纵Excel文件并不是一件容易的事。在Web应用日益盛行的今天,通过Web来操作Excel文件的需求越来越强烈,目前较为流行的操作是在JSP或Servlet 中创建一个CSV (comma separated values)文件,并将这个文件以MIME,text/csv类型返回给浏览器,接着浏览器调用Excel并且显示CSV文件。这样只是说可以访问到Excel文件,但是还不能真正的操纵Excel文件,本文将给大家一个惊喜,向大家介绍一个开放源码项目,Java Excel API,使用它大家就可以方便地操纵Excel文件了。

  Java Excel API简介

  Java Excel是一开放源码项目,通过它Java开发人员可以读取Excel文件的内容、创建新的Excel文件、更新已经存在的Excel文件。使用该API非Windows操作系统也可以通过纯Java应用来处理Excel数据表。因为是使用Java编写的,所以我们在Web应用中可以通过JSP、Servlet来调用API实现对Excel数据表的访问。

  现在发布的稳定版本是V2.0,提供以下功能:

   从Excel 95、97、2000等格式的文件中读取数据;
   读取Excel公式(可以读取Excel 97以后的公式);
   生成Excel数据表(格式为Excel 97);
   支持字体、数字、日期的格式化;
   支持单元格的阴影操作,以及颜色操作;
   修改已经存在的数据表;

  现在还不支持以下功能,但不久就会提供了:

   不能够读取图表信息;
   可以读,但是不能生成公式,任何类型公式最后的计算值都可以读出;
 
   应用示例

  1、从Excel文件读取数据表

  Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表。读取Excel数据表的第一步是创建Workbook(术语:工作薄),下面的代码片段举例说明了应该如何操作:(完整代码见ExcelReading.java)


import java.io.*;
import jxl.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//直接从本地文件创建Workbook
//从输入流创建Workbook
InputStream is = new FileInputStream(sourcefile);
jxl.Workbook rwb = Workbook.getWorkbook(is);
}
catch (Exception e)
{
e.printStackTrace();
}


  一旦创建了Workbook,我们就可以通过它来访问Excel Sheet(术语:工作表)。参考下面的代码片段:


//获取第一张Sheet表
Sheet rs = rwb.getSheet(0);


  我们既可能通过Sheet的名称来访问它,也可以通过下标来访问它。如果通过下标来访问的话,要注意的一点是下标从0开始,就像数组一样。

  一旦得到了Sheet,我们就可以通过它来访问Excel Cell(术语:单元格)。参考下面的代码片段:


//获取第一行,第一列的值
Cell c00 = rs.getCell(0, 0);
String strc00 = c00.getContents();

//获取第一行,第二列的值
Cell c10 = rs.getCell(1, 0);
String strc10 = c10.getContents();

//获取第二行,第二列的值
Cell c11 = rs.getCell(1, 1);
String strc11 = c11.getContents();

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());


  如果仅仅是取得Cell的值,我们可以方便地通过getContents()方法,它可以将任何类型的Cell值都作为一个字符串返回。示例代码中Cell(0, 0)是文本型,Cell(1, 0)是数字型,Cell(1,1)是日期型,通过getContents(),三种类型的返回值都是字符型。

  如果有需要知道Cell内容的确切类型,API也提供了一系列的方法。参考下面的代码片段:


String strc00 = null;
double strc10 = 0.00;
Date strc11 = null;

Cell c00 = rs.getCell(0, 0);
Cell c10 = rs.getCell(1, 0);
Cell c11 = rs.getCell(1, 1);

if(c00.getType() == CellType.LABEL)
{
LabelCell labelc00 = (LabelCell)c00;
strc00 = labelc00.getString();
}
if(c10.getType() == CellType.NUMBER)
{
NmberCell numc10 = (NumberCell)c10;
strc10 = numc10.getvalue();
}
if(c11.getType() == CellType.DATE)
{
DateCell datec11 = (DateCell)c11;
strc11 = datec11.getDate();
}

System.out.println("Cell(0, 0)" + " value : " + strc00 + "; type : " + c00.getType());
System.out.println("Cell(1, 0)" + " value : " + strc10 + "; type : " + c10.getType());
System.out.println("Cell(1, 1)" + " value : " + strc11 + "; type : " + c11.getType());


  在得到Cell对象后,通过getType()方法可以获得该单元格的类型,然后与API提供的基本类型相匹配,强制转换成相应的类型,最后调用相应的取值方法getXXX(),就可以得到确定类型的值。API提供了以下基本类型,与Excel的数据格式相对应,如下图所示:

  每种类型的具体意义,请参见Java Excel API document.

  当你完成对Excel电子表格数据的处理后,一定要使用close()方法来关闭先前创建的对象,以释放读取数据表的过程中所占用的内存空间,在读取大量数据时显得尤为重要。参考如下代码片段:


//操作完成时,关闭对象,释放占用的内存空间
rwb.close();


  Java Excel API提供了许多访问Excel数据表的方法,在这里我只简要地介绍几个常用的方法,其它的方法请参考附录中的Java Excel API document.
Workbook类提供的方法

  1. int getNumberOfSheets()

  获得工作薄(Workbook)中工作表(Sheet)的个数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
int sheets = rwb.getNumberOfSheets();


  2. Sheet[] getSheets()

  返回工作薄(Workbook)中工作表(Sheet)对象数组,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
Sheet[] sheets = rwb.getSheets();


  3. String getVersion()

  返回正在使用的API的版本号,好像是没什么太大的作用。


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
String apiVersion = rwb.getVersion();


  Sheet接口提供的方法

  1) String getName()

  获取Sheet的名称,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
String sheetName = rs.getName();


  2) int getColumns()

  获取Sheet表中所包含的总列数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsColumns = rs.getColumns();


  3) Cell[] getColumn(int column)

  获取某一列的所有单元格,返回的是单元格对象数组,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getColumn(0);


  4) int getRows()

  获取Sheet表中所包含的总行数,示例:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
int rsRows = rs.getRows();


  5) Cell[] getRow(int row)

  获取某一行的所有单元格,返回的是单元格对象数组,示例子:


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell[] cell = rs.getRow(0);


  6) Cell getCell(int column, int row)

  获取指定单元格的对象引用,需要注意的是它的两个参数,第一个是列数,第二个是行数,这与通常的行、列组合有些不同。


jxl.Workbook rwb = jxl.Workbook.getWorkbook(new File(sourcefile));
jxl.Sheet rs = rwb.getSheet(0);
Cell cell = rs.getCell(0, 0);


  2、生成新的Excel工作薄

  下面的代码主要是向大家介绍如何生成简单的Excel工作表,在这里单元格的内容是不带任何修饰的(如:字体,颜色等等),所有的内容都作为字符串写入。(完整代码见ExcelWriting.java)

  与读取Excel工作表相似,首先要使用Workbook类的工厂方法创建一个可写入的工作薄(Workbook)对象,这里要注意的是,只能通过API提供的工厂方法来创建Workbook,而不能使用WritableWorkbook的构造函数,因为类WritableWorkbook的构造函数为protected类型。示例代码片段如下:


import java.io.*;
import jxl.*;
import jxl.write.*;
… … … …
try
{
//构建Workbook对象, 只读Workbook对象
//Method 1:创建可写入的Excel工作薄
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile));

//Method 2:将WritableWorkbook直接写入到输出流
/*
OutputStream os = new FileOutputStream(targetfile);
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(os);
*/
}
catch (Exception e)
{
e.printStackTrace();
}


  API提供了两种方式来处理可写入的输出流,一种是直接生成本地文件,如果文件名不带全路径的话,缺省的文件会定位在当前目录,如果文件名带有全路径的话,则生成的Excel文件则会定位在相应的目录;另外一种是将Excel对象直接写入到输出流,例如:用户通过浏览器来访问Web服务器,如果HTTP头设置正确的话,浏览器自动调用客户端的Excel应用程序,来显示动态生成的Excel电子表格。

  接下来就是要创建工作表,创建工作表的方法与创建工作薄的方法几乎一样,同样是通过工厂模式方法获得相应的对象,该方法需要两个参数,一个是工作表的名称,另一个是工作表在工作薄中的位置,参考下面的代码片段:


//创建Excel工作表
jxl.write.WritableSheet ws = wwb.createSheet("Test Sheet 1", 0);

"这锅也支好了,材料也准备齐全了,可以开始下锅了!",现在要做的只是实例化API所提供的Excel基本数据类型,并将它们添加到工作表中就可以了,参考下面的代码片段:
//1.添加Label对象
jxl.write.Label labelC = new jxl.write.Label(0, 0, "This is a Label cell");
ws.addCell(labelC);

//添加带有字型Formatting的对象
jxl.write.WritableFont wf = new jxl.write.WritableFont(WritableFont.TIMES, 18, WritableFont.BOLD, true);
jxl.write.WritableCellFormat wcfF = new jxl.write.WritableCellFormat(wf);
jxl.write.Label labelCF = new jxl.write.Label(1, 0, "This is a Label Cell", wcfF);
ws.addCell(labelCF);

//添加带有字体颜色Formatting的对象
jxl.write.WritableFont wfc = new jxl.write.WritableFont(WritableFont.ARIAL, 10, WritableFont.NO_BOLD, false,
Underlinestyle.NO_UNDERLINE, jxl.format.Colour.RED);
jxl.write.WritableCellFormat wcfFC = new jxl.write.WritableCellFormat(wfc);
jxl.write.Label labelCFC = new jxl.write.Label(1, 0, "This is a Label Cell", wcfFC);
ws.addCell(labelCF);

//2.添加Number对象
jxl.write.Number labelN = new jxl.write.Number(0, 1, 3.1415926);
ws.addCell(labelN);

//添加带有formatting的Number对象
jxl.write.NumberFormat nf = new jxl.write.NumberFormat("#.##");
jxl.write.WritableCellFormat wcfN = new jxl.write.WritableCellFormat(nf);
jxl.write.Number labelNF = new jxl.write.Number(1, 1, 3.1415926, wcfN);
ws.addCell(labelNF);

//3.添加Boolean对象
jxl.write.Boolean labelB = new jxl.write.Boolean(0, 2, false);
ws.addCell(labelB);

//4.添加DateTime对象
jxl.write.DateTime labelDT = new jxl.write.DateTime(0, 3, new java.util.Date());
ws.addCell(labelDT);

//添加带有formatting的DateFormat对象
jxl.write.DateFormat df = new jxl.write.DateFormat("dd MM yyyy hh:mm:ss");
jxl.write.WritableCellFormat wcfDF = new jxl.write.WritableCellFormat(df);
jxl.write.DateTime labelDTF = new jxl.write.DateTime(1, 3, new java.util.Date(), wcfDF);
ws.addCell(labelDTF);


  这里有两点大家要引起大家的注意。第一点,在构造单元格时,单元格在工作表中的位置就已经确定了。一旦创建后,单元格的位置是不能够变更的,尽管单元格的内容是可以改变的。第二点,单元格的定位是按照下面这样的规律(column, row),而且下标都是从0开始,例如,A1被存储在(0, 0),B1被存储在(1, 0)。

  最后,不要忘记关闭打开的Excel工作薄对象,以释放占用的内存,参见下面的代码片段:


//写入Exel工作表
wwb.write();

//关闭Excel工作薄对象
wwb.close();


  这可能与读取Excel文件的操作有少少不同,在关闭Excel对象之前,你必须要先调用write()方法,因为先前的操作都是存储在缓存中的,所以要通过该方法将操作的内容保存在文件中。如果你先关闭了Excel对象,那么只能得到一张空的工作薄了。

  3、拷贝、更新Excel工作薄

  接下来简要介绍一下如何更新一个已经存在的工作薄,主要是下面二步操作,第一步是构造只读的Excel工作薄,第二步是利用已经创建的Excel工作薄创建新的可写入的Excel工作薄,参考下面的代码片段:(完整代码见ExcelModifying.java)


//创建只读的Excel工作薄的对象
jxl.Workbook rw = jxl.Workbook.getWorkbook(new File(sourcefile));

//创建可写入的Excel工作薄对象
jxl.write.WritableWorkbook wwb = Workbook.createWorkbook(new File(targetfile), rw);

//读取第一张工作表
jxl.write.WritableSheet ws = wwb.getSheet(0);

//获得第一个单元格对象
jxl.write.WritableCell wc = ws.getWritableCell(0, 0);

//判断单元格的类型, 做出相应的转化
if(wc.getType() == CellType.LABEL)
{
Label l = (Label)wc;
l.setString("The value has been modified.");
}

//写入Excel对象
wwb.write();

//关闭可写入的Excel对象
wwb.close();

//关闭只读的Excel对象
rw.close();


  之所以使用这种方式构建Excel对象,完全是因为效率的原因,因为上面的示例才是API的主要应用。为了提高性能,在读取工作表时,与数据相关的一些输出信息,所有的格式信息,如:字体、颜色等等,是不被处理的,因为我们的目的是获得行数据的值,既使没有了修饰,也不会对行数据的值产生什么影响。唯一的不利之处就是,在内存中会同时保存两个同样的工作表,这样当工作表体积比较大时,会占用相当大的内存,但现在好像内存的大小并不是什么关键因素了。

  一旦获得了可写入的工作表对象,我们就可以对单元格对象进行更新的操作了,在这里我们不必调用API提供的add()方法,因为单元格已经于工作表当中,所以我们只需要调用相应的setXXX()方法,就可以完成更新的操作了。

  尽单元格原有的格式化修饰是不能去掉的,我们还是可以将新的单元格修饰加上去,以使单元格的内容以不同的形式表现。

  新生成的工作表对象是可写入的,我们除了更新原有的单元格外,还可以添加新的单元格到工作表中,这与示例2的操作是完全一样的。

  最后,不要忘记调用write()方法,将更新的内容写入到文件中,然后关闭工作薄对象,这里有两个工作薄对象要关闭,一个是只读的,另外一个是可写入的。