Java基础14(Enum 枚举类 BigInteger类BigDecimal类 内部类)

2024-05-23 22:04

本文主要是介绍Java基础14(Enum 枚举类 BigInteger类BigDecimal类 内部类),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一. Enum 枚举类

1. enum的定义

2. enum的比较:

3. enum类型

4. enum方法

4.1 name()方法

4.2 ordinal()方法

4.3 toString()方法

5. switch 

6. 小结

二. BigInteger类 

三. BigDecimal类 

1. 一些方法

1.1 scale()方法

1.2 stripTrailingZeros()方法

1.3 setScale()方法

1.4 一些运算

1.5 divideAndRemainder()方法

2. 比较BigDecimal

四. Random类和SecureRandom类

1. Random类

2. SecureRandom类

五. 内部类

1. Inner Class 内部类

2. Anonymous Class 匿名类

3. Static Nested Class静态内部类


一. Enum 枚举类

1. enum的定义

为了让编译器能自动检查某个值在枚举的集合内,并且,不同用途的枚举需要不同的类型来标记,不能混用,我们可以使用enum来定义枚举类

枚举类Weekday :

public enum Weekday {SUN, MON, TUE, WED, THU, FRI, SAT;
}

枚举类Color:

public enum Color {RED, GREEN, BLUE;
}

测试类:

public static void main(String[] args) {//设置枚举值://编译器帮助我们进行数据类型的校验,以及取值范围的校验Weekday day = Weekday.SUN;if (day == Weekday.SAT || day == Weekday.SUN) {System.out.println("休息");} else {System.out.println("学习");}}

枚举和int定义的常量相比,使用enum定义枚举有如下好处:
●enum常量本身带有类型信息,即Weekday.SUN类型是Weekday,编译器会自动检查出类型错误例如,下面的语句不可能编译通过:

int day = 1;// Compile error: bad operand types for binary operator '=='
if (day == Weekday.SUN) {}

●不可能引用到非枚举的值,因为无法通过编译。
●不同类型的枚举不能互相比较或者赋值,因为类型不符。
例如,不能给一个Weekday枚举类型的变量赋值为Color枚举类型的值:

Weekday x = Weekday.SUN; // ok!
Weekday y = Color.RED; // Compile error: incompatible types

2. enum的比较:

引用类型比较,要使用equals()方法,如果使用==比较,它比较的是两个引用类型的变量是否是同一个对象。因此,引用类型比较,要始终使用equals()方法,但enum类型可以例外。

因为enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用比较:

Weekday day = Weekday.SUN;if (day == Weekday.SAT || day == Weekday.SUN) {System.out.println("休息");}

3. enum类型

enum定义的类型就是class,只不过它有以下几个特点:
●定义的enum类型总是继承自java.lang.Enum,且无法被子类继承;
●只能定义出enum的实例,而无法通过new操作符创建enum的实例;
●定义的每个实例都是引用类型的唯一实例;
●可以将enum类型用于switch语句。

Color枚举类:

public enum Color {RED(1, "红色"), BLUE(2, "蓝色"), GREEN(3, "绿色");private final int value;private final String name;private Color(int value, String name) {this.value = value;this.name = name;}@Overridepublic String toString() {return value + name;}public int getValue() {return value;}public String getName() {return name;}}

Color01类:

public final class Color01  { // extends Enum// 每个实例均为全局唯一:public static final Color01 RED = new Color01(1,"红色");public static final Color01 BLUE = new Color01(2,"蓝色");public static final Color01 GREEN = new Color01(3,"绿色");public final int value;public final String name;// private构造方法,确保外部无法调用new操作符:private Color01(int value, String name) {super();this.value = value;this.name = name;}
}

编译后的enum类和普通class并没有任何区别。但是我们自己无法按定义普通class那样来定义enum,必须使用enum关键字,这是Java语法规定的。

4. enum方法

由于enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:

4.1 name()方法

作用:返回常量名

//name()作用:返回常量名String colorName = Color.BLUE.name();System.out.println(colorName);//BLUE

4.2 ordinal()方法

作用:返回定义的常量的顺序,从0开始计数

//ordinal()作用:返回定义的常量的顺序,从0开始计数int i = Color.BLUE.ordinal();System.out.println(i);//1

因为enum本身是class,所以我们可以定义private的构造方法,并且,给每个枚举常量添加字段,这样就无需担心顺序的变化,新增枚举常量时,也需要指定一个int值.

public enum Color {RED(1, "红色"), BLUE(2, "蓝色"), GREEN(3, "绿色");private final int value;private final String name;private Color(int value, String name) {this.value = value;this.name = name;}
@Overridepublic String toString() {return value + name;}
}

注意:枚举类的字段也可以是非final类型,即可以在运行期修改,但是不推荐这样做!

4.3 toString()方法

作用:默认情况下,对枚举常量调用toString()会返回和name()一样的字符串。但是,toString()可以被覆写,而name()则不行。

//toString()默认情况下,会返回和name()一样的字符串,但是此方法没有重写,我们可以手动进行重写String str1 = Color.BLUE.toString();System.out.println(str1);//2蓝色
public enum Color {RED(1, "红色"), BLUE(2, "蓝色"), GREEN(3, "绿色");private final int value;private final String name;private Color(int value, String name) {this.value = value;this.name = name;}@Overridepublic String toString() {return value + name;}
}

5. switch 

枚举类可以应用在switch语句中。因为枚举类天生具有类型信息和有限个枚举常量,所以比int、String类型更适合用在switch语句中:

public static void main(String[] args) {Color c = Color.BLUE;switch (c) {case BLUE:System.out.println("蓝色");break;case RED:System.out.println("红色");break;case GREEN:System.out.println("绿色");break;default:break;}
}

注意:加上default语句,可以在漏写某个枚举常量时自动报错,从而及时发现错误。

6. 小结

●Java使用enum定义枚举类型,它被编译器编译为final class Xxx extends Enum { … }
●通过name()获取常量定义的字符串,注意不要使用toString()
●通过ordinal()返回常量定义的顺序(无实质意义)
●可以为enum编写构造方法、字段和方法
●enum的构造方法要声明为private,字段强烈建议声明为final
●enum适合用在switch语句中

二. BigInteger类 

java.math.BigInteger就是用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数:

BigInteger bi = new BigInteger("1234567890");// 2867971860299718107233761438093672048294900000
System.out.println(bi.pow(5)); 

对做运算的时候,只能使用实例方法:

        BigInteger bg1 = new BigInteger("100");BigInteger bg2 = new BigInteger("3");System.out.println(bg1.add(bg2));//加 103System.out.println(bg1.subtract(bg2));//减 97System.out.println(bg1.multiply(bg2));//乘 300System.out.println(bg1.divide(bg2));//除 33System.out.println(bg1.pow(3));//幂 1000000

 BigInteger和Integer、Long一样,也是不可变类,并且也继承自Number类。因为Number定义了转换为基本类型的几个方法:
○转换为byte:byteValue()
○转换为short:shortValue()
○转换为int:intValue()
○转换为long:longValue()
○转换为float:floatValue()
○转换为double:doubleValue()
因此,通过上述方法,可以把BigInteger转换成基本类型。如果BigInteger表示的范围超过了基本类型的范围,转换时将丢失高位信息,即结果不一定是准确的。如果需要准确地转换成基本类型,可以使用intValueExact()、longValueExact()等方法,在转换时如果超出范围,将直接抛出ArithmeticException异常。

BigInteger i = new BigInteger("123456789000");
System.out.println(i.longValue()); // 123456789000
System.out.println(i.multiply(i).longValueExact()); // java.lang.ArithmeticException: BigInteger out of long range

注意:使用longValueExact()方法时,如果超出了long型的范围,会抛出ArithmeticException。

三. BigDecimal类 

BigDecimal和BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。

        double d1 = 0.1;double d2 = 0.2;System.out.println(d1+d2);//0.30000000000000004BigDecimal b1 = new BigDecimal(0.1);BigDecimal b2 = new BigDecimal(0.2);System.out.println(b1.add(b2));//0.3000000000000000166533453693773481063544750213623046875//使用BigDecima1时候一定要注意,构造方法要为String类型BigDecimal a = new BigDecimal("0.1");BigDecimal b = new BigDecimal("0.2");System.out.println(a.add(b));//0.3

1. 一些方法

1.1 scale()方法

BigDecimal用scale()表示小数位数:

BigDecimal b1 = new BigDecimal("123.45");BigDecimal b2 = new BigDecimal("123.4500");BigDecimal b3 = new BigDecimal("1234500");//获取BigDecimal类型的小数位数,方法scale()System.out.println(b1.scale());//2System.out.println(b2.scale());//4System.out.println(b3.scale());//0

1.2 stripTrailingZeros()方法

通过BigDecimal的stripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal:

BigDecimal b1 = new BigDecimal("123.45");BigDecimal b2 = new BigDecimal("123.4500");BigDecimal b3 = new BigDecimal("1234500");//stripTrailingzeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal:System.out.println(b1.stripTrailingZeros().scale());//2System.out.println(b2.stripTrailingZeros().scale());//2System.out.println(b3.stripTrailingZeros().scale());//-2

如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。

1.3 setScale()方法

可以对一个BigDecimal设置它的scale,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断:

●四舍五入:

BigDecimal bg1 = new BigDecimal("123.45");// 四舍五入BigDecimal bigDecimal1 = bg1.setScale(1, RoundingMode.HALF_UP);System.out.println(bigDecimal1);// 123.5

●直接截断:

// 直接截断BigDecimal bg2 = new BigDecimal("123.44");BigDecimal bigDecimal2 = bg2.setScale(1, RoundingMode.DOWN);System.out.println(bigDecimal2);// 123.4

1.4 一些运算

BigDecimal bg1 = new BigDecimal("1");BigDecimal bg2 =new BigDecimal("0.3");System.out.println(bg1.add(bg2));//加 1.3System.out.println(bg2.subtract(bg1));//减 -0.7System.out.println(bg1.multiply(bg2));//乘 0.3//除法运算的时候,除不尽,在进行除法运算的时候推荐使用三参的divide,保留结果的位数,//System.out.println(bg1.divide(bg2));// 报错:ArithmeticException,因为除不尽System.out.println(bg1.divide(bg2,3,RoundingMode.HALF_UP));//3.333

1.5 divideAndRemainder()方法

调用divideAndRemainder()方法时,返回的数组包含两个BigDecimal,分别是商和余数,其中商总是整数,余数不会大于除数。我们可以利用这个方法判断两个BigDecimal是否是整数倍数:

BigDecimal bg1 = new BigDecimal("1");BigDecimal bg2 = new BigDecimal("0.2");// divideAndRemainder()返回的数组包含两个BigDecimal,第一个元素是商,第二个元素是余BigDecimal[] result = bg1.divideAndRemainder(bg2);System.out.println(Arrays.toString(result));//[5, 0.0]if (result[1].doubleValue() == 0.0) {System.out.println("可以除尽");}System.out.println("===============");System.out.println(new BigDecimal("0.00000000").signum());System.out.println("===============");//	signum>0 返回1
//		  =0     0
//          <0 返回-1if (result[1].signum() == 0) {System.out.println("可以除尽");}

2. 比较BigDecimal

在比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等:

        BigDecimal bg1 = new BigDecimal("12.35");BigDecimal bg2 = new BigDecimal("12.3500");System.out.println("地址是否相同:" + (bg1 == bg2));//false// 内容比较精准度一样bg1=bg1.stripTrailingZeros();bg2=bg2.stripTrailingZeros();System.out.println("内容比较"+bg1.equals(bg2));//true

必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。

//	>0 第一个比第二个大
//	=0 相等
//	<0 第一个小于第二个BigDecimal bg1 = new BigDecimal("12.35");BigDecimal bg2 = new BigDecimal("12.3500");int result = bg1.compareTo(bg2);System.out.println("内容相等比较:" + (result == 0));//true

 ●BigDecimal用于表示精确的小数,常用于财务计算
● 比较BigDecimal的值是否相等,必须使用compareTo()而不能使用equals()

四. Random类和SecureRandom类

Java的核心库提供了大量的现成的类供我们在开发编程时使用。

1. Random类

Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。要生成一个随机数,可以使用nextInt()、nextLong()、nextFloat()、nextDouble():

//产生随机序列的随机数对象Random random = new Random();System.out.println(random.nextInt());//每次都不一样System.out.println(random.nextInt(10));//生成一个[0,10)之间的intSystem.out.println(random.nextDouble());//生成一个[0,1)之间的doubleSystem.out.println(random.nextLong());//每次都不一样//使用随机数填充字节数组byte[] bytes = new byte[10];random.nextBytes(bytes);System.out.println(Arrays.toString(bytes));

 因为我们创建Random实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同。如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列:

// 随机数种子相同,随机序列完全相同Random r1 = new Random(10);Random r2 = new Random(10);for (int i = 0; i < 10; i++) {System.out.print(r1.nextInt(100) + " ");}System.out.println();for (int i = 0; i < 10; i++) {System.out.print(r2.nextInt(100) + " ");}

Math.random()实际上内部调用了Random类,所以它也是伪随机数,只是我们无法指定种子

2. SecureRandom类

SecureRandom就是用来创建安全的随机数的:

SecureRandom无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器:

// 获取普通的安全随机数生成器SecureRandom sc = new SecureRandom();System.out.println(sc.nextInt());System.out.println(sc.nextInt(10));System.out.println(sc.nextDouble());// 获取高强度安全随机数生成器SecureRandom sc1 = SecureRandom.getInstanceStrong();System.out.println(sc1.nextInt());System.out.println(sc1.nextInt(10));System.out.println(sc1.nextDouble());//随机数填充字节数组byte[] bytes = new byte[10];sc1.nextBytes(bytes);System.out.println(Arrays.toString(bytes));
SecureRandom sc = null;try {// 获取高强度安全随机数生成器sc=SecureRandom.getInstanceStrong();} catch (NoSuchAlgorithmException e) {// 获取普通的安全随机数生成器sc = new SecureRandom();}System.out.println(sc.nextInt());// 用安全随机数填充bytesbyte[] bytes =new byte[10];sc.nextBytes(bytes);System.out.println(Arrays.toString(bytes));

SecureRandom的安全性是通过操作系统提供的安全的随机种子来生成随机数。时刻牢记必须使用SecureRandom来产生安全的随机数。

注意:需要使用安全随机数的时候,必须使用SecureRandom,绝不能使用Random!

五. 内部类

1. Inner Class 内部类

语法规则:

class Outer {class Inner {// 定义了一个Inner Class}
}

Outer是一个普通类,而Inner是一个Inner Class,它与普通类最大的区别:Inner Class的对象实例不能单独存在,必须依附于一个Outer Class外部类的对象实例 

外部类和内部类:

public class Outer {private String name = "zkt";private static int VALUE = 18;public static void todo() {System.out.println("这个是外部类的todo方法");}public void test() {System.out.println("这个是外部类的test方法");}public Outer() {}static {System.out.println("这个是外部类的静态代码块");}class Inner {String name = "zkt1";public static final int VALUE1 = 18;//		不能有静态变量
//		private static  int  VALUE1 = 18;//		内部类不能有静态的方法
//		public static void todo1() {
//			System.out.println("这个是内部类的todo1方法");
//		}//		不能有静态代码块
//		static{
//			System.out.println("这个是内部类的构造代码块");
//		}// this调用内部类自己的成员变量// 外部类名.this.成员变量名public Inner(String name) {this.name = name;Outer.this.name = name;}public void todo() {System.out.println("这个是内部类的todo方法");}// 局部>内部成员变量>外部成员变量public void sayHello() {System.out.println("局部name:" + name);System.out.println("内部成员name:" + this.name);System.out.println("外部成员name:" + Outer.this.name);System.out.println(VALUE);System.out.println(VALUE1);Outer.todo();test();}}}

创建方式1:

//1.需要先定义出来外部类的对象,通过外部类的对象去创建内部类对象Outer out = new Outer();Outer.Inner inner = out.new Inner("张三");inner.sayHello();

创建方式2:

//2.内部类对象创建方法2Inner  inner1 = new Outer().new Inner("李四");inner1.sayHello();System.out.println(Inner.VALUE1);

Inner Class和普通Class相比,除了能引用Outer实例外,还有一个额外的“特权”,就是可以修改Outer Class的private字段,因为Inner Class的作用域在Outer Class内部,所以能访问Outer Class的private字段和方法。


观察Java编译器编译后的.class文件可以发现,Outer类被编译为Outer.class,而Inner类被编译为Outer$Inner.class。

2. Anonymous Class 匿名类

还有一种定义Inner Class的方法,它不需要在Outer Class中明确地定义这个Class,而是在方法内部,通过匿名类(Anonymous Class)来定义。

Outer类:

public class Outer1 {private String name;public Outer1(String name) {this.name = name;}public void test() {System.out.println("这个是我的test方法");//Runnable是一个接口,无法实例化对象//{}内的为Runnable接口重写其内部方法的过程,是一个匿名类//通过赋值向上声明成Runnable类型Runnable  runs = new Runnable() {@Overridepublic void run() {System.out.println("这个是run方法"+name);}};new Thread(runs).start();}}

Demo02类:

//匿名内部类
public class Demo02 {public static void main(String[] args) {Outer1 out = new Outer1("zkt");out.test();}
}运行结果:
这个是我的test方法
这个是run方法zkt

观察test()方法,我们在方法内部实例化了一个Runnable。Runnable本身是接口,接口是不能实例化的,所以这里实际上是定义了一个实现了Runnable接口的匿名类,并且通过new实例化该匿名类,然后转型为Runnable。

匿名类和Inner Class一样,可以访问Outer Class的private字段和方法。之所以我们要定义匿名类,是因为在这里我们通常不关心这个类的定义,并且该类仅在定义的位置一次性使用,比直接定义Inner Class可以少写很多代码。

3. Static Nested Class静态内部类

最后一种内部类和Inner Class类似,但是使用static修饰,称为静态内部类(Static Nested Class):

内部类使用原因:
1.内部类对象可以访问创建它的对象实现,包含私有数据
2.对同一个包中的其他类,内部类可以隐藏
3.内部类方法定义和回调
4.使用内部类可以更加优雅的设计程序结构

//1.静态内部类,内部类被static修饰
//2.静态内部类不依附于外部类的对象,无法使用外部类.this方法或者成员变量
//3.静态内部类可以访问静态的成员变量和方法
public class Outer2 {public int age = 19;public static String nameString = "zkt1";static {System.out.println("这个是我外部类的静态代码块");}public static void test() {System.out.println("这个是外部类的静态方法");}static class Inner2 {public int age1 = 13;public static String nameString = "zkt1";static {System.out.println("这个是我内部类的静态代码块");}public static void todo() {System.out.println("这个是内部类的静态方法");Outer2.nameString = "zkt2";System.out.println(Outer2.nameString);System.out.println(nameString);test();}public void eat() {System.out.println("这个是内部类的eat方法");}}}
public class Demo03 {public static void main(String[] args) {
//		Outer2.Inner2 in2 = new Outer2.Inner2();	
//		in2.todo();//静态方法Outer2.Inner2.todo();}
}运行结果:
这个是我内部类的静态代码块
这个是内部类的静态方法
这个是我外部类的静态代码块
zkt2
zkt1
这个是外部类的静态方法

用static修饰的内部类和Inner Class有很大的不同,它不再依附于Outer的实例,而是一个完全独立的类,因此无法引用Outer.this,但它可以访问Outer的private静态字段和静态方法。如果把StaticNested移到Outer之外,就失去了访问private的权限。

这篇关于Java基础14(Enum 枚举类 BigInteger类BigDecimal类 内部类)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/996538

相关文章

Spring常见错误之Web嵌套对象校验失效解决办法

《Spring常见错误之Web嵌套对象校验失效解决办法》:本文主要介绍Spring常见错误之Web嵌套对象校验失效解决的相关资料,通过在Phone对象上添加@Valid注解,问题得以解决,需要的朋... 目录问题复现案例解析问题修正总结  问题复现当开发一个学籍管理系统时,我们会提供了一个 API 接口去

Java操作ElasticSearch的实例详解

《Java操作ElasticSearch的实例详解》Elasticsearch是一个分布式的搜索和分析引擎,广泛用于全文搜索、日志分析等场景,本文将介绍如何在Java应用中使用Elastics... 目录简介环境准备1. 安装 Elasticsearch2. 添加依赖连接 Elasticsearch1. 创

Spring核心思想之浅谈IoC容器与依赖倒置(DI)

《Spring核心思想之浅谈IoC容器与依赖倒置(DI)》文章介绍了Spring的IoC和DI机制,以及MyBatis的动态代理,通过注解和反射,Spring能够自动管理对象的创建和依赖注入,而MyB... 目录一、控制反转 IoC二、依赖倒置 DI1. 详细概念2. Spring 中 DI 的实现原理三、

SpringBoot 整合 Grizzly的过程

《SpringBoot整合Grizzly的过程》Grizzly是一个高性能的、异步的、非阻塞的HTTP服务器框架,它可以与SpringBoot一起提供比传统的Tomcat或Jet... 目录为什么选择 Grizzly?Spring Boot + Grizzly 整合的优势添加依赖自定义 Grizzly 作为

Java后端接口中提取请求头中的Cookie和Token的方法

《Java后端接口中提取请求头中的Cookie和Token的方法》在现代Web开发中,HTTP请求头(Header)是客户端与服务器之间传递信息的重要方式之一,本文将详细介绍如何在Java后端(以Sp... 目录引言1. 背景1.1 什么是 HTTP 请求头?1.2 为什么需要提取请求头?2. 使用 Spr

Java如何通过反射机制获取数据类对象的属性及方法

《Java如何通过反射机制获取数据类对象的属性及方法》文章介绍了如何使用Java反射机制获取类对象的所有属性及其对应的get、set方法,以及如何通过反射机制实现类对象的实例化,感兴趣的朋友跟随小编一... 目录一、通过反射机制获取类对象的所有属性以及相应的get、set方法1.遍历类对象的所有属性2.获取

Java中的Opencv简介与开发环境部署方法

《Java中的Opencv简介与开发环境部署方法》OpenCV是一个开源的计算机视觉和图像处理库,提供了丰富的图像处理算法和工具,它支持多种图像处理和计算机视觉算法,可以用于物体识别与跟踪、图像分割与... 目录1.Opencv简介Opencv的应用2.Java使用OpenCV进行图像操作opencv安装j

java Stream操作转换方法

《javaStream操作转换方法》文章总结了Java8中流(Stream)API的多种常用方法,包括创建流、过滤、遍历、分组、排序、去重、查找、匹配、转换、归约、打印日志、最大最小值、统计、连接、... 目录流创建1、list 转 map2、filter()过滤3、foreach遍历4、groupingB

SpringBoot如何使用TraceId日志链路追踪

《SpringBoot如何使用TraceId日志链路追踪》文章介绍了如何使用TraceId进行日志链路追踪,通过在日志中添加TraceId关键字,可以将同一次业务调用链上的日志串起来,本文通过实例代码... 目录项目场景:实现步骤1、pom.XML 依赖2、整合logback,打印日志,logback-sp

Java操作PDF文件实现签订电子合同详细教程

《Java操作PDF文件实现签订电子合同详细教程》:本文主要介绍如何在PDF中加入电子签章与电子签名的过程,包括编写Word文件、生成PDF、为PDF格式做表单、为表单赋值、生成文档以及上传到OB... 目录前言:先看效果:1.编写word文件1.2然后生成PDF格式进行保存1.3我这里是将文件保存到本地后