第二章
2.1 class 文件的生成
java 文件为源代码文件
class 为程序.
class 文件实时修改.
eclipse 自动生成.
project 下面 clean.
2.2 jar 文件
如何将有用的类传给别人使用.
1. 把 *.java 文件发给对方.
2. 把 *.class 打包发给对方.
导出为 jar 文件.
右键 export Java JAR file
2.3 使用 jar 文件
new java project test2
右键 new folder libs
复制 jar 粘贴到 libs
右键 jar build path add to
然后调用里面的方法, 再声明就行了
库 Library
2.4 系统 jar 文件
- String java.lang.String
- ArrayList java.util.ArrayList
第三方 jar
系统库 System Library
三方库
第三章
3.1 抽象类 Abstract Class
创建抽象类
- public abstract class XXX // 抽象类
- {
- public abstract void yyy(); // 抽象方法 不能有大括号(方法体)
- }
抽象方法可以没有定义, 称为抽象方法
抽象类不可实例化: FunnyThing f=new FunnyThing(); // 错误 不可以实例化.
抽象类仅仅用于描述一类事情: 应该有什么, 应该能做什么. 它不是具体类, 不能创建对象.
所有抽象类方法的定义在子类里实现.
3.2 抽象类的用法
暂时不需要写抽象类
- java.io.InputStream
- java.io.OutputStream
第四章
4.1 接口
接口 interface(或翻做: 界面)
定义一个接口
- public interface AudioOutput
- {
- public void play(AudioData s); // 不能写方法体{
- } 必须是 public 默认接口都是抽象方法.
- }
使用接口
和抽象类一样 必须派生一个子类;
- public class XiaoMi implements AudioOutput
- {
- @Override
- public void play(AudioData s)
- {
- }
- }
接口和抽象类相似, 区别为:
1. 用 implements 而不是 extends(不表示继承关系)
2. 一个类可以 implements 多个接口
public class X extends Y implements A,B,C
3 接口不应该添加属性.(可以添加, 但没有意义)
接口和继承是两个不同的设计概念
4.2 接口的使用
当一个系统与另外一个系统对接时.
接口的使用
第五章 内部类
5.1 内部类
当一个类写在另一个类内部时, 称为内部类
内部类通常使用 private, 外部不可见.
如果你想在外部使用内部类, 定义为 public.
5.2 内部类的使用
在内部类的里面可以访问外部类的所有方法和属性.
访问外部类时 + class 名. this.
- public class Example
- {
- private String name;
- private void show()
- {
- System.out.println("名字:"+name);
- }
- public void test()
- {
- ABC abc=new ABC();
- abc.work();
- }
- public class ABC
- {
- public void work()
- {
- Example.this.name="shao fa";
- Example.this.show();
- }
- }
- }
5.3 静态内部类
在外面创建内部类对象
- public class Example
- {
- public class ABC
- {
- }
- }
- Example e=new Example(); // 实例化 a
- Example.ABC a=e.new ABC(); // 实例化 a
静态内部类
- public class X
- {
- public String name;
- public static class Y
- {
- }
- }
- Example.ABC a=new Example.ABC(); // 实例化 a
使用静态内部类 就无法使用 Example.this.name="shao fa";
5.4 匿名内部类 // 非常常用
内部类的简化写法
- public interface XXX
- XXX a=new XXX(){
- };
直接在大括号内重写 XXX 接口的方法
不用创建子类.
匿名内部类
还有一个写法
- c.xxx(new XXX(){
- })
第六章 静态对象
静态 static
在 Java 中表示 "全局的"
静态对象, 即全局对象, 一直存在的.
定义静态对象
- public class MMM
- {
- public static XXX a=new XXX();
- }
使用静态对象.
MMM.a.yyy(); //yyy 为 XXX 下面的方法
要点
1. 在第一次使用时, 静态对象被创建
例如: Example 类被使用时, Example.a 被创建
如果 Example 从未被使用, Exampee.a 永不创建
2. 静态对象不会被系统回收
3. 静态对象只有一个实例
无论创建多少个 Example 对象, Example.a 只创建一次.
6.2 单例模式
全局 && 唯一实例 全局使用 static 实现, 单例使用 private 实现.
在程序运行期间一直存在, 唯一: 只有一个实例
- public class Earth
- {
- public static Earth i=new Earth(); //static 成为了全局实例.
- private Earth() // 使用 private 不能在外面添加新的实例, 成为单例. 限制实例的创建. 让构造方法私有化
- {
- }
- public void showCountries()
- {
- System.out.print("唯一地球");
- }
- }
第七章 出错处理
7.1 出错处理
考虑 2 种情况, 如果输入的字符串有问题.
1. 输入非法字符.
2. 用户输入过长的字符, 超出 int 的极限.
- public class Converter
- {
- public int status = 0;
- // 把一个字符串转成整数
- // 例如: "123" -> 123
- public int str2int (String str)
- {
- status = 0;
- if(str.length()>11)
- {
- status = -2; // 第 2 种情况
- return 0;
- }
- int result = 0;
- for(int i=0; i<str.length(); i++)
- {
- char ch = str.charAt(i);
- if( ! isValid(ch) )
- {
- status = -1; // 第 1 种情况
- return 0;
- }
- result = result * 10 + (ch - '0');
- }
- return result;
- }
- private boolean isValid(char ch)
- {
- if(ch>= '0' && ch <= '9')return true;
- if(ch == '-') return false;
- return false;
- }
- }
- public class Test
- {
- public static void main(String[] args)
- {
- Converter conv = new Converter();
- int result = conv.str2int("2201234");
- if(conv.status == 0)
- {
- System.out.println("转换结果:" + result);
- }
- else
- {
- if(conv.status == -1)
- System.out.println("非法字符");
- else if(conv.status == -2)
- System.out.println("超出范围");
- }
- }
- }
可预期的错误情况.
7.2 异常机制
1. 在方法里, 抛出异常
- int str2int(String str)throws Exception
- {
- if(错误 1 发生)
- throw new Exception("错误 1");
- if(错误 2 发生)
- throw new Exception("错误 2");
- }
- public int str2int (String str) throws Exception // 添加 throws Exception
- {
- status = 0;
- if(str.length()>11)
- throw new Exception("超出范围"); // 抛出异常 也可以写作 Exception ex=new Exception("超出范围")
- int result = 0; //throw ex;
- for(int i=0; i<str.length(); i++)
- {
- char ch = str.charAt(i);
- if( ! isValid(ch) )
- throw new Exception("非法字符"); // 抛出异常
- result = result * 10 + (ch - '0');
- }
- return result;
- }
- public static void main(String[] args) throws Exception // 在 main 方法的入口添加 throws Exception
2. 在调用时, 捕获异常
调用时, 用 try...catch... 来捕获异常
try: 监视若干行代码, 如果里面抛出异常, 则进入 catch{}
catch: 出错处理, 参数为刚刚抛出的异常对象, 所有出错信息都在异常对象里.
- public static void main(String[] args) // 或者使用 try catch
- {
- Converter conv = new Converter();
- int result;
- try
- {
- result = conv.str2int("12134");
- System.out.println("正常"+result); // 正常情况下走 try
- }
- catch (Exception e)
- {
- System.out.println(e.getMessage()); // 异常时走 catch
- }
Java 里普遍使用异常机制来进行出错处理
Exception 对象本身就可以携带所有出错信息
7.3 自定义异常
自定义异常 派生出 Exception 的子类
- try {
- int result = conv.str2int("2023321238");
- System.out.println("正常:" + result);
- }
- catch( InvalidCharException e1)
- {
- System.out.println(e1.getMessage());
- }
- catch ( TooLargeException e2)
- {
- System.out.println(e2.getMessage());
- }
- catch( Exception e)
- {
- System.out.println(e.getMessage());
- }
- public class InvalidCharException extends Exception
- {
- public int pos; // 非法字符出现的位置
- public char ch; // 非法字符
- public InvalidCharException(int pos, char ch)
- {
- this.pos = pos;
- this.ch = ch;
- }
- @Override
- public String getMessage()
- {
- return "非法字符'" + ch + "', 位置:" + pos;
- }
- }
- public int str2int (String str) throws Exception
- {
- if(str.length()>11)
- {
- Exception ex = new TooLargeException(str.length()); //throw new TooLargeException(str.length());
- throw ex;
- }
- int result = 0;
- for(int i=0; i<str.length(); i++)
- {
- char ch = str.charAt(i);
- if( ! isValid(ch) )
- throw new InvalidCharException(i, ch);
- result = result * 10 + (ch - '0');
- }
- return result;
- }
7.4 异常运行规则
1. 抛出异常时, 退出当前的方法
运行规则: 捕获异常时
1.try{...}中出现异常时, 退出 try, 进入 catch
2. 如果没有匹配到 catch.
中断当前方法
继续向上抛出
上层调用者有义务抓住这个异常
main()-->a()-->b()-->c()
3. 如果一个异常最终没被抓住, 程序崩溃.
7.5 退出清理
当异常出现时, doCleanJobs 没有机会执行
退出清理 finally
使用 finally 语句可以保证 tyu{}退出时执行某些退出清理代码
- tyu{
- }
- catch..... // 若干个
- finally{
- }
规则: 当退出 try 时, 总是执行 finally 中的语句.
当异常发生时, 跳出当前执行 finally 后结算.
e.printStackTrace(); // 相对于 e.getMessage 有更多的信息
包含:- 出错的代码行位置
异常的类型
每层方法的调用位置(函数栈)
7.6 基本异常
语法类(基本异常)
1. 空指针异常 NullPointerException
2. 数组越界异常 ArrayIndexOutOfBoundsException
3. 除零异常 ArithmeticException
即使没有 throws 声明, 也能捕获异常.
Throwable 父类 Error 子类 Exception 子类.
第八章 泛型
8.1 泛型
通用的类型,
通用链表 GenericList 封装一个通用的链表设计
设计思路
1. 不限制节点类型.
- private static class GenericNode
- {
- Object value; // 使用 Object 做为节点类型.
- GenericNode next;
- }
2. 迭代器
8.2 泛型的定义
泛型一般用于描述一种通用的算法, 数据结构, 对象类型其实不关心
在 Java 中泛型的规范写法.
- public class Sample<T>
- {
- }
在类的定义里, T 代表一个通用的类型, 把 T 称为类型参数
T 只是一个代号.
泛型的使用
- public class Sample<T>
- {
- T value;
- public void setValue(T value)
- {
- this.value=value;
- }
- public T getValue()
- {
- return this.value;
- }
- }
- Simple<Student> sa=new Simple<Student>();
- sa.setValue(new Student(1,"s","sd"));
- Student s=sa.getValue();
- System.out.println(s);
8.3 常用的泛型
- List,ArrayList
- Map,HashMap
通常不需要我们自己写泛型, JDK 自带的两个泛型足够使用
ArrayList: 数组链表.
- ArrayList<Student>
- ArrayList<String>
- ArrayList<Integer>
必须使用包装类.
使用
添加, 插入
遍历: 数组形式遍历, 链表遍历
删除: 数组形式删除, 链表删除
1. 排序
- ArrayList<Student> list = new ArrayList<Student>();
- list.add( new Student(1, "shao", "13810012345"));
- list.add( new Student(2, "wang", "15290908889"));
- list.add( new Student(3, "li", "13499230340"));
- list.add( 0,new Student(4, "xxx", "1293023923923")); // 从 0 的位置去插入
2. 遍历
- import java.util.Iterator;
- Iterator<Student> iter=list.iterator();
- while(iter.hasNext())
- {
- Student s=iter.next();
- System.out.println(s);
- }
链表的方式遍历
数组的方式遍历
- for(int i=0;i<list.size();i++)
- {
- Student s=list.get(i);
- System.out.println(s);
- }
3. 删除元素
数组方式删除
list.remove(index)
链表方式删除
Iterator<Student>iter=list.iterator();
4. 排序
- package my;
- import java.util.ArrayList;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Iterator;
- public class Test
- {
- public static void main(String[] args)
- {
- ArrayList<Student> list=new ArrayList<Student>();
- list.add(new Student(1,"陈","139")); // 添加
- list.add(new Student(2,"人","138"));
- list.add(new Student(-1,"好","137"));
- list.add(0,new Student(4,"我","136")); // 插入
- // Iterator<Student> iter=list.iterator();// 删除
- // while(iter.hasNext())
- // {
- // Student s=iter.next();
- // if(s.id==2)
- // {
- // iter.remove();
- //
- }
- //
- }
- // 排序
- Comparator<Student> comp=new Comparator<Student>() // 定义一个比较器
- {
- @Override
- public int compare(Student a,Student b)
- {
- if(a.id<b.id)
- return -1;
- if(a.id>b.id)
- return 1;
- return 0;
- }
- }; // 有分号
- // 排序
- Collections.sort(list, comp);
- Iterator<Student> iter1=list.iterator(); // 遍历
- while(iter1.hasNext())
- {
- Student s=iter1.next();
- System.out.println(s);
- }
- }
- }
- 8.4
哈希映射表 HashMap
HashMap 存储 key<=>value
HashMap 的创建 需要制定 key 和 value 的类型
1. 创建
- HashMap<Integer,Student> map=.... // 指定 key 的类型是 Interger,value 的类型是 Student.
- HashMap<Integer,Student>map=new HashMap();
2 添加对象
- map.put(1, new Student(1,"2","3")); //map.put(key,new value()); 添加
- map.put(2, new Student(4,"5","6")); //key 为 1,2,3,3, value 为 Student 对象
- map.put(3, new Student(7,"8","9"));
- map.put(3, new Student(11,"12","13")); // 同 key 值以后面一个为准
3. 查找
- Student s=map.get(3); // 查找的是 key 值
- if(s!=null)
- {
- System.out.println(s);
- }
4. 删除
删除一个对象
map.remove(key); // 输入要删除的 key 值
删除所有
map.clear();
5 遍历所有 key 和 value
通常情况下 map 不遍历
- // 遍历所有的 key
- import java.util.Set; // 声明
- Set<Integer>keys=map.keySet(); // 遍历所有的 key
- for(Integer k:keys)
- {
- System.out.println("key:"+k);
- }
- // 遍历所有的 value
- for(Student v:map.values())
- {
- System.out.println("values:"+v);
- }
重点能否以姓名做为 key?
key 的要求
1. 可以比较 //String 可以比较
2. 唯一不重复 // 如果姓名不重复, 则可以.
Map 的查找比 list 快?
HashMap 查找更快
第九章 JDK 和 JRE
9.1jdk jre
jdk Java 开发工具包
jre Java 运行环境
JDK 是开发时的环境, JRE 时程序运行环境.
JRE 时 JDK 的子集;
9.2Java 命令行
类的加载
类加载器 ClassLoader
ClassLoader 里寻找并加载需要的类
加载主类: my.Hello
根据 import 声明加载需要的类.
9.3 可执行 JAR 包
- cmd
- cd /d d:\eclipse-workspace // 移动到 d 盘目录下
- java -jar example804.jar // 执行 jar 包
jar 包中有哪些东西
所有的 class, 所依赖的 jar 包里的 class
清单文件 META-INF/MANIFEST.MF
可发现, 在清单文件里已经指定了 Main Class
打包 jar 包 右键 JAVA -Runnable JAR file
Java 应用程序发布
-JRE 环境
-class,jar
- 执行脚本(双击运行).bat .cmd .sh
9.4 命令行参数
命令行里的参数将被传递给 main 方法
- public static void main(String[] args)
- {
- }
main 方法的参数: 来自命令行参数;
- java -cp bin my.Hello 2 3
- 9.5JVM
JVM Java 虚拟机
一个虚拟主机可以运行 class 文件
当 Java 程序运行时
打开 jconsole, 连接到 java 进程(JVM), 观测 CPU / 内存 / 线程等信息
jdk1.8 下面
做网站时会用到 jconsole 工具.
第十章
Java 官方文档
https://docs.oracle.com/en/
常用 API
Java API 即 java 再带的类库
常用的
lang util io.NET math>sql security nio>.......
java 官方文档相当于字典.
左上 package
左下 class
10.2 集成文档和源码
多看文档, 少看源码
10.3
文档的使用方法
整数 1234, 转换成 2 进制字符串
第 11 章
11.1 时间的表示
long 时间值
java.util.Date 时间对象
java.util.Calendar 时间操作工具
java.text.SimpleDateFormat 格式化工具类.
时间值 long 单位: 毫秒 从 1970 开始的时间值
运行一段代码花费的时间
- public class Test
- {
- public void someBusyWork()
- {
- long start =System.currentTimeMillis(); // 初始时间
- for(int i=0;i<2002000;i++)
- {
- double a=Math.sin(i);
- }
- long duration=System.currentTimeMillis()-start; // 求时间
- System.out.println(duration);
- }
- public static void main(String[] args)
- {
- long now =System.currentTimeMillis();
- Test t=new Test();
- t.someBusyWork();
- }
- }
java.util.Date 基本都是 Deprecated 的类. 不赞成使用.
java.util.Calendar 计算年月日时分秒
封装的时间工具.
- Calendar cal=Calendar.getInstance();
- int year=cal.get(Calendar.YEAR);
- int month=cal.get(Calendar.MONTH); // 月份的时间是从 0 开始计算, 其他都是正常.
- int day=cal.get(Calendar.DAY_OF_MONTH);
- int house=cal.get(Calendar.HOUR);
- int minute=cal.get(Calendar.MINUTE);
- int second=cal.get(Calendar.SECOND);
Calendar 与 long 的转换.
- //Calendar ->long
- long ms=cal.getTimeInMillis();
- //long->Calendar
- cal.setTimeInMillis(ms);
Calendar 与 Date 转换.
- //Calendar->Date
- Dated=cal.getTime();
- //Date->Calendar
- cal.setTime(d);
11.2 时间的处理
- 时间与日期的差值
计算 2018 年 2 月 10 日 - 2018 年 3 月 10 日之间多少天.
- public static void main(String[] args)
- {
- Calendar c1=Calendar.getInstance(); // 创建一个 c1 的实例 getInstance 实例化.
- Calendar c2=Calendar.getInstance();
- c1.set(2018, 1, 10, 0, 0, 0); // 日期转为 long 的毫米 月份的起始数为 0,0 表示 1 月, 1 表示 2 月.
- c2.set(2018, 2, 10, 0, 0, 0);
- long ms=c2.getTimeInMillis()-c1.getTimeInMillis(); // 相差的毫米
- long days=ms/(24*3600*1000); // 因为结果是毫米所以 * 1000
- System.out.println("相差天数为:"+days);
- }
日期的推算.
- public static void main(String[] args)
- {
- Calendar c1=Calendar.getInstance();
- c1.set(2018, 2, 2, 0, 0, 0); //3 月 2 日
- c1.add(Calendar.DAY_OF_MONTH,+32); // 第一个参数表示要修改哪个字段, 第二个参数表示差值
- System.out.printf("结果为:%d-%d-%d",
- c1.get(Calendar.YEAR),
- c1.get(Calendar.MONTH)+1,
- c1.get(Calendar.DAY_OF_MONTH)); // 月份因为初始值为 0 所有要 + 1 显示.
- }
其中, add()的第一个参数表示要修改哪个字段, 第二个参数表示差值
时间和日期的格式化
- Date->String
- public static void main(String[] args)
- {
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 大写的 HH 24 小时制, 小写的 hh12 小时制.
- Date now =new Date();
- String str=sdf.format(now);
- System.out.println("日期:"+str);
- }
- String->Date
- public static void main(String[] args)
- {
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- try
- {
- String s1="2018-3-2 11:29:00";
- Date t1=sdf.parse(s1);
- }
- catch(ParseException e)
- {
- e.printStackTrace();
- }
第 12 章文件的操作
File 文件, 用于进行文件 / 目录相关操作
java.io.File
在 Java 文档里找到此类的说明.
1.File 对象用于指向一个文件或者目录
- "e:/examples/abc.txt"
- "e\\examples\\abc.txt" (要转义)
并未创建实际对象, 只是创建 File 对象.
- public static void main(String[] args)
- {
- File f=new File("e:/examples/abc.txt");
- }
2. 判断对象是否存在.
- public static void main(String[] args)
- {
- File f=new File("e:/examples/abc.txt");
- if(f.exists()) // 判断语句 f.exists
- {
- System.out.println("文件存在"+f);
- }
- else
- {
- System.out.println("文件不存在");
- }
- }
3. 获取文件的属性
是文件还是目录 isFile() isDirectory()
文件长度 length()
最后修改时间 lastModified()
- public static void main(String[] args)
- {
- File a=new File("D:\\eclipse\\abc.txt"); // 文件是否存在判断
- if(a.isFile())
- {
- System.out.println("是文件");
- }
- long size=a.length(); // 长度判断
- System.out.println(size);
- long s=a.lastModified(); // 最后的修改时间获取和输出
- SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- String timestr =sdf.format(s);
- System.out.println(timestr);
- }
可读, 可写, 可执行?
4 文件的操作
重命名 File.renameTo
- public static void main(String[] args)
- {
- File a=new File("D:\\eclipse\\abc.txt");
- if(a.isFile())
- {
- System.out.println("是文件");
- }
- a.renameTo(new File("D:\\eclipse\\abcd.txt"));
- }
5. 绝对路径与相对路径
相对路径
- "../other/video.mp4"
- "subdir/xy.doc"
绝对路径 即是完整路径
- .getAbsolutePath();
- "D:\\eclipse\\abc.txt"
12.2 目录的操作
File 类
if(f.exists)判断目录是否存在
if(f.isDirectory)
判断是否是目录
创建层级目录
- public static void main(String[] args)
- {
- File d=new File("E:\\abc\\a\\b\\c");
- d.mkdirs();
- }
会创建没有的目录
遍历子项
使用 listFile()可以遍历目录下的子目录 / 文件
仅扫描一级目录
- public static void main(String[] args)
- {
- File d=new File("D:\\eclipse");
- File[]subFiles=d.listFiles(); // 设立一个数组 subFiles 存储
- for(File f:subFiles)
- {
- if(f.isDirectory())
- {
- System.out.println("扫描到目录"+f);
- }
- else
- {
- System.out.println("扫描到文件"+f);
- }
- }
可以在 listFilter 的时候设置一个过滤器
- File d=new File("D:\\eclipse");
- FileFilter filter=new FileFilter() // 过滤器 fliter 创建一个匿名类
- {
- @Override
- public boolean accept(File pathname)
- {
- String filePath=pathname.getAbsolutePath(); // 创建一个字符创 filePath 获得他的绝对路径
- if(filePath.endsWith(".rar")) // 判断结尾是否是. rar .endsWith();
- return true;
- return false;
- }
- };
- File[]subFiles=d.listFiles(filter); // 设立一个数组 subFiles 存储, 把 filter 过滤器参数传给他
- for(File f:subFiles)
- {
- if(f.isDirectory())
- {
- System.out.println("扫描到目录"+f);
- }
- else
- {
- System.out.println("扫描到文件"+f);
- }
- }
3 思考如何做递归查找, 继续查找子目录
如何创建一个子目录
- File homeDir =new File("e:/examples"); // 先写出父目录
- File f=new File(homeDir,"some.txt"); // 再创建子目录
newFile 时可以直接指定它的父目录.
File 时个轻量级对象, 不必转为 String
12.3 相对路径
绝对路径从根目录开始指定.
相对路径, 指定一个相对于当前目录的路径
File f=new File("src/my/Test.java");
当前目录
E:/JavaProjects/example1203
全路径
E:/JavaProjects/example1203/src/my/Test.java
相对路径示例
- ./data/config.txt
- ../example1101/src
- ../../WebProjects/
- ./src/../data/config.txt
. 表示本目录
.. 表示父目录
工作目录
我们所说的当前路径, 指的是程序的工作目录
默认的, 工作目录时程序运行时的起始目录
注意: 工作目录不是 class 文件所在的目录
注意: 在 Eclipse|Run Configuration 里可以指定 就是运行这个程序时的起始目录.
- String workDir=System.getProperty("user.dir"); // 查询当前的工作目录在哪
- // 修改工作目录
- System.setProperty("user.dir","d:");
12.4 复制与移动
apache commons io
使用第三方的库来完成文件和目录操作
新建一个项目, 添加 commons-io-2.4.jar
(1)创建目录 libs
(2)拷贝 commons-io-2.4.jar 到 libs
(3)右键 jar 文件, 选 Add to Build Path
常用文件操作
FileUtils 类支持的文件和目录
FileUtils.deleteQuietly()删除文件
FileUtils.copyFile()拷贝文件
FileUtils.copyFileToDirectory()复制文件到目录
FileUtils.moveFile()移动文件
FileUtils.moveFileDirectory()移动目录
- // 拷贝文件示例
- public static void main(String[] args)
- {
- File src=new File("E:\\abc.txt");
- File dst=new File("E:\\abc\\a.txt");
- try
- {
- FileUtils.copyFile(src, dst);
- }catch(IOException e)
- {
- e.printStackTrace();
- }
- System.out.println("完成");
- }
第 13 章
13.1 文件的存储
文件: 视频, 音频, 图片, 文档, 程序....
数据存储在文件中, 关闭电脑后, 数据不丢失.
无论是各种文件里面存储的都是 byte[]字节数据
字符串的存储
- String<=>byte[]
- String=>byte[]
- String text="abcdefg";
- byte[] data=text.getBytes("UTF-8");
- byte[]=>String
- String text2=new String(data,"UTF-8");
写入文件 output 写入
- public static void main(String[] args)
- {
- File dir=new File("e:/examples");
- dir.mkdirs(); // 创建目录
- File f=new File(dir,"123.txt");
- String text="人人人 1234aaaa";
- try
- {
- FileOutputStream outputStream=new FileOutputStream(f);
- byte[] data=text.getBytes("UTF-8"); //data 把字符串编码成字节数据
- outputStream.write(data); // 将字节数据写入文件
- outputStream.close(); // 关闭文件
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- }
win10 下面不能再分区根目录下面创建文件.
读取文件 input 读取
- public static void main(String[] args)
- {
- File dir=new File("e:/examples");
- File f=new File(dir,"123.txt");
- try {
- FileInputStream inputStream=new FileInputStream(f);
- int size=(int)f.length();
- byte[]data=new byte[size]; // 设立 data[]的大小根据 f.length
- inputStream.read(data); // 读取数据
- inputStream.close();
- // 将读取来的数据转换成 String
- String text=new String(data,"UTF-8");
- System.out.println(text);
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- }
13.2 数据的格式化存储
整数, 小数, 布尔, 字符串, 日期....
统一的解决方法: 把各项数据转换为 String
写入
- public static void main(String[] args)
- {
- Student stu=new Student(2018,"仁豪","1399009900");
- String text="";
- text+=("id:"+stu.id+",");
- text+=("姓名:"+stu.name+",");
- text+=("电话:"+stu.phone+",");
- // 存储到文件 //
- File f=new File("e:/examples/student.txt");
- f.getParentFile().mkdirs();
- try
- {
- FileOutputStream outputStream=new FileOutputStream(f);
- byte[]data=text.getBytes("UTF-8");
- outputStream.write(data);
- outputStream.close();
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("exit");
- }
读取 // 没完全理解
- public class ReadFile
- {
- public static String readTextFile(File f) // 读取文档
- {
- try {
- FileInputStream inputStream = new FileInputStream(f);
- int size = (int) f.length();
- byte[] data = new byte[size];
- inputStream.read(data);
- inputStream.close();
- // 将读取来的数据转成 String
- String text = new String(data, "UTF-8");
- return text;
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- return "";
- }
- public static HashMap parseText (String text) //HashMap
- {
- HashMap<String,String> values = new HashMap();
- String[] abc = text.split(",");
- for(String k : abc)
- {
- k = k.trim();
- if(k.length() == 0) continue;
- String[] nv = k.split(":");
- String name = nv[0];
- String value = nv[1];
- values.put(name, value);
- }
- return values;
- }
- public static void main(String[] args)
- {
- File dir = new File("e:/examples");
- File f = new File(dir, "student.txt");
- // 从文件读出字符串
- String text = readTextFile(f);
- // 将字符串解析为 key-value
- HashMap<String,String> values = parseText(text);
- // 提取出 Student 信息
- Student stu = new Student();
- stu.id = Integer.valueOf( values.get("id"));
- stu.name = values.get("name");
- stu.phone = values.get("phone");
- System.out.println("exit");
- }
- }
- 13.3
- xml Extensible Markup Language
可扩展标记语言 (类似 HTML), 一种用文本化表示的数据格式
特点: 简单, 使用广泛.
一个 Student 对象的存储
- <?xml version="1.0" encoding="UTF-8"?> // 示例 1
- <student>
- <id> 20180001</id>
- <name> 邵发 </name>
- <sex> true </sex>
- <cellphone> 13810012345</cellphone>
- </student>
version 表示版本号
- 第一行是固定不变的, 叫 xml 的声明
- 后面内容主体是一个树状层次的元素节点树.
- 每个元素成对出现都是闭合的, 以 < XXX > 开始, 以</XXX > 结束.
- <?xml version="1.0" encoding="UTF-8"?> // 示例 2
- <student>
- <id> 20180001</id>
- <name> 邵发 </name>
- <sex> true </sex>
- <cellphone> 13810012345</cellphone>
- <score>
- <chinese> 99 </chinese>
- <math> 98 </math>
- <english> 97 </english>
- </score>
- </student>
- <?xml version="1.0" encoding="UTF-8"?> // 示例 3
- <data>
- <student> // 同级元素可重名, 可以保存数组类型.
- <id> 20180001</id>
- <name> shao </name>
- <sex> true </sex>
- <cellphone> 13810012345</cellphone>
- </student>
- <student>
- <id> 20180002</id>
- <name> wang </name>
- <sex> true </sex>
- <cellphone> 13822244452</cellphone>
- </student>
- </data>
13.3.2 创建 xml 文件
1. 用 notepad++ 创建
直接创建 xml 后缀的文件打开, 把编码改为 utf8 格式编码.
2 在 eclipse 中创建 xml
右键 project new file
创建. xml
右键 properties other UTF-8
xml 教程 3:dom4j
在 dom4j 中, 定义了以下几个术语:
Document : 指整个 xml 文档
Element : 指元素
Element Text : 指元素的值
Element Attribute : 元素的属性 ( 后面介绍)
如何创建和添加元素
- public class MyTest
- {
- public static void haha() throws Exception
- {
- // 创建一个空的 Document
- Document x_doc = DocumentHelper.createDocument();
- // 添加根元素: <root>
- Element x_root = x_doc.addElement( "root" );
- // 添加两个子元素: <server>, <port>
- Element e1 = x_root.addElement( "server" ) .addText( "www.afanihao.cn" );
- Element e2 = x_root.addElement( "port" ) .addText( String.valueOf(80));
- Element e3 = x_root.addElement("xxxx");
- e3.addText("你好");
- // 输出到文件
- File xmlFile = new File("output.xml");
- OutputStream outputStream = new FileOutputStream(xmlFile);
- try {
- OutputFormat format = OutputFormat.createPrettyPrint();
- format.setEncoding("UTF-8"); // 字符编码为 UTF-8
- XMLWriter writer = new XMLWriter(outputStream , format);
- writer.write( x_doc ); // 把 xml 文档输出到文件里
- writer.close();
- }finally
- {
- // 确保文件句柄被关闭
- try {
- outputStream.close();
- }catch(Exception e) {
- }
- }
- }
- public static void main(String[] args)
- {
- try
- {
- haha();
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("hahha exit");
- }
- }
13.3.4 生成 xml
- public static void writeXML( Student s, File xmlFile) throws Exception
- {
- // 创建一个空的 Document
- Document x_doc = DocumentHelper.createDocument();
- // 添加根元素: <root>
- Element x_root = x_doc.addElement( "root" );
- // 添加两个子元素: <server>, <port>
- x_root.addElement( "id" ) .addText(String.valueOf(s.id));
- x_root.addElement( "name" ) .addText( s.name);
- x_root.addElement( "sex" ) .addText( s.sex ? "男" : "女");
- // 男 male , 女 female 三目运算符 b?x:y
- // 先计算条件 b, 然后进行判断. 如果 b 的值为 true, 计算 x 的值, 运算结果为 x 的值; 否则, 计算 y 的值, 运算结果为 y 的值.
- x_root.addElement( "cellphone" ) .addText(s.cellphone);
- // 输出到文件
- OutputStream outputStream = new FileOutputStream(xmlFile);
- try {
- OutputFormat format = OutputFormat.createPrettyPrint();
- format.setEncoding("UTF-8"); // 字符编码为 UTF-8
- XMLWriter writer = new XMLWriter(outputStream , format);
- writer.write( x_doc ); // 把 xml 文档输出到文件里
- writer.close();
- }finally
- {
- // 确保文件句柄被关闭
- try
- {
- outputStream.close();
- }
- catch(Exception e)
- {
- }
- }
- }
- public static void main(String[] args)
- {
- try
- {
- Student s = new Student(20180001, "邵", true, "13810012345");
- writeXML ( s , new File("output1.xml"));
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("exit");
- }
添加数组元素
- public static void writeXML( List<Student> sss, File xmlFile) throws Exception
- {
- // 创建一个空的 Document
- Document x_doc = DocumentHelper.createDocument();
- // 添加根元素: <root>
- Element x_root = x_doc.addElement( "root" );
- Element x_student_list = x_root.addElement( "student_list" );
- for( Student s:sss )
- {
- Element x_student = x_student_list.addElement( "student" );
- x_student.addElement( "id" ) .addText( String.valueOf(s.id));
- x_student.addElement( "name" ) .addText( s.name);
- x_student.addElement( "sex" ) .addText( s.sex ? "male" : "female"); // 男 male , 女 female
- x_student.addElement( "cellphone" ) .addText(s.cellphone);
- }
- // 输出到文件
- OutputStream outputStream = new FileOutputStream(xmlFile);
- try {
- OutputFormat format = OutputFormat.createPrettyPrint();
- format.setEncoding("UTF-8"); // 字符编码为 UTF-8
- XMLWriter writer = new XMLWriter(outputStream , format);
- writer.write( x_doc ); // 把 xml 文档输出到文件里
- writer.close();
- }finally
- {
- // 确保文件句柄被关闭
- try {
- outputStream.close();
- }catch(Exception e) {
- }
- }
- }
- public static void main(String[] args)
- {
- try
- {
- List<Student> sss = new ArrayList<>();
- sss.add( new Student(20180001, "shao", true, "13810012345") );
- sss.add( new Student(20180002, "wang", true, "17489938290") );
- sss.add( new Student(20180003, "li", false, "19982998898") );
- writeXML ( sss , new File("output3.xml"));
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("exit");
- }
解析数据
- public static Student readXML (File xmlFile) throws Exception
- {
- FileInputStream inputStream = new FileInputStream(xmlFile);
- SAXReader xmlReader = new SAXReader(); // SAXReader 用于解析 xml
- Document x_doc = xmlReader.read(inputStream);// 得到一个 Document 对象
- inputStream.close(); // 得到 Document 后即可关闭文件
- Element x_root = x_doc.getRootElement();
- Student s = new Student();
- s.id = Integer.valueOf( x_root.element("id").getText());
- s.name = x_root.element("name").getText().trim(); //.trim()的作用是清除两边的空格.
- String sex = x_root.elementText("sex");
- s.sex = sex.equals("male");
- s.cellphone = x_root.elementText("cellphone");
- return s;
- }
- public static void main(String[] args)
- {
- File xmlFile = new File("data1.xml");
- try
- {
- Student s = readXML (xmlFile);
- System.out.println("read complete");
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("exit");
- }
(1)取值时要注意两边的空白
String name = x_root.elementText ("name").trim();
(2)由于 xml 里只存储文本, 所以在读取 Text 之后, 需要自己转成 int, boolean 或其他类型的变量.
例如,
int id = Integer.valueOf( x_root.element("id").getText());
(3)取值时一般要注意 null 的判断
- Element sex = x_root.element("sex");
- if ( sex != null)
- {
- }
(4)注意关闭文件句柄 (高级话题)
在 Java 程序里, 所有的文件 FileInputStream/FileOutputStream 在用完之后, 都得确保 close 掉. 否则会引起句柄泄露.
- public static Document parseXmlDocument(File xmlFile) throws Exception
- {
- FileInputStream inputStream = new FileInputStream(xmlFile);
- try {
- SAXReader xmlReader = new SAXReader();
- Document doc = xmlReader.read(inputStream);
- return doc;
- }finally {
- // 确保关闭文件句柄
- try{
- inputStream.close();
- }catch(Exception e) {
- }
- }
- }
假设从 < parent > 元素下查找子元素 < id > 的值, 则有 3 种写法:
第一种写法:
- Element e = parent.element("id");
- String text = e.getText();
第二种写法:
String text = parent.element("id").getText();
第三种写法:
String text = parent.elementText("id");
xml 教程 6
元素的属性
使用第三方库 dom4j 来操作 xml
先前我们已经学到的, 如果用 xml 来表示一个学生的信息, 可以表示为:
- <student>
- <id>
- 20180001
- </id>
- <name>
- 邵发
- </name>
- <sex>
- true
- </sex>
- <cellphone>
- 13810012345
- </cellphone>
- </student>
也可以表示为:
- <student id="20180001">
- <name>
- 邵发
- </name>
- <sex>
- true
- </sex>
- <cellphone>
- 13810012345
- </cellphone>
- </student>
其中, id 作为元素 < student > 的属性出现. 属性的名字为 id, 值为 "20180001", 必须使用双引号.
也可以表示为:
- <student id="20180001" name="邵发" sex="true" cellphone="13810012345">
- </student>
其中,<student > 元素有 4 个属性.
由于 < student > 元素没有 Text 内容, 所以可以简写为:
<student id="20180001" name="邵发" sex="true" cellphone="13810012345" />
添加元素
- Element x_student = x_root.addElement("student");
- x_student.addAttribute("id", String.valueOf(s.id));
- x_student.addAttribute( "name" , s.name);
- x_student.addAttribute( "sex" , s.sex ? "male" : "female");
- x_student.addAttribute( "cellphone", s.cellphone);
也可以连在一起写,
- Element x_student = x_root.addElement("student")
- .addAttribute("id", String.valueOf(s.id))
- .addAttribute( "name" , s.name)
- .addAttribute( "sex" , s.sex ? "male" : "female")
- .addAttribute( "cellphone", s.cellphone);
之所以可以连在一起写, 是因为 addAttribute() 的返回值仍然是当前的 Element 对象.
读取元素
- public static Student readXML (File xmlFile) throws Exception
- {
- FileInputStream inputStream = new FileInputStream(xmlFile);
- SAXReader xmlReader = new SAXReader(); // SAXReader 用于解析 xml
- Document x_doc = xmlReader.read(inputStream);// 得到一个 Document 对象
- inputStream.close(); // 得到 Document 后即可关闭文件
- Element x_root = x_doc.getRootElement();
- Element x_student = x_root.element("student");
- Student s = new Student();
- s.id = Integer.valueOf( x_student.attributeValue("id").trim());
- s.name = x_student.attributeValue("name").trim();
- String sex = x_student.attributeValue("sex").trim();
- s.sex = sex.equals("male");
- s.cellphone = x_student.attributeValue("cellphone").trim();
- return s;
- }
- public static void main(String[] args)
- {
- File xmlFile = new File("student.xml");
- try
- {
- Student s = readXML (xmlFile);
- System.out.println("read complete");
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- System.out.println("exit");
- }
.addAttribute 输入
.attributeValue 读取
常用工具类. AfXml
13.4 JSON
xml 多用于配置文件, JSON 多用于数据传输
- {
- "id":20111111, // 示例
- "name":"仁豪",
- "phone":"1300099";
- }
使用 JSON-ORG 或者 jsonlib 来操作 JSON
其中, 大括号表示一个 JSON 对象. 在这个对象里, 包含 4 个字段.
例如,
"id" 字段的值为 20180001, 是数字类型 ( Number )
"name" 字段的值为 "邵", 是字符串类型 ( String )
"sex" 字段的值为 true, 是布尔类型 (Boolean)
可以很直观的发现,
如果值是一个 String, 则应该用双引号, 如 "邵发"
如果值是一个 Number, 则不加双引号, 如 20180001
如果值是一个 Boolean, 则应该是 true 或者是 false, 不加双引号
加入 JSON-java JSON-lib
- JSONObject jobj = new JSONObject();
- jobj.put("id", 20180001); // Integer
- jobj.put("name", "邵发"); // String
- jobj.put("sex", true); // Boolean
- jobj.put("cellphone", "13810012345"); // String
- String jsonstr = jobj.toString(2);
- System.out.println(jsonstr);
JSONObject 里的字段显示顺序是无关的, 谁先上, 谁在下不影响最终结果.
JSON 的语法格式
用 JSON 可以表示 Object 信息, 以一对大括号包围, 里面可以多个字段.
例如:
- {
- "name": "邵发",
- "id": 1208439,
- "sex": true,
- "phone": "13810012345"
- }
语法要点:
- 以大括号包围
- 字段名称要加双引号
- 字段的值可以为 String, Integer, Boolean, Array, null 等类型
- 每个字段以逗号分隔 (最后一个字段末尾不能加逗号)
- 字段的顺序无关
注: JSONObject 在概念上对应于 Java 里的 Map
JSON Array
例如
- [
- {
- "name": "shao",
- "id": 2018001,
- "phone": "13810012345",
- "sex": true
- },
- {
- "name": "wang",
- "id": 2018002,
- "phone": "13810043245",
- "sex": true
- },
- {
- "name": "li",
- "id": 2018003,
- "phone": "1345015445",
- "sex": true
- }
- ]
语法要点:
- 以中括号包围
- 元素类型可以是任意类型. 例如, 元素类型可以是 String, 也可以是 Object
注: JSONObject 在概念上对应于 Java 里的数组
嵌套
Object 的字段类型可以是 Array
例如
- {
- "classId": 201801,
- "className": "计科 1801",
- "members":
- [
- "shaofa",
- "wang",
- "li"
- ]
- }
也就是说, Object 和 Array 是可以互相嵌套的.
JSON 的生成与解析
JSON 的生成
- public class CreateJSON
- {
- // 生成 JSONObject
- public static void test1()
- {
- JSONObject j1 = new JSONObject();
- j1.put("name", "邵发");
- j1.put("id", 1239349);
- j1.put("sex", true);
- j1.put("phone", "13810012345");
- String jsonstr = j1.toString(2); // 缩进 2
- System.out.println(jsonstr);
- }
- // 生成 JSONArray : 元素是字符串
- public static void test2()
- {
- JSONArray j = new JSONArray();
- j.put("shao");
- j.put("wang");
- j.put("li");
- j.put("chen");
- String jsonstr = j.toString(2); // 缩进 2
- System.out.println(jsonstr);
- }
- // POJO -> JSONObject
- // 如果是 POJO 对象 ( 已经添加了 getter ), 则可以直接转成 JSONObject
- public static void test3()
- {
- Student stu = new Student("邵发", 1238909, true, "13810012345");
- JSONObject j = new JSONObject(stu);
- String jsonstr = j.toString(2); // 缩进 2
- System.out.println(jsonstr);
- }
- // List -> JSONArray
- public static void test4()
- {
- List<Student> sss = new ArrayList<Student>();
- sss.add(new Student("shao", 1238901, true, "13810012345"));
- sss.add(new Student("wang", 1238902, true, "13456678895"));
- sss.add(new Student("qian", 1238903, false, "1381432435"));
- sss.add(new Student("chen", 1238904, true, "13342353446"));
- JSONArray jarray = new JSONArray();
- for( Student s : sss)
- {
- JSONObject j1 = new JSONObject(s);
- jarray.put( j1 );
- }
- String jsonstr = jarray.toString(2); // 缩进 2
- System.out.println(jsonstr);
- }
- // List -> JSONArray
- public static void test5()
- {
- List<Student> sss = new ArrayList();
- sss.add(new Student("shao", 1238901, true, "13810012345"));
- sss.add(new Student("wang", 1238902, true, "13456678895"));
- sss.add(new Student("qian", 1238903, false, "1381432435"));
- sss.add(new Student("chen", 1238904, true, "13342353446"));
- // 直接把 ArrayList 转成 JSON, 前提是 ArrayList 里的元素是可以转化的
- JSONArray jarray = new JSONArray(sss);
- String jsonstr = jarray.toString(2); // 缩进 2
- System.out.println(jsonstr);
- }
JSON 存储到文件 13.4_05 网盘
JSON.org 库文件
13.5 Properties
另外一种配置文件
- # 开头是注释
- port=80
- server=127.0.0.1
*.properties 文件的格式;
- # 开头是注释行
- key=value
显然 *.properties 里的 key 不允许重复.
不适合保存树状形式的数据
- public class Config
- {
- public String server;
- public int port;
- public void load(File f) throws Exception
- {
- Properties props = new Properties();
- // 从 *.properties 文件加载数据
- InputStream inputStream = new FileInputStream(f);
- try {
- props.load( new FileInputStream(f));
- }finally
- {
- inputStream.close();
- }
- // 获取 Property 配置
- server = props.getProperty("server");
- port = Integer.valueOf( props.getProperty("port", "80"));
- }
- public void save (File f) throws Exception
- {
- Properties props = new Properties();
- props.put("server", server);
- props.put("port", String.valueOf(port));
- // 存储到 *.properties 文件
- OutputStream outputStream = new FileOutputStream(f);
- try {
- props.store(outputStream, "just test");
- }finally {
- outputStream.close();
- }
- }
- }
- java.util.Properties
Properties 工具类, 可以存储多个 key-value
-getProperties/-setProperties
-load/store 读取文件 / 写入文件
第 14 章 反射机制
java.lang.Class
它是 Java 的基础类, 用于描述一个 class 对象.
在文件系统中, class 以文件的形式存在 Student.class 在运行时的 JVM(Java 虚拟机)中,
该 *.class 文件被加载到内存中成为一个对象, 对象的类型是 java.lang.Class
- Class cls = Student.class;
- System.out.println("Name:" + cls.getName());
其中, cls 这个对象就是这个 Class 的描述.
- Student obj = new Student();
- Class cls = obj.getClass();
其中, obj 是一个对象, obj.getClass()则是获取它的 Class 描述.
Class 有什么用
用于判断一个运行时对象的类型
- public static void test1(Object obj)
- {
- Class cls = Student.class;
- if(cls.isInstance(obj))
- {
- // 判断 obj 是不是 Student 类型
- System.out.println("is an instance of Student");
- }
- else
- {
- System.out.println("不是 an instance of Student");
- }
- }
其中, cls.Instance(obj)意思是判断 obj 是否为 my.Student 的一个实例.
另一种写法, 也可以判断一个运行时对象的类型
- public static void test2(Object obj)
- {
- String clsName = obj.getClass().getName();
- if(clsName.equals("my.Student"))
- {
- System.out.println("is an instance of Student");
- }
- else
- {
- System.out.println("不是 an instance of Student");
- }
- }
比较 S1 和 s2
- Student s1 = new Student(123, "shaofa", "199900000");
- Student s2 = new Student(124, "shaofa", "199900000");
- if(s1.equals(s2))
- {
- System.out.println("equal");
- }
- else
- {
- System.out.println("not equal");
- }
- public boolean equals(Object obj)
- {
- // 与一个 Student 对象比较
- if(this.getClass().isInstance(obj))
- {
- Student other = (Student) obj;
- return other.id == this.id;
- }
- // 与一个 String 对象比较
- if(String.class.isInstance(obj))
- {
- String other = (String)obj;
- return other.equals(this.name);
- }
- // 与一个 Integer 对象比较
- if(Integer.class.isInstance(obj))
- {
- Integer other = (Integer)obj;
- return this.id == other;
- }
- return false;
- }
14.2 反射
Reflection
给定一个 *.class 文件中, 我们可以得到以下信息:
类名 (含 package 路径)
函数 (名称, 参数类型, 返回值)
域 (名称, 类型)
实现的接口 (interfaces) ......
使用 java.lang.reflect.* 下的类来实现...
注意: 不需要源文件, 只需要 *.class 文件
- public static Class loadClass() throws Exception
- {
- // 加载 my/Student.class
- Class cls = Class.forName("my.Student");
- // 获取函数列表
- Method[] methods = cls.getMethods();
- // 获取成员变量列表
- Field[] fields = cls.getFields();
- for(Method m : methods)
- {
- System.out.println("函数:" + m.toString());
- }
- return cls;
- }
- public static void main(String[] args)
- {
- try
- {
- // 得到 Class
- Class cls = loadClass();
- //test2();
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- }
也就是说, 虽然我们不知道 Student 类的代码, 但是这个 class 文件本身可以反映 (reflect) 出这些信息...
结论: 通过 Reflection 机制, 我们可以直接 从 class 文件反推出它有哪个成员变量, 有哪 些函数
遍历 Method
已经函数名, 找到对象的
- // 从 Class 中获取 Method : 按名称查找 (假设没有重名的函数)
- public static void findMethod(Class cls) throws Exception
- {
- String methodName = "setId";
- // 获取所有 Method 列表, 顺序比对
- Method[] methods = cls.getMethods();
- for(Method m : methods) // 遍历
- {
- if(m.getName().equals(methodName)) // 比较
- {
- break;
- }
- }
- }
例 2 查找 Method
已经函数名, 参数列表, 寻找 Method
- public static void findMethod2(Class cls) throws Exception
- {
- String methodName = "setId";
- Class[] parameterTypes = {
- int.class
- };
- Method m = cls.getMethod(methodName, parameterTypes);
- System.out.println("got the method");
- }
例 3 Reflection 的运用
- // 一个完整的 reflection 测试
- public static void test1() throws Exception
- {
- // 加载 my/Student.class
- Class cls = Class.forName("my.Student");
- // 创建一个实例, 要求有一个不带参数的构造函数
- Object obj = cls.newInstance();
- // 找到 method
- Class[] parameterTypes = {
- int.class
- };
- Method m1 = cls.getMethod("setId", parameterTypes); //"setId" 参数是函数名, parameterTypes 是函数值类型.
- // 调用 method
- Object[] paramters = {
- 123
- };
- m1.invoke(obj, paramters);
- // 显示结果
- System.out.println("result:" + obj.toString());
- }
- public static void main(String[] args)
- {
- try
- {
- // 得到 Class
- Class cls = loadClass();
- test1();
- }catch(Exception e)
- {
- e.printStackTrace();
- }
- }
Class.forName(...) 可以加载一个 class 文件..
(1)由谁负责加载? 由 Class Loader 负责加载, 具体地讲, JVM 提供了一 个内置的 Bootstrap class loader.
(2)从哪里加载? 从 classpath 下寻找 class, 以及该 class 里面 import 的 class
小结 1.Reflection: 从 *.class 文件中, 可以 reflect 得到它 的描述信息: 类名, 函数名等
2. 通过 Class, 可以创建一个实例 instance
3. 通过 Class, 可以找到它的某个 Method, 进而就 可以调用这个函数.
4. 需要知道 Class Loader 的存在及其作用
反射机制
Reflection 应用框架
一般地, 当设计一个应用框架时才有可能用 到 Reflection 技术.
例如, 著名的 Struts Spring Hibernate 框架
AfShell: Command Ui Framework
AfShell: 一个自定义的 Framework, 使用这个 Framework 可以很方便的写出基于命令行界面的应用 程序.
添加 AfShell 框架支持
(1)加入 afshell.jar 到 BuildPath
(2)添加 afshell.properties
(3)在 main 中调用 AfShell.start() 完成!
AfShell: Command Ui Framework
添加命令处理
(1)自定义 Action 类 此类必须有一个函数 public int execute()
(2)配置 afshell.properties login=my.LoginAction
AfShell: Command Ui Framework
添加命令行参数的支持 例如, 用户输入 login username=shaofa password=123456
(1)添加成员变量 username password
(2)添加 setter setUsername() setPassword() 则 AfShell 框架会自动的把参数值传进来, 然后再调用 execute()函数...
设计方法
那么, 这样的一个 Framework 该如何实现? 例如, 用户输入了 login username=shaofa password=123456
(1)得到命令名 "login"
(2)通过配置文件, 找到 my.LoginAction
(3)加载 my.LoginAction, 创建一个实例
(4)根据 username=shaofa, 找到 setter 函数 setUsername, 调用此函数
(5)根据 password=123456, 找到 setter 函数并调用
(6)调用 excute()函数, 得到返回值 完成!
小结 介绍一个应用框架 AfShell 的使用方法
如果要使用这个框架, 并不需要知道 Reflection 技术
如果自己设计一个框架给别人使用, 则需要精确掌 握和运用 Reflection 技术.
请试着使用 Reflection 技术, 自己来实现这个框架.
反射 4
(1)得到命令名 "login"
(2)通过配置文件, 找到 my.LoginAction
(3)加载 my.LoginAction, 创建一个实例
(4)根据 username=shaofa, 找到 setter 函数 setUsername, 调用此函数
(5)根据 password=123456, 找到 setter 函数并调用
(6)调用 excute()函数, 得到返回值 完成!
了解 AfShell 框架的架构方法.
15.1 线程
: 你手下管理着两名员工, 一个是 Confucian, 一 个是 Buddhist, 如何让他们同时工作?
- Confucian e1 = new Confucian(); e1.doJob();
- Buddhist e2 = new Buddhist(); e2.doJob();
我们发现, 只有 e1 先工作完成了, 才能开始 e2 的工作, 两者无法 "同时" 进行...
线程 Thread
引入线程机制, 可以并行地做多件事情...
继承于 Thread 类, 重写 run(), 填上它要做的工作.. extends Thread
- public class Buddhist extends Thread
- {
- @Override
- public void run()
- {
- for(int i=1; i<=500; i++)
- {
- System.out.println("ma mi ma mi hong ..." + i);
- try
- {
- Thread.sleep(100);
- } catch (InterruptedException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args)
- {
- Confucian e1 = new Confucian();
- e1.start();
- }
线程 Thread
启动线程 Confucian e1 = new Confucian(); e1.start();
Buddhist e2 = new Buddhist(); e2.start();
启动一个线程 start()
线程主函数 run() : 描述了这个线程的工作任务
线程的调度
CPU 只有一个, 而系统中同时运行的进程有几十 个. 每个进程又同时运行 n 个线程. 忙得过来?
时间片划分: 将 1 秒钟划分为 N 个小的时间片, 大家 轮流运行.
比如, 以 5ms 为一个时间片, 每个线程运行 5ms 之后 就轮到下一个线程运行.
这样, 所有的线程都有机会被运行, 由于切换的速 度很快, 给用户的感觉是: 它们好像同时在运行.
Sleep 暂歇
线程可以执行 Sleep 操作, 用于告诉操作系统: 我要 休息一会, 接下来的一段时间内不要管我...
例如, Thread.sleep(10000); 表示接下来将休息 10 秒钟, 在这 10 秒钟内该线程主 动放弃 CPU 的使用权...
排队机制
假设线程1休息了 10 秒之后, 那么醒来之后就能立 即获得 CPU 的使用权吗? 不能. 醒来之后要排队!
系统中有一个队列, 所有需要 CPU 的线程在这里排队.
操作系统根据特定的算法, 来决定下一个是谁. (比始说, 有的线程优先级较高, 而有的较低)
使用 sleep
当创建线程时, sleep 是一个经常要使用的函数 原则: 尽可能少的占用 CPU, 让别的线程也有机制运行.
试想, 如果一个线程里是这样的:
- while(true)
- {
- System.out.println("我就赖着 CPU...");
- }
那其他的线程会高兴吗?
虽然我们看到在任务管理器下很多线程, 但在同一时刻, 大多数线程都在 sleep
引入线程的概念 (多条线并行发展) 介绍了线程调度的机制, 由操作系统负责调度 介绍了 sleep 的作用, 及其重要意义
15.2 线程的创建
第一种方法继承 Thread
extends Thread
重写 run 方法, 在主函数入口用 start()启动.
第二章方法: 实现 Runnable 接口
- public class Buddhist implements Runnable
- {
- @Override
- public void run()
- {
- for(int i=1;i<=500;i++)
- {
- System.out.println("emmmmmmmm.");
- try
- {
- Thread.sleep(100);
- }catch(InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args)
- {
- Buddhist e2 = new Buddhist();
- Thread t=new Thread(e2);
- t.start();
- }
这是因为 java 不允许多重继承, 如果你的类已经继承于别的类, 又要用线程来运行, 则使用这种方法.
有时候使用匿名类更方便一些
- Thread t=new Thread(){
- public void run()
- {
- }
- };
- t.start();
- public static void main(String[] args)
- {
- Thread t2=new Thread() {
- @Override
- public void run()
- {
- for(int i=1;i<=500;i++)
- {
- System.out.println("emmmmmmmm.");
- try
- {
- Thread.sleep(100);
- }catch(InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- }
- };
- t2.start();
- }
15.3 线程
线程的终止
当一个线程的 start()调用后, 线程为 Alive 状态. 当一个线程的主函数 run()退出后, 线程死亡(Dead).
- public void run()
- {
- for(int i=1; i<=10; i++)
- {
- System.out.println("..." + i);
- }
- }
该线程打印 10 句后终止.
(可以类比一下主线程, 当 main()中退出时, 主程序终止...)
让一个线程终止, 就是要想办法让它从 run()中退出. 例如, 设置一个标识变量,
- public boolean quitflag = false;
- public void run()
- {
- for(int i=1; i<=10; i++)
- {
- if(quitflag) break; // 退出循环
- System.out.println("..." + i);
- }
- }
例子
- public class Confucian extends Thread
- {
- public boolean quitflag = false;
- @Override
- public void run()
- {
- for(int i=1; i<=10; i++)
- {
- if(quitflag) break;
- System.out.println("人之初, 性本善 ..." + i);
- try
- {
- Thread.sleep(1000);
- } catch (InterruptedException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- System.out.println("Confucian exit.");
- }
- }
- public class Test1
- {
- public static void main(String[] args)
- {
- // 创建并启动线程
- Confucian t1 = new Confucian();
- t1.start();
- // 按回车后, 中止线程, 然后退出程序
- InputStreamReader m = new InputStreamReader(System.in);
- BufferedReader reader = new BufferedReader(m);
- try{
- reader.readLine(); // 由这句来读回车
- reader.close();
- // 控制线程的退出
- t1.quitflag = true;
- }
- catch(Exception e)
- {
- }
- }
- }
sleep 与 interrupt
背景: Buddhist 中每 5 秒输出一次, sleep 的时间很长, 如何让它立即退出??
方法: 调用 t2.interrupt()来中断目标线程. 打断线程的 sleep
则 sleep()函数将抛出异常, 结束 sleep 状态
- public static void main(String[] args)
- {
- // 创建并启动线程
- Buddhist t2 = new Buddhist();
- t2.start();
- // 按回车后, 中止线程, 然后退出程序
- InputStreamReader m = new InputStreamReader(System.in);
- BufferedReader reader = new BufferedReader(m);
- try{
- reader.readLine();
- reader.close();
- // 控制线程的退出
- t2.quitflag = true;
- t2.interrupt(); // 打断 sleep
- }
- catch(Exception e)
- {
- }
等待线程的退出
- Buddhist t3 = new Buddhist(); t3.start();
- // 等待 t4 退出 t3.join();
join() 函数会阻塞住当前线程, 等待目标线程退出后, 再继续执行...
注: 在 Java 里, join()只起等待作用(线程死亡后不需 要手工回收, 由 JVM 自动回收)
- public static void main(String[] args)
- {
- // 创建并启动线程
- Buddhist t3 = new Buddhist();
- t3.start();
- // 等待 t3 退出
- try
- {
- t3.join(); // 阻塞住当前线程, 等待目标线程退出后, 再继续执行.
- } catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- // 在 t3 线程完成之后, 再执行下面的事情
- for(int i=0; i<3; i++)
- {
- System.out.println("我自己的事情");
- }
- System.out.println("program exit");
- }
注意事项
本质上, 线程只有一种退出方式: 从 run()函数自然退出. 我们需要通过程序设计, 控制它从 run()中退出.
不能 stop() , 不能 destroy()... deprecated
很多初学者会有一个想法: 有没有简单暴力的手段, 直接杀死一个线程?
比如 kill() ? 千万不要这么想! (正在使用 ATM 存钱的时候, 断电了可以吗?)
小结
线程只有一种终止方式: 从 run()中退出.
介绍如何控制一个线程的退出.
15.4
引例
密钥 Key : 一个 16 字节的数组
密钥更新线程 KeyUpdater: 定期更新密钥
密钥获取线程 KeyPrinter: 打印显示密钥
密钥的完整性: 密钥的 16 字节必须相同.
例如,
下面是有效密钥 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A
下面是无效密钥 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1A 1B 1B 1B
密钥更新时应保证密钥的完整性.
什么样的情况算是出错? KeyPrinter 获取密钥, 发现密钥 不完整(16 个字节不全相同), 则认为此密钥无效!
分析代码, 推断有没有可能出错... 运行代码, 看看没有出错...
构造出错条件: 假设 key.update()需要 10ms 才能更新完成...
线程的同步机制
两个线程同时访问一个对象时, 可能发生数据不同步的现象.
数据不同步:
比如, 线程 KeyUpdater 正在更新密钥...
0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A 0A xx xx xx xx 当更新过程完成了 75% 的时候...
另一个线程 KeyPrinter 要求获取当前密钥...
它拿走了密 钥, 发现它是一个不完整的密钥...
出错
为了实现多线程对同一对象的同步访问, 引入互斥锁的 概念.
- synchronized (lock) // 申请锁
- {
- // 上锁 locked
... 关键代码 ... } // 解锁 unlocked
(1)若别的线程正持有该锁(locked), 则本线程阻塞等待
(2)若此锁空闲(unlocked), 则本线程持有锁(locked), 进入大括号执行, 完毕之后释放锁(unlocked).
- synchronized (key)
- {
- seed ++;
- if(seed> 100) seed = 0;
- key.update(seed);
- }
形象理解: 好比一间房子, 如果是空的, 就能进入使用里面的资源, 并在里面把门反锁, 此时别人无法进入.
(保证了资源的独 占使用). 此时有人想用房子, 就得门口等着...
- synchronized ( lock ) // 等待进入
- {
- // 进入, 从里面反锁
... 使用资源 res ...
- } // 出来
- synchronized ( lock )
在 Java 里, synchronized (lock) 括号中的锁可以是任意 对象, 甚至是数组对象.
比如,
- Object obj = new Object();
- synchronized (obj) {
- }
- byte[] data = new byte[];
- synchronized (data) {
- }
为了增强可读性, 可以直接把要访问的那个对象当成锁, 类似下面这样:
- synchronized (key)
- {
- seed ++;
- if(seed> 100) seed = 0;
- key.update(seed);
- }
则可以一目了然地表明: 大括号中要锁定的资源是 key 对象.
介绍了线程同步的概念: 多个线程访问同一对象时, 一个读, 一个写, 则容易发生不同步的现象.
线程同步: 就是让两个线程步调一致, 有序地访问 的访问同一对象.
synchronized 代码块用于同步访问.
写在内部此时, 外部就可以放心地调用 update 和 get, 不必担心不同步的问题.
- public void update(byte seed)
- {
- synchronized (this)
- {
- for(int i=0; i<data.length; i++)
- {
- data[i] = seed;
- }
- }
- }
- // 获取密钥
- public synchronized byte[] get()
- {
- synchronized (this)
- {
- // 复制一份传出去
- byte[] copy = new byte[data.length];
- System.arraycopy(data, 0, copy, 0, data.length);
- return copy;
- }
- }
由于要锁定的资源对象就是自己, 所以也可以使用 synchronized (this) { }
进一步简化的写法 public synchronized void update() { }
相当于 public void update()
- {
- synchronized (this)
- {
- }
- }
如果函数内的所有语句都被 synchronized 括起来, 则可以这么简写.
死锁 deadlock
死锁: 因为不恰当地使用锁, 导致线程锁死.
第一种情况: synchronized (lock)
- {
- synchronized (lock)
- {
- }
- }
自己就在房子里, 另一个自己在房子外面敲门
死锁 deadlock
第二种情况: 两个线程互相等待...
线程1
- synchronized (lock1)
- {
- synchronized (lock2)
- {
- }
- }
线程2
- synchronized (lock2)
- {
- synchronized (lock1)
- {
- }
- }
大括内的代码尽可能快地完成, 以提高多线程的运行效率.
- (让别人少等一会)
- synchronized (lock)
- {
- }
建议: 把不影响共享资源访问的耗时操作都移出去
引例
鸡蛋 Egg 篮子 ArrayList<Egg>
母鸡 Hen : 每隔一段时间产出一个鸡蛋, 放入篮子
男孩 Boy : 负责将篮子里的鸡蛋取出来吃掉
要求: 确保篮子里的鸡蛋被及时取走. (如果取走不及时, 鸡蛋就会堆积在篮子里...)
生产者 - 消费者 模型
由于 Boy 不确定母鸡什么时候会产出鸡蛋, 只好频繁的 去检查篮子里有没有鸡蛋. 有则取出, 没有则空跑一趟.
轮询机制
假设母鸡每 5-10 秒会随机产出一个鸡蛋
则小明为了确保能及时取走鸡蛋, 可以每隔 1 秒去检查 一次... 这样,
虽然会空跑很多次, 但能够完成目标.
这种实现机制称为: 轮询机制.(反复查询) 特点: 效率低, 但实现起来简单.
通知机制
为了提升小明的效率, 不让小明空跑, 则可以使用通知机制.
小明等待通知 basket.wait ... 母鸡在产出鸡蛋后, 立即通知小明来取 basket.notify ...
这样, 小明就能够在第一时间取走鸡蛋.
这种实现机制称为: 通知机制 特点: 效率高, 但实现起来复杂
wait / notify
wait/notify 使用注意事项: (1)必须结合 synchronized 使用 (2)synchronized 对象和 wait/notify 的对象必须是同一 个
这是 Java 特定的机制, 必须这么使用(官方说法是, 在 wait/notify 之前, 必须先成为该对象的监视者 Monitor)
当然, 也可以使用别的方法来实现通知机制. 但这套机 制是 Java 自带的.
notify / notifyAll
notify : 通知一个线程 如果有很多线程都在 wait, 则由系统选择一个 notifyAll : 通知所有线程
wait / notify
wait / notify 是 Object 类的方法
发出通知
- synchronized (basket)
- {
- basket.add(egg);
- basket.notify();
- }
等待通知
- synchronized (basket)
- {
- try
- {
- basket.wait();
- }
- catch (InterruptedException e)
- {
- }
- if(basket.size()> 0)
- egg = basket.remove(0);
- }
实例代码
- public class Test2
- {
- // 鸡蛋
- class Egg
- {
- int id; // 每个鸡蛋有个编号
- public Egg(int id)
- {
- this.id = id;
- }
- public String toString()
- {
- return "Egg("+id+")";
- }
- }
- // 母鸡 (生产者)
- class Hen extends Thread
- {
- ArrayList<Egg> basket ;
- public Hen(ArrayList<Egg> basket)
- {
- this.basket = basket;
- }
- public void run()
- {
- Random r = new Random();
- int count = 0;
- while(true)
- {
- // 生产一个蛋, 放在篮子里
- Egg egg = new Egg(++count);
- System.out.println("产出:" + egg);
- synchronized (basket)
- {
- basket.add(egg);
- basket.notify(); // 通知, notify 和 wait 的对象必须是统一的对象.
- }
- // 休息一段时间: 1~5 秒 (随机)
- int interval = 1 + r.nextInt(4);
- try
- {
- Thread.sleep(interval * 1000);
- } catch (InterruptedException e1)
- {
- }
- }
- }
- }
- // 小孩 (消费者)
- class Boy extends Thread
- {
- ArrayList<Egg> basket ;
- public Boy(ArrayList<Egg> basket)
- {
- this.basket = basket;
- }
- public void run()
- {
- while(true)
- {
- // 查看篮子里有没有鸡蛋
- Egg egg = null;
- synchronized (basket)
- {
- try
- {
- basket.wait(); // 等待通知, 阻塞线程. notify 和 wait 的对象必须是统一的对象.
- } catch (InterruptedException e)
- {
- }
- if(basket.size()> 0)
- {
- // 从篮子里取出鸡蛋
- egg = basket.remove(0);
- }
- }
- if(egg != null)
- {
- // 吃掉这个鸡蛋
- System.out.println("吃掉 :" + egg);
- }
- }
- }
- }
- public void test()
- {
- ArrayList<Egg> basket = new ArrayList<Egg>();
- Hen xiaoji = new Hen(basket);
- Boy xiaoming = new Boy(basket);
- xiaoji.start();
- xiaoming.start();
- }
- public static void main(String[] args)
- {
- Test2 t = new Test2();
- t.test();
- }
- }
注解 Annotation
@开头的都是注解
@Deprecated 意思是 "废弃的, 过时的"
@Override 意思是 "重写, 覆盖"
@SuppressWarnings 意思是 "压缩警告"
注解和注释的区别
//Comment 是给程序员看的, 不会被编译到 class 文件
注解 @是给编译器和 IDE 看的
来源: http://www.bubuko.com/infodetail-2915702.html