注解
一、注解简介
从 Java 5 版本之后可以在源代码中嵌入一些补充信息,这种补充信息称为注解(Annotation),是 Java 平台中非常重要的一部分。注解都是 @ 符号开头的,例如我们在学习方法重写时使用过的 @Override 注解。同 Class 和 Interface 一样,注解也属于一种类型。
Annotation 可以翻译为“注解”或“注释”,一般翻译为“注解”,因为“注释”一词已经用于说明“//”、“/*.../”和“/.../”等符号了,这里的“注释”是英文 Comment 翻译。
注解并不能改变程序的运行结果,也不会影响程序运行的性能。有些注解可以在编译时给用户提示或警告,有的注解可以在运行时读写字节码文件信息。
注解可以元数据这个词来描述,即一种描述数据的数据。所以可以说注解就是源代码的元数据。例如以下代码:
@Override public String toString() { return "中关村"; }
上面的代码重写了 Object 类的 toString() 方法并使用了 @Override 注解。如果不使用 @Override 注解标记代码,程序也能够正常执行。那么这么写有什么好处吗?事实上,使用 @Override 注解就相当于告诉编译器这个方法是一个重写方法,如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。这样可以防止不小心拼写错误造成麻烦。
例如,在没有使用 @Override 注解的情况下,将 toString() 写成了 toStrring(),这时程序依然能编译运行,但运行结果会和所期望的结果大不相同。
注解常见的作用有以下几种: 1、生成帮助文档。这是最常见的,也是 Java 最早提供的注解。常用的有 @see、@param 和 @return 等; 2、跟踪代码依赖性,实现替代配置文件功能。比较常见的是 Spring 2.5 开始的基于注解配置。作用就是减少配置。现在的框架基本都使用了这种配置来减少配置文件的数量; 3、在编译时进行格式检查。如把 @Override 注解放在方法前,如果这个方法并不是重写了父类方法,则编译时就能检查出。
无论是哪一种注解,本质上都一种数据类型,是一种接口类型。到 Java 8 为止 Java SE 提供了 11 个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包。有 6 个是元注解,它们来自于 java.lang.annotation 包,自定义注解会用到元注解。
提示:元注解就是负责注解其他的注解。
基本注解包括:@Override、@Deprecated、@SuppressWarnings、@SafeVarargs 和 @FunctionalInterface。
二、@Override注解
Java 中 @Override 注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
使用 @Override 注解示例代码如下:
public class Person { private String name = ""; private int age; ... @Override public String t0String() { //toString() return "Person [name=" + name + ", age=" + age + "]"; } }
上述代码第 6 行是重写 Object 类的 toString() 方法,该方法使用 @Override 注解。如果 toString() 不小心写成了 t0String(),那么程序会发生编译错误。会有如下的代码提示:
类型为 Person 的方法t0String()必须覆盖或实现超类型方法
所以 @Override 的作用是告诉编译器检查这个方法,保证父类要包含一个被该方法重写的方法,否则就会编译出错。这样可以帮助程序员避免一些低级错误。
当然如果代码中的方法前面不加 @Override 注解,即便是方法编辑错误了,编译器也不会有提示。这时 Object 父类的 toString() 方法并没有被重写,将会引起程序出现 Bug(缺陷)。
三、@Deprecated注解
Java 中 @Deprecated 可以用来注解类、接口、成员方法和成员变量等,用于表示某个元素(类、方法等)已过时。当其他程序使用已过时的元素时,编译器将会给出警告。
使用 @Deprecated 注解示例代码如下:
@Deprecated public class Person { @Deprecated protected String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Deprecated public void setNameAndAge(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } }
上述代码第 2 行类 Person、第 4 行的成员变量 name 和第 24 行的 setNameAndAge 方法都被 @Deprecated 注解。在 Eclipse 中这些被注解的 API 都会被画上删除线。调用这些 API 代码也会有删除线,示例代码如下。
public class Demo07 { public static void main(String[] args) { Person p = new Person(); p.setNameAndAge("java", 20); p.name = "Java教程"; } }
从图中可以看到代码中不仅有删除线,而且还有编译警告。
Java 9 为 @Deprecated 注解增加了以下两个属性: forRemoval:该 boolean 类型的属性指定该 API 在将来是否会被删除。 since:该 String 类型的属性指定该 API 从哪个版本被标记为过时。
示例代码如下所示:
class Test { // since属性指定从哪个版本开始被标记成过时,forRemoval指定该API将来会被删除 @Deprecated(since = "9", forRemoval = true) public void print() { System.out.println("这里是C语言中文网Java教程!"); } } public class DeprecatedTest { public static void main(String[] args) { // 下面使用info()方法时将会被编译器警告 new Test().print(); } }
上面程序的第 12 行代码使用了 Test 的 print() 方法,而 Test 类中定义 info() 方法时使用了 @Deprecated 修饰,表明该方法已过时,所以将会引起编译器警告。
@Deprecated 的作用与文档注释中的 @deprecated 标记的作用基本相同,但它们的用法不同,前者是 Java 5 才支持的注解,无须放在文档注释语法(/** ... */部分)中,而是直接用于修饰程序中的程序单元,如方法、类和接口等。
四、@SuppressWarnings:抑制编译器警告
Java 中的 @SuppressWarnings 注解指示被该注解修饰的程序元素(以及该程序元素中的所有子元素)取消显示指定的编译器警告,且会一直作用于该程序元素的所有子元素。例如,使用 @SuppressWarnings 修饰某个类取消显示某个编译器警告,同时又修饰该类里的某个方法取消显示另一个编译器警告,那么该方法将会同时取消显示这两个编译器警告。
@SuppressWarnings 注解主要用在取消一些编译器产生的警告对代码左侧行列的遮挡,有时候这样会挡住我们断点调试时打的断点。
如果你确认程序中的警告没有问题,可以不用理会。通常情况下,如果程序中使用没有泛型限制的集合将会引起编译器警告,为了避免这种编译器警告,可以使用 @SuppressWarnings 注解消除这些警告。
注解的使用有以下三种: 抑制单类型的警告:@SuppressWarnings("unchecked") 抑制多类型的警告:@SuppressWarnings("unchecked","rawtypes") 抑制所有类型的警告:@SuppressWarnings("unchecked")
抑制警告的关键字如下表所示。
关键字 | 用途 |
---|---|
all | 抑制所有警告 |
boxing | 抑制装箱、拆箱操作时候的警告 |
cast | 抑制映射相关的警告 |
dep-ann | 抑制启用注释的警告 |
deprecation | 抑制过期方法警告 |
fallthrough | 抑制在 switch 中缺失 breaks 的警告 |
finally | 抑制 finally 模块没有返回的警告 |
hiding | 抑制相对于隐藏变量的局部变量的警告 |
incomplete-switch | 忽略不完整的 switch 语句 |
nls | 忽略非 nls 格式的字符 |
null | 忽略对 null 的操作 |
rawtypes | 使用 generics 时忽略没有指定相应的类型 |
restriction | 抑制禁止使用劝阻或禁止引用的警告 |
serial | 忽略在 serializable 类中没有声明 serialVersionUID 变量 |
static-access | 抑制不正确的静态访问方式警告 |
synthetic-access | 抑制子类没有按最优方法访问内部类的警告 |
unchecked | 抑制没有进行类型检查操作的警告 |
unqualified-field-access | 抑制没有权限访问的域的警告 |
unused | 抑制没被使用过的代码的警告 |
使用 @SuppressWarnings 注解示例代码如下:
public class Demo07 { @SuppressWarnings({"deprecation"}) public static void main(String[] args) { Person p = new Person(); p.setNameAndAge("java", 20); p.name = "Java教程"; } }
上述代码第 2 行使用 @SuppressWarnings({ "deprecation" }) 注解了 main 方法。在@Deprecated注解中的 Person 代码中,这些 API 已经过时了,所以代码第 4 行~第 6 行是编译警告,但是在使用了 @SuppressWarnings 注解之后会发现程序代码的警告没有了。
五、@SafeVarargs注解
在介绍 @SafeVarargs 注解用法之前,先来看看如下代码:
public class HelloWorld { public static void main(String[] args) { // 传递可变参数,参数是泛型集合 display(10, 20, 30); // 传递可变参数,参数是非泛型集合 display("10", 20, 30); // 会有编译警告 } public static <T> void display(T... array) { for (T arg : array) { System.out.println(arg.getClass().getName() + ":" + arg); } } }
代码第 10 行声明了一种可变参数方法 display,display 方法参数个数可以变化,它可以接受不确定数量的相同类型的参数。可以通过在参数类型名后面加入...
的方式来表示这是可变参数。可变参数方法中的参数类型相同,为此声明参数是需要指定泛型。
但是调用可变参数方法时,应该提供相同类型的参数,代码第 4 行调用时没有警告,而代码第 6 行调用时则会发生警告,这个警告是 unchecked(未检查不安全代码),就是因为将非泛型变量赋值给泛型变量所发生的。
可用 @SafeVarargs 注解抑制编译器警告,修改代码如下:
public class HelloWorld { public static void main(String[] args) { // 传递可变参数,参数是泛型集合 display(10, 20, 30); // 传递可变参数,参数是非泛型集合 display("10", 20, 30); // 没有@SafeVarargs会有编译警告 } @SafeVarargs public static <T> void display(T... array) { for (T arg : array) { System.out.println(arg.getClass().getName() + ":" + arg); } } }
上述代码在可变参数 display 前添加了 @SafeVarargs 注解,当然也可以使用 @SuppressWarnings("unchecked") 注解,但是两者相比较来说 @SafeVarargs 注解更适合。
注意:@SafeVarargs注解不适用于非 static 或非 final 声明的方法,对于未声明为 static 或 final 的方法,如果要抑制 unchecked 警告,可以使用 @SuppressWarnings 注解。
六、@FunctionalInterface注解
在学习 Lambda 表达式时,我们提到如果接口中只有一个抽象方法(可以包含多个默认方法或多个 static 方法),那么该接口就是函数式接口。@FunctionalInterface 就是用来指定某个接口必须是函数式接口,所以 @FunInterface 只能修饰接口,不能修饰其它程序元素。
函数式接口就是为 Java 8 的 Lambda 表达式准备的,Java 8 允许使用 Lambda 表达式创建函数式接口的实例,因此 Java 8 专门增加了 @FunctionalInterface。
例如,如下程序使用 @FunctionalInterface 修饰了函数式接口。
@FunctionalInterface public interface FunInterface { static void print() { System.out.println("C语言中文网"); } default void show() { System.out.println("我正在学习C语言中文网Java教程"); } void test(); // 只定义一个抽象方法 }
编译上面程序,可能丝毫看不出程序中的 @FunctionalInterface 有何作用,因为 @FunctionalInterface 注解的作用只是告诉编译器检查这个接口,保证该接口只能包含一个抽象方法,否则就会编译出错。
@FunctionalInterface 注解主要是帮助程序员避免一些低级错误,例如,在上面的 FunInterface 接口中再增加一个抽象方法 abc(),编译程序时将出现如下错误提示:
“@FunctionInterface”批注无效;FunInterface不是functional接口
七、Java 元注解作用及使用
元注解是负责对其它注解进行说明的注解,自定义注解时可以使用元注解。Java 5 定义了 4 个注解,分别是 @Documented、@Target、@Retention 和 @Inherited。Java 8 又增加了 @Repeatable 和 @Native 两个注解。这些注解都可以在 java.lang.annotation 包中找到。下面主要介绍每个元注解的作用及使用。
1、@Documented
@Documented 是一个标记注解,没有成员变量。用 @Documented 注解修饰的注解类会被 JavaDoc 工具提取成文档。默认情况下,JavaDoc 是不包括注解的,但如果声明注解时指定了 @Documented,就会被 JavaDoc 之类的工具处理,所以注解类型信息就会被包括在生成的帮助文档中。
下面通过示例来了解它的用法,代码如下所示。
例 1:
@Documented @Target({ ElementType.TYPE, ElementType.METHOD }) public @interface MyDocumented { public String value() default "这是@Documented注解"; }
测试类:
@MyDocumented public class DocumentedTest { /** * 测试document */ @MyDocumented public String Test() { return "C语言中文网Java教程"; } }
打开 Java 文件所在的目录,分别输入如下两条命令行:
javac MyDocumented.java DocumentedTest.java javadoc -d doc MyDocumented.java DocumentedTest.java
运行成功后,打开生成的帮助文档,可以看到在类和方法上都保留了 MyDocument 的注解信息。如下图所示:
2、@Target
@Target 注解用来指定一个注解的使用范围,即被 @Target 修饰的注解可以用在什么地方。@Target 注解有一个成员变量(value)用来设置适用目标,value 是 java.lang.annotation.ElementType 枚举类型的数组,下表为 ElementType 常用的枚举常量。
名称 | 说明 |
---|---|
CONSTRUCTOR | 用于构造方法 |
FIELD | 用于成员变量(包括枚举常量) |
LOCAL_VARIABLE | 用于局部变量 |
METHOD | 用于方法 |
PACKAGE | 用于包 |
PARAMETER | 用于类型参数(JDK 1.8新增) |
TYPE | 用于类、接口(包括注解类型)或 enum 声明 |
例 2:
自定义一个 MyTarget 注解,使用范围为方法,代码如下所示。
@Target({ ElementType.METHOD }) public @interface MyTarget { } class Test { @MyTarget String name; }
如上代码第 6 行会编译错误,错误信息为:
The annotation @MyTarget is disallowed for this location
提示此位置不允许使用注解 @MyDocumented,@MyTarget 不能修饰成员变量,只能修饰方法。
3、@Retention
@Retention 用于描述注解的生命周期,也就是该注解被保留的时间长短。@Retention 注解中的成员变量(value)用来设置保留策略,value 是 java.lang.annotation.RetentionPolicy 枚举类型,RetentionPolicy 有 3 个枚举常量,如下所示。 SOURCE:在源文件中有效(即源文件保留) CLASS:在 class 文件中有效(即 class 保留) RUNTIME:在运行时有效(即运行时保留)
生命周期大小排序为 SOURCE < CLASS < RUNTIME,前者能使用的地方后者一定也能使用。如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解;如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS 注解;如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 SOURCE 注解。 @Inherited @Inherited 是一个标记注解,用来指定该注解可以被继承。使用 @Inherited 注解的 Class 类,表示这个注解可以被用于该 Class 类的子类。就是说如果某个类使用了被 @Inherited 修饰的注解,则其子类将自动具有该注解。
例 3:
创建一个自定义注解,代码如下所示:
@Target({ ElementType.TYPE }) @Inherited @Retention(RetentionPolicy.RUNTIME) public @interface MyInherited { }
测试类代码如下:
@MyInherited public class TestA { public static void main(String[] args) { System.out.println(TestA.class.getAnnotation(MyInherited.class)); System.out.println(TestB.class.getAnnotation(MyInherited.class)); System.out.println(TestC.class.getAnnotation(MyInherited.class)); } } class TestB extends TestA { } class TestC extends TestB { }
运行结果为:
@MyInherited() @MyInherited() @MyInherited()
4、@Repeatable
@Repeatable 注解是 Java 8 新增加的,它允许在相同的程序元素中重复注解,在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
例 4:
Java 8 之前的做法:
public @interface Roles { Role[] roles(); } public @interface Roles { Role[] value(); } public class RoleTest { @Roles(roles = {@Role(roleName = "role1"), @Role(roleName = "role2")}) public String doString(){ return "这是C语言中国网Java教程"; } }
Java 8 之后增加了重复注解,使用方式如下:
public @interface Roles { Role[] value(); } @Repeatable(Roles.class) public @interface Role { String roleName(); } public class RoleTest { @Role(roleName = "role1") @Role(roleName = "role2") public String doString(){ return "这是C语言中文网Java教程"; } }
不同的地方是,创建重复注解 Role 时加上了 @Repeatable 注解,指向存储注解 Roles,这样在使用时就可以直接重复使用 Role 注解。从上面例子看出,使用 @Repeatable 注解更符合常规思维,可读性强一点。
两种方法获得的效果相同。重复注解只是一种简化写法,这种简化写法是一种假象,多个重复注解其实会被作为“容器”注解的 value 成员的数组元素处理。
5、@Native
使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
八、Java 自定义注解
声明自定义注解使用 @interface 关键字(interface 关键字前加 @ 符号)实现。定义注解与定义接口非常像,如下代码可定义一个简单形式的注解类型。
// 定义一个简单的注解类型 public @interface Test {}
上述代码声明了一个 Test 注解。默认情况下,注解可以在程序的任何地方使用,通常用于修饰类、接口、方法和变量等。
定义注解和定义类相似,注解前面的访问修饰符和类一样有两种,分别是公有访问权限(public)和默认访问权限(默认不写)。一个源程序文件中可以声明多个注解,但只能有一个是公有访问权限的注解。且源程序文件命名和公有访问权限的注解名一致。
不包含任何成员变量的注解称为标记注解,例如上面声明的 Test 注解以及基本注解中的 @Override 注解都属于标记注解。根据需要,注解中可以定义成员变量,成员变量以无形参的方法形式来声明,其方法名和返回值定义了该成员变量的名字和类型。代码如下所示:
public @interface MyTag { // 定义带两个成员变量的注解 // 注解中的成员变量以方法的形式来定义 String name(); int age(); }
以上代码中声明了一个 MyTag 注解,定义了两个成员变量,分别是 name 和 age。成员变量也可以有访问权限修饰符,但是只能有公有权限和默认权限。
如果在注解里定义了成员变量,那么使用该注解时就应该为它的成员变量指定值,如下代码所示。
public class Test { // 使用带成员变量的注解时,需要为成员变量赋值 @MyTag(name="xx", age=6) public void info() { ... } ... }
注解中的成员变量也可以有默认值,可使用 default 关键字。如下代码定义了 @MyTag 注解,该注解里包含了 name 和 age 两个成员变量。
public @interface MyTag { // 定义了两个成员变量的注解 // 使用default为两个成员变量指定初始值 String name() default "中关村"; int age() default 7;}
如果为注解的成员变量指定了默认值,那么使用该注解时就可以不为这些成员变量赋值,而是直接使用默认值。
public class Test { // 使用带成员变量的注解 // MyTag注释的成员变量有默认值,所以可以不为它的成员变量赋值 @MyTag public void info() { ... } ... }
当然也可以在使用 MyTag 注解时为成员变量指定值,如果为 MyTag 的成员变量指定了值,则默认值不会起作用。
根据注解是否包含成员变量,可以分为如下两类。
-
标记注解:没有定义成员变量的注解类型被称为标记注解。这种注解仅利用自身的存在与否来提供信息,如前面介绍的 @Override、@Test 等都是标记注解。
-
元数据注解:包含成员变量的注解,因为它们可以接受更多的元数据,所以也被称为元数据注解。
一、XML基础
1.1、XML是什么?
XML(可扩展标记语言)是一种很流行的简单的基于文本的语言来用作应用程序之间的通信模式。它被认为是传输标准装置和存储数据。JAVA提供了极好的支持和丰富的库来解析,修改或查询XML文档。
XML是一种简单的基于文本的语言,它被设计为储存和运输以纯文本格式的数据。它代表着可扩展标记语言。以下是一些XML的显着特征。 XML是一种标记语言。 XML是一种标记语言就像HTML一样。 XML标签不是像HTML那样预定义。 可以定义自己的标签,这就是为什么它被称为可扩展的语言。 XML标签被设计成自描述性的。 XML是W3C推荐用于数据存储和传输。
1.2、XML能干什么?
描述数据、存储数据、传输(交换)数据。
优缺点:
优势 以下是XML提供的优势: 技术无关 - 作为普通文本,XML是技术独立。它可以用于由任何技术进行数据的存储和传输的目的。 人类可读 - XML使用简单的文本格式。它是人类可读和可以理解的。 可扩展性 - 在XML,自定义标签可以创建和很容易使用。 允许验证 - 使用XSD,DTD和XML结构可以很容易地验证。 缺点 下面是使用XML的缺点: 冗余的语法 - 通常XML文件中包含大量的重复计算。 冗余 - 作为一个冗长的语言,XML文件大小增加了传输和存储成本。
1.3、XML与HTML区别
1、目的不一样 2、XML 被设计用来描述数据,其焦点是数据的内容。 3、HTML 被设计用来展示数据,其焦点是数据的外观。 4、HTML可以不关闭标签(即标签可以不成对出现),但XML必须关闭标签(即标签必须成对出现)。 5、HTML中的标签标识文本如何展示,而XML中的标签标识文本是什么含义(什么类型的文本)。 XML文档节点类型 文档(document) 元素(element) 属性(attribute) 文本(PCDATA--parsed character data) 注释(comment) DOCTYPE :主要验证文档内容的正确性 实体(ENTITIES) CDATA(character data)
1.4、XML语法
1、声明:<?xml version="1.0" encoding="UTF-8"?> 2、根节点:必须只能有一个根节点 3、标签:标签必须有结束且区分大小写,标签必须顺序嵌套 4、属性:必须引号引起值 5、空格会被保留,HTML空格最多保留一个 6、命名规则:命名必须见名知意 a)名字可包含字母、数字以及其他的字符 b)名字不能以数字或者标点符号开始 c)名字不能以字符“xml”(或者XML、Xml)开始 7、名字不能包含空格 8、 不应在 XML 元素名称中使用 ":" ,这是由于它用于命名空间(namespaces)的保留字。 9、标签优先于属性。 10、XML 命名空间可提供避免元素命名冲突的方法。 11、CDATA:字符数据,<![CDATA[字符数据]]> ,字符数据不进行转义 12、实体:&实体;
<?xml version='1.0' encoding='UTF-8' ?><!--文档声明 version='1.0'为必须字段--> <users><!--根节点,有且只有一个根节点--> <!-- 子节点,随便写,符合规则即可 --> <user id='Z' number="zpark-001"> <name>zhangsan</name> <age>23</age> <gender>nan</gender> </user> <user id='L' number="zpark-002"> <name>lisi</name> <age>24</age> <gender>nv</gender> </user> </users>
1.5、Xml约束
1.5.1、XML DTD 约束
DTD(DocType Definition 文档类型定义)的作用是定义 XML 文档的合法构建模块。它使用一系列的合法元素来定义文档结构。用于约定XML格式。
1、DTD引用方式 1.1、内部 <!DOCTYPE 根元素 [元素声明]>
例如:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <!DOCTYPE beans [ <!ELEMENT beans (bean*)> <!ELEMENT bean (name,author,money)> <!ELEMENT name (#PCDATA)> <!ELEMENT author (#PCDATA)> <!ELEMENT money (#PCDATA)> ]>
<beans> <bean> <name>张三</name> <author>56</author> <money>3500</money> </bean> ... </beans>
1.2、外部私有的 SYSTEM 一般是我们自己定义的,可能只是一个公司内部使用,<!DOCTYPE 根元素 SYSTEM "dtd文件位置">
例如:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE 书架 SYSTEM "book.dtd"> <beans> <bean> <name>张三</name> <author>56</author> <money>3500</money> </bean> ... </beans>
1.3、外部公有的 PUBLIC 一般是一些标准,可能非常多的人用,<!DOCTYPE 根元素 PUBLIC "命名空间" "dtd文件位置">,首先根据“命名空间”去问环境要相应的dtd文件,如果有,直接提供,如果没有再根据dtd文件位置找。
例如:
<!DOCTYPE web-app PUBLIC "-//SunMicrosystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">
<?xml version="1.0"?> <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> <note> <to>Tove</to> <from>Jani</from> <heading>Reminder</heading> <body>Don't forget me this weekend</body> </note>
相关参考文档及DTD教程:DTD 教程 | 菜鸟教程
1.5.2、XML Schema 约束
XML Schema 是基于 XML 的 DTD 替代者。XML Schema 描述 XML 文档的结构。XML Schema 语言也称作 XML Schema 定义(XML Schema Definition,XSD)。 DTD不是通过XML语法定义文档结构, 不能定义数据类型和限制Schema通过XML语法定义文档结构,可以定义数据类型和限制
约定XML格式 定义可出现在文档中的元素 定义可出现在文档中的属性 定义哪个元素是子元素 定义子元素的次序 定义子元素的数目 定义元素是否为空,或者是否可包含文本 定义元素和属性的数据类型 定义元素和属性的默认值以及固定值 1、为何使用Schema XML Schema 是 DTD 的继任者 XML Schema 可针对未来的需求进行扩展 XML Schema 更完善,功能更强大 XML Schema 基于 XML 编写 XML Schema 支持数据类型和限制 XML Schema 支持命名空间
2、Schema引用方式
<users xmlns="命名空间" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="命名空间 Schema位置">
如何找Schema?和DTD一样,首先根据命名空间问环境要,找不到再根据Schema位置找。
例子:
<?xml version="1.0" encoding="UTF-8"?> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" <!--xs="http://www.w3.org/2001/XMLSchema" 声名了w3c的名称空间,方便下面调用 --> targetNamespace="http://www.zhong.cn" elementFormDefault="qualified"> <!-- schema 是根元素 xmlns:xs="http://www.w3.org/2001/XMLSchema" 指明了在schema中使用的元素和数据种类来自http://www.w3.org/2001/XMLSchema名称空间(namespace)。 它也指定了来自"http://www.w3.org/2001/XMLSchema"名称空间(namespace)的元素和数据种类必须带前缀“xs:” targetNamespace="http://www.zhong.cn"(将全部元素绑定给这个名称空间) 暗示了由这份schema(shiporder, orderperson, shipto, ....)定义的元素来自"http://www.zhong.com"名称空间 xmlns="http://www.w3schools.com" 指明了默认名称空间(namespace)是http://www.w3schools.com. elementFormDefault="qualified" (“unqualified”)将根节点绑定到名称空间 将所有元素绑定到名称空间 --> <!--xs:element 指的是element这个元素来自于xs名称空间 --> <xs:element name="shiporder"> <!-- 定义一个元素 shiporder --> <xs:complexType> <!-- 类型是:复合类型(里面包含元素或者属性) --> <xs:sequence> <!-- 元素要有顺序 --> <!-- 定义一个元素 orderperson 类型为:字符串 --> <xs:element name="orderperson" type="xs:string"/> <!-- 定义一个元素 shipto 最少出现1次,最多出现1次 --> <xs:element name="shipto" minOccurs="1" maxOccurs="1"> <xs:complexType> <!-- shipto元素也是复合类型 --> <xs:sequence> <!-- 元素要有顺序 --> <xs:element name="name" type="xs:string"/> <!-- 在shipto元素中定义一个元素 name 类型为:字符串 --> <xs:element name="address" type="xs:string"/> <xs:element name="city" type="xs:string"/> <xs:element name="country" type="xs:string"/> </xs:sequence> </xs:complexType> </xs:element> <!-- 在shiporder元素中定义一个元素 item 出现次数可以无限次 --> <xs:element name="item" maxOccurs="unbounded"> <xs:complexType> <xs:sequence> <xs:element name="title" type="xs:string"/> <xs:element name="note" type="xs:string" minOccurs="0"/> <xs:element name="quantity" type="xs:positiveInteger"/> <xs:element name="price" type="xs:decimal"/> </xs:sequence> </xs:complexType> </xs:element> </xs:sequence> <xs:attribute name="orderid" type="xs:string" use="required"/> </xs:complexType> </xs:element> </xs:schema>
schema教程及相关参考文档:XML Schema 教程 | 菜鸟教程
二、Java XML 教程
1、Java XML 解析器
1.1、什么是XML解析?
解析XML是指将通过XML文档访问数据或修改数据的一个操作或方法。
Java库中提供了两种XML解析器: 1、像文档对象模型(Document Object Model,DOM)解析器这的树型解析器(tree parse),它们将读入的XML文档转换 成树结构。 2、像XML简单API(Simple API for XML,SAX)解析器这样的流机制解析器(streaming parser),它们在读入XML文档时生 成相应的事件。
1.2、XML解析器是什么?
XML解析器提供方法来访问或修改XML文档中的数据。 Java提供了多种选择来解析XML文档。以下是各种类型解析器其通常用于解析XML文档。 Dom解析器 - 解析通过加载该文件的全部内容,并创建其完整分级树中存储的文件。 SAX解析器 - 解析基于事件触发器的文档。不完整(部分)的文件加载到存储器中。 JDOM解析器 - 解析以类似的方式,以DOM解析器但更简单的方法的文档。 StAX解析器 - 解析以类似的方式,以SAX解析器但在更高效的方式的文档。 XPath解析器 - 解析基于表达式XML并广泛选择使用XSLT。 DOM4J解析器 - Java库来解析XML,XPath和使用Java集合框架XSLT,为DOM,SAX和JAXP的支持。
2、Java DOM解析器
2.1、DOM解析器简介
文档对象模型是万维网联盟(W3C)的官方推荐。它定义了一个接口,使程序能够访问和更新样式,结构和XML文档的内容。支持DOM实现该接口的XML解析器。
何时使用? 在以下几种情况时,应该使用DOM解析器: 1、需要知道很多关于文档的结构 2、需要将文档的部分周围(例如,可能需要某些元素进行排序) 3、需要使用的文件中的信息超过一次
会得到什么? 当使用DOM 解析器解析一个XML文档,会得到一个树形结构,其中包含的所有文档的元素。 DOM提供了多种可用于检查文档的内容和结构的函数。
优势 DOM是用于处理文档结构的通用接口。它的一个设计目标是Java代码编写一个DOM兼容的解析器,运行在任何其他的DOM兼容的解析器不会有变化。
DOM接口 DOM定义了几个Java接口。这里是最常见的接口: 1、节点 - DOM的基本数据类型。 2、元素 - 要处理的对象绝大多数是元素。 3、Attr - 代表元素的属性。 4、文本 - 元素或Attr的实际内容。 5、文档 - 代表整个XML文档。文档对象是通常被称为DOM树。
常见的DOM方法 当正在使用DOM,有经常用到的几种方法: 1、Document.getDocumentElement() - 返回文档的根元素。 2、Node.getFirstChild() - 返回给定节点的第一个子节点。 3、Node.getLastChild() - 返回给定节点的最后一个子节点。 4、Node.getNextSibling() - 这些方法返回一个特定节点的下一个兄弟节点。 5、Node.getPreviousSibling() - 这些方法返回一个特定节点的前一个兄弟节点。 6、Node.getAttribute(attrName) - 对于给定的节点,则返回所请求的名字的属性。
2.2、Java DOM解析器 - 解析XML文档
使用DOM的步骤 以下是在使用DOM解析器解析文档使用的步骤。 1、导入XML相关的软件包。 2、创建DocumentBuilder 3、从文件或流创建一个文档 4、提取根元素 5、检查属性 6、检查子元素
document对象: 要操作XML,先就得有Document对象,把一个XML文件加载进内存的时候,在内存中形成所谓的一种树状结构,我们把这一个结构称之为DOM树.
注意: 我们在Java代码中所做的增/删/改/查操作,都仅仅是操作的是内存中的Document对象,和磁盘中的XML文件没有关系。比如:删除一个联系人信息之后,XML文件中数据依然存在,此时出现内存中的数据和磁盘文件中的数据不同步。所以,对于增删改操作,我们需要做同步操作(把内存中的数据和磁盘的XML文件数据保持一致)。 DOM:在第一次的时候就会把XML文件加载进内存,如果XML文件过大,可能会造成内存的溢出. DOM:在做增删改查操作的时候比较简单,,但是性能却不高(线性搜索).
获取Document对象:
演示示例:
步骤1:创建 ’Java-DOM解析器‘ 工程,并且在resources目录下创建domDemo.xml文件
<?xml version="1.0" encoding="utf-8" ?> <class> <student rollno="393"> <firstname>dinkar</firstname> <lastname>kad</lastname> <nickname>dinkar</nickname> <marks>85</marks> </student> <student rollno="493"> <firstname>Vaneet</firstname> <lastname>Gupta</lastname> <nickname>vinni</nickname> <marks>95</marks> </student> <student rollno="593"> <firstname>jasvir</firstname> <lastname>singn</lastname> <nickname>jazz</nickname> <marks>90</marks> </student> </class>
步骤2:创建dom解析类 ‘com.zpark.DOMParseDemo’
package com.zpark; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; public class DOMParseDemo { public static void main(String[] args) throws Exception { /* 1):表示出需要被操作的XML文件的路径,注意是文件的路径,不是文件所在的目录. File f = new File(...); 2):根据DocumentBuilderFactory类,来获取DocumentBuilderFactory对象. 注意:工厂设计模式往往体现着单例设计模式. DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance() ; 3):根据DocumentBuilderFactory对象,构建DocumentBuilder对象. 注意:XxxFactory,就是用来创建Xxx对象的. DocumentBuilder build = factory .newDocumentBuilder(); 4):根据DocumentBuidler对象,构建Document对象. Document doc = build.parse(f); */ // 获取xml文件路径 String path = DOMParseDemo.class.getClassLoader().getResource("domDemo.xml").getPath(); // 由于路径中包含中文,所以需要对路径解析解码 path = URLDecoder.decode(path, "utf-8"); File file = new File(path); // 获取DocumentBuilderFactory对象 DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); // 根据DocumentBuilderFactory对象,构建DocumentBuilder对象 DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); // 根据DocumentBuidler对象,构建Document对象 Document document = documentBuilder.parse(path); // 获取给定标签名元素内容 NodeList student = document.getElementsByTagName("student"); for (int i = 0; i < student.getLength(); i++) { // 获取节点对象 Node node = student.item(i); // 将node转为element,node为element父接口 Element element = (Element)node; // 根据标签名字获取标签的text值 String firstname = element.getElementsByTagName("firstname").item(0).getTextContent(); System.out.println(firstname); } } }
程序运行结果:
3、Java SAX解析器
3.1、Java SAX解析器简介
SAX解析器在解析XML输入数据的各个组成部分时会报告事件,但不会以任何方式存储文档,而是由事件处理器建立相应的数据结构。实际上DOM解析器是在SAX解析器的基础上构建的,它在接收到解析器事件时构建DOM树。
SAX(针对XML的简单API)是基于事件为XML文档的解析器。不像DOM解析器,SAX解析器创建没有解析树。 SAX是一个流接口用于XML的,这意味着使用SAX应用接收事件通知有关XML文档被处理的元素,属性,在按顺序每次开始在文档的顶部,并与所述闭合结束根元素。 1、读取XML文件从上到下,构成一个结构完整的XML文档的标记 2、令牌以相同的顺序进行处理,它们出现在文档中 3、报告应用程序,因为它们所出现解析器遇到标记的特性 4、应用程序提供了必须的解析器注册的“事件”处理程序 5、作为标记标识,在处理程序回调方法相关信息调用
什么时候使用? 应该使用SAX解析器的时候: 1、可以在XML文档从上往下处理以线性方式 2、该文件并不深层次嵌套 3、处理一个非常大的XML文档,DOM树会占用太多的内存。典型DOM的实现使用10字节的存储器以表示XML的一个字节 4、解决的问题涉及的XML文档的一部分 5、数据是可用的,只要它是由解析器看出,这样的SAX可以很好地用于到达流的XML文档
SAX的缺点 1、它是在一个只进入处理随机访问方式XML文档 2、如果需要跟踪的数据分析器已经看到或更改项目的顺序,必须自已编写代码和数据存储
ContentHandler接口 此接口指定SAX解析器用来通知XML文档,已经看到部件应用程序的回调方法。
方法 | 方法描述 |
---|---|
void startDocument() | 调用在一个文件的开头。 |
void endDocument() | 调用在一个文件的末尾。 |
void startElement(String uri, String localName, String qName, Attributes atts) | 调用在一个元素的开头 |
void endElement(String uri, String localName,String qName) | 调用在一个元件的末端。 |
void characters(char[] ch, int start, int length) | 字符数据出现时调用。 |
void ignorableWhitespace( char[] ch, int start, int length) | 当DTD是当前和忽略空白遇到时调用。 |
void processingInstruction(String target, String data) | 当处理指令的认可时调用。 |
void setDocumentLocator(Locator locator)) | 提供可用于识别文档中的位置的定位器。 |
void skippedEntity(String name) | 一个尚未解决实体遇到时调用。 |
void startPrefixMapping(String prefix, String uri) | 当一个新的命名空间的映射定义调用。 |
void endPrefixMapping(String prefix) | 当一个命名空间定义结束其范围时调用。 |
属性接口 这种接口指定用于处理连接到一个元素的属性的方法。 int getLength() - 返回属性的数目。 String getQName(int index) String getValue(int index) String getValue(String qname)