Java,使用泛型构建自己的工具包——包装System.out

【原创】转载请注明出处,谢谢合作!

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.”都省了,-_-,懒惰使人类进步!

posted @ 2013-06-28 16:05  Snuby  阅读(1667)  评论(0编辑  收藏  举报