类加载器
-
作用
负责将编译后的java文件(即
.class
文件)加载到内存中供虚拟机执行 -
类加载的时机------总结一句话:用到类就加载,不用就不加载
- 创建类的实例
- 调用类的方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方法来强制创建某个类或接口对应的
java.lang.Class
对象 - 初始化某个类的子类
- 直接使用
java.exe
命令来运行某个主类
-
类加载的过程
-
加载
首先,通过一个类的全限定名来获取定义此类的二进制字节流(即通过包名+类名,来获取这个类,并准备用流进行传输)------通过包名+类名,获取这个类,准备用流进行传输
然后,将这个字节流所代表的静态存储结构转化为运行时数据结构(即通过刚刚得到的流把字节码文件加载到内存中)------在这个类加载到内存中
最后,在内存中生成一个代表这个类的
java.lang.Class
对象,任何类被使用时,系统都会为之建立一个java.lang.Class
对象(即当一个类加载到内存后,虚拟机会创建一个.class
对象来存储类中对应的java内容)------加载完毕创建一个.class
对象 -
链接(链接中分为三步)
-
验证
目的是为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的自身安全(即检查文件中的信息是否符合虚拟机规范,有没有安全隐患)
-
准备
负责为类的类变量(即静态变量:被static修饰的变量)分配内存并设置默认初始化值
-
解析
将类的二进制数据流中的符号引用替换为直接引用(即若本类中用到了其他的类就需要找到对应的类)
解释:
当一个字节码文件加载到内存中时,虚拟机会创建一个class对象,并为其分配内存,假设该class对象在内存中的地址是0x0011,该class对象中会记录类的成员信息,这些成员信息是有类型的,如下图所示,name属于String类型的,而String是特殊的数据类型,因为它是一个类,那么最初在加载Student类的时候,是否加载了String类以及String类在哪里都不知道, 则此时String则会用符号代替,这就是符号引用。
当进行到解析这一步骤时,此时系统就会去找String这个类在哪里(此处假设String类在内存中的地址为0x0022),找到后就会把这个临时的符号变为实际的String引用,如图所示
-
-
-
初始化
根据程序员通过程序制定的主观计划去初始化类变量和其他资源(即给静态变量赋值以及初始化其他资源)
-
注意
当一个类被使用的时候才会加载到内存
-
类加载器分类
- 启动类加载器(Booststrap ClassLoader):虚拟机内置的加载器,底层用C++实现,当虚拟机启动时,它会随着自动启动
- 平台类加载器(Platform ClassLoader):负责加载JDK中一些特殊的模块
- 系统类加载器(System ClassLoader):负责加载用户类路径上所指定的类库
- 自定义类加载器(UserClassLoader):允许开发人员定义自己的类加载器来加载类文件
-
类加载器的双亲委派模型------即类加载器之间的层次关系
解释:除了启动类加载器之外,剩下的三种加载器都应该有自己的父类加载器。自定义类加载器的父类是系统类加载器,系统类加载器的父类是平台类加载器,平台类加载器的父类是启动类加载器
注意:
这里的父子关系并不是代码中的
extends
继承,而是逻辑上的继承。解释:
假设我现在用自定义类加载器去加载一个字节码文件,它不会自己去尝试加载,它会把这个加载任务委派给系统类加载器完成,而系统类加载器又会将其委托给平台类加载器完成,而平台类加载器又会委托给启动类加载器去完成,而启动类加载器是最顶层的加载器,所以不会再继续往上委托,因此所有的加载请求最后都会传递到最顶层的启动类加载器中,将这种传递委托关系认为是逻辑上的继承
当顶层的启动类加载器无法完成加载请求时,才会一层一层往下返回,每返回到一个子类加载器时,子类加载器才会去尝试着完成加载请求,若仍无法完成请求则会继续往下返回直至返回到自定义类加载器
ClassLoader类
-
ClassLoader
类中的方法静态方法 解释 public static ClassLoader getSystemClassLoader()
返回系统类加载器。 普通方法 解释 public ClassLoader getParent()
返回委托的父类加载器。 (即获取父类加载器,多次调用该方法可返回最顶层的加载器) public InputStream getResourceAsStream(String name)
加载某一个资源文件(利用获取到的加载器去获取某一个资源文件) name
:资源文件的路径;返回值为一个字节流InputStream
,文件中的数据均在该字节流中public class Student { public static void main(String[] args) { //获得系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //获得系统类加载器的父加载器------平台类加载器 ClassLoader platformClassLoader = systemClassLoader.getParent(); //获得平台类加载器的父加载器------启动类加载器 ClassLoader booststrapClassLoader = platformClassLoader.getParent(); System.out.println("系统类加载器为:" + systemClassLoader); System.out.println("平台类加载器为:" + platformClassLoader); System.out.println("启动类加载器为:" + booststrapClassLoader); } }
public class TestOne {
public static void main(String[] args) throws IOException {
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//利用加载器加载一个指定的文件
InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
Properties prop = new Properties();
if (is != null) {
prop.load(is);
}
System.out.println(prop);
is.close();
}
}
注意:
public InputStream getResourceAsStream(String name)
:该方法的参数表示的相对路径为当前类所在的src
文件,而不是工程根目录Project01,如图所示,该Student类在Day18下,所以prop.properties
文件的相对路径就是在Day18下的src
文件夹下
反射
-
定义
在运行状态中,对于任意一个类,都能够知道该类的所有属性和方法
对于任意一个对象都能够调用它的任意属性和方法
这种动态获取信息以及动态调用对象方法的功能称为Java反射机制
-
特点
- 利用反射可以无视修饰符获取类中所有的属性和方法(即此时可以调用任何属性和方法,不论修饰符是否是private)
- 先获取配置文件信息,动态获取信息并创建对象和调用方法(即在读取配置文件中的内容时读到什么就创建什么读到什么就运行什么)
反射获取Class类对象(即字节码文件对象)
- 先导
Class类对象解释:当一个Class文件加载到内存后创建的Class对象,该对象中包含了类中所有的信息(如:属性、构造方法、成员方法等等)
我们从Class类对象中所获取的所有成员变量就可认为是成员变量对象(即
Field
对象)获取的构造方法就可认为是构造方法对象(即
Constructor
对象)获取的成员方法就可认为是成员方法对象(即
Method
对象)所以
Field
对象、Constructor
对象、Method
对象就是Class类
对象的组成部分
Java文件运行的过程解析如上图所示,可知共有三个阶段:源代码阶段、Class对象阶段、Runtime运行时阶段,不同阶段获取Class类对象的方式不同
Student类代码
public class Student {
private String name;
private int age;
public Student() {}
public Student(String name, int age) {
this.name = name;
this.age = 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;
}
public void study() {
System.out.println("学习");
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 源代码阶段
利用Class类中的静态方法public static Class <?> forName(String className)
------返回与具有给定字符串名称的类或接口关联的 Class类
对象。
className
:一个字符串,表示要加载的类的完全限定名(即全类名:包名+类名)
获取Student类的
Class类对象
获取Class类
对象
public class TestTwo {
public static void main(String[] args) throws Exception {
//1.Class类中的静态方法forName("全类名")
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Student");
/*
等同于
Class<at.guigu.leijiazaiqi.Student> clazz = (Class<Student>) Class.forName("at.guigu.leijiazaiqi.Student");
*/
System.out.println(clazz);
}
}
注意:
(1)Class类中的静态方法
forName(String className)
返回的是与具有给定字符串名称的类或接口关联的Class类
对象。 所以Class<at.guigu.leijiazaiqi.Student> clazz = (Class<Student>) Class.forName("at.guigu.leijiazaiqi.Student");
为什么要进行强制转换?
Class.forName
方法的返回类型是Class<?>
,它表示任何类型的Class
对象,而不是特定类型,即使在编译时你将Class<?>
写为了Class<at.guigu.leijiazaiqi.Student>
,但是Class.forName("at.guigu.leijiazaiqi.Student")
对于编译器来说它默认返回的还是Class<?>
即使你知道它返回的是StudentClass类
对象,所以为了不报编译错误,你需要进行强转(2)通常情况下,我们在使用
Class.forName
方法时并不知道要加载的类的具体类型,因此使用泛型通配符<?>
是一种常见的做法,所以我们一般使用Class<?> clazz = Class.getforName(“全类名”)
这种格式来获取Class类
对象,而不使用Class<全类名> clazz = (Class<类名>)Class.forName("全类名")
的格式获取Class类
对象
- Class对象阶段
利用类名.class
方法来获取Class类
对象
获取Student类的
Class类对象
public class TestTwo {
public static void main(String[] args) throws Exception {
//通过class属性获取
Class clazz = Student.class;
System.out.println(clazz);
}
}
- Runtime运行时阶段
利用对象.getClass()
方法来获取Class类
对象
public class TestTwo {
public static void main(String[] args) throws Exception {
//利用对象的getClass()方法获取Class对象
Student stu = new Student();
Class clazz = stu.getClass();
System.out.println(clazz);
}
}
反射获取Constructor
对象
Person类代码如下:
public class Person {
private String name;
private int age;
//私有的有参构造方法
private Person(String name) {
System.out.println("name的值为:" + name);
System.out.println("private...有参构造方法");
}
//公共的无参构造方法
public Person() {
System.out.println("public...无参构造方法");
}
//公共的有参构造方法
public Person(String name, int age) {
System.out.println("name的值为:" + name + ",age的值为:" + age);
System.out.println("public...有参构造方法");
}
}
- 用到的Class类中的方法
方法 | 解释 |
---|---|
public Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组。(即返回一个数组,该数组包括所有公共的构造方法) |
public Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组。(即返回一个数组,该数组包括所有公共的构造方法和私有的构造方法) |
public Constructor<T> getConstructor(Class<?>...parameterTypes) | 返回单个公共构造方法对象。(只能返回公共的构造方法) |
public Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes) | 返回单个构造方法对象。(不仅能返回公共的构造方法还可以返回私有的构造方法) |
注意:
Class<?>... parameterTypes
:一个可变数量的参数,表示要获取的构造方法的参数类型。可以提供一个或多个参数类型,用于精确匹配所需的构造方法。如果不提供任何参数,则表示获取无参数的构造方法。
public class TestConstructor {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Person");
//方式1:public Constructor<?>[] getConstructors()
Constructor<?>[] constructors = clazz.getConstructors();
/*
等同于Constructor[] constructors = clazz.getConstructors();
*/
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("------------------");
//方式2:`public Constructor<?>[] getDeclaredConstructors()
Constructor<?>[] constructors1 = clazz.getDeclaredConstructors();//等同于Constructor[] constructors1 = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors1) {
System.out.println(constructor);
}
System.out.println("------------------");
//方式3:public Constructor<T> getConstructor(Class<?>...parameterTypes)
//获取公共的有参构造方法
Constructor<?> constructor = clazz.getConstructor(String.class,int.class);
System.out.println(constructor);
/*等同于
Constructor constructor = clazz.getConstructor(String.class,int.class);或者
Constructor<Person> constructor = (Constructor<Person>) clazz.getConstructor(String.class,int.class);
*/
//获取公共的无参构造方法
Constructor<?> constructor1 = clazz.getConstructor();
System.out.println(constructor1);
System.out.println("------------------");
//方式4:public Constructor<T> getDeclaredConstructor(Class<?>...parameterTypes)
//获取私有的有参构造方法
Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);
System.out.println(constructor2);
/*等同于
Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
Constructor<Person> constructor2 = (Constructor<Person>)clazz.getDeclaredConstructor(String.class);
*/
}
}
利用Constructor
创建对象
Constructor
类中创建对象用到的方法
方法 | 解释 |
---|---|
public T newInstance(Object... initargs) | 根据指定的构造方法创建对象。Object... initargs :一个可变数量的参数,表示要传递给构造函数的初始化参数。这些参数的类型必须与构造函数的参数类型相匹配。 |
public void setAccessible(boolean flag) | 是否允许访问此对象的反射对象在其私有成员上执行操作。如果为 true ,则允许访问,否则不允许。 |
- 创建公共的有参构造器对象
public class TestConstructor1 {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Person");
//第二步:获取Constructor对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
//第三步:利用newInstance(Object... initargs)方法创建对象
Person person = (Person) constructor.newInstance("张三", 15);
/*等同于
Object object = constructor.newInstance("张三", 15);
*/
System.out.println(person);
}
}
- 创建公共的空参构造器对象
public class TestConstructor1 {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Person");
//第二步:获取Constructor对象
Constructor<?> constructor = clazz.getConstructor();
//第三步:利用newInstance(Object... initargs)方法创建对象
Person person = (Person) constructor.newInstance();
/*等同于
Object objct = constructor.newInstance();
*/
System.out.println(person);
}
}
创建空参构造器对象的简写格式代码如下:
public class TestConstructor1 {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Person");
//第二步:调用Class类中的ewInstance(Object... initargs)方法创建对象------已过时
Person person = (Person) clazz.newInstance();
System.out.println(person);
}
}
注意:
Class类中的
public T newInstance(Object... initargs)
方法已过时,此处了解即可
- 创建私有的有参构造器对象
在创建私有的有参构造器对象时,与创建共有的构造器对象不同,其不能直接使用Constructor
类的newInstance(Object... initargs)
方法来获取对象,因为其是被private
修饰的成员。 若用反射强行获取并使用,则需要临时取消访问检查,即将setAccessible(boolean flag)
参数设为true
public class TestConstructor1 {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Person");
//第二步:获取私有的有参构造方法
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class);
//第三步:允许访问此对象的反射对象在其私有成员上执行操作
constructor.setAccessible(true);
//第三步:获取私有的有参构造器对象
Person person = (Person) constructor.newInstance("张三");
System.out.println(person);
}
}
反射获取Field
对象
Teacher类代码如下:
public class Teacher {
public String name;
public int age;
public String gender;
private int money = 300;
}
- 用到的
Field
类中的方法
方法 | 解释 |
---|---|
public Field[] getFields() | 返回所有公共成员变量对象的数组 |
public Field[] getDeclaredFields() | 返回所有成员变量对象的数组**(即公有和私有的都可返回)** |
public Field getField(String name) | 返回单个公共成员变量对象。String name :表示要获取的字段的名称 |
public Field getDeclaredField(String name) | 返回单个成员变量对象**(即公有和私有的都可返回)**。String name :表示要获取的字段的名称 |
public class TestField {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Teacher");
//第二步:获取Field对象
//方式1:public Field[] getFields()
Field[] fields1 = clazz.getFields();
for (Field field : fields1) {
System.out.println(field);
}
System.out.println("------------------------");
//方式2:public Field[] getDeclaredFields()
Field[] fields2 = clazz.getDeclaredFields();
for (Field field : fields2) {
System.out.println(field);
}
System.out.println("------------------------");
//方式3:public Field getField(String name)
Field field1 = clazz.getField("name");
System.out.println(field1);
System.out.println("------------------------");
//方式4:public Field getDeclaredField(String name)
Field field2 = clazz.getDeclaredField("money");
System.out.println(field2);
}
}
利用Field
对象赋值、获取值
Field
类中用到的方法
方法 | 解释 |
---|---|
public void set(Object obj, Object value) | 将指定对象参数上此 字段 对象表示的字段设置为指定的新值。(即给指定对象的成员变量赋值) |
public Object get(Object obj) | 返回指定对象上此 字段 表示的字段的值。(即返回指定对象的Field 的值) |
public void setAccessible(boolean flag) | 是否允许访问此字段的反射对象在其私有成员上执行操作 |
注意:
(1)
Object obj
:表示要设置或返回字段值的对象实例
Object value
:表示要设置的新值。该值的类型必须与字段的类型相匹配,否则将抛出IllegalArgumentException
异常。(2)
返回私有成员变量的值时要先取消一下访问检查------利用Field类中的
setAccessible(boolean flag)
方法(3)
在赋值和返回值之前都需要先获取
Constructor
对象并利用Constructor
创建对象,然后才能使用Field
类中的set
和get
方法来赋值和返回值
public class TestField1 {
public static void main(String[] args) throws Exception{
//第一步:获取Person的Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Teacher");
//第二步:获取Field对象
Field fieldName = clazz.getField("name");
Field fieldAge = clazz.getField("age");
Field fieldGender = clazz.getField("gender");
Field fieldMoney = clazz.getDeclaredField("money");
//第三步:利用Set(Object obj, Object value)方法进行赋值
//3.1先创建一个Teacher对象,有了Teacher对象才能给指定对象赋值
Constructor constructor = clazz.getConstructor();
Teacher teacher = (Teacher) constructor.newInstance();
fieldName.set(teacher, "张三");
fieldAge.set(teacher, 15);
fieldGender.set(teacher, "男");
System.out.println(teacher);
//第四步:获取值
//获取普通成员变量的值
String name = (String) fieldName.get(teacher);
System.out.println(name);
//获取私有成员变量的值
fieldMoney.setAccessible(true);
int money = (int) fieldMoney.get(teacher);
System.out.println(money);
}
}
反射获取Method
对象
Worker类如下:
public class Worker {
private void show() {
System.out.println("私有的show方法,无参无返回值");
}
public void function1() {
System.out.println("公共的function1方法,无参无返回值");
}
public void function2(String name) {
System.out.println("公共的function2方法,有参无返回值,参数为:" + name);
}
public String function3() {
System.out.println("公共的function3方法,无参有返回值");
return "aaa";
}
public String function4(String name) {
System.out.println("公共的function4方法,有参有返回值,参数为:" + name);
return "aaa";
}
}
- 用到的
Class
类中的方法如下:
方法 | 解释 |
---|---|
public Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
public Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的。(包括私有方法和公共方法) |
public Method getMethod(String name, Class<?>... parameterTypes) | 返回单个公共成员方法对象 |
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象(包括私有方法和公共方法) |
解释:
String name
: 这是要获取的方法的名称,需要提供方法的准确名称作为参数。
Class<?>... parameterTypes
: 这是一个变长参数,用于指定方法的参数类型。这表示你可以传递零个或多个参数类型作为参数。参数类型是Class<?>
对象的可变数量。Class<?>
表示一个未知的类型,这允许方法接受任何类型的参数。通常,你会传递实际的类对象来指定方法的参数类型。 后两个方法不会返回继承的方法
以上四种方法若找不到指定的方法将返回
null
public class TestMethod {
public static void main(String[] args) throws Exception{
//第一步:获取Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Worker");
//第二步:获取Method对象
//方式1:public Method[] getMethods()
Method[] methods1 = clazz.getMethods();
for (Method method : methods1) {
System.out.println(method);
}
System.out.println("--------------------------------------");
//方式2:public Method[] getDeclaredMethods()
Method[] methods2 = clazz.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(method);
}
System.out.println("--------------------------------------");
//方式3:public Method getMethod(String name, Class<?>... parameterTypes)
Method method1 = clazz.getMethod("function1");
System.out.println(method1);
Method method2 = clazz.getMethod("function2", String.class);
System.out.println(method2);
Method method3 = clazz.getMethod("function3");
System.out.println(method3);
Method method4 = clazz.getMethod("function4", String.class);
System.out.println(method4);
System.out.println("--------------------------------------");
//方式4:public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method methodPrivate = clazz.getDeclaredMethod("show");
System.out.println(methodPrivate);
}
}
利用Method
对象运行方法
Method
类中用到的方法
方法 | 解释 |
---|---|
public Object invoke(Object obj, Object... args) | 在具有指定参数的指定对象上调用此 方法 对象表示的基础方法。(即在指定的对象上调用方法) |
注意:
(1)参数解释
Object obj
: 这是要调用方法的对象。如果方法是静态的,则可以将null
传递给此参数
Object... args
: 这是一个可变数量的参数,表示方法调用时传递的参数。你可以传递零个或多个参数给方法。这些参数的类型应该与要调用的方法的参数类型匹配。
Object
返回值:invoke
方法的返回值是被调用方法的返回值。因为被调用方法的返回类型可能是任何类型,所以这里返回的是Object
。若没有返回值就不写
public class TestMethod1 {
public static void main(String[] args) throws Exception {
//第一步:获取Class对象
Class<?> clazz = Class.forName("at.guigu.leijiazaiqi.Worker");
//第二步:创建Worker对象
Constructor<?> constructor = clazz.getConstructor();
Worker worker = (Worker) constructor.newInstance();
//第三步:获取Method对象
Method method4 = clazz.getMethod("function4", String.class);
//第四步:利用Method对象运行方法
String a = (String) method4.invoke(worker, "张三");
System.out.println(a);
}
}
XML
- 前导
properties作为配置文件的缺点:
若配置文件中运行的方法比较多,则只能在“=”的后面写多个值,并且用逗号或者一个指定的符号隔开,在利用代码解析properties文件时还需要用逗号进行切割,这样就比较麻烦。如图所示
而对于XML来说,里面的每个值都是独立存在的,不需要进行切割,操作方便如图所示
-
XML学习网址:https://www.w3school.com.cn/
-
XML文件都会存在XML文件夹下
-
概述
全称为Extensible Markup Language,是一种可扩展的标记语言 (可理解为XML文件是由很多标签组成的,而标签名是可以自定义的)
可扩展:标签的名字是可以自定义的
- 标记语言:通过标签(也称为元素)来描述数据的一门语言
-
作用
- 用于进行存储数据和传输数据
- 作为软件的配置文件
-
XML标签规则
- 标签由一对尖括号和合法标识符组成,如:
<student>
- 标签必须成对出现(即需要有开始和结束两个标签),如:
<student> </student>
- 特殊标签可以不成对出现,但必须有结束标记
/
,如:<adress/>
- 标签中可以定义属性,属性和标签名空格隔开,属性值必须用引号引起来,如:
<student id="1"> </student>
- 标签需要正确的嵌套,如下所示
<student id="1"> <name>张三</name> </student> <!--下列为错误格式 <student id="1"> <name>张三 </student> </name> -->
- 标签由一对尖括号和合法标识符组成,如:
-
XML语法规则
-
XML文件后缀名为:
xml
-
文档声明必须是第一行第一列
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<?xml ... ?>
是声明的开始和结束标记,在这个标记中,我们可以指定以下信息:version
:该属性必须存在,这是XML版本号。它指定了XML规范的版本。通常情况下,它的值是 “1.0” 或 “1.1”encoding
:该属性非必须存在。这是XML文档所使用的字符编码(一般取值都为UTF-8
)。字符编码指定了文档中的字符如何转换为字节序列。standalone
:该属性非必须存在。指定了文档是否是独立的,即是否依赖外部资源。如果文档声明中没有standalone
属性,那么文档的独立性会根据具体的XML解析器的默认行为来决定。 -
XML文件中必须存在一个根标签,但是有且只能有一个
-
XML文件可以定义注释信息
-
XML文件中的特殊字符
<
------<
------小于>
------>
------大于&
------&
------和号'
------'
------单引号"
------"
------引号
-
XML文件中可以存在
CDATA
区------可以包含任意的文本数据,而且不需要对其中的特殊字符进行转义。<![CDATA[...内容...]]>
<?xml version="1.0" encoding="UTF-8" ?> <students><!--根标签--> <!-- 第一个学生信息--> <student id="1"> <name>张三</name> <age>23</age> <info>学生<>信息</info> <message><![CDATA[...<内容>...]]></message> </student> <!-- 第二个学生信息--> <student id="2"> <name>李四</name> <age>24</age> </student> </students>
-
-
判断XML文件中代码的正确性方法
- idea中会自动进行错误标红提示
- 用浏览器打开
xml
文件,若有误则会有错误提示,如图所示
XML解析
-
常见的解析思想
DOM(Document Object Model,文档对象模型) ,是一种表示XML或HTML文档结构的标准接口。它将文档中的每个组成部分(如元素、属性、文本)都表示为一个对象,这些对象可以被程序(通常是JavaScript、Java等)访问和操作
它会把
xml
文件全部加载到内存,在内存中形成一个树形结构,再获取对应的值 -
DOM解析思想
是一种解析
XML
或HTML
文档的方法,它将文档解析为一个树状的对象模型,xml
文档中的所有信息均以树状形式显示
注意:
- Document:整个xml文档对象
- 图中蓝色的标签为
Element
对象,标签之间的内容叫做标签体- id是一个属性,属性属于
Attribute
对象- 图中紫色为标签中的内容,属于
标签体
,叫做 Text(文本)对象- id后面的值不是一个对象,可以通过id直接获取到,只是一个普通的属性值
Element
对象、Attribute
对象、Text
对象有一个共同的父类Node
对象
-
常见的解析工具
- JAXP
- JDOM------开源组织提供
- DOM4J(全称Dom For Java)------开源组织提供------以后用dom4j解析
- pull:主要应用在Android手机端解析XML
-
解析工作准备
- 在项目根目录下创建一个名为libs的文件夹(以后所有的jar包都是放在其中)
- 将
dom4j
jar
包放入,放入后右键jar
包→Add as Library
→OK
(查看jar包是否成功导入方法在day17日志技术中已提到)
-
解析用到dom4j架包中的的类及方法
SAXReader 构造器 | 解释 |
---|---|
public SAXReader() | 创建一个 SAXReader 解析器对象,该对象将使用默认的配置和设置来解析 XML 文档。 |
SAXReader 类方法 | 解释 |
public Document read(File file) | 从指定的文件中读取 XML 文档并将其解析为一个 DOM4J 文档对象(即利用解析器把xml文件加载到内存中并返回一个Document 对象) |
Document 方法 | 解释 |
public Element getRootElement() | 获取 XML 文档的根元素Element 对象(即获取根标签) |
Element 接口方法 | 解释 |
public List elements() | 获取该元素对象的所有一级子元素,每个子元素都表示为一个 Element 对象。如果该元素没有子元素,则返回一个空的 List 。(即获取调用者的所有一级子标签并把这些子标签放到一个集合中返回) |
public List elements(QName, qName) | 获取该元素中指定 QName 的所有一级子元素,每个子元素都表示为一个 Element 对象。如果该元素没有子元素,则返回一个空的 List 。 QName qName :这是一个 QName 对象,用于指定要获取的子元素的名称。QName 是 DOM4J 中的一个类,用于表示 XML 元素的限定名称。QName 对象包含了元素的命名空间 URI、本地名称和前缀。(即获取调用者指定的所有一级子标签并把这些子标签放到一个集合中返回) |
public Element element(String s) | 获取调用者指定的子标签。String s :指定的子标签字符串名 |
public Attribute attribute(String name) | 获取Element 对象指定名称的属性,如果指定名称的属性不存在,则返回 null 。(即获取标签中的属性) |
public String getText() | 获取标签的标签体内容 |
Attribute 方法 | 解释 |
public String getValue() | 获取指定属性的值 |
- 解析代码实现
public class XmlParse {
public static void main(String[] args) throws DocumentException {
//创建一个ArrayList集合用来装学生对象
ArrayList<Student> stuList = new ArrayList<>();
//第一步:获取解析器对象
SAXReader saxReader = new SAXReader();
//第二步:利用解析器把xml文件加载到内存中并返回一个对象
Document document = saxReader.read(new File("xml\\student.xml"));
//第三步:获取根标签
Element rootElement = document.getRootElement();
//第四步:通过根标签来获取Student标签
//elements():获取调用者的所有一级子标签并把这些子标签放到一个集合中返回
List<Element> list = rootElement.elements();
System.out.println(list.size());//查看根目录下有几个一级子标签
System.out.println("----------------------------");
//List elements(QName, qName):可以获取调用者指定的所有一级子标签,并把这些子标签放在List集合中
List<Element> stuElements = rootElement.elements("student");
for (Element stuElement : stuElements) {
//获取标签中的属性
//获取id这个属性
Attribute idAttribute = stuElement.attribute("id");
System.out.println(idAttribute);
//获取id的属性值
String idValue = idAttribute.getValue();
System.out.println(idValue);
//获取name标签
Element nameElement = stuElement.element("name");
System.out.println(nameElement);
//获取name标签的标签体
String nameText = nameElement.getText();
System.out.println(nameText);
//获取age标签
Element ageElement = stuElement.element("age");
String ageText = ageElement.getText();
System.out.println(ageText);
Student s = new Student(idValue, nameText, Integer.parseInt(ageText));
stuList.add(s);
}
System.out.println("---------------------------");
for (Student student : stuList) {
System.out.println(student);
}
}
}
注意:
String类型的数据不能直接强制转换为int类型。这是因为String类型和int类型是不兼容的数据类型,它们之间没有继承或兼容关系。可以使用
Integer
类提供的parseInt()
方法或valueOf()
方法将String类型的数据转换为int类型,或者在需要时使用自动装箱和拆箱来进行转换
XML文件(文档)约束
-
什么是约束
用来限定
xml
文件中可使用的标签和属性(说白了就是告诉程序员xml该怎么写,自己不要随便写标签名和属性名等内容,按照要求来) -
约束分类
DTD
约束------需掌握内容如下:- 能在
xml
中引入约束文档 - 能够简单阅读约束文档
- 根据约束编写
xml
文件
- 能在
schema
约束
-
schema
约束文件和dtd
约束文件区别-
schama
语法更加复杂 -
dtd
文件中元素类型取值比较单一,常见的是PCDATA
类型;在schema
文件中可支持多个数据类型 -
dtd
约束文件不是xml
文件;而schema
约束文件是XML
文件,符合XML
语法,该文件后缀名为.xsd
-
一个
xml
文件中可以引用多个schema
约束文件,多个schema
使用名称空间区分(名称空间类似于java
包名) -
schema文件用来约束
xml
文件但由于它是一个xml
文件,所以它同时也被其他文件约束着
-
DTD
约束
person.xml
文件如下:
-
编写
dtd
约束文件的步骤-
创建一个后缀名为
.dtd
的文件 -
看
xml
文件中使用了哪些元素(即在此☞标签),并用<!ELEMENT>
去定义这些元素,写法为:<!ELEMENT 标签名>
,如:在上图中有四个标签,所以要定义这四个标签,代码如下:<!ELEMENT persons><!--复杂元素--> <!ELEMENT person><!--复杂元素--> <!ELEMENT name><!--简单元素--> <!ELEMENT age><!--简单元素-->
-
判断元素是简单元素(没有子元素)还是复杂元素(有子元素),如上所示,但是我们一般用代码来显示是简单元素还是复杂元素,格式为:
- 简单元素:
<!ELEMENT 标签名 (#PCDATA)>
- 复杂元素:
<!ELEMENT 标签名 (该标签的一级子标签名,多个用逗号隔开)>
,
- 简单元素:
-
person.xml
文件对应的完整persondtd.dtd
约束文件代码如下所示:
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
XML
文件引入dtd
约束文件
- 第一种方法:引入本地
dtd
文件
文档声明下面第一行写入代码,格式为:<!DOCTYPE 根标签名 SYSTEM 'dtd文件路径及后缀名'>
,如下代码所示
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE persons SYSTEM 'persondtd.dtd'>
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
其中SYSTEM
代表从本地引入dtd
文件,成功引入之后按住Ctrl
+ 鼠标左键单击dtd
文件路径,即可跳转到dtd
文件
引入dtd
文件后,若定义dtd
文件中没有的标签名,则系统会自动报错,如下图所示
- 第二种方法:在
xml
文件内部引入—即dtd
文件的内容跟xml
文件的内容在同一个文件中
文档声明下面第一行写入dtd文件内容的代码格式为:<!DOTYPE persons [方括号中写dtd文件内容]>
,详细代码如下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE persons [
<!ELEMENT persons (person)>
<!ELEMENT person (name,age)>
<!ELEMENT name (#PCDATA)>
<!ELEMENT age (#PCDATA)>
]>
<persons>
<person>
<name>张三</name>
<age>23</age>
</person>
</persons>
- 第三种方法:引入网络中的dtd文件
文档声明下面第一行写入dtd
文件内容的代码格式为:<!DOCTYPE 根标签名 PUBLIC "dtd文件名称" "dtd文档的URL">
注意:
第一种方法中用的单引号,第三种方法中用的是双引号
DTD
语法规则
-
定义一个元素的格式为:
<!ELEMENT 元素名 元素类型>
-
简单元素
EMPTY
:表示标签体为空ANY
:表示标签体可以为空也可以不为空#PCDATA
:表示该元素的内容为部分字符串
-
复杂元素------直接写子元素名称
- 多个子元素可以用
,
或者|
隔开。其中,
表示定义子元素的顺序,|
表示子元素只能出现任意一个,还有一些特殊的符号意义如下:
?
:零次或一次+
:一次或多次*
:零次或多次若不写则表示出现一次
- 多个子元素可以用
-
举例说明
-
-
定义一个属性的格式为:
<!ATTLIST 元素名称 属性名称 属性的类型 属性的约束>
-
属性的类型
CDATA
类型:普通的字符串
-
属性的约束
#REQUIRED
:属性是必须的#IMPLIED
:属性不是必须的#FIXED
:属性值是固定的
-
举例说明
- 属性值固定,此时格式为:
<!ATTLIST 元素名称 属性名称 属性的类型 #FIXED "属性值">
- 属性值固定,此时格式为:
-
属性值是必须,此时必须有属性值,但是属性值可以相同也可不同,格式为:
<!ATTLIST 元素名称 属性名称 属性的类型 #REQUIRED>
-
属性值非必须,此时属性值可有可无,且属性值可相同也可不相同,格式为:
<!ATTLIST 元素名称 属性名称 属性的类型 #IMPLIED>
-
schema
约束
person.xml
文件如下:
-
编写schema约束文件的步骤
- 创建一个名为
.xsd
的文件 - 定义文档声明
<?xml version="1.0" encoding="UTF-8" ?>
- schema文件的根标签为:
<schema>
- 在
<schema>
中定义属性:xmlns=http://www.w3.org/2001/XMLSchema
,表示当前的文件是一个约束文件(对xml
文件进行约束)targetNamespace=唯一的url地址
,作用是指定当前schema
文件的名称空间elementFormDefault="qualified"
,表示当前schema文件是一个质量良好的文件
- 通过element来定义元素,并判断当前元素是简单元素还是复杂元素
代码结构如下:
<?xml version="1.0" encoding="UTF-8" ?> <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="自己的命名空间" elementFormDefault="本文件质量良好"> <element name="根标签名"> <complexType><!--复杂元素--> <sequence><!--里面的子元素必须要按照顺序定义--> </sequence> </complexType> </element> </schema>
- 创建一个名为
person.xml
文件对应的完整persondtd.xsd
约束文件代码如下所示:
<?xml version="1.0" encoding="UTF-8" ?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://blog.csdn.net/cgrs5572/javase"
elementFormDefault="qualified">
<!--定义persons复杂元素-->
<element name="persons">
<complexType>
<sequence>
<!--定义person复杂元素-->
<element name="person">
<complexType>
<sequence>
<!--定义name和age简单元素-->
<element name="name" type="string"></element>
<element name="age" type="string"></element>
</sequence>
</complexType>
</element>
</sequence>
</complexType>
</element>
</schema>
XML
文件引入schema
约束
-
引入步骤
- 在根标签定义属性:
xmlns="http://www.w3.org/2001/XMLSchema-instance"
,代表该xml
文件是被别人约束的 - 通过
xmlns
引入约束文件的targetNamespace
命名空间(即该xml
文件是被哪个约束的,要用xmlns
属性写出约束文件的命名空间来告诉系统) - 给某一个
xmlns
属性添加一个标识,用于区分不同的名称空间,格式为:xmlns:标识="名称空间地址"
(注意:标识可以是任意的,但是一般取值都是xsi
) - 通过
xsi:schemaLocation
指定名称空间所对应的约束文件路径格式为:xsi:schemaLocation="名称空间url 文件路径"
(注意:双引号中由两部分即名称空间url
和文件路径)
代码如图所示
- 在根标签定义属性:
Schema
定义属性
- 代码格式:
<attribute name="属性名" type="属性值的数据类型" use="属性是否必须"></attribute>
use的参数如下:
required:该属性必须有
optional:该属性可有可无
注意:给复杂元素添加属性的代码要写在该标签最外层的
</sequence>
和</complexType>
之间
代码示例如图所示