文章目录
- 常用类库
-
- 泛型
-
- 泛型类
- 泛型方法
- 泛型限制
- 通配符
- Objects
- Math
- Arrays
- BigDecimal
- Date
-
- DateFormat
- Calendar
- System
- String
-
- 常用方法
- 集合
-
- List接口
- ArrayList
- LinkedList
- IO
-
- 字节流
- 多线程
-
- Thread
- 网络编程
- 调试和优化
-
- IDEA快捷键
- XML/Json
-
- XML语法
- 解析XML文件
- XPATH解析XML
- 生成XML
- json
- Java—Json
- 枚举
- 注解
-
- 内置注解
- 自定义注解
- 反射
-
- Class
- 内省
常用类库
- 常用的库:握字符串、时间、集合、IO、多线程、网络编程、XML、JSON 等
泛型
- 将参数的类型也设置成可变参数
- 提高代码复用率,泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)
泛型类
- 类名后跟上泛型参数
public class Person<T> { private int age;private String name;private T city; // 由泛型决定public T getCity() { return city;}public void setCity(T city) { this.city = city;} }public class PersonTest { public static void main(String[] args) { Person<String> p = new Person<>(); // 后面的<String>可省略p.setCity("北京"); // city属性在此对象中被设置为String类型} }
- 类似的,还有泛型接口
public interface Students<T> { T getData(); // 返回值类型为T,默认abstract }public class StudentsTest implements Students<String> { // 也可以不指定类型,直接使用<T>private String text; // 这里也用<T>public String getText() { return text;}public void setText(String text) { this.text = text;}@Overridepublic String getData() { // 这里也用<T>return text;} }
- 实例化或继承时指明类型
- 继承接口时可以不指定类型,因为实现接口的类还需要实例化
泛型方法
- 形如:
private static <T> T 方法名(T a, T b) {}
public class PersonTest { public static void main(String[] args) { show("Roy"); // 根据传入参数类型而定}public static <T> void show(T a){ // 前面的<T>不能省略System.out.println(a);} }
- 声明泛型,传泛型参数
泛型限制
- 在使用泛型时, 可以指定泛型的区域(范围)
- 例如:必须是xx类的子类或xx接口的实现类
public class PersonTest { JiaFeiMao<Cat> cat = new JiaFeiMao<>(); }interface Animals { }class Cat implements Animals{ }class JiaFeiMao<T extends Animals>{ // 泛型包含的类型必须是动物类的子类,这里只能用CatT color; }
通配符
- 除了在定义时限制泛型的范围,还可以通过通配符
?
规定上限和下限public class PersonTest { // 这里?就是泛型,但是必须是Animals的子类,即规定了上限JiaFeiMao<? extends Animals> cat = new JiaFeiMao<Cat>(); // 后面的Cat可以不写// 必须是Cat的父类,即规定了下限JiaFeiMao<? super Cat> dog = new JiaFeiMao<Animals>(); // 后面的Animals可以不写 }interface Animals { }class Cat implements Animals{ }class JiaFeiMao<T>{ T color; }
- 如果只指定
?
,即没有限制泛型类型,相当于普通的<T>,一旦实例化就确定了对象的类型(不变) - 如果规定了上下限,例如
Object
,则实例化的对象可以是其子类(变) - 小结:最常用的还是泛型类
Objects
java.lang.Object
是所有类的父类,他的一些子类定义的方法我们很常用,称之为核心类库- 第一个:
java.util.Objects
- 查阅API文档可以发现:
public final class Objects
- 此类包含static实用程序方法,用于操作对象或在操作前检查某些条件
public Foo(Bar bar, Baz baz) { this.bar = Objects.requireNonNull(bar, "bar must not be null");this.baz = Objects.requireNonNull(baz, "baz must not be null"); } // 或者可以抛出异常
Math
public final class Math
,静态运算方法public static void main(String[] args) { System.out.println(Math.max(100,200)); // 两个参数System.out.println(Math.round(-100.2)); // -100 四舍五入(大)System.out.println(Math.floor(4.5)); // 小于参数的最大整数 4.0 }
- 遇到运算先别慌,Math用着才更香
Arrays
java.util.Arrays
,该类包含用于操作数组的各种方法(例如排序和搜索)。 此类还包含一个静态工厂,允许将数组视为列表public static void main(String[] args) { int[] arr1 = { 2,4,8,9,78,7,15};for (int i=0; i<7; i++){ System.out.println(arr1[i]); // 比较麻烦}Arrays.sort(arr1); // 原对象操作,默认升序System.out.println(Arrays.toString(arr1)); // [2, 4, 7, 8, 9, 15, 78] 字符串arr1 = Arrays.copyOf(arr1, 15); // 扩容为15(newLength)System.out.println(arr1.length); // 15 }
- 这些方法都比较常用
BigDecimal
java.lang.Number
的子类java.math.BigDecimal
,不可变的,任意精度的带符号十进制数(最高精度)public static void main(String[] args) { // float同类型和double类型运算时会有误差System.out.println(0.1+0.2); // 0.30000000000000004BigDecimal b1 = new BigDecimal("0.13");BigDecimal b2 = new BigDecimal("0.2");BigDecimal b3 = b1.add(b2);BigDecimal b4 = b1.subtract(b2);System.out.println(b3); // 0.33System.out.println(b4); // -0.07 }
Date
- Date类表示特定的时刻,精度为毫秒
- 为了更符合国际化规范,
Calendar
类应该用于在日期和时间字段之间进行转换,而DateFormat
类应该用于格式化和解析日期字符串
DateFormat
java.text.DateFormat
日期/时间格式化子类的抽象类- 作用:格式化和分析日期或时间,主要是这个格式,使用
format
方法public static void main(String[] args) { SimpleDateFormat s = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); // M和H大写String time = s.format(new Date());System.out.println(time); // 2021年09月03日 23:26:38Date date = null;try { // 解析,返回Datedate = s.parse("2021年8月18日 18:23:34"); // 1629282214000} catch (ParseException e) { e.printStackTrace();}System.out.println(date.getTime()); }
Calendar
- Calendar的
getInstance()
方法返回一个Calendar对象,其日历字段已使用当前日期和时间初始化public static void main(String[] args){ Calendar c1 = Calendar.getInstance(); // 不是newint year = c1.get(Calendar.YEAR); // 这个YEAR是一个全局常量,存的是一个数组下标,获取年System.out.println(year); // 2021c1.set(Calendar.YEAR, 2022); // 也可以设置int day = c1.get(Calendar.DAY_OF_YEAR); // 今天是今年的第几天System.out.println(day);c1.add(Calendar.MONTH, 2);System.out.println(c1.get(Calendar.MONTH)); // 10 现在是9月,但月份是从0-11,玛德// 注:后续操作是在之前更改的对象的基础上,即直接操作原数据Date d = c1.getTime(); // 转换成Date对象System.out.println(d.getTime()); // 1667530255970 }
System
- System类提供的设施包括标准输入,标准输出和错误输出流; 访问外部定义的属性和环境变量; 加载文件和库的方法; 以及用于快速复制阵列的一部分的实用方法
Scanner input = new Scanner(System.in);// 返回当前时间戳 currentTimeMillis() // 将指定源数组中的数组从指定位置开始复制到目标数组的指定位置 arraycopy?(Object src, int srcPos, Object dest, int destPos, int length)
- 详细查看API文档
String
- 字符串属于“不可变类型”,不论是否为对象(值或引用),不可变意味着修改时只会产生新的内容(返回新的对象)
- 之前了解了栈、堆、方法区
- 方法区存储类、方法的定义,因为只需要实例化时加载一次(一套模型、多个实例)
- 堆在逻辑上分为三类
- 新生代
- 老年代
- 永久代:不会GC
- 字符串常量池还是存放在方法区,JDK1.8以前使用永久代实现方法区(注意这个关系)
- 1.8用元空间代替了永久代,常量池还是在方法区
- 既然放在了方法区,而且String对象是不可变的,所以可以共享它们
String str = "abc"; // 最常用的构造方法 // 等价于 char data[] = { 'a', 'b', 'c'}; String str = new String(data);
- 从存放的位置和可变类型来理解数据类型和结构,更深入!
- 一般可变类型的实例(都是对象),存放在堆的新生代到老年代,用栈指针指向
常用方法
- 根据API文档
- 字符串拼接
- 最常见的可能是
+
,但要注意,这种操作会产生很多垃圾,因为拼接后并不会回收各个字符串,字符串常量池中无用变量太多 - 如何操作更优雅呢?用两个常见类
- 最常见的可能是
StringBuffer
- 线程安全,可变的字符序列。 字符串缓冲区类似于String ,但可以进行修改
- 在任何时间点它都包含一些特定的字符序列,但序列的长度和内容可以通过某些方法调用来改变
- 根据API文档,有很多种构造方法
StringBuffer() // 构造一个字符串缓冲区,其中没有字符,初始容量为16个字符。 StringBuffer?(int capacity) // 构造一个字符串缓冲区,其中没有字符和指定的初始容量。 StringBuffer?(CharSequence seq) // 构造一个字符串缓冲区,其中包含与指定的 CharSequence相同的字符。 StringBuffer?(String str) // 构造一个初始化为指定字符串内容的字符串缓冲区。
- 常用的方法是
append()
和转为String
的toString()
StringBuilder
- 一个可变的字符序列。 此类提供与StringBuffer兼容的API,但不保证同步(所以更快)
- 此类设计用作StringBuffer替代品,用于单个线程使用字符串缓冲区的位置
- 常用方法相同
- 任务训练
package com.strings;public class StringFunc { /****/// 翻转一句话中每个单词public String revStr(String str){ String[] arr = str.split(" "); // 空格分隔开的每个单词StringBuffer newStr = new StringBuffer();for (String s : arr){ StringBuffer i = new StringBuffer(s);i.reverse();newStr.append(i).append(" ");}return newStr.toString();}// 将字符串信息存放到对应的类中// String s="name=王五 age=18 classNum=1101";public void stuInfo(){ Student stu = new Student();String s="name=王五 age=18 classNum=1101";String[] arr = s.split(" ");for (String i : arr){ String[] j = i.split("=");if (j[0].equals("name")){ stu.setName(j[1]);}else if (j[0].equals("age")){ stu.setAge(Integer.valueOf(j[1])); // 从字符串提取数字 包装成Integer}else if (j[0].equals("classNum")){ stu.setClassNum(Integer.valueOf(j[1]));}else{ stu.setName("Roy");stu.setAge(18);stu.setClassNum(666);}}System.out.println(stu.toString());}// 字符串压缩 字符串“aabccdd”会变成“a2b1c2d2”public String comStr(String s){ /*算法思想:两个标志位,ch比较当前字符,count计数当前字符;ch如果发现跟前一个字符不一样,ch变化,计数清零*/// 校验if (s.equals("")){ return s;}StringBuffer sb = new StringBuffer();char ch = s.charAt(0); // 当前检测的字符int count = 0; // 压缩字符个数for (int i = 0; i < s.length(); i++) { if (ch == s.charAt(i)){ count++;}else { // 上一个字符的情况保存sb.append(ch);sb.append(count);ch = s.charAt(i);count=1;}}// 追加最后一个sb.append(ch);sb.append(count);return sb.length()>s.length()?s:sb.toString(); // 如果压缩完更长久不用压缩了}// 写一个和 trim 功能相同的方法,即去掉字符串前后的空格public String myTrim(String s){ /*思路:从前从后遍历,找到第一个不是空格的字符串,记录索引并截取!*/int idx1 = 0;int idx2 = s.length()-1;for (int i = 0; i < s.length(); i++) { if (s.charAt(i) != ' '){ idx1 = i;break;}}for (int i = s.length()-1; i >= 0; i--) { if (s.charAt(i) != ' '){ idx2 = i;break;}}return s.substring(idx1, idx2+1); // 取头不取尾}public static void main(String[] args) { // 主函数中测试StringFunc funcs = new StringFunc();String s = "I am a handsome boy";String result = funcs.revStr(s);System.out.println(result); // I ma a emosdnah yob// funcs.stuInfo();// System.out.println(funcs.comStr("aabbbbccdd")); // a2b4c2d2// System.out.println(funcs.myTrim(" roy handsome "));} }
集合
- 类集,可以认为是核心类库最重要的部分,实现了很多常用的数据结构
- 如果将语言的数据类型分为两种:基本数据类型、数据结构
- Java中的基本类型包括Int/Double/String/Array等
- 为了避免动态扩容等操作,实现了数据结构,类似Python中的列表,字典,迭代器等
- 其中最常用的接口:集合Collections、Map映射、Iterator迭代器
List接口
- Collection的子接口,有更多的操作方法
public class Main { public static void main(String[] args) { // 接口的实现类:ArrayListList<Integer> data = new ArrayList<>();data.add(1);data.add(2);data.get(0); // 根据index获取data.remove(0); // 根据index删除System.out.println(data.size());}// get 和 remove 方法重写了,根据索引进行// 重载实现于一个类中;重写实现于子类中 }
- ArrayList(用的最多,基于动态数组)、Vector、LinkedList是List的常用实现类
ArrayList
- 底层使用数组实现,当容量满时,使用grow方法动态扩容
- 查找快,增删慢,要注意
- 有一个动态扩容的算法,每次扩容到原来的1.5倍(移位运算);0和1时需要特殊考虑
public class Main { public static void main(String[] args) { // 如果不指定初始化大小,默认10ArrayList<Integer> data = new ArrayList<>();System.out.println(data.size()); // 0// 添加int时会自动装箱data.add(1); // 添加时会动态扩容data.get(0); // index} }
- Vector,类似ArrayList,但它通过维护capacity和capacityIncrement来优化存储管理,而不是动态算法(Vector比较简单)
- 如果没有定义Increment,每次翻倍扩容
public class Main { public static void main(String[] args) { // 如果不指定初始化大小,默认10Vector<Integer> data = new Vector<>();System.out.println(data.size()); // 0data.add(1);data.add(10);System.out.println(data.size()); // 0System.out.println(data.get(1)); // 10} }
LinkedList
- 使用双向链表结构,增删快,查找慢
- 里面封装了链表的各种操作方法,但是不常用
public class Main { public static void main(String[] args) { //LinkedList<Integer> stack = new LinkedList<>();stack.addFirst(1);stack.removeFirst(); // 先进后出stack.push(2);stack.pop(); // 相当于上面的两个方法LinkedList<Integer> queue = new LinkedList<>();queue.addFirst(1);queue.removeLast(); // 先进先出} }
IO
- 除了常见的输入输出
Scanner input = new Scanner(System.in); String n = input.nextLine(); System.out.println(n)
- File,文件对象
public class Main { public static void main(String[] args) throws IOException { // 创建文件,路径必须存在File fs = new File("C://Users//Windows10//Desktop//test//1.txt");boolean f = fs.createNewFile();System.out.println(f?"创建成功":"创建失败");// boolean d = fs.delete();// System.out.println(d?"删除成功":"删除失败");// 创建文件夹(已存在就会失败)File dirs = new File("C://Users//Windows10//Desktop//test1");boolean dir = dirs.mkdir();System.out.println(dir?"创建文件夹成功":"创建文件夹失败");File dirs1 = new File("C://Users//Windows10//Desktop//test2//test3");boolean dir1 = dirs1.mkdirs(); // 递归创建System.out.println(dir1?"创建文件夹成功":"创建文件夹失败");// 指明已存在文件夹,创建文件(第一种分开写)File dfs = new File("C://Users//Windows10//Desktop//test1", "b.txt");// boolean dd = dfs.mkdir();// System.out.println(dd?"创建文件成功":"创建文件失败");boolean df = dfs.createNewFile();System.out.println(df?"创建文件成功":"创建文件失败");// 没有同时创建文件夹和文件的File file = new File("C://Users//Windows10//Desktop//test//1.txt");File rfile = new File("C://Users//Windows10//Desktop//test1//2.txt");boolean r = file.renameTo(rfile); // 移动并重命名System.out.println(r?"移动文件成功":"移动文件失败");} }
public class Main { public static void main(String[] args) throws IOException { // 路径分隔符,不同路径分号隔开System.out.println(File.pathSeparator); // ;// 名称分隔符,路径内使用\连接System.out.println(File.separator); // \// 由操作系统决定,但是Windows这个\在很多地方是转义,所以需要两个再转义File fs = new File("C:\\Users\\Windows10\\Desktop\\test\\c.txt");boolean f = fs.createNewFile();System.out.println(f?"创建成功":"创建失败"); // 创建成功} }
字节流
- 包括
OutputStream
和InputStream
,任何流在底层传输时都是二进制 - 先从文件流说起:
public class Main { public static void main(String[] args) throws IOException { // 文件字节流,文件不存在会创建,路径要存在FileOutputStream file = new FileOutputStream("C://Users//Windows10//Desktop/test/a.txt");byte[] con1 = { 65,66,67,68,69}; // ABCDEfile.write(con1);byte[] con2 = "abcde".getBytes(); // abcdefile.write(con2, 1, 3); // 开始下标和长度file.close(); // 及时关闭,关闭后不能再写入System.out.println("写入!"); // ABCDEbcd} } // 再次运行文件时是以w模式,覆盖原来的内容
- 案例:序列化对象到文件,然后反序列化
- 有两大方式:使用
Serializable
和Externalizable
- 后者还是继承前者的接口,都可以实现序列化对象的自定义
import java.io.IOException; import java.io.Serializable; import java.util.List;public class Student implements Serializable { private transient String stuNum; // 学号// 使用static修饰,会序列化;但反序列化时不会从序列化文件读取,可以修改private static String stuName;// 直接跳过序列化,反序列化得到的是nullprivate transient List<String> teacherList;// 全参构造方法public Student(String stuNum, String stuName, List<String> teacherList) { this.stuNum = stuNum;this.stuName = stuName;this.teacherList = teacherList;}// 优先级高于transient,但static修饰仍然生效private void writeObject(java.io.ObjectOutputStream out) throws IOException { /*指定要序列化的属性方法必须是 private void 名称必须为 writeObject;在 ObjectStreamClass 有定义*/System.out.println("序列化..........");out.writeObject(stuNum);out.writeObject(stuName);}private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { System.out.println("反序列化.........");stuNum = (String)in.readObject();stuName = (String)in.readObject();}@Overridepublic String toString() { return "Student{" +"stuNum='" + stuNum + '\'' +", stuName='" + stuName + '\'' +", teacherList=" + teacherList +'}';} }
import java.io.*; import java.util.List;public class Student implements Externalizable { private transient String stuNum; // 学号// 使用static修饰,会序列化;但反序列化时不会从序列化文件读取;相当于反序列化时有个默认值private static String stuName;// 直接跳过序列化,反序列化得到的是nullprivate transient List<String> teacherList;//无参数构造方法public Student() { }// 全参构造方法public Student(String stuNum, String stuName, List<String> teacherList) { this.stuNum = stuNum;this.stuName = stuName;this.teacherList = teacherList;}@Overridepublic void writeExternal(ObjectOutput out) throws IOException { /*使用Externalizable,重写两个方法即可,还要定义无参构造方法还是Serializable使用频率高一些*/out.writeObject(stuNum);out.writeObject(stuName);out.writeObject(teacherList);}@Overridepublic void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { /*优先级还是高于transient*/stuNum = (String) in.readObject();stuName = (String) in.readObject();teacherList = (List<String>) in.readObject();} }
- 然后就可以改造快递柜的功能,不用每次重启代码后数据清空
- 有两大方式:使用
多线程
- 相关概念
- 同步:排队执行,强制约关系
- 异步:同时执行(任务之间没有强制约关系),效率高但数据不安全
- 并发:同一时刻只有一个线程执行,一段时间内多个线程执行(针对单核)
- 并行:同一时刻都在执行,需要多核CPU
- 并发并行要看核的个数,异步同步指的是任务之间的制约关系;异步才有并发并行
Thread
- 实现多线程方法之一
public class MyThread extends Thread{ @Overridepublic void run() { for (int i = 0; i < 5; i++) { System.out.println("666");}} }public class Test { public static void main(String[] args) { // 和主线程并发执行,抢占时间片MyThread mt = new MyThread();mt.start();// 主线程for (int i = 0; i < 20; i++) { System.out.println("999");}} }
网络编程
调试和优化
- debug
- Junit
- 创建lib文件夹,引入 junit-4.8.jar 包
- 在Modules的Dependencies就能看到,而且必须看到
- 工程目录下创建test文件夹,并更改为TestResourceRoot
- 接下来在想要测试的类中使用快捷键:
Ctrl+shift+t
,选择junit4,选择要测试的方法// MyThread.java public class MyThread extends Thread{ @Overridepublic void run() { for (int i = 0; i < 20; i++) { System.out.println("666");}} }// test下 快捷生成的测试方法 import org.junit.Test;import static org.junit.Assert.*;public class MyThreadTest { @Testpublic void run() { // 快捷方法只能在test下创建相应的类和方法(就是名称相关)// 下面的测试逻辑是自定义的MyThread mt = new MyThread();mt.start();// 主线程for (int i = 0; i < 20; i++) { System.out.println("999");}} }
- 创建lib文件夹,引入 junit-4.8.jar 包
IDEA快捷键
- 代码生成
Alt + Enter IDEA 根据光标所在问题,提供快速修复选择 Ctrl + Alt + T 对选中的代码弹出环绕选项弹出层(try,if等语句包裹) Alt + Insert 代码自动生成,如生成对象的 set / get 方法,构造函数,toString() 等 Ctrl + O 选择可重写的方法 Ctrl + I 选择可实现的方法 Ctrl + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
- 选择和移动
Ctrl + W 递进式选择代码块 ctrl+Shift+w 递进式取消选择代码块 Ctrl + D 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面 Ctrl+Shift+↑或↓ : 移动当前行或代码结构 Ctrl + Y 删除光标所在行 或 删除选中的行 Ctrl + X 剪切光标所在行 或 剪切选择内容
- 优化
Ctrl + Alt + O 优化导入的类,可以对当前文件和整个包目录使用 Ctrl + Alt + L 格式化代码,可以对当前文件和整个包目录使用
- 阅读、替换和查找
Ctrl + F12 弹出当前文件结构层(类的方法属性等),可以在弹出的层上直接输入,进行筛选 Ctrl + Q 光标所在的变量 / 类名 / 方法名等上面(也可以在提示补充的时候按),显示文档内容Ctrl + F 在当前文件进行文本查找 Ctrl + Shift + F 根据输入内容查找整个项目 或 指定目录内文件 ? Ctrl + R 在当前文件进行文本替换 Ctrl + Shift + R 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件Shift + End 选中光标到当前行尾位置 Shift + Home 选中光标到当前行头位置
XML/Json
- xml文件的作用
- 数据传输
- 数据存储
- 配置文件
- json文件的作用
- 数据传输(更常用)
XML语法
- 文档声明
<?xml version="1.0" encoding="UTF-8"?>
- 标记,全部都是由标记组成
<name>Roy</name> <!-- 规则: 1.名称可以含字母、数字以及其他的字符 2.名称不能以数字或者标点符号开始 3.名称不能以字符 “xml”(或者 XML、Xml)开始 4.名称不能包含空格,不能包含冒号(:) 5.名称区分大小写 -->
- 必须有且仅允许有一个根标记
<names><name>张三</name><name>李四</name> </names> <!-- 不能直接写两个name标记 --> <!-- name标记里还可以嵌套 -->
- 标记除了开始和结束,还能有属性(键值对,不限个数)
<persons><person id="10001" groupid="1"><name>李四</name><age>18</age></person><person id="10002" groupid="1"><name>李四</name><age>20</age></person> </persons>
- 注释,不能写在文档文档声明前
<!-- 这是一个可爱的注释! -->
- CDATA, 是不应该由 XML 解析器解析的文本数据,类似python的三引号
<![CDATA[ 这里想写啥就写啥,< & 也能随便写,xml不解析,不报错! ]]>
解析XML文件
- 使用工具解析,不是随便搞的
- 同样的,复制jar包到lib,然后project structure 下 new project library新建库,确保modules引入了
package com.xml;import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader;import javax.print.Doc; import javax.xml.parsers.SAXParser; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.nio.file.FileSystem; import java.util.List;public class XmlReader { public static void main(String[] args) throws IOException, DocumentException { FileInputStream fs = new FileInputStream("D:\\IDEA\\IDEAProjects\\xinzhike\\chapter4\\fanxing\\src\\com\\xml\\books.xml");SAXReader sr = new SAXReader(); // xml解析工具Document doc = sr.read(fs);Element root = doc.getRootElement();System.out.println(root.getName());// 获取标记信息 // Element book = root.element("book"); // Element name = book.element("name"); // System.out.println(name.getText());// 或者这样搞List<Element> e = root.elements();for (int i = 0; i < e.size(); i++) { Element book = e.get(i);System.out.println(book.attributeValue("id")); // getName只是标记名,这个是属性值System.out.println(book.elementText("name")); // element+getTextSystem.out.println(book.elementText("price"));}fs.close();// 解析网络文件,获取xml输入流,后面都一样String phone = "18516955565";URL url = new URL("http://apis.juhe.cn/mobile/get?phone="+phone+"&dtype=xml&key=9f3923e8f87f1ea50ed4ec8c39cc9253");URLConnection conn = url.openConnection();InputStream is = conn.getInputStream();// 后面都一样} }
XPATH解析XML
- xpath的语法可以在这里学习一下,简单总结
1. / : 从根节点开始查找2. // : 从发起查找的节点位置 查找后代节点 ***3. . : 查找当前节点4. .. : 查找父节点5. @ : 选择属性. * 属性使用方式:[@属性名='值'][@属性名>'值'][@属性名<'值'][@属性名!='值']
生成XML
- 生成xml文档的基本步骤,哎还不如自己写呢
1. 通过文档帮助器 (DocumentHelper) , 创建空的文档对象Document doc = DocumentHelper.createDocument(); 2. 通过文档对象, 向其中添加根节点Element root = doc.addElement("根节点名称"); 3. 通过根节点对象root , 丰富我们的子节点Element e = root.addElement("元素名称"); 4. 创建一个文件输出流 ,用于存储XML文件FileOutputStream fos = new FileOutputStream("要存储的位置"); 5. 将文件输出流, 转换为XML文档输出流XMLWriter xw = new XMLWriter(fos); 6. 写出文档xw.write(doc); 7. 释放资源xw.close();
- 使用xstream,根据类属性生成
Person p = new Person(1001, "张三", "不详"); XStream x = new XStream(); x.alias("haha", Person.class); String xml = x.toXML(p); System.out.println(xml); // 直接生成以haha为根节点的包含name/age标记的xml格式
- 面试:解析xml文件有几种方式?
- 四种:SAX、DOM、JDOM、DOM4J
- 本质上就两种:
- SAX逐行解析
- DOM创建树模型,最常用的是DOM4J,集成了Xpath
json
- JSON: JavaScript Object Notation JS对象简谱 , 是一种轻量级的数据交换格式
- json对象全部使用字符串存储数据,所以也叫json字符串
- 有很多优点,很多地方取代了xml
- 体积小
- 解析快
- 太长了不易读
- 看个例子便知
js:var b = new Object();b.name = "金苹果";b.info = "种苹果";XML: <book><name>金苹果</name><info>种苹果</info> </book>JSON: { "name":"金苹果","info":"种苹果" }
Java—Json
- 将Java中的对象快速的转换为 JSON格式的字符串
- 将JSON格式的字符串,转换为Java的对象
- 有两种方式:Gson(谷歌)和FastJson(阿里)
枚举
-
JDK1.5引入了新的类型——枚举,比较简单
- 之前,我们定义常量都是:
public static fianl....
,难管理
- 之前,我们定义常量都是:
-
反射和注解用的都比较浅,涉及框架,很多是别人写好的
-
看个枚举的例子
public enum Level { // 相当于上面使用final,传入levelValueLOW(30), MEDIUM(15), HIGH(7), URGENT(1);private int levelValue;// 只有private构造方法private Level(int levelValue) { this.levelValue = levelValue;}public int getLevelValue() { return levelValue;} }// 更常见的是这样定义:如果不重写构造方法,不能指定数字 class enum Level { LOW, MEDIUM, HIGH; // 不用指定数值,英文意思表示即可 }
-
自定义的枚举类默认继承了父类的方法,包括:
-
所有的枚举都继承自
java.lang.Enum
类,由于Java 不支持多继承,所以枚举对象不能再继承其他类 -
这个问题就需要定义接口解决,也叫枚举接口
interface LShow{ void show(); }public enum Level implements LShow{ LOW{ @Overridepublic void show(){ System.out.println("低级别");}}, MEDIUM{ @Overridepublic void show(){ System.out.println("中级别");}},HIGH{ @Overridepublic void show(){ System.out.println("高级别");}},URGENT{ @Overridepublic void show(){ System.out.println("紧急!");}};// private int levelValue;//// private Level(int levelValue) { // this.levelValue = levelValue;// }// public int getLevelValue() { // return levelValue;// } }
-
枚举类不能有子类,因为其枚举类默认被final修饰
-
只能有private构造方法,不用提供set方法
-
switch中使用枚举时,直接使用常量名,不用携带类名
注解
- 可以理解为给机器看的注释机制(运行代码过程中会产生点影响,不是完全没用)
- 可以使用在类、方法、变量、参数和包等,类似python的装饰器,扩展代码
- 标注(注解)可以被嵌入到字节码中,可以通过反射获取标注内容
- 一般用于:
- 编译格式检查
- 反射中解析
- 生成帮助文档
- 跟踪代码依赖
内置注解
- 就是Java设置好的注解,例举几个
@Override // 定义在java.lang.Override @SafeVarargs // 忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告 @Deprecated // 废弃;这个方法被废弃,但仍支持以前调用此方法的代码 @SuppressWarnings // 抑制编译时的警告信息
- 元注解:注解的注解,自定义注解时用到
@Retention // 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。 @Documented // 标记这些注解是否包含在用户文档中 javadoc。 @Target // 标记这个注解应该是哪种 Java 成员。 @Inherited // 标记这个注解是自动继承的
- 注解继承有以下特点(接口不传、类传)
- 子类会继承父类使用的注解中被
@Inherited
修饰的注解 - 接口继承关系中,子接口不会继承父接口中的任何注解,不管父接口中使用的注解有没有
被@Inherited修饰 - 类实现接口时不会继承任何接口中定义的注解
- 子类会继承父类使用的注解中被
自定义注解
- 类似python自定义装饰器,注解的基类是
java.lang.Annotation
- 任何注解的定义都是以下结构:必须定义ElementType和RetentionPolicy
import java.lang.annotation.*;@MyAnnotation public class Demo { public static void main(String[] args) { }@MyAnnotationpublic void add() { } }@Documented // 可以作用在类和方法上 @Target({ ElementType.TYPE, ElementType.METHOD}) // 推荐使用runtime @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation { }
- 注解中的每一个方法,实际是声明的注解配置参数,后面学反射会理解
反射
- JAVA反射机制是在运行状态中,获取任意一个类的结构;创建对象 , 得到方法、属性,执行方法
- 类加载器
- 是Java运行时环境(Java Runtime Environment)的一部分
- 负责动态加载Java类到Java虚拟机的内存空间中
- 类通常是按需加载,掌握Java的委派概念很重要
- 我们可以设置目录为
ResourceRoot
,用类加载器获取文件流public class Demo2 { public static void main(String[] args) throws IOException { InputStream in = Demo2.class.getClassLoader().getResourceAsStream("test.txt");// 假设这是个配置文件BufferedReader br = new BufferedReader(new InputStreamReader(in));String text = br.readLine();System.out.println(text);br.close();} }
- 每一个字节码文件,被加载到内存后,都变成一个对应的Class类型的对象
- 如图所示,加载到内存后,类定义的class文件变成了Class类的对象,即Class对象
- 这个Class对象包含类定义的信息,是反射要用的
Class
- 回到反射,反射可以获取类的结构,即获取
Class对象
,有哪些方式呢? - 三种方式
public class Demo3 { public static void main(String[] args) throws ClassNotFoundException { // 方式一:知道类的名称, 且类已经存在Class<Demo2> c1 = com.annotation.Demo2.class;System.out.println(c1);// 方式二:拥有类的对象Demo2 d2 = new Demo2();Class<Demo2> c2 = (Class<Demo2>) d2.getClass();System.out.println(c1==c2);// 方式三:知道类的名称Class<Demo2> c3 = (Class<Demo2>) Class.forName("com.annotation.Demo2");System.out.println(c1==c3); // true} }
- 在调用时, 如果类定义在内存中不存在, 则会加载到内存 ! 如果类已经在内存中存在, 不会重复加载, 而是重复利用(类的定义只需要加载一次)
- 获取类的构造方法,通过构造方法创建对象
public class Demo4 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { Class<Demo2> c3 = (Class<Demo2>) Class.forName("com.annotation.Demo2");Constructor<Demo2> con2 = c3.getConstructor();// 创建类对象Demo2 d2 = con2.newInstance();System.out.println(d2);// 获取含参构造器Constructor<Demo2> con3 = c3.getConstructor(String.class, int.class);Demo2 d3 = con3.newInstance("Roy", 18);// 获取所有权限的构造方法Constructor<Demo2> con4 = c3.getDeclaredConstructor();con4.setAccessible(true); // 忽略权限检查,让private修饰的构造方法也可使用} }
- 类似的,可以获取method、field、annotation