1. 构造可以传递任意Java对象的Transferable类——DataFlavor的本质:
1) JDK同样没有提供这样的实现,只不过StringSelection使用的是stringFlavor格式标签,图像类实现Transferable使用的是imageFlavor,而任意Java对象的传递使用的DataFlavor稍有特殊,JDK并没有提供静态常量表示这一格式标签,最主要原因是传递的是自定义类,那么你的DataFlavor就必须指明该类是什么(即类名);
2) DataFlavor的实质:
i. 里面包装着一个MIME码和数据所对应的Java类,数据的格式其实是由里面的MIME指定的;
ii. MIME由主类型和子类型共同确定,比如:application/pdf,主类型application表示该数据是一种应用程序数据(需要通过特定的应用程序打开),子类pdf表示它的数据格式是pdf类型的;
iii. StringSelection对应的DataFlavor其实是:text/plain;class=java.lang.String
iv. 可以看到DataFlavor的第一部分就是MIME,即text/plain,表示类型是文本,格式是纯文本类型字符串(plaint),数据所对应的Java类是String类型;
3) 任意Java对象传递时的DataFlavor:
i. MIME部分好办,是固定的,内容是application/x-java-jvm-local-objectref,该字符串DataFlavor提供了一个静态常量javaJVMLocalObjectMimeType;
!!可以看到Java对象类型的数据的MIME是一种应用程序数据(即必须用Java虚拟机打开的数据),其数据格式就是x-java那一串;
ii. 而class=XXX的部分就需要自己定夺了,这里填写的就是要传递的数据的Java类的类名(该类一般是自定义的任意类),Java类的类名是一种字符串,格式很讲究,一般都不会直接自己写出来,可以通过getClass方法以及getName方法获得,比如:
MyClass obj = new MyClass();
String s = "class=" + obj.getClass().getName();
!这样既可得到标准的Java类名了;
4) 构造自定义DataFlavor:
i. 这是自定义Transferable的最重要的一步,其实很简单,只需要用到DataFlavor的构造器既可:DataFlavor(String mimeType);
ii. mimeType的格式就是:MIME码;class=XXX
iii. 由于是自定义的任意Java类型,因此MIME码就取DataFlavor.javaJVMLocalObjectMimeType,class部分就调用getClass、getName获取既可
5) 实现Transferable:
i. 这是最后一步,即实现可以传递任意Java对象的Transferable类;
ii. 原理很简单,步骤和之前传输图像完全一样;
iii. 首先在类里要包含自定义数据类型的数据,接着就是实现Transferable全部接口方法即可;
iv. 其中最重要的就是getTransferDataFlavors方法,该方法将返回其所有支持的数据格式,在这里必须要返回自定义的那个DataFlavor,因此需要在该方法中构造出自定义的DataFlavor对象,然后返回即可;
!!Transferable类型定义好后就跟普通的StringSelection一样使用剪切板进行数据传递即可,即Clipboard的setContents、getData等用法不变;
2. 本地剪切板是如何传递数据的?
1) 由于任意Java类型数据(Java对象)基本上都是用本地剪切板传递的,当然系统剪切板也可以传递Java对象,只不过麻烦一点儿,传递的数据需要实现Serializable接口(必须可串行化,能和本地操作系统上的磁盘进行交流),还有种种其它限制条件;
2) 其次是本地剪切板是Java虚拟机实现的,完全支持Java对象的传递,没有任何限制,并且效率更高;
3) 为什么效率高?
i. 因为它传递的不是数据本身,并没有将完整的数据复制到剪切板中;
ii. 仅仅是将Java对象的引用放入剪切板,因此剪切板在Java虚拟机的内存中,访问非常迅速,取数据时仅仅是根据引用找到内存中的对象;
4) 缺点:
i. 正因为剪切板位于虚拟机本地,所以这样的剪切板只能在同一虚拟机中共享(即同一个虚拟机上运行的多个Java程序之间);
ii. 正因为放在虚拟机的内存中,因此虚拟机一旦关闭整个剪切板全部销毁,下次打开时不复存在;
3. 示例:
class ObjectSelection implements Transferable {
private Object obj;
public ObjectSelection(Object obj) {
this.obj = obj;
}
@Override // 只支持text/plain和obj
public DataFlavor[] getTransferDataFlavors() {
// TODO Auto-generated method stub
// return null;
DataFlavor[] flavors = new DataFlavor[2];
String mimeType = DataFlavor.javaJVMLocalObjectMimeType + ";class=" + obj.getClass().getName();
try { // 这里返回两种,一种是Object Java对象,另一种是text/plain
flavors[0] = new DataFlavor(mimeType);
flavors[1] = DataFlavor.stringFlavor;
return flavors;
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
}
@Override // 只支持text/pain和obj两种
public boolean isDataFlavorSupported(DataFlavor flavor) {
// TODO Auto-generated method stub
// return false;
return flavor.equals(DataFlavor.stringFlavor) ||
flavor.getPrimaryType().equals("application") &&
flavor.getSubType().equals("x-java-jvm-local-objectref") &&
flavor.getRepresentationClass().isAssignableFrom(obj.getClass());
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
// TODO Auto-generated method stub
// return null;
if (!isDataFlavorSupported(flavor)) { // 如果不是这两种则直接抛出异常
throw new UnsupportedFlavorException(flavor);
}
if (flavor.equals(DataFlavor.stringFlavor)) { // 要么是text/plain
return obj.toString();
}
return obj; // 要么就是obj
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
// return super.toString();
return "Person[name=" + name + ", age=" + age + "]";
}
}
public class AwtTest extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
// TODO Auto-generated method stub
// super.windowClosing(e);
System.exit(0);
}
public void init() {
Frame f = new Frame("Java Object Local Clipboard");
Panel tp = new Panel(); f.add(tp, BorderLayout.NORTH);
Label lName = new Label("Name"); tp.add(lName);
TextField tName = new TextField(15); tp.add(tName);
Label lAge = new Label("Age"); tp.add(lAge);
TextField tAge = new TextField(15); tp.add(tAge);
TextArea ta = new TextArea(3, 30); f.add(ta);
Panel bp = new Panel(); f.add(bp, BorderLayout.SOUTH);
Button bCopy = new Button("Copy"); bp.add(bCopy);
Button bPaste = new Button("Paste"); bp.add(bPaste);
Clipboard cb = new Clipboard("cb");
f.addWindowListener(this);
bCopy.addActionListener(e -> {
Person p = new Person(tName.getText(), Integer.parseInt(tAge.getText()));
ObjectSelection os = new ObjectSelection(p);
cb.setContents(os, null);
});
bPaste.addActionListener(e -> {
try {
Person person = new Person("", 0);
DataFlavor personFlavor = new DataFlavor(
DataFlavor.javaJVMLocalObjectMimeType + ";class=" +
person.getClass().getName());
if (cb.isDataFlavorAvailable(DataFlavor.stringFlavor)) { // 这里仅仅为了检测这两种格式,没有什么意义
Person p = (Person)cb.getData(personFlavor);
ta.setText(p.toString());
}
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
});
f.pack();
f.setVisible(true);
}
public static void main(String[] args) {
new AwtTest().init();
}
}
1) 深入理解Clipboard.isDataFlavorAvailable和Transferable接口方法之间的联系:
i. 其实在isDataFlavorAvailable中会调用剪切板中数据的isDataFlavorSupported方法来检测格式是否支持;
ii. 而一般会在isDataFlavorSupported方法中调用getTransferDataFlavors方法检验指定格式是否在格式列表中;
iii. 而Clipboard.getData会调用数据的getTransferData方法,其中会调用isDataFlavorSupported方法检测格式是否支持;
iv. 因此总的调用关系是:getData -> getTransferData -> isDataFlavorAvailable -> isDataFlavorSupported -> getTransferDataFlavors
!因此理论上在调用getData之前无需调用isDataFlavorAvailable进行检查格式是否支持,但是这三个接口方法都是自己实现的,如果没有实现这样的检查逻辑那么在调用getData之前调用isDataFlavorAvailable就是必须的了;
2) 接下来看DataFlavor的三个get方法:
i. String getPrimaryType(); // 返回MIME Type的主类型
ii. String getSubType(); // 返回MIME Type的子类型
iii. Class<?> getRepresentationClass(); // 返回该格式背后所支撑的数据类型
!最后的:boolean Class.isAssignableFrom(Class<?> cls); // 检查this是否是cls的父类或相等;
!!因此这里可以看到不能直接让flavor和"application/x-java-jvm-local-objectref;class=java.lang.Object"比较,因为检查的数据类型是Person,Person明显不等于Object,但是Person明显是Object的子类,因此isDataFlavorSupported中一定要调用getRepresentationClass和isAssignableFrom方法检测,而不能直接检测是否相等;
4. 利用系统剪切板传递Java对象:
1) 在支持富文本的操作系统平台上(Win、Mac等)也允许在系统剪切板中传递Java对象,但不过有几点限制:
i. 系统剪切板由于是多程序共享,并不一定要求只在Java虚拟机中的不同程序间共享,因此不局限在Java虚拟机内部;
ii. 正因为这个特点,系统剪切板传递的Java对象保存的并不是对象的引用了,而是完整的对象数据了;
iii. 既然要保存对象数据,而系统剪切板通常需要在磁盘中建立缓存(特别是非常大的数据需要使用到剪切板时),因此这些Java对象必须要实现Serializable接口可供串行化(String类、Image类都是可串行化的,因此无需自己对它们实施串行化,而自定义的其它类型的数据则需要自己手动实现串行化);
iv. 由于Java对象这种数据的类型是独有的(体现在MIME码上),因此这种数据也只能由Java虚拟机读取,因此系统剪切板中的Java对象只能在同一个Java虚拟机内或者不同Java虚拟机之间进行共享,但Java虚拟机外的其它程序无法读取剪切板中的Java对象;
2) 想通过系统剪切板传递Java对象就必须用到以下两种MIME码之一:
i. DataFlavor.javaSerializedObjectMimeType:可供本机使用(可串行化)的Java对象格式;
ii. DataFlavor.javaRemoteObjectMimeType:可进行远程传递(可串行化)的Java对象格式(即剪切板可以在网络中的多台计算机上共享);
3) 应用:
i. 完全和前面讲过的一样;
ii. 首先是建立Transferable时数据obj的类型必须是实现Serializable或者RemoteObject接口的类;
iii. 其次是在接口方法中使用的DataFlavor必须是javaSerializedObjectMimeType或javaRemoteObjectMimeType,但别忘了还有class=???的部分;
iv. 剪切板的操作完全没变;