[疯狂Java]AWT剪切板:本地(系统)剪切板传递Java对象

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. 剪切板的操作完全没变;

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值