网站交易中心 在这里你可以购买或者出售你的网站。
网站信息发布中心 在这里有各种交易信息的发布。同时提供
一些软件的免费使用(附有源码)。
网站博客系统 这里你可以注册自己的博客。一个账户无限量博客
联系方式:support@websiteempire.cn
QQ:563828566
MSN:zhuhailin123@hotmail.com
DarkKnight
Email:allwefantasy@gmail.com contact me!
Saturday, 11 October 2008
Sunday, 28 September 2008
排序算法的实际应用
上次在研究算法的时候详细的介绍了快速排序。
今天有个朋友托我将一些域名排序,按域名长度进行排序。
我看了轨迹至少有300多万行,处理出的结果(txt文件)多大将近100M.
数据量好大啊。
下面是程序清单。核心算法还是复用上次研究过的排序算法。由于数据量巨大,内存会溢出,所以基本解决解决方式是先将文件分段成多个小文件,再分别排序,最后合并到一个文件,删除那些小文件。这对性能造成了影响。所以我直接全部在内存中运算。
五个文件。
Compare.java接口 定义比较排序方式的接口
StringCompare.java Compare的实现类 比如我们这是以域名长度为排序结果
QuickSortVector.java 快速排序算法
GetFile.java 负责文件的输入输出
Main.java主函数入口
——————————————————————————
package sort;
interface Compare {
boolean lessThan(Object sou, Object des);
boolean lessThanOrEqual(Object sou, Object des);
}
——————————————————————
package sort;
public class StringCompare implements Compare
{
public boolean lessThan(Object sou, Object des)
{
return ((String)sou).length()-((String)des).length()<0;
}
public boolean lessThanOrEqual(Object sou, Object des)
{
return((String)sou).length()-((String)des).length()<=0;
}
}
————————————————————————————————
package sort;
import java.util.Vector;
public class QuickSortVector extends Vector{
private Compare compare;
public QuickSortVector(Compare com){
this.compare=com;
}
public void sort(){
quickSort(0,size()-1);
}
public void quickSort(int left,int right){
if(right<=left)
return ;
else{
Object pivot=elementAt(right);
int leftPtr=partition(left,right,pivot);
quickSort(left,leftPtr-1);
quickSort(leftPtr+1,right);
}
}
public int partition(int left,int right,Object pivot){
int leftPtr=left-1;
int rightPtr=right;
while(true){
while(compare.lessThan(elementAt(++leftPtr), pivot)){}
while(rightPtr>0&&compare.lessThan(pivot, elementAt(--rightPtr))){}
if(leftPtr<rightPtr)
swap(leftPtr,rightPtr);
else break;
}
swap(leftPtr,right);
return leftPtr;
}
public void swap(int left,int right)
{
Object temp=elementAt(left);
setElementAt(elementAt(right), left);
setElementAt(temp, right);
}
}
————————————————————————————————————
package sort;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class GetFile
{
private File file;
private File newFile;
private QuickSortVector qsv;
BufferedReader br=null;
BufferedWriter bw=null;
public GetFile(String file,String newFile,QuickSortVector qsv)
{
this.file=new File(file);
this.newFile=new File(newFile);
this.qsv=qsv;
}
public void process(File dir)
{
if (dir.isDirectory())
{
File[] dirs = dir.listFiles();
for (File temp : dirs)
{
if (temp.isDirectory())
{
process(temp);
} else if (temp.isFile())
{
try
{
getQSV(temp);
} catch (IOException e)
{
e.printStackTrace();
}
}
}
} else
{
try
{
getQSV(dir);
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public BufferedReader getReader(File file)
{
BufferedReader br=null;
try
{
br=new BufferedReader(new FileReader(file));
} catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return br;
}
public BufferedWriter getWriter(File file)
{
BufferedWriter bw=null;
try
{
bw=new BufferedWriter(new FileWriter(file));
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return bw;
}
public QuickSortVector getQSV(File file) throws IOException
{
br=getReader(file);
String temp=null;
while((temp=br.readLine())!=null)
{
qsv.addElement(temp);
}
br.close();
qsv.sort();
bw=getWriter(new File(this.newFile.getPath()+File.separator+file.getName()));
for(Object cc:qsv)
{
try
{
System.out.println((String)cc);
bw.write((String)cc+"\n");
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try
{
bw.close();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return qsv;
}
}
————————————————————————————————————————
package sort;
import java.io.File;
import java.io.IOException;
public class Main
{
// private qsv;
public static void main(String[] args)
{
Compare compare=new StringCompare();
QuickSortVector qsv=new QuickSortVector(compare);
GetFile getFile=new GetFile("D:/sort","D:/sort/result",qsv);
getFile.process(new File("D:/sort"));
[code=Java][/code]
}
}
今天有个朋友托我将一些域名排序,按域名长度进行排序。
我看了轨迹至少有300多万行,处理出的结果(txt文件)多大将近100M.
数据量好大啊。
下面是程序清单。核心算法还是复用上次研究过的排序算法。由于数据量巨大,内存会溢出,所以基本解决解决方式是先将文件分段成多个小文件,再分别排序,最后合并到一个文件,删除那些小文件。这对性能造成了影响。所以我直接全部在内存中运算。
五个文件。
Compare.java接口 定义比较排序方式的接口
StringCompare.java Compare的实现类 比如我们这是以域名长度为排序结果
QuickSortVector.java 快速排序算法
GetFile.java 负责文件的输入输出
Main.java主函数入口
——————————————————————————
package sort;
interface Compare {
boolean lessThan(Object sou, Object des);
boolean lessThanOrEqual(Object sou, Object des);
}
——————————————————————
package sort;
public class StringCompare implements Compare
{
public boolean lessThan(Object sou, Object des)
{
return ((String)sou).length()-((String)des).length()<0;
}
public boolean lessThanOrEqual(Object sou, Object des)
{
return((String)sou).length()-((String)des).length()<=0;
}
}
————————————————————————————————
package sort;
import java.util.Vector;
public class QuickSortVector extends Vector{
private Compare compare;
public QuickSortVector(Compare com){
this.compare=com;
}
public void sort(){
quickSort(0,size()-1);
}
public void quickSort(int left,int right){
if(right<=left)
return ;
else{
Object pivot=elementAt(right);
int leftPtr=partition(left,right,pivot);
quickSort(left,leftPtr-1);
quickSort(leftPtr+1,right);
}
}
public int partition(int left,int right,Object pivot){
int leftPtr=left-1;
int rightPtr=right;
while(true){
while(compare.lessThan(elementAt(++leftPtr), pivot)){}
while(rightPtr>0&&compare.lessThan(pivot, elementAt(--rightPtr))){}
if(leftPtr<rightPtr)
swap(leftPtr,rightPtr);
else break;
}
swap(leftPtr,right);
return leftPtr;
}
public void swap(int left,int right)
{
Object temp=elementAt(left);
setElementAt(elementAt(right), left);
setElementAt(temp, right);
}
}
————————————————————————————————————
package sort;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class GetFile
{
private File file;
private File newFile;
private QuickSortVector qsv;
BufferedReader br=null;
BufferedWriter bw=null;
public GetFile(String file,String newFile,QuickSortVector qsv)
{
this.file=new File(file);
this.newFile=new File(newFile);
this.qsv=qsv;
}
public void process(File dir)
{
if (dir.isDirectory())
{
File[] dirs = dir.listFiles();
for (File temp : dirs)
{
if (temp.isDirectory())
{
process(temp);
} else if (temp.isFile())
{
try
{
getQSV(temp);
} catch (IOException e)
{
e.printStackTrace();
}
}
}
} else
{
try
{
getQSV(dir);
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public BufferedReader getReader(File file)
{
BufferedReader br=null;
try
{
br=new BufferedReader(new FileReader(file));
} catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return br;
}
public BufferedWriter getWriter(File file)
{
BufferedWriter bw=null;
try
{
bw=new BufferedWriter(new FileWriter(file));
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return bw;
}
public QuickSortVector getQSV(File file) throws IOException
{
br=getReader(file);
String temp=null;
while((temp=br.readLine())!=null)
{
qsv.addElement(temp);
}
br.close();
qsv.sort();
bw=getWriter(new File(this.newFile.getPath()+File.separator+file.getName()));
for(Object cc:qsv)
{
try
{
System.out.println((String)cc);
bw.write((String)cc+"\n");
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try
{
bw.close();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return qsv;
}
}
————————————————————————————————————————
package sort;
import java.io.File;
import java.io.IOException;
public class Main
{
// private qsv;
public static void main(String[] args)
{
Compare compare=new StringCompare();
QuickSortVector qsv=new QuickSortVector(compare);
GetFile getFile=new GetFile("D:/sort","D:/sort/result",qsv);
getFile.process(new File("D:/sort"));
[code=Java][/code]
}
}
Tuesday, 19 August 2008
观察者模式 observer 模拟监听器的实现
学过awt,尤其是swing的同学就知道,swing中observer模式被大量的使用。比如,button.addActionListener(...)后,一旦你点击button后就能触发相应的事件。很多同学一定想知道内部的机制。今天我正好也对这个议题感兴趣,那么我们一起来看一下。
假设我有一台电脑。电脑里面有一个事件,当你按下电源按钮的时候,那么显示器会亮,电源指示灯也会亮。
这是典型的一个类似于GUI中按钮事件模拟。
我们来分析一下需要几个类
电脑肯定是需要的,我们得出Computer类。Computer类内部有一个按电源按钮的动作,我们叫做clickButton.这个事件我们叫做EventPowerOn.于是得出了EventPowerOn类。另外,对比GUI里面的button,他们都会添加监听器,所以应该有个监听器类,我们叫做EventPowerOnListener。好到这里我们基本上找出了几个必须的类Computer,EventPowerOn ,EventPowerOnListener
我们写出他们的框架:
public class Computer implements Runnable{
public void clikButton(){}//该方法如果被调用,那么说明电源按钮被按下
}
public class EventPowerOn {
private Object source;//事件的来源,也就是时间是由哪一个对象激发的
private long time;//这个属性是随意添加的 比如社么时候电源按钮被按下
public EventPowerOn( long time, Object object) {
super();
this.source = object;
this.time = time;
}
public Object getObject() {
return source;
}
public void setObject(Object object) {
this.source = object;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
public interface EventPowerOnListener {
public void performanceToPowerOn(EventPowerOn eventPowerOn);//改方法是电源按钮被
//按后应的响应
}
刚才我们说,电源按钮被按下,那么显示器和电源指示器会变亮。那么显示器和电源指示器应该都另外做一个类
所以有:
public class Screen implements EventPowerOnListener{
public void performanceToPowerOn(EventPowerOn eventPowerOn) {
System.out.println(eventPowerOn.getTime());
System.out.println("Screen is on");
}
}
public class Light implements EventPowerOnListener{
public void performanceToPowerOn(EventPowerOn eventPowerOn) {
System.out.println(eventPowerOn.getTime());
System.out.println("power light is on");
}
}
为什么我们要用他们实现监听器类呢,因为对computer来说,他们都是监听器。一旦电源按钮被按下,显示器和电源指示器就应该要有反应。
那么如何将screen 和light添加进computer 呢。我们做如下的改动
public class Computer {
private List listeners=new ArrayList();
public void addEventPowerOnListener(EventPowerOnListener eventPowerOnListener)
{
listeners.add(eventPowerOnListener);
}
public void clikButton(){
for(EventPowerOnListener temp:listeners)
{
temp.performanceToPowerOn(new EventPowerOn(System.currentTimeMillis(),this) );
}
}
}
好了,一个模拟的例子就完成了。我们来测试一下:
public class testObserver {
public static void main(String[] args){
Computer computer=new Computer();
Screen screen=new Screen();
Light light=new Light();
computer.addEventPowerOnListener(screen);
computer.addEventPowerOnListener(light);
computer.click();
};
}
看戏结果:
1219215665656
Screen is on
1219215665656
power light is on
说明clickButton,那么显示器和电源指示灯都会亮。我们还可以添加另外一些监听器,比如,硬盘会转等。。。只需实现listner接口以及添加到computer的监听列表即可。。。
(最近回过头来看java的一些基础的东西,看swing的时候,突然很想知道observer模式到底是则么实现的,研究了一下,看了一些源码,有了些心得,就顺便写出来)
假设我有一台电脑。电脑里面有一个事件,当你按下电源按钮的时候,那么显示器会亮,电源指示灯也会亮。
这是典型的一个类似于GUI中按钮事件模拟。
我们来分析一下需要几个类
电脑肯定是需要的,我们得出Computer类。Computer类内部有一个按电源按钮的动作,我们叫做clickButton.这个事件我们叫做EventPowerOn.于是得出了EventPowerOn类。另外,对比GUI里面的button,他们都会添加监听器,所以应该有个监听器类,我们叫做EventPowerOnListener。好到这里我们基本上找出了几个必须的类Computer,EventPowerOn ,EventPowerOnListener
我们写出他们的框架:
public class Computer implements Runnable{
public void clikButton(){}//该方法如果被调用,那么说明电源按钮被按下
}
public class EventPowerOn {
private Object source;//事件的来源,也就是时间是由哪一个对象激发的
private long time;//这个属性是随意添加的 比如社么时候电源按钮被按下
public EventPowerOn( long time, Object object) {
super();
this.source = object;
this.time = time;
}
public Object getObject() {
return source;
}
public void setObject(Object object) {
this.source = object;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}
public interface EventPowerOnListener {
public void performanceToPowerOn(EventPowerOn eventPowerOn);//改方法是电源按钮被
//按后应的响应
}
刚才我们说,电源按钮被按下,那么显示器和电源指示器会变亮。那么显示器和电源指示器应该都另外做一个类
所以有:
public class Screen implements EventPowerOnListener{
public void performanceToPowerOn(EventPowerOn eventPowerOn) {
System.out.println(eventPowerOn.getTime());
System.out.println("Screen is on");
}
}
public class Light implements EventPowerOnListener{
public void performanceToPowerOn(EventPowerOn eventPowerOn) {
System.out.println(eventPowerOn.getTime());
System.out.println("power light is on");
}
}
为什么我们要用他们实现监听器类呢,因为对computer来说,他们都是监听器。一旦电源按钮被按下,显示器和电源指示器就应该要有反应。
那么如何将screen 和light添加进computer 呢。我们做如下的改动
public class Computer {
private List
public void addEventPowerOnListener(EventPowerOnListener eventPowerOnListener)
{
listeners.add(eventPowerOnListener);
}
public void clikButton(){
for(EventPowerOnListener temp:listeners)
{
temp.performanceToPowerOn(new EventPowerOn(System.currentTimeMillis(),this) );
}
}
}
好了,一个模拟的例子就完成了。我们来测试一下:
public class testObserver {
public static void main(String[] args){
Computer computer=new Computer();
Screen screen=new Screen();
Light light=new Light();
computer.addEventPowerOnListener(screen);
computer.addEventPowerOnListener(light);
computer.click();
};
}
看戏结果:
1219215665656
Screen is on
1219215665656
power light is on
说明clickButton,那么显示器和电源指示灯都会亮。我们还可以添加另外一些监听器,比如,硬盘会转等。。。只需实现listner接口以及添加到computer的监听列表即可。。。
(最近回过头来看java的一些基础的东西,看swing的时候,突然很想知道observer模式到底是则么实现的,研究了一下,看了一些源码,有了些心得,就顺便写出来)
内嵌接口 内部类 匿名类 内嵌类
内嵌接口与内部类(nesting interface and inner class)
Nesting 接口和内部类 有诸多有趣的特性。尽管我们平时用的不是很多,但在很多时候能够让你的设计更优雅些。
这里我采用了 java思想变成里面的例子,因为该例子简单易懂,毕竟是个牛人写的呵呵(为了演示方便,我修改了一些内容)
下面是一个claass A类
class A
{
interface B
{
void f();
}
public class BImp implements B
{
public void f() {}
}
private class BImp2 implements B
{
public void f() {}
}
public interface C
{
//(7)private interface M{}
void f();
}
class CImp implements C
{
public void f() {}
}
private class CImp2 implements C
{
public void f() {}
}
private interface D
{
//(8)private interface M{}
void f();
}
private class DImp implements D
{
public void f() {}
}
public class DImp2 implements D
{
public void f() {}
}
public D getD()
{
return new DImp2();
}
private D dRef;
public void receiveD(D d)
{
dRef = d;
dRef.f();
}
}
interface E
{
interface G
{
void f();
}
public interface H
{
void f();
}
void g();
}
接着是一个测试类
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//(1)A.D ad=a.getD();
//(2) A.DImp2 ad =(A.DImp2) a.getD();
//(3) A.B ab=a.new BImp();
//(4) a.getD().f();
//(5) ((A.Dimp2)a.getD()).f();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~
在举例之前,我们先看看A类。A类是完全正确的。内嵌类的接口可以有访问修饰符。这和我们平时接触的接口有很大区别,而且修饰符也对其他类的访问有很大的影响。在A内部,大部分访问时不成问题的。但在NestingInterfaces类里面就有许多值得思考的地方了
我们做的主要测试都在第二个类的main 方法里面。我将它们注释掉,并且标上记号,方便讲解。
(1) 会报A.D不可见。原因是D为私有接口。
(2) 这个没有问题。原因在于Dimp2是公有类 如果是Dimp就不行了。另外虽然getD ()方法内部是return new Dimp2();当时返回的仍然是A.D,所以需要类型转换。这个我也没弄明白。
(3) 这句也没问题。
(4) 这个仍然提示A.D不可见。问题同(2)
(5) 这个可以运行
另外需要注意的一个问题是
A类中的(7),(8)都会提示应该声明为public,说明了接口套接口时,内部接口必须为public ,当然你也可以省略修饰词,他会自动为public。
上面的例子是关于内嵌接口的。下面我们再看看内部类有什么特点。
先看第一个演示的类
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
}
正如注释所说的,使用内部类就如同你使用其他一般的类一样,前提条件是你在外部类中使用他们,这里的外部类是Parcel1.
第二个例子:
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
}
现在我们可以看到得出一个结论:
除了在外部类的非静态方法中,如果我们需要实例化某一个内部类,那么实例化的形式是OuterClassName.InnerClassName;
下面仍然是类是于上面的例子,但做了一些变动:
public interface Contents {
int value();
}
public interface Destination {
String readLabel();
}
class Parcel3 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
public class TestParcel {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
Parcel3.PContents pc = p.new PContents(); //错误,无法实例化
}
}
正如在源代码中最后一句所暗示的,我们可以给内部类添加访问修饰符,我们知道,平常的类我们只能是public 或是包属性。而不能是private或者protected。但是内部类却可以。另外,上面代码和上上面代码还有一个不同点是Content 和Destination 类都实现了各自的接口。从代码封装来看,我们根本无法知道具体的实现类,这也许是内部类另外一个有用只处。
上面的都是一些内部类基础的性质。下面我们谈谈他的高级一点的东西,其中也涉及到了匿名类的一些东西。(PS:有时候我们也别他深究,比如学汉字,只要学会5000个基本的就好了,没必要把康熙字典中的五万个汉字都背下来,所以下面的了解就好)
public class Parcel4 {
public Destination dest(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
//(9)Destination d=p.new Destination(“kitty”);
}
}
这一段代码 不知大家看出和上一段的代码之间的区别没。对,Pdestinatio类被放在外部类的一个方法里面。其实,内部类可以放在外部类的任何一个作用域中。
自然由于有作用域的缘故,那就应该注意他的生命周期。比如(9)中的代码就是错误的,应为编译器根本无法解析Destination.
下面我们再看看匿名类的用法与注意事项:
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; //注意这里有个分号
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
}
注意到匿名类的语法:第一 new 后面接的是要实现的接口,当然也可以是一个类。第二,后面接你具体实现Contens接口的代码或者类中需要的方法。第三,匿名类的结尾需要分号标志结束。
下面嗨哟一段代码
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
}
如果在方法dest中传递的形参如果不是final类的话,编译器就会报错。这也是很重要的一点,往往会被人忽略,不过幸运的是编译器也会提示你,你很快就会纠正过来。不过下面就有一个有趣的是奇怪了,让我们来看看---
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
你会惊讶的发现,传递给Base的i 竟然不是final的。很郁闷吧,呵呵,其实你加上final也是没有问题的。但是为什么能够传递一个非final的值,原因很简单,编译器规定,如果这个数十给构造函数的话,并且没有在匿名函数起的地方使用,那么可以不是final的。不过我的建议是为了简单起见,我们不妨加上final。
PS:内部类有几个限制:一,内部类不能再嵌套一个内部类,第二,内部类不允许有静态方法与变量
此外,内部类还有很大的好处,他能够无条件的访问外部类的一切,包括私有变量。这在图形设计里面用的很广泛。比如你的一个button按钮需要实现一个监听器用来实现事件处理,那么你需要一个监听器类,如果你把它当成一个通常的类来用的话,那么监听器类如果需要访问被监听类的成员便只能通过构造函数或者传递一个类实例进去,并且要提供相应的访问它私有成员变量的公共方法。而使用内部类就可以简单的解决这一个问题,我们来看一下一个具体的实例:
package com.allwefantasy.java2d;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class Button extends JFrame
{
private JButton jbutton;
private Container container;
private JTextArea jtext;
class buttonLisener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}
}
}
public Button()
{
container=getContentPane();
jbutton=new JButton("kitty");
jtext=new JTextArea();
jbutton.addActionListener(new buttonLisener());
container.add(jbutton,BorderLayout.SOUTH);
container.add(jtext,BorderLayout.NORTH);
}
public static void main(String[] args)
{
Button button=new Button();
button.setBounds(100, 100,200, 300);
button.setVisible(true);
button.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
button.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);
}
});
}
}
通过观察我们可以发现,在内部类buttonLisener中,我们设置button类的JtextArea的值的时候我们是直接调用jtext.setText("awesome,get it");并没有传递外部类的任何参数进去。倘若我们将内部类作为一个平常的类定义,那就很麻烦了。另外值得一提的是,如果将内部类当作一个一个内部匿名类的话,将可以简化代码。如上上面的代码可以改成如下的:
jbutton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}
}
});
同时删除上一个内部类。不过个人不喜欢这样,因为感觉这样的话可读性变差。
写了一个早上了。呵呵本唉想留在后面写的,不过还是一鼓作气的写完比较好。
最好一个议题是:如果我觉得内部类能够访问外部类的一切,这总是让人觉得不爽,那我们怎么办?
给内部类添加一个static 描述符。Thinking in java 的作者将此事的内部类成为nesting class(内嵌类)(PS:不知道这么翻译是否准备去 呵呵)
(PS again 前面我讲的一些限制在内嵌类中并不是限制)
内部类还有很多性质,大家可以多多讨论。对于这篇文章大家如有疑问或者认为哪里有错误可以给我邮件:allwefantasy@gmail.com
Nesting 接口和内部类 有诸多有趣的特性。尽管我们平时用的不是很多,但在很多时候能够让你的设计更优雅些。
这里我采用了 java思想变成里面的例子,因为该例子简单易懂,毕竟是个牛人写的呵呵(为了演示方便,我修改了一些内容)
下面是一个claass A类
class A
{
interface B
{
void f();
}
public class BImp implements B
{
public void f() {}
}
private class BImp2 implements B
{
public void f() {}
}
public interface C
{
//(7)private interface M{}
void f();
}
class CImp implements C
{
public void f() {}
}
private class CImp2 implements C
{
public void f() {}
}
private interface D
{
//(8)private interface M{}
void f();
}
private class DImp implements D
{
public void f() {}
}
public class DImp2 implements D
{
public void f() {}
}
public D getD()
{
return new DImp2();
}
private D dRef;
public void receiveD(D d)
{
dRef = d;
dRef.f();
}
}
interface E
{
interface G
{
void f();
}
public interface H
{
void f();
}
void g();
}
接着是一个测试类
public class NestingInterfaces {
public class BImp implements A.B {
public void f() {}
}
class CImp implements A.C {
public void f() {}
}
// Cannot implement a private interface except
// within that interface's defining class:
//! class DImp implements A.D {
//! public void f() {}
//! }
class EImp implements E {
public void g() {}
}
class EGImp implements E.G {
public void f() {}
}
class EImp2 implements E {
public void g() {}
class EG implements E.G {
public void f() {}
}
}
public static void main(String[] args) {
A a = new A();
// Can't access A.D:
//(1)A.D ad=a.getD();
//(2) A.DImp2 ad =(A.DImp2) a.getD();
//(3) A.B ab=a.new BImp();
//(4) a.getD().f();
//(5) ((A.Dimp2)a.getD()).f();
// Cannot access a member of the interface:
//! a.getD().f();
// Only another A can do anything with getD():
A a2 = new A();
a2.receiveD(a.getD());
}
} ///:~
在举例之前,我们先看看A类。A类是完全正确的。内嵌类的接口可以有访问修饰符。这和我们平时接触的接口有很大区别,而且修饰符也对其他类的访问有很大的影响。在A内部,大部分访问时不成问题的。但在NestingInterfaces类里面就有许多值得思考的地方了
我们做的主要测试都在第二个类的main 方法里面。我将它们注释掉,并且标上记号,方便讲解。
(1) 会报A.D不可见。原因是D为私有接口。
(2) 这个没有问题。原因在于Dimp2是公有类 如果是Dimp就不行了。另外虽然getD ()方法内部是return new Dimp2();当时返回的仍然是A.D,所以需要类型转换。这个我也没弄明白。
(3) 这句也没问题。
(4) 这个仍然提示A.D不可见。问题同(2)
(5) 这个可以运行
另外需要注意的一个问题是
A类中的(7),(8)都会提示应该声明为public,说明了接口套接口时,内部接口必须为public ,当然你也可以省略修饰词,他会自动为public。
上面的例子是关于内嵌接口的。下面我们再看看内部类有什么特点。
先看第一个演示的类
public class Parcel1 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
// Using inner classes looks just like
// using any other class, within Parcel1:
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tanzania");
}
}
正如注释所说的,使用内部类就如同你使用其他一般的类一样,前提条件是你在外部类中使用他们,这里的外部类是Parcel1.
第二个例子:
public class Parcel2 {
class Contents {
private int i = 11;
public int value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents cont() {
return new Contents();
}
public void ship(String dest) {
Contents c = cont();
Destination d = to(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tanzania");
Parcel2 q = new Parcel2();
// Defining references to inner classes:
Parcel2.Contents c = q.cont();
Parcel2.Destination d = q.to("Borneo");
}
}
现在我们可以看到得出一个结论:
除了在外部类的非静态方法中,如果我们需要实例化某一个内部类,那么实例化的形式是OuterClassName.InnerClassName;
下面仍然是类是于上面的例子,但做了一些变动:
public interface Contents {
int value();
}
public interface Destination {
String readLabel();
}
class Parcel3 {
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination dest(String s) {
return new PDestination(s);
}
public Contents cont() {
return new PContents();
}
}
public class TestParcel {
public static void main(String[] args) {
Parcel3 p = new Parcel3();
Contents c = p.cont();
Destination d = p.dest("Tanzania");
Parcel3.PContents pc = p.new PContents(); //错误,无法实例化
}
}
正如在源代码中最后一句所暗示的,我们可以给内部类添加访问修饰符,我们知道,平常的类我们只能是public 或是包属性。而不能是private或者protected。但是内部类却可以。另外,上面代码和上上面代码还有一个不同点是Content 和Destination 类都实现了各自的接口。从代码封装来看,我们根本无法知道具体的实现类,这也许是内部类另外一个有用只处。
上面的都是一些内部类基础的性质。下面我们谈谈他的高级一点的东西,其中也涉及到了匿名类的一些东西。(PS:有时候我们也别他深究,比如学汉字,只要学会5000个基本的就好了,没必要把康熙字典中的五万个汉字都背下来,所以下面的了解就好)
public class Parcel4 {
public Destination dest(String s) {
class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
return new PDestination(s);
}
public static void main(String[] args) {
Parcel4 p = new Parcel4();
Destination d = p.dest("Tanzania");
//(9)Destination d=p.new Destination(“kitty”);
}
}
这一段代码 不知大家看出和上一段的代码之间的区别没。对,Pdestinatio类被放在外部类的一个方法里面。其实,内部类可以放在外部类的任何一个作用域中。
自然由于有作用域的缘故,那就应该注意他的生命周期。比如(9)中的代码就是错误的,应为编译器根本无法解析Destination.
下面我们再看看匿名类的用法与注意事项:
public class Parcel6 {
public Contents cont() {
return new Contents() {
private int i = 11;
public int value() { return i; }
}; //注意这里有个分号
}
public static void main(String[] args) {
Parcel6 p = new Parcel6();
Contents c = p.cont();
}
}
注意到匿名类的语法:第一 new 后面接的是要实现的接口,当然也可以是一个类。第二,后面接你具体实现Contens接口的代码或者类中需要的方法。第三,匿名类的结尾需要分号标志结束。
下面嗨哟一段代码
public class Parcel8 {
// Argument must be final to use inside
// anonymous inner class:
public Destination dest(final String dest) {
return new Destination() {
private String label = dest;
public String readLabel() { return label; }
};
}
public static void main(String[] args) {
Parcel8 p = new Parcel8();
Destination d = p.dest("Tanzania");
}
}
如果在方法dest中传递的形参如果不是final类的话,编译器就会报错。这也是很重要的一点,往往会被人忽略,不过幸运的是编译器也会提示你,你很快就会纠正过来。不过下面就有一个有趣的是奇怪了,让我们来看看---
abstract class Base {
public Base(int i) {
System.out.println("Base constructor, i = " + i);
}
public abstract void f();
}
public class AnonymousConstructor {
public static Base getBase(int i) {
return new Base(i) {
{
System.out.println("Inside instance initializer");
}
public void f() {
System.out.println("In anonymous f()");
}
};
}
public static void main(String[] args) {
Base base = getBase(47);
base.f();
}
}
你会惊讶的发现,传递给Base的i 竟然不是final的。很郁闷吧,呵呵,其实你加上final也是没有问题的。但是为什么能够传递一个非final的值,原因很简单,编译器规定,如果这个数十给构造函数的话,并且没有在匿名函数起的地方使用,那么可以不是final的。不过我的建议是为了简单起见,我们不妨加上final。
PS:内部类有几个限制:一,内部类不能再嵌套一个内部类,第二,内部类不允许有静态方法与变量
此外,内部类还有很大的好处,他能够无条件的访问外部类的一切,包括私有变量。这在图形设计里面用的很广泛。比如你的一个button按钮需要实现一个监听器用来实现事件处理,那么你需要一个监听器类,如果你把它当成一个通常的类来用的话,那么监听器类如果需要访问被监听类的成员便只能通过构造函数或者传递一个类实例进去,并且要提供相应的访问它私有成员变量的公共方法。而使用内部类就可以简单的解决这一个问题,我们来看一下一个具体的实例:
package com.allwefantasy.java2d;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.*;
public class Button extends JFrame
{
private JButton jbutton;
private Container container;
private JTextArea jtext;
class buttonLisener implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}
}
}
public Button()
{
container=getContentPane();
jbutton=new JButton("kitty");
jtext=new JTextArea();
jbutton.addActionListener(new buttonLisener());
container.add(jbutton,BorderLayout.SOUTH);
container.add(jtext,BorderLayout.NORTH);
}
public static void main(String[] args)
{
Button button=new Button();
button.setBounds(100, 100,200, 300);
button.setVisible(true);
button.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
button.addWindowListener(new WindowAdapter()
{
public void windowClosed(WindowEvent e)
{
System.exit(0);
}
});
}
}
通过观察我们可以发现,在内部类buttonLisener中,我们设置button类的JtextArea的值的时候我们是直接调用jtext.setText("awesome,get it");并没有传递外部类的任何参数进去。倘若我们将内部类作为一个平常的类定义,那就很麻烦了。另外值得一提的是,如果将内部类当作一个一个内部匿名类的话,将可以简化代码。如上上面的代码可以改成如下的:
jbutton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
if(e.getSource()==jbutton)
{
jtext.setText("awesome,get it");
}
}
});
同时删除上一个内部类。不过个人不喜欢这样,因为感觉这样的话可读性变差。
写了一个早上了。呵呵本唉想留在后面写的,不过还是一鼓作气的写完比较好。
最好一个议题是:如果我觉得内部类能够访问外部类的一切,这总是让人觉得不爽,那我们怎么办?
给内部类添加一个static 描述符。Thinking in java 的作者将此事的内部类成为nesting class(内嵌类)(PS:不知道这么翻译是否准备去 呵呵)
(PS again 前面我讲的一些限制在内嵌类中并不是限制)
内部类还有很多性质,大家可以多多讨论。对于这篇文章大家如有疑问或者认为哪里有错误可以给我邮件:allwefantasy@gmail.com
Wednesday, 13 August 2008
如何制作Java可执行程序以及安装程序
首先我们介绍一下生成 运行Java程序的三种方式-----
Java 生成的jar包执行方式一般有三种:
1直接用系统j2se binnary执行。由于jar包也可以用winrar打开,造成冲突,你可以将文件关联设置一下即可。这样的话jar包和.exe文件并无二致。而且不损失其平台型。
2 编写脚本。在windows下为.bat ,在linux 或者unix下为.sh。其实个人感觉这个更简单。而且很多大程序也是用这种方式运作的。比如tomcat 以及weblogic都是以这种脚本的方式启动。好处是可以设置各种系统环境变量,检测运行的环境,改变启动参数等等。比如在windows下我们只要加一句:start java –jar xxx.jar就可以了。
3 利用各种各种第三方工具将jar包制作成.bin(unix),或者.exe(windows)文件。但这是以失去跨平台特性为条件的。当然好处是带来了更好的用户体验。
第一种只要你安装了j2se就可以
第二种 我们可以举个例子
假设有个allwefantasy.jar的jar包(里面含有manifest文件),于是在相同目录下我建一个allwefantasy.bat(名字可以随意定,如果是在linux下平台就改成sh后缀名)。内容为
start java –jar xxx.jar
。以后双击就可运行。
第三种是我这篇文章的重点。
将 jar包包装成exe可执行文件
其实这种方式在JDK里面就在应用了。不知道你有没有注意到,%JAVA_HOME%\bin里面全是.exe文件,但仔细看看这些文件都只有几个kb而已,这是为什么呢,因为这只是一种wrapper.包装,真正在运行的仍然是jar文件。也可以称作伪exe文件。那么,如何将jar文件制作成可以直接运行的exe文件呢?这里我介绍两款我用过的。NativeJ 以及开源的jsmooth.
我个人比较喜欢用开源的,呵呵那先来介绍一下Jsmooth吧。
图片(2)
使用很简单,在Skeleton选项卡中的第一个下拉框中windowsed wrapper.
图片(4)
接着选择Executable选项卡,第一个空是你要生成的exe文件是叫什么,你自己随意定。第二个空的你想给自己的exe文件弄个什么图标,你可以实现做好一个ico图形文件然后使用。
第三个空就是你jar包所在目录。
图片(6)
在Application选显卡中第一个空粘帖你的 main-class 的全路径。不知道我说清楚了没,也就是你主函数所在目录的包名+主函数名字。比如我要讲的例子中drawsmart.itsv.AppMain。
然后再勾选use an embedded.jar.
最后选择你所需要的jar包就可以了。
最后点击编译按钮就好了。Project –compile—
接着介绍nativeJ,顾名思义,nativej就是本地化java的意思。
这个软件需要购买,不然启动的时候会弹出可恶的框框,跟可恶的是生成的exe。文件在执行的时候还会弹出申明这个程序是由它生成的。。
呵呵 抱怨的话不多讲,做软件的人也不容易。
图片(8)
一开始用的时候也觉得莫名其妙,不得要领,后来琢磨了一下,才闹明白。你事先建立一个目录里面,并且在目录里面建一个空的后缀名为njp文本文件。
然后点击new project wizard,
图片(10)
选择刚才建好的工程文件就可以了。接着就是依次填入一些参数。参数跟Jsmooth差不多,主要有两个,主类的全路径(包名+类名)以及jar包。
最后就能生成我们期待的exe文件了。
图片(12)
这是我生成的文件。
需要注意的是Jsmooth 和nativeJ生成的exe文件还是有所不同的。我不知道设置会不会改变这种不同。
nativeJ中,如上图所示,由于jar并没有将所有的文件都打入包中,比如images.所以生成的exe文件依然是以来jar包以及其他文件的。个人感觉有点像bat文件。
而在jsmooth中,生成了一个exe文件,但是文件却很大,似乎包含了整个jar包以及资源文件,可是却不能单独执行(为什么,我也没弄明白)。
图片(14)
如何为java程序制作安装程序
制作安装程序并不是一项简单的工作。特别对于大型程序来说。你看oracle光安装就的花你半个小时。安装制作程序有很多,大型商业类的有intallAnyWhere 以及installShield .功能超级强大,但毕竟是商业软件,是需要花银子的。此外,因为功能强,学习他的曲线也是比较陡峭的。
这里我还是介绍一个开源的软件。IzPack。你可以到官网去下。
在安装的时候你可以看到他用自己做的安装界面。
图片(16)
安装后必须自己找到他的目录。这里有两点大家可能不习惯,第一,他没有图形界面,第二,他在你给他写好他所需要的install.xml文件时候,是无法运行的。
也许你还不太理解。没关系,我们讲讲他的设计理念。
安装程序无非就几个界面板块,语言选择,如上面的第一副图片,许可申明,如第二副图,安装进程,安装结束界面等。在IzPack中这些叫面板。IzPack就是利用install.xml文件配置这些面板。当然他还有很多内部细节,比如注册表的注册等,当然这是另外一回事了。然后生成一个jar包(又是jar包,为什么不直接生成exe文件呢,呵呵这个我也不知道,你得去问设计的人,呵呵PS:其实还是为了跨平台)。
那么如何写install,xml 文档呢,说明书有一百五十多业,还是自己慢慢看。不过我可以拿一个自己做的范例来看看。
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<installation version="1.0">
//info 主要是一些版本 作者信息
<info>
<appname>我的画画本</appname>
<appversion>1.4 beta </appversion>
<authors>
<author name="GuanSheng" email="wgs@superman.org"/>
<author name="HailLIN" email="zhl@hisdomain.com"/>
</authors>
<url>http://www.superman.net/</url>
</info>
//guiprefs主要设置安装界面的大小,以及是否可以改变尺寸
<guiprefs width="640" height="480" resizable="no"/>
//这里很java里面的国际化很像,我没数过,但IzPack支持很多国家的语言,我这里面选了 英文以及中文。注意,他的语言的缩写和java里面不一致,你要参考文档
<locale>
<langpack iso3="eng"/>
<langpack iso3="chn"/>
</locale>
//资源文件爱你,我这里放了许可申明以及阅readme文件
<resources>
<res id="LicencePanel.licence" src="Licence.txt"/>
<res id="InfoPanel.info" src="Readme.txt"/>
</resources>
//这个就是各个板块了 比如开始,安装,申明板块都在这定义了
<panels>
<panel classname="HelloPanel"/>
<panel classname="InfoPanel"/>
<panel classname="LicencePanel"/>
<panel classname="TargetPanel"/>
<panel classname="PacksPanel"/>
<panel classname="InstallPanel"/>
<panel classname="FinishPanel"/>
</panels>
<packs>
<pack name="Base" required="yes">
<description>The base files</description>
<file src="Readme.txt" targetdir="$INSTALL_PATH"/>
<file src="Licence.txt" targetdir="$INSTALL_PATH"/>
<file src="kitty.exe" targetdir="$INSTALL_PATH"/>
<parsable targetfile="$INSTALL_PATH/kitty.exe"/>
//文件复制到安装文件夹里面就靠这个了
</pack>
<pack name="image" required="yes">
<description>The documentation</description>
<file src="images" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="xmlfile" required="yes">
<description>The sources</description>
<file src="xmlfile" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="temp" required="yes">
<description>The sources</description>
<file src="temp" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="drawflow" required="yes">
<description>The sources</description>
<file src="drawflow3_0.jar" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="service" required="yes">
<description>The sources</description>
<file src="service.ico" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="addon" required="yes">
<description>The sources</description>
<file src="add-on" targetdir="$INSTALL_PATH"/>
</pack>
</packs>
</installation>
写好这个文件后将它放在你要制作成安装文件的主目录下,然后用compile.bat运行它,就能得到一个jar文件(PS:注意,在cmd 中一定要在install所在目录执行install不然会提示找不到各个文件,我就被他卡在这很多时间)
那么生成一个jar文件有什么用呢,呵呵,别急
看最后一步
将jar包的安装程序转换成exe安装程序
实际上到这一步,就是用jsmooth将jar安装程序wrapper成exe.那么最后就大功告成了。
所以用了三个步骤。Jar 程序--jsmooth---exe程序----IzPack---jar安装程序---jsmooth—exe安装程序。
当然如果你想更专业点,可以用installsheild或者intallanywhere。简单说说这两个软件,图形界面,可操作性相当的好。而且对于学习用来说,他不需要注册码。唯一的缺点是,如果你不注册,你制作的安装程序在安装的时候就会弹出该产品未注册的提示框。
我也是在网上看到很多网友问着一方面的问题却往往得不到好的解答。所以在此总结一下。希望和大家多多交流。如果有问题可以联系我:allwefantasy@gmail.com
(注意:我截的图片一直贴不到博客里面,正在解决中。。。。)
Java 生成的jar包执行方式一般有三种:
1直接用系统j2se binnary执行。由于jar包也可以用winrar打开,造成冲突,你可以将文件关联设置一下即可。这样的话jar包和.exe文件并无二致。而且不损失其平台型。
2 编写脚本。在windows下为.bat ,在linux 或者unix下为.sh。其实个人感觉这个更简单。而且很多大程序也是用这种方式运作的。比如tomcat 以及weblogic都是以这种脚本的方式启动。好处是可以设置各种系统环境变量,检测运行的环境,改变启动参数等等。比如在windows下我们只要加一句:start java –jar xxx.jar就可以了。
3 利用各种各种第三方工具将jar包制作成.bin(unix),或者.exe(windows)文件。但这是以失去跨平台特性为条件的。当然好处是带来了更好的用户体验。
第一种只要你安装了j2se就可以
第二种 我们可以举个例子
假设有个allwefantasy.jar的jar包(里面含有manifest文件),于是在相同目录下我建一个allwefantasy.bat(名字可以随意定,如果是在linux下平台就改成sh后缀名)。内容为
start java –jar xxx.jar
。以后双击就可运行。
第三种是我这篇文章的重点。
将 jar包包装成exe可执行文件
其实这种方式在JDK里面就在应用了。不知道你有没有注意到,%JAVA_HOME%\bin里面全是.exe文件,但仔细看看这些文件都只有几个kb而已,这是为什么呢,因为这只是一种wrapper.包装,真正在运行的仍然是jar文件。也可以称作伪exe文件。那么,如何将jar文件制作成可以直接运行的exe文件呢?这里我介绍两款我用过的。NativeJ 以及开源的jsmooth.
我个人比较喜欢用开源的,呵呵那先来介绍一下Jsmooth吧。
图片(2)
使用很简单,在Skeleton选项卡中的第一个下拉框中windowsed wrapper.
图片(4)
接着选择Executable选项卡,第一个空是你要生成的exe文件是叫什么,你自己随意定。第二个空的你想给自己的exe文件弄个什么图标,你可以实现做好一个ico图形文件然后使用。
第三个空就是你jar包所在目录。
图片(6)
在Application选显卡中第一个空粘帖你的 main-class 的全路径。不知道我说清楚了没,也就是你主函数所在目录的包名+主函数名字。比如我要讲的例子中drawsmart.itsv.AppMain。
然后再勾选use an embedded.jar.
最后选择你所需要的jar包就可以了。
最后点击编译按钮就好了。Project –compile—
接着介绍nativeJ,顾名思义,nativej就是本地化java的意思。
这个软件需要购买,不然启动的时候会弹出可恶的框框,跟可恶的是生成的exe。文件在执行的时候还会弹出申明这个程序是由它生成的。。
呵呵 抱怨的话不多讲,做软件的人也不容易。
图片(8)
一开始用的时候也觉得莫名其妙,不得要领,后来琢磨了一下,才闹明白。你事先建立一个目录里面,并且在目录里面建一个空的后缀名为njp文本文件。
然后点击new project wizard,
图片(10)
选择刚才建好的工程文件就可以了。接着就是依次填入一些参数。参数跟Jsmooth差不多,主要有两个,主类的全路径(包名+类名)以及jar包。
最后就能生成我们期待的exe文件了。
图片(12)
这是我生成的文件。
需要注意的是Jsmooth 和nativeJ生成的exe文件还是有所不同的。我不知道设置会不会改变这种不同。
nativeJ中,如上图所示,由于jar并没有将所有的文件都打入包中,比如images.所以生成的exe文件依然是以来jar包以及其他文件的。个人感觉有点像bat文件。
而在jsmooth中,生成了一个exe文件,但是文件却很大,似乎包含了整个jar包以及资源文件,可是却不能单独执行(为什么,我也没弄明白)。
图片(14)
如何为java程序制作安装程序
制作安装程序并不是一项简单的工作。特别对于大型程序来说。你看oracle光安装就的花你半个小时。安装制作程序有很多,大型商业类的有intallAnyWhere 以及installShield .功能超级强大,但毕竟是商业软件,是需要花银子的。此外,因为功能强,学习他的曲线也是比较陡峭的。
这里我还是介绍一个开源的软件。IzPack。你可以到官网去下。
在安装的时候你可以看到他用自己做的安装界面。
图片(16)
安装后必须自己找到他的目录。这里有两点大家可能不习惯,第一,他没有图形界面,第二,他在你给他写好他所需要的install.xml文件时候,是无法运行的。
也许你还不太理解。没关系,我们讲讲他的设计理念。
安装程序无非就几个界面板块,语言选择,如上面的第一副图片,许可申明,如第二副图,安装进程,安装结束界面等。在IzPack中这些叫面板。IzPack就是利用install.xml文件配置这些面板。当然他还有很多内部细节,比如注册表的注册等,当然这是另外一回事了。然后生成一个jar包(又是jar包,为什么不直接生成exe文件呢,呵呵这个我也不知道,你得去问设计的人,呵呵PS:其实还是为了跨平台)。
那么如何写install,xml 文档呢,说明书有一百五十多业,还是自己慢慢看。不过我可以拿一个自己做的范例来看看。
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<installation version="1.0">
//info 主要是一些版本 作者信息
<info>
<appname>我的画画本</appname>
<appversion>1.4 beta </appversion>
<authors>
<author name="GuanSheng" email="wgs@superman.org"/>
<author name="HailLIN" email="zhl@hisdomain.com"/>
</authors>
<url>http://www.superman.net/</url>
</info>
//guiprefs主要设置安装界面的大小,以及是否可以改变尺寸
<guiprefs width="640" height="480" resizable="no"/>
//这里很java里面的国际化很像,我没数过,但IzPack支持很多国家的语言,我这里面选了 英文以及中文。注意,他的语言的缩写和java里面不一致,你要参考文档
<locale>
<langpack iso3="eng"/>
<langpack iso3="chn"/>
</locale>
//资源文件爱你,我这里放了许可申明以及阅readme文件
<resources>
<res id="LicencePanel.licence" src="Licence.txt"/>
<res id="InfoPanel.info" src="Readme.txt"/>
</resources>
//这个就是各个板块了 比如开始,安装,申明板块都在这定义了
<panels>
<panel classname="HelloPanel"/>
<panel classname="InfoPanel"/>
<panel classname="LicencePanel"/>
<panel classname="TargetPanel"/>
<panel classname="PacksPanel"/>
<panel classname="InstallPanel"/>
<panel classname="FinishPanel"/>
</panels>
<packs>
<pack name="Base" required="yes">
<description>The base files</description>
<file src="Readme.txt" targetdir="$INSTALL_PATH"/>
<file src="Licence.txt" targetdir="$INSTALL_PATH"/>
<file src="kitty.exe" targetdir="$INSTALL_PATH"/>
<parsable targetfile="$INSTALL_PATH/kitty.exe"/>
//文件复制到安装文件夹里面就靠这个了
</pack>
<pack name="image" required="yes">
<description>The documentation</description>
<file src="images" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="xmlfile" required="yes">
<description>The sources</description>
<file src="xmlfile" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="temp" required="yes">
<description>The sources</description>
<file src="temp" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="drawflow" required="yes">
<description>The sources</description>
<file src="drawflow3_0.jar" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="service" required="yes">
<description>The sources</description>
<file src="service.ico" targetdir="$INSTALL_PATH"/>
</pack>
<pack name="addon" required="yes">
<description>The sources</description>
<file src="add-on" targetdir="$INSTALL_PATH"/>
</pack>
</packs>
</installation>
写好这个文件后将它放在你要制作成安装文件的主目录下,然后用compile.bat运行它,就能得到一个jar文件(PS:注意,在cmd 中一定要在install所在目录执行install不然会提示找不到各个文件,我就被他卡在这很多时间)
那么生成一个jar文件有什么用呢,呵呵,别急
看最后一步
将jar包的安装程序转换成exe安装程序
实际上到这一步,就是用jsmooth将jar安装程序wrapper成exe.那么最后就大功告成了。
所以用了三个步骤。Jar 程序--jsmooth---exe程序----IzPack---jar安装程序---jsmooth—exe安装程序。
当然如果你想更专业点,可以用installsheild或者intallanywhere。简单说说这两个软件,图形界面,可操作性相当的好。而且对于学习用来说,他不需要注册码。唯一的缺点是,如果你不注册,你制作的安装程序在安装的时候就会弹出该产品未注册的提示框。
我也是在网上看到很多网友问着一方面的问题却往往得不到好的解答。所以在此总结一下。希望和大家多多交流。如果有问题可以联系我:allwefantasy@gmail.com
(注意:我截的图片一直贴不到博客里面,正在解决中。。。。)
Friday, 8 August 2008
(转载)详解Eclipse+MyEclipse完全绿色版制作方法
原文地址:http://www.blogjava.net/bolo/archive/2008/08/07/220696.html
现在在Java开发中,使用的开发工具大部分都是Eclipse,并且和Eclipse关系紧密的要数MyEclipse了,但是MyEclipse是一个EXE可执行程序,对于没有安装Eclipse与MyEclilpse的电脑来说,首先得先解压Eclipse,然后再安装MyEclipse,这不光很麻烦,而且还很费时,对于已经安装好的电脑来说,如果哪天电脑出了问题或是Eclipse崩溃了,导致工具不能用,这时又不得不重新安装时,那可真够郁闷滴~~~,因此,大象本着我为人人,人人为我的奉献精神,在此,将Eclipse+MyEclipse的完全绿色版制作方法写出来,和大家一起分享,让大家都能享受到这种方便。
在这里,大象采用Eclipse3.3.1与MyEclipse_6.0.1GA_E3.3.1_Installer版来举例说明,其它的版本做法与此相同。
第1步:下载Eclipse3.3.1和MyEclipse_6.0.1GA
这里我要提醒大家注意一下:下载Eclipse时不要选择3.2的版本,因为MyEclipse6.0需要3.3版本以上的支持,另外就是下载MyEclipse时不要下完全版,而应该只下插件版,我的这个MyEclipse6.0.1的插件版是176M。
第2步:解压Eclipse3.3.1
将Eclipse3.3.1的压缩包解压到D盘根目录下。
做这个绿色版,把它放在根目录下是因为这样做很方便,在这里,大象以D盘为例,来说明制作方法。
第3步:安装MyEclipse6.0.1GA
双击"MyEclipse_6.0.1GA_E3.3.1_Installer.exe"开始安装MyEclipse,在第3步:"Choose Eclipse Folder"时,注意 "Please Choose Existing Eclipse Installation Folder",点击"Choose...",请选择你解压的Eclipse文件夹,选择好之后如下图:
点击"Next",出现"Where Would You Like to Install MyEclipse 6.0.1?",点击"Choose...",选择上面的eclipse文件夹,这时记得在eclipse后面加一个目录名,否则,MyEclipse的安装文件就会全部放在eclipse的根目录下,这可不是我们希望看到滴,设置好之后如下图:
下面的安装没什么好说的,就是一路Next了。安装结束后,可以在eclipse目录下看到有一个"MyEclipse 6.0.1GA"这个文件夹,进去看看,是不是有两个文件夹,两个文件?
OK,到此MyEclipse插件已经安装完成了,下面来进行我们的绿色插件制作。
第4步:插件制作
在eclipse目录下,新建一个文件夹,命名为"ThirdPlugins"(你要取别的名字也可以,不过一定要和links目录里面的配置文件中的路径一致,后面会有说明),将"MyEclipse 6.0.1GA"这个文件夹复制到"ThirdPlugins"目录下,别用剪切喔,这可是刚才安装MyEclipse的目录,等会还要缷载MyEclipse,如果这个目录没有了,到时缷载不了,出了什么问题可不要怪大象喔!
MyEclipse安装好之后,会在eclipse目录下生成一个links文件夹,里面有一个"com.genuitec.eclipse.MyEclipse.link"文件,我们删除它,另外新建一个"MyEclipse 6.0.1GA.ini"文件,内容为:path=ThirdPlugins/MyEclipse 6.0.1GA
保存完之后,我们的插件制作也结束了,然后就是缷载MyEclipse,千万不要直接把那个文件夹删掉,而应该缷载它。
其实所有的插件都可以按这个方式来做,这样做的好处就是,想用就放进去,不想用就删掉,如果放到eclipse的features和plugins里面,会很不好管理。
第5步:配置参数
虽然插件已经安装好了,但是,此时我们还不能启动它,应该对eclipse的启动参数设置一下,提高它的启动速度和运行时的稳定性。在eclipse.exe上点右键,选择"创建快捷方式",在快捷方式上点右键,选择"属性",在"D:\eclipse\eclipse.exe后面加上空格,将这些参数加在后面:
-vmargs -Xverify:none -XX:+UseParallelGC -XX:PermSize=20M -XX:MaxPermSize=128M -Xms256M -Xmx512M
-Xms256M -Xmx512M:这是堆,根据内存大小来设置,比如大象的内存是1G,我就设成256和512,这样一般都够用了。
当然了,你也可以什么都不设置,不过大象还是建议设置这些参数,可以很大程度上提升eclipse的启动速度。在安装完MyEclipse时,还会生成一个eclipse.ini的备份文件,这个不需要,删掉。我们可以修改下eclipse.ini文件,原始的如下:
-showsplash
com.genuitec.myeclipse.product
--launcher.XXMaxPermSize
256m
-vmargs
-Xms128m
-Xmx512m
-Dosgi.splashLocation=D:\eclipse\MyEclipse 6.0.1GA\eclipse\MyEclipseSplash.bmp
-Duser.language=en
-XX:PermSize=128M
-XX:MaxPermSize=256M 其实这个文件为空都没关系,大象试过,全部删除,没有错误,不过我还是建议大家里面至少保留这些东东
-vmargs
-Xms256m
-Xmx512m 我将128改成了256,如果你想在MyEclipse插件中用"MyEclipse 6.0.1"快捷方式来启动的话,可以写成这样
-vmargs
-Xms256m
-Xmx512m
-Dosgi.splashLocation=D:\Eclipse-3.3.1\ThirdPlugins\MyEclipse 6.0.1GA\eclipse\MyEclipseSplash.bmp
最下面一行是启动时,显示MyEclipse的图片,如果没有这句话运行"MyEclipse 6.0.1"快捷方式,则会显示eclipse的启动画面,其实"MyEclipse 6.0.1"快捷方式还是连接着eclipse.exe这个执行程序,在"MyEclipse 6.0.1"上点右键,选择属性,在目标里就可以看到。
第6步:注册MyEclipse
MyEclipse6.0的注册一定要断开网络,否则肯定不成功!
6.0.1GA注册码
Subscriber: administrator
Subscription Code: nLR7ZL-655342-54657656405281154
这里有一点大象要提醒大家注意,如果你电脑上现在正有使用的MyEclipse,就是说已经注册了,那么在C:\Documents and Settings\"自己的用户名" 目录下,会有一个".myeclipse.properties"文件,这时请先备份此文件,然后删除它,断开网络,再来注册MyEclipse6.0.1,成功后如下:
第7步:打包eclipse
到现在所有的工作都已经完成,启动eclipse的速度快不快?好了,该做最后一步操作了,将"configuration"文件夹下的内容除"config.ini"文件外全部删除,另外再把workspace文件夹删除,大象一般会把workspace放在eclipse根目录下,方法是在第一次启动选择路径时把前面的目录都删除,只保留workspace(前面什么都不要保留)。这样方便管理,你要放在其它的地方随便,这个看各人喜好。做完这两步之后,最好还是在eclipse目录下建一个txt文本文件,把上面的注册码放到里面,另外加上一句话:"注册时一定要断开网络,否则肯定注册不成功!"这样以后用时,可以提醒自己一下。里面有注册码,要用时很方便。
在eclipse文件夹上点右键,选择"添加到eclipse.rar",等到压缩完成,至此,终于大功告成!
大家尽管放心按着我的方法试,大象前前后后做了不下十遍,今天又在公司的电脑上做了一遍,图片都是刚刚截取的,嘿嘿,今天老板不在,大象小小的放松一下,写写博客。有了这个压缩包,以后大家在使用时就会方便很多,特别是保存到移动硬盘里,想在哪用就在哪用。哇哈哈哈哈~~~~~~~~祝大家好运,都成功做出来!
(注: 此帖为菠萝大象原创,而非我写的,由于图片链接很麻烦,所以我没提供图片,大家可以到原博客去看看,也算是度原作者的一个支持。。。。。。)
现在在Java开发中,使用的开发工具大部分都是Eclipse,并且和Eclipse关系紧密的要数MyEclipse了,但是MyEclipse是一个EXE可执行程序,对于没有安装Eclipse与MyEclilpse的电脑来说,首先得先解压Eclipse,然后再安装MyEclipse,这不光很麻烦,而且还很费时,对于已经安装好的电脑来说,如果哪天电脑出了问题或是Eclipse崩溃了,导致工具不能用,这时又不得不重新安装时,那可真够郁闷滴~~~,因此,大象本着我为人人,人人为我的奉献精神,在此,将Eclipse+MyEclipse的完全绿色版制作方法写出来,和大家一起分享,让大家都能享受到这种方便。
在这里,大象采用Eclipse3.3.1与MyEclipse_6.0.1GA_E3.3.1_Installer版来举例说明,其它的版本做法与此相同。
第1步:下载Eclipse3.3.1和MyEclipse_6.0.1GA
这里我要提醒大家注意一下:下载Eclipse时不要选择3.2的版本,因为MyEclipse6.0需要3.3版本以上的支持,另外就是下载MyEclipse时不要下完全版,而应该只下插件版,我的这个MyEclipse6.0.1的插件版是176M。
第2步:解压Eclipse3.3.1
将Eclipse3.3.1的压缩包解压到D盘根目录下。
做这个绿色版,把它放在根目录下是因为这样做很方便,在这里,大象以D盘为例,来说明制作方法。
第3步:安装MyEclipse6.0.1GA
双击"MyEclipse_6.0.1GA_E3.3.1_Installer.exe"开始安装MyEclipse,在第3步:"Choose Eclipse Folder"时,注意 "Please Choose Existing Eclipse Installation Folder",点击"Choose...",请选择你解压的Eclipse文件夹,选择好之后如下图:
点击"Next",出现"Where Would You Like to Install MyEclipse 6.0.1?",点击"Choose...",选择上面的eclipse文件夹,这时记得在eclipse后面加一个目录名,否则,MyEclipse的安装文件就会全部放在eclipse的根目录下,这可不是我们希望看到滴,设置好之后如下图:
下面的安装没什么好说的,就是一路Next了。安装结束后,可以在eclipse目录下看到有一个"MyEclipse 6.0.1GA"这个文件夹,进去看看,是不是有两个文件夹,两个文件?
OK,到此MyEclipse插件已经安装完成了,下面来进行我们的绿色插件制作。
第4步:插件制作
在eclipse目录下,新建一个文件夹,命名为"ThirdPlugins"(你要取别的名字也可以,不过一定要和links目录里面的配置文件中的路径一致,后面会有说明),将"MyEclipse 6.0.1GA"这个文件夹复制到"ThirdPlugins"目录下,别用剪切喔,这可是刚才安装MyEclipse的目录,等会还要缷载MyEclipse,如果这个目录没有了,到时缷载不了,出了什么问题可不要怪大象喔!
MyEclipse安装好之后,会在eclipse目录下生成一个links文件夹,里面有一个"com.genuitec.eclipse.MyEclipse.link"文件,我们删除它,另外新建一个"MyEclipse 6.0.1GA.ini"文件,内容为:path=ThirdPlugins/MyEclipse 6.0.1GA
保存完之后,我们的插件制作也结束了,然后就是缷载MyEclipse,千万不要直接把那个文件夹删掉,而应该缷载它。
其实所有的插件都可以按这个方式来做,这样做的好处就是,想用就放进去,不想用就删掉,如果放到eclipse的features和plugins里面,会很不好管理。
第5步:配置参数
虽然插件已经安装好了,但是,此时我们还不能启动它,应该对eclipse的启动参数设置一下,提高它的启动速度和运行时的稳定性。在eclipse.exe上点右键,选择"创建快捷方式",在快捷方式上点右键,选择"属性",在"D:\eclipse\eclipse.exe后面加上空格,将这些参数加在后面:
-vmargs -Xverify:none -XX:+UseParallelGC -XX:PermSize=20M -XX:MaxPermSize=128M -Xms256M -Xmx512M
-Xms256M -Xmx512M:这是堆,根据内存大小来设置,比如大象的内存是1G,我就设成256和512,这样一般都够用了。
当然了,你也可以什么都不设置,不过大象还是建议设置这些参数,可以很大程度上提升eclipse的启动速度。在安装完MyEclipse时,还会生成一个eclipse.ini的备份文件,这个不需要,删掉。我们可以修改下eclipse.ini文件,原始的如下:
-showsplash
com.genuitec.myeclipse.product
--launcher.XXMaxPermSize
256m
-vmargs
-Xms128m
-Xmx512m
-Dosgi.splashLocation=D:\eclipse\MyEclipse 6.0.1GA\eclipse\MyEclipseSplash.bmp
-Duser.language=en
-XX:PermSize=128M
-XX:MaxPermSize=256M 其实这个文件为空都没关系,大象试过,全部删除,没有错误,不过我还是建议大家里面至少保留这些东东
-vmargs
-Xms256m
-Xmx512m 我将128改成了256,如果你想在MyEclipse插件中用"MyEclipse 6.0.1"快捷方式来启动的话,可以写成这样
-vmargs
-Xms256m
-Xmx512m
-Dosgi.splashLocation=D:\Eclipse-3.3.1\ThirdPlugins\MyEclipse 6.0.1GA\eclipse\MyEclipseSplash.bmp
最下面一行是启动时,显示MyEclipse的图片,如果没有这句话运行"MyEclipse 6.0.1"快捷方式,则会显示eclipse的启动画面,其实"MyEclipse 6.0.1"快捷方式还是连接着eclipse.exe这个执行程序,在"MyEclipse 6.0.1"上点右键,选择属性,在目标里就可以看到。
第6步:注册MyEclipse
MyEclipse6.0的注册一定要断开网络,否则肯定不成功!
6.0.1GA注册码
Subscriber: administrator
Subscription Code: nLR7ZL-655342-54657656405281154
这里有一点大象要提醒大家注意,如果你电脑上现在正有使用的MyEclipse,就是说已经注册了,那么在C:\Documents and Settings\"自己的用户名" 目录下,会有一个".myeclipse.properties"文件,这时请先备份此文件,然后删除它,断开网络,再来注册MyEclipse6.0.1,成功后如下:
第7步:打包eclipse
到现在所有的工作都已经完成,启动eclipse的速度快不快?好了,该做最后一步操作了,将"configuration"文件夹下的内容除"config.ini"文件外全部删除,另外再把workspace文件夹删除,大象一般会把workspace放在eclipse根目录下,方法是在第一次启动选择路径时把前面的目录都删除,只保留workspace(前面什么都不要保留)。这样方便管理,你要放在其它的地方随便,这个看各人喜好。做完这两步之后,最好还是在eclipse目录下建一个txt文本文件,把上面的注册码放到里面,另外加上一句话:"注册时一定要断开网络,否则肯定注册不成功!"这样以后用时,可以提醒自己一下。里面有注册码,要用时很方便。
在eclipse文件夹上点右键,选择"添加到eclipse.rar",等到压缩完成,至此,终于大功告成!
大家尽管放心按着我的方法试,大象前前后后做了不下十遍,今天又在公司的电脑上做了一遍,图片都是刚刚截取的,嘿嘿,今天老板不在,大象小小的放松一下,写写博客。有了这个压缩包,以后大家在使用时就会方便很多,特别是保存到移动硬盘里,想在哪用就在哪用。哇哈哈哈哈~~~~~~~~祝大家好运,都成功做出来!
(注: 此帖为菠萝大象原创,而非我写的,由于图片链接很麻烦,所以我没提供图片,大家可以到原博客去看看,也算是度原作者的一个支持。。。。。。)
Friday, 1 August 2008
xdoclet 遇到 泛型符号出错
用XDoclet tag 生成 hibernate hbm.xml映射文件时 出错.
Error parsing File G:\workbench\futureCore\src\com\hailin\model\ItemCategory.java:Encountered "<" at line 14, column 14.
[hibernatedoclet] Was expecting one of:
[hibernatedoclet] ...
[hibernatedoclet] "[" ...
[hibernatedoclet] "." ...
[hibernatedoclet] "(" ...
凡是我用了泛型的地方,也就是有 <>的地方都会报错...
在网上查了下,所示xdoclet不支持jdk1.5的annotation,把 xjavadoc1.1.jar换成 xjavadoc.1.5.snapshot.jar 也没有用...
到现在也没有解决问题.
只能不使用泛型,郁闷........
还好我的POJO只有十多个 不然改都要改老半天.........
不知道有谁 解决了这个问题不.......
Error parsing File G:\workbench\futureCore\src\com\hailin\model\ItemCategory.java:Encountered "<" at line 14, column 14.
[hibernatedoclet] Was expecting one of:
[hibernatedoclet]
[hibernatedoclet] "[" ...
[hibernatedoclet] "." ...
[hibernatedoclet] "(" ...
凡是我用了泛型的地方,也就是有 <>的地方都会报错...
在网上查了下,所示xdoclet不支持jdk1.5的annotation,把 xjavadoc1.1.jar换成 xjavadoc.1.5.snapshot.jar 也没有用...
到现在也没有解决问题.
只能不使用泛型,郁闷........
还好我的POJO只有十多个 不然改都要改老半天.........
不知道有谁 解决了这个问题不.......
Subscribe to:
Posts (Atom)