java编程思想之注解

注解 (元数据) 为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后的某个时刻非常方便的使用这些数据。

注解在一定程度上是在把元数据与源代码文件结合在一起,而不是保存在外部文档中。注解是众多引入 javaSE5 中的重要语言变化之一。他们可以提供用来完整地描述程序所需的信息,而这些信息是无法用 Java 来表达的。注解可以用来生成描述文件,甚至或是新的类定义,并且有助于编写减轻样板代码的负担。通过使用注解,我们可以将这些元数据保存在 Java 源代码中,并利用 annotation API 为自己的注解构造处理工具,同时,注解的优点还包括:更加干净易读的代码以及编译期类型检查等。虽然 Java SE5 预先定义了一些元数据,但一般来说,主要还是需要程序员自己添加新的注解,并且按照自己的方式使用它们。

注解的语法比较简单,除了 @ 符号的使用外,它基本与 Java 固有的语法一样。Java SE5 内置了三种,定义在 java.lang 中的注解:

  • @Override ,表示当前的方法定义将覆盖超类中的方法。
  • @Deprecated ,如果程序员使用了注解为它的元素,那么编译器会发出警告信息。
  • @SuppressWarnings ,关闭不当的编译器警告信息。Java SE5 之前的版本也可以使用这个注解,不过会被忽略不起作用。

每当你创建了描述性质的类或接口时,一旦其中包含了重复性的工作,那就可以考虑使用注解来简化与自动化该过程。注解是在实际的源代码级别保存所有的信息,而不是某种注释性的文字,这使得代码更整洁,且便于维护。通过使用扩展的 annotation API,或外部的字节码工具类库,程序员拥有对源代码以及字节码强大的检查与操作能力。

基本语法

下面示例中,使用 @Test 对 TestExecute() 方法进行注解。这个注解本身并不做任何事情,但是编译器要确保在其构造路径上必须有 @Test 注解的定义。

public @interface Test {

}

public class Testble {
	public void execute() {
		System.out.println("Executing..");
	}

	@Test
	void testExecute(){
		execute();
	}
}

被注解的方法与其他的方法没有区别。 @Test 可以与任何修饰符共同作用域方法。

定义注解

上面的例子注解的定义我们已经看到了。注解的定义看起来很像接口的定义。事实上与任何 Java 文件一样,注解也会被编译为 class 文件。除了 @ 符号以外, @Test 的定义很像一个空的接口。定义注解时会需要一些元注解,如 @Target 和 @Retention 。 @Target 用来定义你的注解将应用于什么地方。 @Deprecated 用来定义应该用于哪一个级别可用,在源代码中、类文件中或者运行时。

在注解中一般都会包含某些元素用以表示某些值。当分析出来注解时,程序和工具可以利用这些值。注解的元素看起来就像接口的方法,唯一的区别是你可以为他指定默认值。没有元素的注解被称为标记注解。

下面是一个简单的注解,它可以跟踪一个项目中的用例。程序员可以在该方法上添加注解,我们就可以计算有多少已经实现了该用例。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public int id();
	public String description() default "没有描述";
}

注意:id 和 description 类似方法的定义。description 元素有一个 default 值,如果在注解某个方法时没有给出 description 的值,则就会使用这个默认值。

下面的三个方法被注解:

public class PasswordUtils {
	@UseCase(id =47,description = "password 哈哈哈防止破解")
	public boolean validatePassword(String password) {
		return (password.matches("\\w*\\d\\w*"));
	}

	@UseCase(id = 48)
	public String encryptPassword(String password) {
		return new StringBuilder(password).reverse().toString();
	}

	@UseCase(id = 49,description = "是否包含在这个密码库中")
	public boolean checkForNewPassword(List<String> prevPassword,String password) {
		return !prevPassword.contains(password);
	}
}

注解的元素在使用时是名值对的形式放入注解的括号内。

元注解

Java 目前只内置了三种标准注解,以及四种元注解。元注解就是注解的注解:

@Target 表示注解可以用在什么地方。ElementType 的参数包括: 
CONSTRUCTOR:构造器的声明 
FIELD:域声明 
LOCAL_VARIABLE:局部变量声明 
METHOD:方法声明 
PACKAGE:包声明 
PARAMETER:参数声明
 TYPE:类、接口或enum声明
@Retention 表示需要在什么级别保存注解信息。可选的RententionPolicy参数: 
SOURCE:注解将被编译器丢失 
CLASS:注解在class文件中可用,但会被 vm 丢失 
RUNTIME:vm 在运行期也保留注解,因此可以通过反射机制读取注解的信息。
@Documented 将此注解包含在 javaDoc 中
@Inherited 允许子类继承父类中的注解

大多数时候我么都是编写字节的注解,并编写自己的处理器处理他们。

编写注解处理器

如果没有用来读取注解的工具,那么注解就不会这有用。使用注解很重要的就是创建和使用注解处理器。Java SE5 扩展了反射机制的 API,方便我们构造这种工具。同时还提供了一个外部工具 apt 帮助我们解析带有注解的 Java 源代码。

下面我们就用反射来做一个简单的注解处理器。我们用它来读取上面的 PasswordUtils 类。

public class UseCaseTracker {

	public static void trackUseCase(List<Integer> useCase,Class<?> cl) {
		for (Method method : cl.getDeclaredMethods()) {
			UseCase uCase = method.getAnnotation(UseCase.class);
			if (uCase != null) {
				System.out.println("方法上的注解信息:"+uCase.id()+"  "+uCase.description());
			}
		}

		for (Integer integer : useCase) {
			System.out.println("参数:"+integer);
		}
	}

	public static void main(String[] args) {
		List<Integer> uList = new ArrayList<>();
		Collections.addAll(uList, 47,48,49,50);
		trackUseCase(uList, PasswordUtils.class);
	}

}

测试结果:

方法上的注解信息:49  是否包含在这个密码库中
方法上的注解信息:48  没有描述
方法上的注解信息:47  password 哈哈哈防止破解
参数:47
参数:48
参数:49
参数:50

上面用到了两个反射的方法:getDeclaredMethods() 和 getAnnotation(),getAnnotation() 方法返回指定类型的注解对象,在这里使用 UseCse。如果被注解的方法上没有改类型的注解,则返回 null 值。然后我们从返回的 UseCase 对象中提取元素的值。

注解元素

标签 @UseCase 由 UseCase,java 定义,包含 int 类型的元素 id,以及一个 String 类型的元素 description。注解元素可以使用的类型包括:

  • 所有的基本类型
  • String
  • Class
  • enum
  • Annotation
  • 以上类型的数组

如果你使用了其他的类型,那么编译器会报错。注意也不允许使用任何包装类型,但是自动打包存在这也不是什么限制。注解也可以作为元素的类型,也就是说注解可以嵌套。

默认值限制

编译器对元素的默认值具有严格的限制。首先,元素不能有不确定的值。也就是说元素必须要具有默认的值,要嘛在使用注解时提供元素的值。其次,对于非基本类型的元素,无论在源代码中声明时,或是在注解接口中定义时,都不能以 null 作为值。为了绕开这个限制,我们只能定义一些特殊的值,例如;空字符串或者是负数表示某个元素不存在:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UseCase {
	public int id() default -1;
	public String description() default " ";
}

生成外部文件

假如我们希望提供一些基本的对象关系映射功能,能够自动生成数据库表,用以存储 Javabean 对象。可以选择使用 XML 描述文件。然而,如果使用注解的话,可以将所有的信息保存在 JavaBean 源文件中。为此我们需要一个新的注解,用以定义与 Bean 关联的数据库表的名字,以及属性关联的列明和 SQL 类型。

下面是一个注解的示例,告诉注解处理器,我们需要生成一个数据库表:

@Retention(RUNTIME)
@Target(TYPE)
public @interface DBtable {
	public String name() default "";
}

注意: @Target 标签内可以有多个值用逗号分开,也可以没有值表示应用所有类型。其中的 name() 元素我们用来为处理器创建数据库表提供名字。

接下来是修饰 javaBean 对象准备的注解:

@Retention(RUNTIME)
@Target(FIELD)
public @interface Constraints {
	boolean primaryKey() default false;
	boolean allowNull() default true;
	boolean unique() default false;
}

@Retention(RUNTIME)
@Target(FIELD)
public @interface SqlString {
	int value() default 0;
	String name() default "";
	Constraints constraints() default @Constraints;
}

@Retention(RUNTIME)
@Target(FIELD)
public @interface SQLInteger {
	String name() default "";
	Constraints constraints() default @Constraints;
}

注解处理器通过 @Constraints 注解提取出数据库表的元数据。虽然对于数据库所能提供的所有约束而言只是一小部分,但足以表达我们的思想。并且我们也为三个元素提供了默认值。另外两个注解定义的是 SQL 类型。这些 sql 类型具有 name() 元素和 constraints() 元素。后者利用注解嵌套的功能将列的约束信息嵌入其中。我们看到 @Constraints 注解类型之后没有指明元素的值而是用一个注解作为默认值。如果要让嵌入的 @Constraints 注解中的 unique() 元素为 true,并以此作为 constraints() 元素的默认值,则需要如下定义:

@Retention(RUNTIME)
@Target(FIELD)
public @interface SQLInteger {
	String name() default "";
	Constraints constraints() default @Constraints(unique = true);
}

下面使我们的 Bean 的示例:

@DBtable(name = "Member")
public class Member {
	@SqlString(30)
	String firstname;

	@SqlString(50)
	String lasttname;

	@SQLInteger
	Integer age;

	@SqlString(value = 30,constraints = @Constraints(primaryKey=true))
	String handle;

	static int menberCount;

	public String getFirstname() {
		return firstname;
	}

	public String getLasttname() {
		return lasttname;
	}

	public Integer getAge() {
		return age;
	}

	public String getHandle() {
		return handle;
	}

	@Override
	public String toString() {
		// TODO Auto-generated method stub
		return handle;
	}
}

类的注解 @DBTable 给定了值 MEMBER,他将会用来作为表的名字。Bean 的属性 firstname 和 lasttname ,都被注解为 @SqlString 类型,并且为其元素赋值为 30。我们看到这其中使用了快捷方式。如果你的注解中定义了名为 value() 的元素,并且该元素在应用的时候是唯一需要赋值的元素。那么此时无需使用名值对的语法,只需要在括号内给出 value 的值即可。

注解不支持继承

不能使用关键字 extends 来继承某个 @interface 。很遗憾如果注解支持继承的话可以大大减少我们打字的工作量,并且使得语法更加整洁。

实现处理器

下面是一个注解处理器的示例,它将读取一个类文件,检查其上的数据库注解,并生成用来创建数据库的 SQL 命令:

public class TableCreator {

	public static void main(String[] args) throws Exception{
		 if(args.length < 1) {
		      System.out.println("arguments: annotated classes");
		      System.exit(0);
		  }
		for (String className : args) {
			Class<?> cl = Class.forName(className);
			DBtable dBtable = cl.getAnnotation(DBtable.class);
			if (dBtable != null) {
				System.out.println("这个类没哟创建数据库:"+className);
				continue;
			}

			//数据库的名字
			String tableName = dBtable.name();
			if (tableName.length()<1) {
				//如果名字没有赋值就用类名并且大写
				tableName = cl.getName().toUpperCase();
			}
			//查询出所有的列
			List<String> columnName = new ArrayList<>();
			for (Field field : cl.getDeclaredFields()) {
				String colum = null;
				//获取对象上的注解
				Annotation[] anns = field.getDeclaredAnnotations();
				if (anns.length <1) {
					continue;
				}

				if (anns[0] instanceof SQLInteger) {
					SQLInteger sInteger = (SQLInteger) anns[0];
					if (sInteger.name().length()<1) {
						colum = field.getName().toUpperCase();
					}else {
						colum = sInteger.name();
					}
					columnName.add(columnName + " INT" +getConstraints(sInteger.constraints()));
				}

				if(anns[0] instanceof SqlString) {
					SqlString sString = (SqlString) anns[0];
			          // Use field name if name not specified.
			          if(sString.name().length() < 1)
			        	  colum = field.getName().toUpperCase();
			          else
			        	  colum = sString.name();
			          columnName.add(columnName + " VARCHAR(" +
			            sString.value() + ")" +
			            getConstraints(sString.constraints()));
			      }

				 StringBuilder createCommand = new StringBuilder(
				          "CREATE TABLE " + tableName + "(");
				        for(String columnDef : columnName)
				          createCommand.append("\n    " + columnDef + ",");
				        // Remove trailing comma
				        String tableCreate = createCommand.substring(
				          0, createCommand.length() - 1) + ");";
				        System.out.println("Table Creation SQL for " +
				          className + " is :\n" + tableCreate);
			}
		}

	}

	private static String getConstraints(Constraints con) {
	    String constraints = "";
	    if(!con.allowNull())
	      constraints += " NOT NULL";
	    if(con.primaryKey())
	      constraints += " PRIMARY KEY";
	    if(con.unique())
	      constraints += " UNIQUE";
	    return constraints;
	  }

}

我们使用注解来解析构造 sql 语句。上面的示例是非常简洁的一个例子。对于真正的对象影射数据库是非常复杂的。现在有很多这样的框架,可以将对象影射到关系数据库。比如:大名鼎鼎的 greenDAO。

使用 apt 处理注解

注解处理工具 apt,这是 sun 为了帮助注解处理的过程提供的工具。与 Javac 一样,apt 被设计为操作 Java 的源文件,而不是编译后的类。默认情况下 apt 会在处理完源文件后编译他们。当注解处理器生成一个新的源文件时,改文件会在新一轮的注解处理中接受检查。该工具会一轮一轮的处理,直到不再有新的源文件产生。

我们定义的每一个注解都需要自己的处理器,而 apt 工具可以很容易的将多个处理器组合在一起。这样我们就可以指定多个要处理的类。通过使用 AnnotationProcessorFactory,apt 能够为每一个它发现的注解生成一个正确的注解处理器。使用 apt 生成注解处理器时,我们无法利用 Java 的反射机制,因为我们操作的是源代码,而不是编译后的类。使用 mirror API 能够解决这个问题,他使得我们能够在未经编译的源代码中查看方法、对象以及类型。

下面是一个自定义的注解,使用它可以把一个类的 public 方法提取出来,构造成一个新的接口:

@Retention(SOURCE)
@Target(TYPE)
public @interface ExtractInterface {
	public String value();
}

我们看到 @Retention(SOURCE) 是 SOURCE。因为我们从使用了该注解的类抽取接口之后没必要在保留这些注解信息。下面的类有一个公共的方法,我们将把他抽取到一个接口中:

@ExtractInterface("Multiplier")
public class Multiplier {

	public int multiply(int x,int y) {
		int total = 0;
		for (int i = 0; i < x; i++) {
			total = add(total, y);
		}
		return total;
	}

	public int  add(int x,int y) {
		return x+y;
	}

	public static void main(String[] args) {
		Multiplier multiplier = new Multiplier();
		System.out.println("11*16=" + multiplier.multiply(11,16));

	}

}

测试结果:

11*16=176

在 Multiplier 类中有一个 multiply() 方法,该方法经过循环调用私有的 add() 方法实现乘法操作。add() 方法不是公共的,因此不将其作为接口的一部分。注解给了类名作为值,这就是将要生成的接口的名字:

import com.sun.mirror.apt.*;
import com.sun.mirror.declaration.MethodDeclaration;
import com.sun.mirror.declaration.Modifier;
import com.sun.mirror.declaration.ParameterDeclaration;
import com.sun.mirror.declaration.TypeDeclaration;

import genericity.New;

public class InterfaceExtractorProcessor implements AnnotationProcessor{

	private final AnnotationProcessorEnvironment aenv;

	private ArrayList<MethodDeclaration> interfaceMethods = new ArrayList<>();

	protected InterfaceExtractorProcessor(AnnotationProcessorEnvironment aenv) {
		super();
		this.aenv = aenv;
	}

	@Override
	public void process() {
		for (TypeDeclaration typeeclaration : aenv.getSpecifiedTypeDeclarations()) {
			ExtractInterface annot = typeeclaration.getAnnotation(ExtractInterface.class);
			if (annot == null) {
				break;
			}

			for (MethodDeclaration methodDeclaration : typeeclaration.getMethods()) {
				if (methodDeclaration.getModifiers().contains(Modifier.PUBLIC) && !(methodDeclaration.getModifiers().contains(Modifier.STATIC))) {
					interfaceMethods.add(methodDeclaration);
				}
			}

			if (interfaceMethods.size() >0) {
				try {
					PrintWriter writer = aenv.getFiler().createSourceFile(annot.value());
					 writer.println("package " +
					 typeeclaration.getPackage().getQualifiedName() +";");
					 writer.println("public interface " +
					 annot.value() + " {");
					 for(MethodDeclaration m : interfaceMethods) {
				            writer.print("  public ");
				            writer.print(m.getReturnType() + " ");
				            writer.print(m.getSimpleName() + " (");
				            int i = 0;
				            for(ParameterDeclaration parm :
				              m.getParameters()) {
				              writer.print(parm.getType() + " " +
				                parm.getSimpleName());
				              if(++i < m.getParameters().size())
				                writer.print(", ");
				            }
				            writer.println(");");
				          }
				          writer.println("}");
				          writer.close();

				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}

			}
		}
	}

}

程序中用到的 mirror 的 jar 包可以到下面的地址下载:

http://www.java2s.com/Code/Jar/a/Downloadaptmirrorapi01jar.htm

所有的工作都在 process() 中完成。在分析一个类的时候,我们用 MethodDeclaration 类以及其上的 getModifiers() 方法找到 public 方法。一旦找到就将其保存到一个 ArrayList 中。然后在一个 .java 文件中创建新的接口中的方法定义。

注意在构造器中以 AnnotationProcessorEnvironment 对象为参数。通过该对象我们知道 apt 正在处理的所有类型,并且可以通过他获取 Messager 对象和 Filer 对象。Filer 对象是一种 PrintWriter,我们可以通过他创建新的文件。不使用普通的 PrintWriter 而是使用 Filer 对象主要原因是:只有这样 apt 才知道我们创建的新文件,从而对新文件进行注解处理,并且在需要的时候编译他们。

createSourceFile() 方法以将要新建的类或接口名字,打开了一个普通的输出流。

apt 工具需要一个工厂类来为其指明正确的处理器,然后它才能调用处理器上的 process() 方法:

public class InterfaceExtractorProcessorFactor implements AnnotationProcessorFactory{

	@Override
	public AnnotationProcessor getProcessorFor(Set<AnnotationTypeDeclaration> arg0,
			AnnotationProcessorEnvironment arg1) {

		return new InterfaceExtractorProcessor(arg1);
	}

	@Override
	public Collection<String> supportedAnnotationTypes() {
		// TODO Auto-generated method stub
		return Collections.singleton("annotations.ExtractInterface");
	}

	@Override
	public Collection<String> supportedOptions() {
		// TODO Auto-generated method stub
		return Collections.emptySet();
	}

}

AnnotationProcessorFactory 接口只有三个方法。其中 getProcessorFor() 方法注解处理器,该方法包含类型声明的 Set 以及 AnnotationProcessorEnvironment 对象作为参数。另外两个方法是 supportedAnnotationTypes() 和 supportedOptions(),可以通过他们检查一下是否 apt 工具发现的所有的注解都有相应的处理器,是否所有控制台输入的参数都是你提供的可选项。

欢迎加入学习交流群569772982,大家一起学习交流。

时间: 2024-11-17 21:29:50

java编程思想之注解的相关文章

《Java编程思想(第4版)》pdf

下载地址:网盘下载 内容简介 编辑 本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形.从Java的基础语法到最高级特性(深入的面向对象概念.多线程.自动项目构建.单元测试和调试等),本书都能逐步指导你轻松掌握.[1] 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作.本书的作者拥有多年教学经验,对C.C++以及Java语言都有独到.深入的见解,以通俗易懂及小而直接的示例解释了一个个晦涩抽象的概

java编程思想总结(三)

java编程思想总结(三) java编程思想总结是一个持续更新的系列,是本人对自己多年工作中使用到java的一个经验性总结,也是温故而知新吧,因为很多基础的东西过了这么多年,平时工作中用不到也会遗忘掉,所以看看书,上上网,查查资料,也算是记录下自己的笔记吧,过一段时间之后再来看看也是蛮不错的,也希望能帮助到正在学习的人们,本系列将要总结一下几点: 面向对象的编程思想 java的基本语法 一些有趣的框架解析 实战项目的整体思路 代码的优化以及性能调优的几种方案 整体项目的规划和视角 其它遗漏的东西

《Java编程思想》读书笔记

前言 这个月一直没更新,就是一直在读这本<Java编程思想>,这本书可以在Java业界被传神的一本书,无论谁谈起这本书都说好,不管这个人是否真的读过这本书,都说啊,这本书很好.然后再看这边书的厚度,哇塞,厚的真的不止一点点,所以很多人看了没多久就放弃了,看不下去,但是基于它的厚度,就说,这是一本好书.也有人说,看了没什么用的一本书,甚至还去嘲笑那些正在看的人,说还不如看点实际的技术.那么在我的世界里,如果一本书没有读过,如果妄加评论的话,没有任何的意义.所以我真的仔仔细细读了下来,书上也写了很

Java编程思想(第4版)pdf高清版免费下载

下载地址:网盘下载 备用地址:网盘下载 内容简介编辑本书赢得了全球程序员的广泛赞誉,即使是最晦涩的概念,在Bruce Eckel的文字亲和力和小而直接的编程示例面前也会化解于无形.从Java的基础语法到最高级特性(深入的面向对象概念.多线程.自动项目构建.单元测试和调试等),本书都能逐步指导你轻松掌握.[1] 从本书获得的各项大奖以及来自世界各地的读者评论中,不难看出这是一本经典之作.本书的作者拥有多年教学经验,对C.C++以及Java语言都有独到.深入的见解,以通俗易懂及小而直接的示例解释了一

《JAVA编程思想》第四版 PDF 下载 中文版和英文版 高清PDF扫描带书签

一.链接: 中文版: https://pan.baidu.com/s/1d07Kp4 密码:x2cd 英文版: https://pan.baidu.com/s/1boOSdAZ 密码: rwgm 文件截图: 二.注意: 中文版 有一页( 文件页码548,书籍页码515 )图像缺失.不过没关系,只是一页源码以及简单说明,不影响整体知识. 网上的所有版本此页都是缺失的. 实在要看,可以看对应的英文版本(文件658页,书籍636页),没什么难度. 三.代码引入: 注解一章中,按照书中的jar包引入代码

Java编程思想【Thinking in java】

Java编程思想[Thinking in java]目录:第1章 对象导论1.1抽象过程1.2每个对象都有一个接口1.3每个对象都提供服务1.4被隐藏的具体实现1.5复用具体实现1.6继承1.6.1“是一个”(is-a)与“像是一个”(is-like-a)关系1.7伴随多态的可互换对象1.8单根继承结构1.9容器1.9.1参数化类型(范型)1.10对象的创建和生命周期1.11异常处理:处理错误1.12并发编程1.13Java与Internet1.13.1Web是什么1.13.2客户端编程1.13

《On Java 8》中文版,又名《Java 编程思想》中文第五版

来源:LingCoder/OnJava8 主译: LingCoder 参译: LortSir 校对:nickChenyx E-mail: [email protected] 本书原作者为 [美] Bruce Eckel,即(Thinking in Java 4th Edition,2006)的作者. 本书是事实上的 Thinking in Java 5th Edition(On Java 8,2017). Thinking in Java 4th Edition 基于 JAVA 5 版本:On

异常笔记--java编程思想

开一个新的系列,主要记一些琐碎的重要的知识点,把书读薄才是目的...特点: 代码少,概念多... 1. 基本概念 异常是在当前环境下无法获得必要的信息来解决这个问题,所以就需要从当前环境跳出,就是抛出异常.抛出异常后发生的几件事: 1.在堆上创建异常对象. 2.当前的执行路径中止                                          3. 当前环境抛出异常对象的引用.                                         4. 异常处理机制接

《Java编程思想》第十三章 字符串

<Java编程思想>读书笔记 1.String作为方法的参数时,会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置,从未动过. 2.显式地创建StringBuilder允许预先为他指定大小.如果知道字符串多长,可以预先指定StringBuilder的大小避免多次重新分配的冲突. 1 /** 2 * @author zlz099: 3 * @version 创建时间:2017年9月1日 下午4:03:59 4 */ 5 public class UsingStringBuilder {