【原创】转载请注明出处,谢谢合作!
Java的泛型对于帮助我们构建自己的工具包,实在是一个利器,接下来我们一起讨论如何构建一些简单易用的工具,欢迎评论:
一、打印函数工具包
一般我们使用系统的打印函数如下:
1 System.out.println();
这条语句的作用是在控制台进行输出,简单的输出直接利用它貌似就可以了,但是这种方式有一些缺陷:
1)我们通常使用一些IDE进行开发java程序,系统打印函数一般直接输出到控制台上,假设这些打印的信息对于帮助查找bug是有利用价值的,那么发布程序后,如果出现错误我们肯定希望能够在日志中查找出错的地方,怎么做呢,一般有两种方案,第一是直接将System.out设为文件流的PrintStream,如:
PrintStream out = null; try { out = new PrintStream("D:/log_"+(new Date()).getTime()+".txt"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.setOut(out);
这样,凡是System.out输出的信息都写入到文件中,这种方式貌似很酷,不过当你想要重新回到控制台输出时,就得重新将System.out设回来;第二种方案是使用一些第三方强大的日志工具,最流行的当然要数log4j,你可以更灵活的记录日志,不过你仍旧得按log4j的调用方法来记录信息。
2)System.out太长了,输出很不便,特别是需要分行输出很多内容的时候,不仅要写一堆的System.out.print,还要用很多的+把要输出的信息连起来。
所以,让我们来包装一下,让系统可以更灵活地记录信息。
第一段代码是简单地封装一下print和println,代码如下:
public class P{
public static <T> void p(T o){ System.out.print(o); } public static <T> void pl(T o){ System.out.println(o); } public static void pl(){ System.out.println(); }
}
一定要使用泛型吗?其实在这里不一定,因为这里的o实际上就是Object,但是这不影响我们使用泛型,我只是想说明,这个函数具有“可以传入任意类型的对象”这一概念。现在我们可以更灵活地使用日志记录了,因为所有的入口都在P这个类里,你可以在这里将要输出的内容放置到任意的终端,控制台、文件,随便,只要改了这里的三个基本实现就行。
第一段代码只是减少了我们敲写那么长的System.out.println函数,但并没有提供其他额外的方便,现在我们需要输出更加丰富的信息,例如,输出一个数组,Map、一个List,甚至按位打印一个Byte,作为示例,我们看看如下的代码(第二段代码):
public static <K,V> void pm(Map<K,V> map){ pl("-----------------------------------------------------"); for(K k:map.keySet()){ pl(k+"\t"+map.get(k)+";"); } pl("-----------------------------------------------------"); } public static <T> void pa(T[] array){ pl("-----------------------------------------------------"); for(T t:array){ pl(t); } pl("-----------------------------------------------------"); } public static void pb(Byte b){ for(int i=7;i>=0;i--){ if(((b&(1<<i))!=0x00 ))p('1'); else p('0'); } pl(); } public static void pb(Byte[] bs){ int cnt = 0; for(byte b:bs){ p((char)b+":"); for(int i=7;i>=0;i--){ if(((b&(1<<i))!=0x00 ))p('1'); else p('0'); } p(" "); if(++cnt%4==0)pl(); } pl(); }
现在我已经能够很方便地使用P.pm、P.pa或者P.pb方便地输出map,array,byte等信息了,它们利用了我们前面提供的基本函数。你还可以添加更丰富的格式化输出函数,这取决于你的创造力。
第二段代码给我们的工作减轻了很多负担,以后你想要输出一个map的内容,就不用自己写for循环了,你想查看一个byte里的位数信息,也不用自己去做转化了,(当然你可以选择Integer.toBinaryString函数,不过这个函数并没有按美观的方式格式化,而且它删除了二进制串前面的0,有时候这并不是我们预想的)。
现在我们还有一个新的需求,我们要输出一系列的变量,把他们打印成一行,并且使用变量之间用指定间隔符间隔开,如果你看到这个需求,初始下你会选择两种解决方案,第一种使用+号,这太麻烦了,考虑如下代码:
String name = ”Snuby"; String code1 = "Java"; String code2 = "JavaScript"; P.pl(name+" "+"like"+" "+code1+" "+"and"+" "+code2);
这里基于假设,将间隔符单独列出来,这样看起来就更清晰了,反映出的问题也更明显,用了太多的“+”号,调用太复杂了,能够更简洁一点吗?
你的第二种方案是使用System.out.printf函数,考虑如下代码:
String name = ”Snuby"; String code1 = "Java"; String code2 = "JavaScript";
System.out.printf("%s like %s and %s\n",name,code1,code2);
貌似简单多了,你完全可以再写一个P.pl多态函数来匹配这种方式,但是当间隔符不是空格的时候,你发现还是很忧伤,例如间隔符需要"\t"的时候,你需要输入n此的"\t",多麻烦啊,让工具包帮我们做吧!
public enum INTEVEL{ COMMA(";"), COLON(":"), MINUS("-"), POINT("."), SPACE(" "), TAB("\t"); private String value; INTEVEL(String value){ this.value = value; } @Override public String toString(){ return this.value; } } public static <T> void pl(T... objects){ INTEVEL intevel = INTEVEL.SPACE; if(objects.length>0&&objects[objects.length-1] instanceof INTEVEL){ intevel = (INTEVEL) objects[objects.length-1]; for(int i =0;i<objects.length-1;i++){ p(objects[i]+intevel.toString()); } pl(); return; } for(Object obj:objects){ p(obj+intevel.toString()); } pl(); }
看这段代码,我们新加入了一个枚举类型,它的作用是枚举一些常见的间隔符,你可以根据需要往里添加,该枚举值会对应它们所代表的的间隔符,可以通过toString返回。接着我们又使用了泛型,这里泛型仍然只是表示“任意类型的数据”这一概念,你可以考虑将它换成Object,这没有影响。
我们的新函数测试正常,而且使用起来很方便,它默认使用space即空格作为间隔符,调用代码如下:
String name = "Snuby"; String code1 = "Java"; String code2 = "JavaScript"; P.pl(name,"like",code1,"and",code2);
如果你要更换间隔符,只要在最后一个参数上添加间隔符类型,就会将默认值给替换掉了,使用方式如下:
String name = "Snuby"; String code1 = "Java"; String code2 = "JavaScript"; P.pl(name,"like",code1,"and",code2,P.INTEVEL.TAB);
现在,是不是舒服很多了呢?你可以根据你的需要往P里面添加函数,这取决于你的审美观以及你的偷懒精神,这是个伟大的精神!
第一部分再贴一点小技巧,看到上面的调用实例没,每次调用都要在前面加个“P.”,嫌麻烦吗,那么在你的类头里加入类似的这条语句吧:
import static com.snuby.P.*;
现在,你连“P.”都省了,-_-,懒惰使人类进步!