Java jdk6 学习笔记


第1章 • 了解Java – 什么是Java –Java的特性 – 如何学习Java 什么是Java • 最早是Sun公司GreenProject中撰写Star7 应用程序的一个程序语言 – JamesGosling的窗外有颗橡树(Oak) • 全球信息网兴起,JavaApplet成为网页互动 技术的代表 • 1995/5/23,Java DevelopmentKits(当时 的JDK全名)1.0a2版本正式对外发表 什么是Java •Java是面向对象(Object-Oriented)程序 语言,具有更高的跨平台可能性 • 在今日,更多时候代表了软件开发的架构 • 开发者版本发表时是以Java DevelopmentKits名称发表,简称JDK • J2SE 5.0(Java 2 Platform Standard Edition5.0)时的JDK称为J2SE Development Kit 5.0 什么是Java • J2SE 5.0(Java 2 Platform Standard Edition5.0)时的JDK称为J2SE Development Kit 5.0 • 从JavaSE 6(Java Platform, Standard Edition6)开始的JDK6则称之为Java SE Development Kit 6 – 不再带有“2”这个号码,版本号6或1.6.0都使用 –6是产品版本(productversion),而1.6.0是开 发者版本(developerversion) Java的特性 • 语言特性 – 简单(Simple) – 面向对象(Object-oriented) – 网络(Network-savvy) – 解译(Interpreted) – 坚固(Robust) – 安全(Secure) – 可携(Portable) – 高效能(High-performance) Java的特性 • 应用平台 – Java SE • Java Platform, Standard Edition – Java EE • Java Platform, Enterprise Edition – Java ME • Java Platform, Micro Edition Java Platform, Standard Edition (Java SE) •Java各应用平台的基础 Java Platform, Standard Edition (Java SE) • JVM –Java虚拟机(Java Virtual Machine,JVM) •JRE –Java执行环境(Java SE Runtime Environment,JRE) •JDK •Java语言 Java Platform, Enterprise Edition (Java EE) • 以JavaSE的基础,定义了一系列的服务、 API、协定等 • 适用于开发分布式、多层式(Multi-tiered)、 以组件为基础、以Web为基础的应用程序 • 技术像是JSP、Servlet、Enterprise JavaBeans(EJB)、Java Remote Method Invocation(RMI)等 Java Platform, Micro Edition (Java ME) • 作为小型数字设备上开发及部署应用程序 的平台,像是消费性电子产品或嵌入式系 统等 • 最为人所熟悉的设备如手机、PDA、股票 机等 活跃的社群与丰富的资源 • 开发工具 • 开放原始码的组件 • 容器 • 测试工具 • 各式各样的软件专案 • 各个社群所支持的讨论区 • 取之不尽的文件 如何学习Java • 奠定Java语法基础 • 运用基本的JavaSE API – 字符串处理、例外处理、对象容器 (Container)、输入输出(I/O)、线程 (Thread) • http://java.sun.com/javase/6/docs/api/inde x.html 如何学习Java • 使用搜索引擎 – http://www.google.com/ • 加入社群参与讨论 – http://www.javaworld.com.tw/ • 学习地图 – http://java.sun.com/developer/onlineTraining/n ew2java/javamap/intro.html 第2章 • 入门准备 – 下载、安装、瞭解JDK – 设定Path与Classpath – 第一个Java程序 – 选择开发工具 下载JDK •JDK6发表日期为2006年12月11日 • 较新的修正版本将以Update名称,加上号 码来表示修正的版本号 • http://java.sun.com/javase/downloads/inde x.jsp 安装JDK 包括公用JRE 一定要记得 安装JDK 一定要记得 了解JDK • 公用JRE是给开发好的程序之执行平台 •JDK本身也有自己的JRE – 位于JDK安装目录的「jre」目录下 •JDK本身所附的JRE比公用JRE多了个 server的VM(VirtualMachine)执行选项 了解JDK JDK的JRE有server选项 了解JDK •JDK的安装目录 – 「bin」目录 •JDK的工具程序 – 「demo」目录 • 范例程序 – 「jre」目录 •JDK自己附带的JRE – 「db」目录 • ApacheDerby数据库,纯Java所撰写的数据库 了解JDK •JDK的安装目录 – 「lib」目录 • 工具程序实际上会使用的Java工具类别 –JDK中的工具程序,大多也是由Java所撰写而 成 –bin文件夹下的工具程序,不过是个包装器 (Wrapper) – 执行javac.exe等程序时,最后会呼叫lib目录中 tools.jar中的对应类别 了解JDK •JDK的安装目录 –src.zip •Java提供的API类别之原始码文件压缩档 设定Path • 找不到javac工具程序 • 必须告诉操作系统,应该到哪些目录下尝 试找到您所想使用的工具程序 – 设定系统变量中的 Path环境变量 设定Path • 必须告诉操作系统,应该到哪些目录下尝 试找到您所想使用的工具程序 – 直接设定目前的环境变量包括Path变数 • Windows下安装JRE时,会将java.exe复制 至「C:\Windows\System32\」路径之下, 而这个路径在Path变量中是默认的路径 set Path= C:\Program Files\Java\jdk1.6.0\bin;%Path% 设定Classpath •Java执行环境本身就是个平台,执行于这 个平台上的程序是已编译完成的Java程序 • 设定Path变量是为了让操作系统找到指定 的工具程序(例如Windowsexe) • 设定Classpath目的就是为了让Java执行环 境找到指定的Java程序(JVMclass) 设定Classpath •JDK6默认会到现行工作目录,以及JDK的 「lib」目录中寻找Java程序 • javac -classpath classpath1;classpath2 … • 对于Windows操作系统来说,Path是让操 作系统可以找到“.exe”执行档的存在 • 对于Java执行环境来说,ClassPath就是让 JVM可以找到".class"执行档的存在 第一个Java程序 第一个Java程序 • 新增一个「文字文件」 • 重新命名文件为「HelloJava.java」 第一个Java程序 •Java的源文件必须以扩展名.java作结束 • 主档名与类别名称必须一致 • 注意每个字母的大小写 • 空白只能是半型空格符或是Tab字符 第一个Java程序 • javac HelloJava.java • error: cannot read: HelloJava.java – javac工具程序找不到您指定的.java档案 • HelloJava.java:1: class HelloJava is public, should be declared in a file named HellJava.java – 类别名称与主档名不符 第一个Java程序 • HelloJava.java:3: cannot find symbol – 程序代码中某些部份打错了,最常发生的原因 可能是没有注意到字母大小写 • ‘javac’不是内部或外部命令、可执行的程序 或批处理文件 –Path设定有误或没有在Path中加入JDK的 「bin」目录 第一个Java程序 • java HelloJava • Exception inthread"main" java.lang.NoClassDefFoundError –java工具程序找不到您所指定的类别 • Exceptionin thread "main" java.lan.NosuchMethodError: main – 没有指定Java程序的进入点(Entrypoint),java工具 程序指定的类别必须要有一个程序进入点,也就是必 须包括main(String[] args)这个方法(method) 选择开发工具 • 从简单的文字编辑辅助工具开始 – UltraEdit(http://www.ultraedit.com/) –Editplus(http://www.editplus.com/) • 简单的开发环境 –JCreater(http://www.jcreator.com/) – BlueJ(http://www.bluej.org/index.html) • 功能更齐全的IDE – Eclipse(http://www.eclipse.org/) – NetBeans(http://www.netbeans.org/) 第3章 • 语法入门 – 第一个Java程序 – 文本模式下与程序互动 – 数据、运算 – 流程控制 第一个Java程序 • 定义类别(Class) • 定义区块(Block) • 定义main()方法(Method) • 撰写陈述(Statement) public class HelloJava { public static void main(String[] args) { System.out.println("嗨!我的第一个Java程序!"); } } 给C使用者的第一個Java程序 • 给了C使用者类似printf()的功能 public class HelloJavaForC { public static void main(String[] args) { System.out.printf("%s! 这是您的第一个Java程序!\n", "C语言Fan"); } } System.out.printf("%s! 这是您的第二个Java程序!", "C语言Fan").println(); System.out.printf("%s! 这是您的第%d 个Java程序!\n", "C语言Fan", 3); 为程序加入批注 • 原始码档案中被标注为批注的文字,编译 程序不会去处理它 /*作者:良葛格 * 功能:示范printf()方法 * 日期:2005/4/30 */ public class ThirdJavaForC { public static void main(String[] args) { // printf()是J2SE5.0的新功能,必须安裝JDK5.0才能编译 System.out.printf("%s! 这是您的第%d个Java程序!\n", "C语言Fan", 3); } } 为程序加入批注 • 不能用巢状方式来撰写多行批注 • 多行批注可以包括单行批注 /*批注文字1……bla…bla /* 批注文字2……bla…bla */ */ /*批注文字1……bla…bla //批注文字2……bla…bla */ 使用Scanner取得输入 • 在J2SE 5.0中,可以使用java.util.Scanner 类别取得使用者的输入 • 可以使用这个工具的next()功能,来取得用 户的输入字符串 Scanner scanner = new Scanner(System.in); System.out.print("请输入您的名字:"); System.out.printf("哈啰!%s!\n", scanner.next()); System.out.print("请输入一个数字:"); System.out.printf("您输入了%d!\n", scanner.nextInt()); 使用BufferedReader取得输入 • BufferedReader建构时接受java.io.Reader 物件 – 可使用java.io.InputStreamReader BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(System.in)); System.out.print("请输入一列文字,可包括空白: "); String text = bufferedReader.readLine(); System.out.println("您输入的文字: " + text); 标准输入输出串流 • System类别中的静态物件out – 提供标准输出串流(Stream)输出 – 通常对应至显示输出(终端机输出) – 可以将输出重新导向至一个档案 – java HelloJava > HelloJavaResult.txt • System标准输入串流in – 在程序开始之后它会自动开启,对应至键盘或 其它的输入来源 标准输入输出串流 • 标准错误输出串流err – 在程序执行后自动开启,将指定的字符串输出 至显示设备或其它指定的装置 –err会立即显示错误讯息 –err输出串流的讯息不会被重新导向 System.out.println("使用out输出讯息"); System.err.println("使用err输出讯息"); java ErrDemo > ErrDemoResult.txt 使用err输出讯息 输出格式控制 控制字符 作用 \\ 反斜杠 \' 单引号' \" 双引号" \uxxxx 以16进位数指定Unicode字符输出 \xxx 以8进位数指定Unicode字符输出 \b 倒退一个字符 \f 换页 \n 换行 \r 游标移至行首 \t 跳格(一个Tab键) System.out.println("\u0048\u0065\u006C\u006C\u006F"); 输出格式控制 • 若是使用J2SE5.0或更高的版本 //输出19的十进制表示 System.out.printf("%d%n", 19); //输出19的八进制表示 System.out.printf("%o%n", 19); //输出19的十六进制表示 System.out.printf("%x%n", 19); 格式字符 作用 %% 在字符串中显示% %d 以10进位整数方式输出,提供的数必须是Byte、Short、 Integer、Long、 或BigInteger %f 将浮点数以10进位方式输出,提供的数必须是Float、Double或 BigDecimal %e, %E 将浮点数以10进位方式输出,并使用科学记号,提供的数必须是Float、 Double或BigDecimal %a, %A 使用科学记号输出浮点数,以16进位输出整数部份,以10进位输出指数 部份,提供的数必须是Float、Double、BigDecimal %o 以8进位整数方式输出,提供的数必须是Byte、Short、 Integer、Long、 或BigInteger %x, %X 将浮点数以16进位方式输出,提供的数必须是Byte、Short、 Integer、 Long、或BigInteger %s, %S 将字符串格式化输出 %c, %C 以字符方式输出,提供的数必须是Byte、Short、Character或 Integer %b, %B 将"true"或"false"输出(或"TRUE"、"FALSE",使用 %B)。另外,非null 值输出是"true",null值输出是"false" %t, %T 输出日期/时间的前置,详请看在线API文件 输出格式控制 • 可以在输出浮点数时指定精度 – System.out.printf("example:%.2f%n", 19.234); –example:19.23 • 可以指定输出时,至少要预留的字符宽度 – System.out.printf("example:%6.2f%n", 19.234); – example: 19.23 – 补上一个空白在前端 基本的数据型态(Primitivetype) • 整数 – 短整数(short)(占2个字节) – 整数(int)(占4个字节) – 长整数(long)(占8个字节) • 字节 – 专门储存位数据 – 占一个字节 • 浮点数 – 浮点数(float)(占4个字节) – 倍精度浮点数(double)(占8个字节) 基本的数据型态(Primitivetype) • 字符 – 采Unicode编码 – 前128个字符编码与ASCII编码兼容 – 每个字符数据型态占两个字节 – 可储存的字符范围由'\u0000'到'\uFFFF' • 布尔数 – 占内存2个字节 – 可储存true与false两个数值 基本的数据型态(Primitivetype) System.out.printf("short \t数值范围:%d ~ %d\n", Short.MAX_VALUE,Short.MIN_VALUE); System.out.printf("int \t数值范围:%d ~ %d\n", Integer.MAX_VALUE, Integer.MIN_VALUE); System.out.printf("long \t数值范围:%d ~ %d\n", Long.MAX_VALUE, Long.MIN_VALUE); System.out.printf("byte \t数值范围:%d ~ %d\n", Byte.MAX_VALUE, Byte.MIN_VALUE); System.out.printf("float \t数值范围:%e ~ %e\n", Float.MAX_VALUE, Float.MIN_VALUE); System.out.printf("double \t数值范围:%e ~ %e\n", Double.MAX_VALUE, Double.MIN_VALUE); 变数、常数 • 在Java中要使用变量,必须先宣告变量名 称与数据型态 • 使用int、float、double、char等关键词来宣 告变量名称并指定其数据型态 – 不可以使用数字作为开头 – 不可以使用一些特殊字符,像是*&^%之类 – 不可以與Java内定的关键词同名 int age; //宣告一个整数变量 double scope; //宣告一个倍精度浮点数变量 变数、常数 • 鼓励用清楚的名称来表明变量的作用 • 不可以宣告变量后,而在未指定任何值给 它之前就使用它 • 编译程序在编译时会回报这个错误 int ageOfStudent; int ageOfTeacher; variable var might not have been initialized 变数、常数 • 使用「指定运算符」'='来指定变数的值 int ageOfStudent = 5; double scoreOfStudent = 80.0; char levelOfStudent = 'B'; System.out.println("年級\t得分\t等級"); System.out.printf("%4d\t %4.1f\t %4c", ageOfStudent, scoreOfStudent, levelOfStudent); 变数、常数 • 宣告变量名称的同时,加上“final”关键词来 限定 • 这个变量一但指定了值,就不可以再改变 它的值 final int maxNum = 10; maxNum = 20; cannot assign a value to final variable maxNum 算术运算 • 加(+)、减(-)、乘(*)、除(/)、余 除运算符(%) – System.out.println(1 + 2 * 3); – System.out.println(1+2+3 / 4); – System.out.println((double)(1+2+3) / 4); 算术运算 • 这段程序会印出什么结果? • 使用下面的方法 int testNumber = 10; System.out.println(testNumber / 3); int testNumber = 10; System.out.println(testNumber / 3.0); System.out.println((double) testNumber / 3); 算术运算 • 将精确度大的值指定给精确度小的变量 时,由于在精确度上会有遗失,编译程序 会认定这是一个错误 int testInteger = 0; double testDouble = 3.14; testInteger = testDouble; System.out.println(testInteger); possible loss of precision found : double required: int testInteger = testDouble ^ 1 error 算术运算 • 必须明确加上转换的限定字,编译程序才 不会回报错误 •'%'运算符是余除运算符 testInteger =(int) testDouble; count = (count + 1) % 360; 比较、条件运算 • 大于(>)、不小于(>=)、小于(<)、 不大于(<=)、等于(==)、不等于 (!=) System.out.println("10 > 5结果" + (10 > 5)); System.out.println("10 >= 5结果" + (10 >= 5)); System.out.println("10 < 5结果" + (10 < 5)); System.out.println("10 <= 5结果" + (10 <= 5)); System.out.println("10 == 5结果" + (10 == 5)); System.out.println("10 != 5结果" + (10 != 5)); 比较、条件运算 • 条件运算符 条件式?成立传回值:失败传回值 System.out.println("该生是否及格? " + (scoreOfStudent >= 60 ? '是' : '否')); System.out.println("是否为奇數? " + (number%2 != 0 ? '是' : '否')); 逻辑、位运算 • 「且」(&&)、「或」(||)、「反相」(!) •&(AND)、|(OR)、^(XOR)与~(补 码) int number = 75; System.out.println((number > 70 && number < 80)); System.out.println((number > 80 || number < 75)); System.out.println(!(number > 80 || number < 75)); System.out.println("0 AND 0\t\t" + (0 & 0)); System.out.println("0 AND 1\t\t" + (0 & 1)); System.out.println("1 AND 0\t\t" + (1 & 0)); System.out.println("1 AND 1\t\t" + (1 & 1)); byte number = 0; System.out.println((int)(~number)); 逻辑、位运算 • 左移(<<)、右移(>>)、>>>运算符 int number = 1; System.out.println( "2的0次: " + number); number =number << 1; System.out.println("2的1次: " + number); number = number << 1; System.out.println("2的2次: " + number); number = number << 1; System.out.println("2的3次:" + number); 00000001 1 00000010 2 00000100 4 00001000 8 递增、递减运算 • 递增、递减运算符 • 将递增或递减运算符撰写在变量之前或变 量之后 int i = 0; System.out.println(++i); System.out.println(--i); int i = 0; int number = 0; number = ++i; //相当於i = i + 1; number = i; System.out.println(number); number = --i; //相当於i = i - 1; number = i; System.out.println(number); 递增、递减运算 • 将递增或递减运算符撰写在变量之前或变 量之后 int i = 0; int number = 0; number = i++; //相当於number = i; i = i + 1; System.out.println(number); number = i--; //相当于number = i; i = i - 1; System.out.println(number); 递增、递减运算 指定运算符 范例 结果 += a += b a = a + b -= a -= b a = a - b *= a *= b a = a * b /= a /= b a = a / b %= a %= b a = a % b &= a &= b a = a & b |= a |= b a = a | b ^= a ^= b a = a ^ b <<= a <<= b a = a << b >>= a >>= b a = a >> b if条件式 • 语法 • 复合陈述句 if(条件式) 陈述句一; else 陈述句二; if(条件式) { 陈述句一; 陈述句二; } else { 陈述句三; 陈述句四; } if条件式 Scanner scanner = new Scanner(System.in); System.out.print("请输入数字: "); int input = scanner.nextInt(); int remain = input % 2; //求除2的余数 if(remain == 1) //如果余数为1 System.out.println(input +"为奇數"); else System.out.println(input +"为偶數"); if条件式 •if中再设定执行的条件 if(条件式一) { 陈述句一; if(条件式二) 陈述句二; 陈述句三; } if(条件式一) { 陈述句一; //其它陈述句 } else if(条件式二) 陈述句二; if(条件式一) { 陈述句一; //其它陈述句 } else if(条件式二) 陈述句二; if条件式 Scanner scanner = new Scanner(System.in); System.out.print("输入分数:"); int score = scanner.nextInt(); if(score >= 90) System.out.println("得A"); else if(score >= 80 && score < 90) System.out.println("得B"); else if(score >= 70 && score < 80) System.out.println("得C"); else if(score >= 60 && score < 70) System.out.println("得D"); else System.out.println("得E(不及格)"); switch条件式 •switch的语法架构 switch(变量名称或表达式) { case符合数字或字符: 陈述句一; break; case符合数字或字符: 陈述句二; break; default: 陈述三; } Scanner scanner = new Scanner(System.in); System.out.print("请输入分數: "); int score = scanner.nextInt(); int level = (int) score/10; switch(level) { case 10: case 9: System.out.println("得A"); break; case 8: System.out.println("得B"); break; case 7: System.out.println("得C"); break; case 6: System.out.println("得D"); break; default: System.out.println("得E(不及格)"); for循环 • 基本语法 for(初始式;判断式;递增式) { 陈述句一; 陈述句二; } for(int j = 1; j < 10; j++) { for(int i = 2; i < 10; i++) { System.out.printf("%d*%d=%2d ",i, j, i * j); } System.out.println(); } for循环 •for括号中的每个陈述区块是以分号';'作区 隔,而在一个陈述区块中若想写两个以上 的陈述句,则使用逗号','作区隔 for (int i = 2, j = 1; j < 10; i = (i==9)?((++j/j)+1):(i+1)) { System.out.printf("%d*%d=%2d%c", i, j, i * j, (i==9 ? '\n' : ' ')); } while循环 Scanner scanner = new Scanner(System.in); int score = 0; int sum = 0; int count = -1; while(score != -1) { count++; sum += score; System.out.print("输入分数(-1结束):"); score = scanner.nextInt(); } System.out.println("平均:" + (double) sum/count) while循环 Scanner scanner = new Scanner(System.in); int input = 0; int replay = 0; do { System.out.print("输入整数值:"); input = scanner.nextInt(); System.out.println("输入数为奇数?" + ((input%2 == 1) ? 'Y': 'N')); System.out.print("继续(1:继续0:结束)?"); replay = scanner.nextInt(); } while(replay == 1); break、continue • break可以离开目前switch、for、while、 dowhile的区块 • continue只会结束其之后区块的陈述句,并 跳回循环区块的开头继续下一个循环 for(int i = 1; i < 10; i++) { if(i == 5) break; System.out.println("i = " + i); } for(int i = 1; i < 10; i++) { if(i == 5) continue; System.out.println("i = " + i); } break、continue • break与continue还可以配合标签使用 back : { for(int i = 0; i < 10; i++) { if(i == 9) { System.out.println("break"); break back; } } System.out.println("test"); } break、continue • break与continue还可以配合标签使用 back1: for(int i = 0; i < 10; i++){ back2: for(int j = 0; j < 10; j++) { if(j == 9) { continue back1; } } System.out.println("test"); } 第4章 • 从autoboxing、unboxing认识对象 – 关于对象 – 自动装箱、拆箱 使用对象 • 想写一个程序取得现在的系统时间,您只 要产生一个java.util.Date工具就可以了 •Date实际上如何向系统取得时间,则无需 您来操心 Date date = new Date(); System.out.println(date.toString()); Tue May 03 16:06:46 GMT+08:00 2005 使用对象 • 字符串就是对象,是java.lang.String类别的 一个实例 String text = "Have a nice day!! :)"; System.out.println("原文:" + text); //传回全为大写的字符串内容 System.out.println("大写:" + text.toUpperCase()); //转回全为小写的字符串内容 System.out.println("小写:" + text.toLowerCase()); //计算字符串长度 System.out.println("长度:" + text.length()); //传回取代文字后的字符串 System.out.println("取代:" + text.replaceAll("nice", "good")); //传回指定位置后的子字符串 System.out.println("子字符串:" + text.substring(5)); 使用对象 • 简单的用户登入程序 System.out.print("使用者名称:"); String username = scanner.next(); System.out.print("用户密码:"); String password = scanner.next(); if("caterpillar".equals(username) && "1975".equals(password)) { System.out.println("秘密信息在此!"); } else { System.out.println(username + "您好,输入的登入数据有误,请重新输入!"); } 包裹(Wrap)基本型态 • Long、Integer、Double、Float、Boolean 等类别是所谓的Wrapper类别 • 主要目的,就是让您提供一个对象实例作 为「壳」,将基本型态包到这个对象之中 • 如此您就可以操作这个对象,就好像您将 基本型态当作对象一样操作 包裹(Wrap)基本型态 int data1 = 10; int data2 = 20; //使用Integer来包里int资料 Integer data1Wrapper = new Integer(data1); Integer data2Wrapper = new Integer(data2); //直接除以3 System.out.println(data1 / 3); //转为double值再除以3 System.out.println(data1Wrapper.doubleValue() / 3); //进行两个值的比较 System.out.println(data1Wrapper.compareTo(data2Wrapper)); 包裹(Wrap)基本型态 自动装箱、拆箱 • 在J2SE5.0之前,要如下才能将int包装为一 个Integer物件 • 在J2SE5.0之后提供了自动装箱的功能 Integer integer = new Integer(10); Integer integer = 10; 自动装箱、拆箱 Integer data1 = 10; Integer data2 = 20; //转为double值再除以3 System.out.println(data1.doubleValue() / 3); //进行两个值的比较 System.out.println(data1.compareTo(data2)); 自动装箱、拆箱 • 自动装箱运用的方法还可以如下: • 更一般化的java.lang.Number类别自动装箱 int i = 10; Integer integer = i; Number number = 3.14f; 自动装箱、拆箱 • 自动拆箱(unboxing) • 在运算时,也可以进行自动装箱与拆箱 Integer fooInteger = 10; int fooPrimitive = fooInteger; Integer i = 10; System.out.println(i + 10); System.out.println(i++); Boolean boo = true; System.out.println(boo && false); 小心使用boxing • 自动装箱与拆箱的功能是编译程序来帮忙 • 自动装箱与拆箱的功能是所谓的「编译程 序蜜糖」(Compilersugar) Integer i = 100; Integer i = new Integer(100); Integer i = null; int j = i; Integer i = null; int j = i.intValue(); NullPointerException 小心使用boxing Integer i1 = 100; Integer i2 = 100; if (i1 == i2) System.out.println("i1 == i2"); else System.out.println("i1 != i2"); Integer i1 = 200; Integer i2 = 200; if (i1 == i2) System.out.println("i1 == i2"); else System.out.println("i1 != i2"); 显示"i1 == i2" 显示"i1 != i2" 小心使用boxing • ‘==’也用于判断两个对象参考名称是否参考 至同一个对象 • 在自动装箱时对于值从-128到127之间的 值,它们被装箱为Integer对象后,会存在 内存之中被重用 Integer i1 = 200; Integer i2 = 200; if (i1.equals(i2)) System.out.println("i1 == i2"); else System.out.println("i1 != i2"); 第5章 • 阵列 – 一维数组、二维数组 – 进阶数组观念 一维数组对象 • 宣告一个数组并初始数组内容 • 指定的索引值不可超出数组范围 – 会发生ArrayIndexOutOfBoundsException • length为数组对象的属性成员 int[] score = {90, 85, 55, 94, 77}; for(int i = 0; i < score.length; i++) System.out.printf("score[%d] = %d\n", i, score[i]); 一维数组对象 • 当您宣告一个数组时,其实就是在配置一 个数组对象 • 一个完整的数组宣告方式如下 int[] arr = new int[10]; 一维数组对象 数据型态 初始值 byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char \u0000 boolean false 一维数组对象 int[] arr = new int[10]; System.out.print("arr初始值: "); for(int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); arr[i] = i; } System.out.print("\narr设定值: "); for(int i = 0; i < arr.length; i++) System.out.print(arr[i] + " "); System.out.println(); 一维数组对象 • 在使用new新增数组时一并指定初始值 int[] score = new int[] {90, 85, 55, 94, 77}; for(int i = 0; i < score.length; i++) System.out.printf("score[%d] = %d\n", i, score[i]); 一维数组对象 int length = scanner.nextInt(); float[] score = new float[length]; //动态配置长度 for(int i = 0; i < score.length; i++) { System.out.print("输入分数:"); float input = scanner.nextFloat(); score[i] = input; } • 可以使用动态的方式来宣告数组长度,而 不用在程序中事先决定数组大小 二维数组对象 • 二维数组使用「名称」与「两个索引」来 指定存取数组中的元素 • 以对象的方式来配置一个二维数组对象 int[][] arr = {{1, 2, 3}, {4, 5, 6}}; for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[0].length; j++) System.out.print(arr[i][j] + " "); System.out.println(); } int[][] arr = new int[2][3]; 二维数组对象 • 以对象的方式来配置一个二维数组对象 int[][] arr = new int[2][3]; 二维数组对象 int[][] arr = {{1, 2, 3}, {4, 5, 6}}; int[] foo = arr[0]; //将arr[0]所参考的数组对象指定给foo for(int i = 0; i < foo.length; i++) { System.out.print(foo[i] + " "); } System.out.println(); foo = arr[1]; //将arr[1]所参考的数组对象指定给foo for(int i = 0; i < foo.length; i++) { System.out.print(foo[i] + " "); } System.out.println(); 二维数组对象 • 使用new配置二维数组一并指定初值 • 宣告三维以上的数组 int[][] arr = new int[][] {{1, 2, 3}, {4, 5, 6}}; nt[][][] arr = { {{1, 2, 3}, {4, 5, 6}}, {{7, 8, 9}, {10, 11, 12}} }; int[][][] arr = new int[2][2][3]; 不规则数组 int arr[][]; arr = new int[2][]; arr[0] = new int[3]; // arr[0]参考至长度为3的一维数组 arr[1] = new int[5]; // arr[1]参考至长度为5的一维数组 for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[i].length; j++) arr[i][j] = j + 1; } for(int i = 0; i < arr.length; i++) { for(int j = 0; j < arr[i].length; j++) System.out.print(arr[i][j] + " "); System.out.println(); } 进阶的数组操作 • 一维数组的参考名称之宣告 • 将同一个对象指定给两个参考名称 int[] arr = null; int[] arr1 = {1, 2, 3, 4, 5}; int[] tmp1 = arr1; int[] tmp2 = arr1; System.out.print("透过tmp1取出数组值:"); for(int i = 0; i < tmp1.length; i++) System.out.print(tmp1[i] + " "); 进阶的数组操作 • 将同一个对象指定给两个参考名称 System.out.print("\n透过tmp2取出数组值:"); for(int i = 0; i < tmp2.length; i++) System.out.print(tmp2[i] + " "); tmp1[2] = 9; System.out.print("\n\n透过tmp1取出数组值:"); for(int i = 0; i < tmp1.length; i++) System.out.print(tmp1[i] + " "); System.out.print("\n透过tmp2取出数组值:"); for(int i = 0; i < tmp2.length; i++) System.out.print(tmp2[i] + " "); System.out.println(); 进阶的数组操作 • 将同一个对象指定给两个参考名称 int[] arr1 = {1, 2, 3, 4, 5}; int[] tmp1 = arr1; int[] tmp2 = arr1; 进阶的数组操作 • int[]arr之后,arr是一维数组的参考名称, 可以参考至任何长度的一维数组对象 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = {5, 6, 7}; int[] tmp = arr1; System.out.print("使用tmp取出arr1中的元素:"); for(int i = 0; i < tmp.length; i++) System.out.print(tmp[i] + " "); tmp = arr2; System.out.print("\n使用tmp取出arr2中的元素:"); for(int i = 0; i < tmp.length; i++) System.out.print(tmp[i] + " "); System.out.println(); 数组复制 • 使用循环作数组复制 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = new int[5]; for(int i = 0; i < arr1.length; i++) arr2[i] = arr1[i]; for(int i = 0; i < arr2.length; i++) System.out.print(arr2[i] + " "); System.out.println(); 数组复制 • 使用System类别所提供的arraycopy()方法 • 在JDK6中,也为Arrays类别新增了数组复 制的copyOf()方法 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = new int[5]; System.arraycopy(arr1, 0, arr2, 0, arr1.length); for(int i = 0; i < arr2.length; i++) System.out.print(arr2[i] + " "); System.out.println(); Arrays类别 名称 说明 sort() 帮助您对指定的数组排序,所使用的是快速排序法 binarySearch() 让您对已排序的数组进行二元搜寻,如果找到指定的值就 传回该值所在的索引,否则就传回负值 fill() 当您配置一个数组之后,会依数据型态来给定默认值,例 如整数数组就初始为 0,您可以使用Arrays.fill()方法来将 所有的元素设定为指定的值 equals() 比较两个数组中的元素值是否全部相等,如果是将传回 true,否则传回 false 数组比较 • 不可用'=='比较两个数组的元素值是否相等 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = {1, 2, 3, 4, 5}; int[] tmp = arr1; System.out.println(arr1 == tmp); System.out.println(arr2 == tmp); 数组比较 • deepEquals()与deepToString() int[][] arr1 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int[][] arr2 = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; int[][] arr3 = {{0, 1, 3}, {4, 6, 4}, {7, 8, 9}}; System.out.println("arr1内容等于arr2 ? " + Arrays.deepEquals(arr1, arr2)); System.out.println("arr1内容等于arr3 ? " + Arrays.deepEquals(arr1, arr3)); System.out.println("arr1 deepToString()\n\t" + Arrays.deepToString(arr1)); foreach与数组 • 加强的for循环(Enhanced forLoop) • J2SE 5.0之前 • 在J2SE5.0之后 for(type element : array) { System.out.println(element).... } int[] arr = {1, 2, 3, 4, 5}; for(int i = 0; i < arr.length; i++) System.out.println(arr[i]); int[] arr = {1, 2, 3, 4, 5}; for(int element : arr) System.out.println(element); foreach与数组 • 如果是对象的话 • 二维数组 String[] names = {"caterpillar", "momor", "bush"}; for(String name : names) System.out.println(name); int[][] arr = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; for(int[] row : arr) { for(int element : row) { System.out.println(element); } } 对象数组 • 以下产生几个对象? • 以下的宣告产生几个对象? • 以下产生几个对象? int[] arr = new int[3]; int[][] arr = new int[2][3]; Integer[] arr = new Integer[3]; 对象数组 • 以下的宣告产生几个对象? Integer[][] arr = new Integer[2][3]; 第6章 • 字串 – 认识字符串(String) – 字符串进阶运用 String类别 • 在某些程序语言中,字符串是以字符数组 的方式存在 • 在Java中字符串不仅仅是字符数组,而是 String类别的一个实例 String text ="字符串的使用"; System.out.println(text); String类别 • 字符串必须使用“”来包括您的文字 • 字符串的字符是使用Unicode字符来建构 • 字符串的串接在Java中可以直接使用'+' String msg ="哈啰!"; msg = msg + "Java程序设计!"; System.out.println(msg); String类别 • 字符串在Java中以String类别的一个实例存 在方法 说明 length() 取得字符串的字符长度 equals() 判断原字符串中的字符是否相等于指定字符串中的字符 toLowerCase() 转换字符串中的英文字符为小写 toUpperCase() 转换字符串中的英文字符为大写 String text = "hello"; System.out.println("字符串内容: " + text); System.out.println("字符串长度: " + text.length()); System.out.println("等於hello? " + text.equals("hello")); System.out.println("转为大寫: " + text.toUpperCase()); System.out.println("转为小寫: " + text.toLowerCase()); String类别 • 将输入的字符串转换为整数、浮点数等 • 指定的字符串无法剖析为指定的数据型态数值, 则会发生NumberFormatException例外 方法 说明 Byte.parseByte(字符串) 将字符串剖析为位 Short.parseShort(字符串) 将字符串剖析为short整数 Integer.parseInt(字符串) 将字符串剖析为int整数 Long.parseLong(字符串) 将字符串剖析为long整数 Float.parseFloat(字符串) 将字符串剖析为float浮点数 Double.parseDouble(字符串) 将字符串剖析为double浮点数 String类别 • 以配置对象的观念来宣告字符串 • 两种宣告方式是有所差别的 String str = new String("caterpillar"); String str = "caterpillar"; String类别 • 使用索引取得字符的相关方法 方法 说明 char charAt(int index) 传回指定索引处的字符 int indexOf(int ch) 传回指定字符第一个找到的索引位置 int indexOf(String str) 传回指定字符串第一个找到的索引位置 int lastIndexOf(int ch) 传回指定字符最后一个找到的索引位置 String substring(int beginIndex) 取出指定索引处至字符串尾端的子字符串 String substring(int beginIndex, int endIndex) 取出指定索引范围子字符串 char[] toCharArray() 将字符串转换为字符数组 String类别 • endsWith()方法 String[] filenames = {"caterpillar.jpg", "cater.gif", "bush.jpg", "wuwu.jpg", "clockman.gif"}; System.out.print("过滤出jpg檔案: "); for(int i = 0; i < filenames.length; i++) { if(filenames[i].endsWith("jpg")) { System.out.print(filenames[i] + " "); } } System.out.println(""); 不可变(immutable)字符串 • 一个字符串对象一旦被配置,它的内容就 是固定不可变的(immutable) • 不要以为下面的陈述就是改变一个字符串 对象的内容 String str = "Just"; str = "Justin"; 不可变(immutable)字符串 • 对于一些可以共享的字符串对象,会先在 String池中查找是否存在相同的String内容 • 当您直接在程序中使用""来包括一个字符串 时,该字符串就会在String池中 String str1 = "flyweight"; String str2 = "flyweight"; System.out.println(str1 == str2); 不可变(immutable)字符串 •String的intern()方法 – 如果池(Pool)中已经包括了相同的String对 象(相同与否由equals()方法决定),那么会 从池中返回该字符串 – 否则的话原String对象会被加入池中,并返回 这个String对象的参考 不可变(immutable)字符串 String str1 = "fly"; String str2 = "weight"; String str3 = "flyweight"; String str4 = null; str4 = str1 + str2; System.out.println(str3 == str4); str4 = (str1 + str2).intern(); System.out.println(str3 == str4); 不可变(immutable)字符串 不可变(immutable)字符串 • 不可用‘==’比较字符串的字符内容是否相同 • 要比较两个字符串对象的字符值是否相 同,您要使用equals()方法 String str1 = new String("caterpillar"); String str2 = new String("caterpillar"); System.out.println(str1 == str2); String str1 = new String("caterpillar"); String str2 = new String("caterpillar"); System.out.println(str1.equals(str2)); StringBuilder类别 • 使用‘+’来串接字符串以达到附加新字符或 字符串的目的,但‘+’会产生一个新的String 实例 • 不建议使用'+'来进行字符串的串接 StringBuilder类别 String text = ""; long beginTime = System.currentTimeMillis(); for(int i = 0; i < 10000; i++) text = text + i; long endTime = System.currentTimeMillis(); System.out.println("运行时间:" + (endTime - beginTime)); StringBuilder builder = new StringBuilder(""); beginTime = System.currentTimeMillis(); for(int i = 0; i < 10000; i++) builder.append(String.valueOf(i)); endTime = System.currentTimeMillis(); System.out.println("运行时间:" + (endTime - beginTime)); 命令行自变量 命令行自变量 • 在main()的参数列撰寫String[]args,目的就 是用来接受一个字符串数组 public static void main(String[] args) { System.out.print("读入的引數: "); for(int i = 0; i < args.length; i++) System.out.print(args[i] + " "); System.out.println(); } java CommandLineArg -file student.dat 读入的自变量: -file student.dat 分离字符串 • 使用String的split() String[] fakeFileData = { "justin\t64/5/26\t0939002302\t5433343", "momor\t68/7/23\t0939100391\t5432343" }; for(String data : fakeFileData) { String[] tokens = data.split("\t"); for(String token : tokens) { System.out.print(token + "\t| "); } System.out.println(); } 使用正则表示式 •matches()、replaceAll()等方法时使用 •Java在J2SE1.4之后开始支持正则表示式 • 可以在API文件的java.util.regex.Pattern类 别中找到支持的正则表示式相关信息 使用正则表示式 • 几个常用的字符比对符号 方法 说明 . 符合任一字符 \d 符合0到9任一个数字字符 \D 符合0-9以外的字符 \s 符合'\t'、'\n'、'\x0B'、'\f'、'\r'等空格符 \w 符合a到z、A到Z、0到9等字符,也就是数字或是字母都符合 \W 符合a到z、A到Z、0到9等之外的字符,也就是除数字与字母外 都符合 使用正则表示式 String text = "abcdebcadxbc"; String[] tokens = text.split(".bc"); for(String token : tokens) { System.out.print(token + " "); } System.out.println(); tokens = text.split("..cd"); for(String token : tokens) { System.out.print(token + " "); } System.out.println(); 使用正则表示式 • Character class 范例 作用 [abc] 符合a、b或c [^abc] 符合「a或b或c」之外的字符 [a-zA-Z] 符合a到z或者是A到Z的字符 [a-d[m-p]] a到d或者是m到p,也可以写成[a-dm-p] [a-z&&[def]] a到z并且是d或e或f,结果就是d或e或f可以符合 [a-z&&[^bc]] a到z并且不是b或c [a-z&&[^m-p]] a到z并且不是m到p 使用正则表示式 • Greedy quantifiers 范例 作用 X? X可出现一次或完全没有 X* X可出现零次或多次 X+ X可出现一次或多次 X{n} X可出现n次 X{n,} X可出现至少n次 X{n, m} X可出现至少n次,但不超过m次 X? X可出现一次或完全没有 Pattern、Matcher • 将正则表示式视为一个对象来重复使用, 可用Pattern的静态方法compile()进行编译 • compile()方法会传回一个Pattern的实例, 这个实例代表您的正则表示式 Pattern、Matcher String phones1 = "Justin的手机号码:0939-100391\n" + "momor的手机号码:0939-666888\n"; Pattern pattern = Pattern.compile(".*0939-\\d{6}"); Matcher matcher = pattern.matcher(phones1); while(matcher.find()) { System.out.println(matcher.group()); } String phones2 = "caterpillar的手机号码:0952-600391\n" + "bush的手机号码:0939-550391"; matcher = pattern.matcher(phones2); while(matcher.find()) { System.out.println(matcher.group()); Pattern、Matcher String text = "abcdebcadxbc"; Pattern pattern = Pattern.compile(".bc"); Matcher matcher = pattern.matcher(text); while(matcher.find()) { System.out.println(matcher.group()); } System.out.println(); 第7章 • 封装 – 定义类别(Class) – 关于方法 以对象思考问题 • 有一个帐户,帐户中有存款余额,您可以 对帐户进行存款与提款的动作,并可以查 询以取得存款余额。 – 识别问题中的对象与属性 – 识别对象上的方法 以对象思考问题 使用class定义类别 • 在Java中使用"class"关键词来定义类别 public class Account { private String accountNumber; privatedouble balance; public Account(){ this("empty", 0.0); } public Account(String accountNumber, double balance) { this.accountNumber = accountNumber; this.balance = balance; } … 定义类别 定义建构方法 使用class定义类别 • 在Java中使用"class"关键词来定义类别 … publicString getAccountNumber() { return accountNumber; } publicdouble getBalance() { return balance; } publicvoid deposit(double money) { balance += money; } publicdouble withdraw(double money) { balance -= money; return money; } } 定义成员 使用class定义类别 • 可根据类别来建构对象 • 要透过公开成员来操作对象或取得对象信 息的话,可以在对象名称后加上「.」运算 符来进行 Account account1= newAccount(); Account account2 =newAccount("123-4567", 100.0); account1.getBalance(); account1.deposit(1000.0); 使用class定义类别 Account account = new Account(); System.out.println("帐戶: " + account.getAccountNumber()); System.out.println("余額: " + account.getBalance()); account = new Account("123-4567", 100.0); account.deposit(1000.0); System.out.println("帐戶: " + account.getAccountNumber()); System.out.println("余額: " + account.getBalance()); 类别成员(Classmember) • 类别成员可用的访问权限修饰词有 “public”、“protected”、“private”三个 • 在宣告成员时不使用存取修饰词,则预设 以「套件」(package)为存取范围 类别成员(Classmember) • 数据成员被宣告为“private”,表示它是 「私 用成员」(Privatemember),私用成员只 能在类别中被使用 • 方法被宣告为"public",表示这些方法可以 藉由对象的参考名称加上"."直接呼叫 存取修饰 传回值型态 方法名称(参数列) { //实作 return传回值; } 类别成员(Classmember) • 方法区块中可以宣告变量(Variable),参 数在方法区块执行结束后就会自动清除 • 方法中的相同变量名称会暂时覆盖数据成 员的作用范围 • 可以使用"this"关键词来特别指定 类别成员(Classmember) class MethodDemo { private int data = 10; public void scopeDemo() { // void表示没有传回值 int data = 100; } public int getData() { return data; } public void setData(int data) { // void表示没有传回值 data = data; //这样写是没用的 //写下面这个才有用 // this.data = data; } } 类别成员(Class member) • 信息的最小化 – 如果数据成员能不公开就不公开 • 透过公开方法存取私用成员的好处 – 如果存取私用成员的流程有所更动,只要在公 开方法中修改就可以了 public double withdraw(double money) { if(balance – money < 0) { return 0; } else { balance -= money; return money; } } 建构方法(Constructor) • 建构方法是与类别名称相同的公开方法成 员,且没有传回值 public class SafeArray { // .. public SafeArray() { //建构方法 // .... } public SafeArray(参数列) {//建构方法 // .... } } 建构方法(Constructor) public class SafeArray { private int[] arr; public SafeArray() { this(10); //预设10个元素 } public SafeArray(int length) { arr = new int[length]; } … } 关于this 方法成员在内存中会只有一份 关于this • 使用参考名称来呼叫对象的方法成员时, 程序会将对象的参考告知方法成员 • 在方法中所撰写的每一个数据成员其实会 隐含一个this参考名称 • this名称参考至呼叫方法的对象 public double getBalance() { return this.balance; } 关于this • 在方法中使用数据成员时,都会隐含的使 用this名称 public Account(String accountNumber, double balance) { this.accountNumber = accountNumber; this.balance = balance; } public Account(String number, double money) { accountNumber = number; //实际等于this.accountNumber = number; this.balance = money; //实际等于this.balance = money; } 关于this • this还有一种可以带自变量的用法,主要是 用于呼叫建构方法 public class Ball { private String name; public Ball() { this(“No name”); //会使用Ball(“No name”)来建构 } public Ball(String name) { this.name = name; .... } } 关於static • 被宣告为“static”的数据成员,又称「静态 数据成员」 • 静态成员是属于类别所拥有,而不是个别 的对象 • 可以将静态成员视为每个对象实例所共享 的数据成员 public class Ball { public static double PI = 3.14159; //宣告static资料 ... } 关于static • 属于类别所拥有,可以在不使用名称参考 下,直接使用类别名称加上‘.’运算符来存取 • 同样遵守“public”、“protected”与“private”的 存取限制 • 设定为“public”成员的话就可以如下存取 • 下面的方式是不被鼓励的 System.out.println("PI = " + Ball.PI); Ball ball = new Ball(); System.out.println("PI = " + ball.PI); 关于static • 可以宣告方法成员为"static"方法,又称 「静态方法」 public class Ball { ... public static double toRadian(double angle) { return 3.14159 / 180 * angle; } } System.out.println("角度90等于径度" + Ball.toRadian (90)); 关于static • 静态方法中不会有this参考名称 • 静态方法中不允许使用非静态成员 • 在静态方法中不能呼叫非静态方法 non-static variable test cannot be referenced from a static context non-static method showHello() cannot be referenced from a static context 关于static • 可以使用“static”定义一个静态区块,并在 当中撰写类别载入时的初始化动作 • 在类别被加载时,默认会先执行静态区块 中的程序代码,且只会执行一次 public class Ball { static { //一些初始化程序代码 } .... } 重载(Overload)方法 • 为类似功能的方法提供统一名称,可根据 参数列的不同而自动呼叫对应的方法 •String的valueOf()方法就提供了多个版本 static String valueOf(boolean b) static String valueOf(char c) static String valueOf(char[] data) static String valueOf(char[] data, int offset, int count) static String valueOf(double d) static String valueOf(float f) static String valueOf(int i) static String valueOf(long l) static String valueOf(Object obj) 重载(Overload)方法 • 参数个数也可以用来设计方法重载 public class SomeClass { //以下重载了someMethod()方法 public void someMethod() { // ... } public void someMethod(int i) { // ... } public void someMethod(float f) { // ... } public void someMethod(int i, float f) { // ... } } 重载(Overload)方法 • 返回值型态不可用作为方法重载的区别根 据,以下是不正确的 public class SomeClass { public int someMethod(int i) { // ... return 0; } public double someMethod(int i) { // ... return 0.0; } } 重载(Overload)方法 • 注意到autoboxing、unboxing的问题 public static void main(String[] args) { someMethod(1); } public static void someMethod(int i) { System.out.println("int版本被呼叫"); } public static void someMethod(Integer integer) { System.out.println("Integer版本被呼叫"); } 重载(Overload)方法 • 注意到autoboxing、unboxing的问题 – 找寻在还没有装箱动作前可以符合自变量个数 与型态的方法 – 尝试装箱动作后可符合自变量个数与型态的方 法 – 尝试设有「不定长度自变量」并可以符合的方 法 – 编译程序找不到合适的方法,回报编译错误 不定长度自变量 • J2SE5.0之后开始支持「不定长度自变量」 (Variable-lengthArgument) • 实际上nums是一个数组 public static int sum(int... nums) { //使用...宣告参数 int sum = 0; for(int num : nums) { sum += num; } return sum; } 不定长度自变量 • 宣告的参数必须设定在参数列的最后一 个,下面的方式是合法的 • 下面的方式是不合法的 public void someMethod(int arg1, int arg2, int... varargs) { // .... } public void someMethod(int... varargs, int arg1, int arg2) { // .... } 不定长度自变量 • 没办法使用两个以上的不定长度自变量, 下面的方式是不合法的 • 如果使用对象的不定长度自变量,宣告的 方法相同 public void someMethod(int... varargs1, int... varargs2) { // .... } public void someMethod(SomeClass... somes) { // .... } 递归方法 • 在方法中呼叫自身同名方法,而呼叫者本 身会先被置入内存「堆栈」(Stack)中 • 堆栈是一种「先进后出」(First in, lastout) 的数据结构 private static int gcd(int m, int n) { if(n == 0) return m; else return gcd(n, m % n); } 垃圾收集 •Java提供垃圾收集机制 • 在适当的时候,Java执行环境会自动检查 对象,看看是否有未被参考的对象 • 如果有的话就清除对象、回收对象所占据 的内存空间 • 在程序执行的空闲时候,您可以建议执行 环境进行垃圾收集,但也仅止于建议 垃圾收集 • finalize()会在对象被回收时执行 • 因为不知道对象资源何时被回收,所以也 就不知道finalize()真正被执行的时间 垃圾收集 public class GcTest { private String name; public GcTest(String name) { this.name = name; System.out.println(name + "建立"); } //对象回收前执行 protected void finalize() { System.out.println(name + "被回收"); } }. 垃圾收集 System.out.println("请按Ctrl +C终止程式........"); GcTest obj1 = new GcTest("object1"); GcTest obj2 = new GcTest("object2"); GcTest obj3 = new GcTest("object3"); //令名称不参考至对象 obj1 = null; obj2 = null; obj3 = null; //建议回收对象 System.gc(); while(true); //不断执行程序 第8章 • 继承、多型 – 继承 – 多型 扩充(extends)父类别 • 使用"extends"作为其扩充父类别的关键词 public class Bird { private String name; public Bird() { } public Bird(String name) { this.name = name; } public void walk() { System.out.println("走路"); } public String getName() { return name; } public void setName(String name) { this.name = name; } } 扩充(extends)父类别 public class Chickenextends Bird { //扩充Bird类别 private String crest; //新增私有成员,鸡冠描述 public Chicken() {super(); } //定义建构方法 public Chicken(String name, String crest) { super(name); this.crest = crest; } //新增方法 public void setCrest(String crest) { this.crest = crest; } public String getCrest() { return crest; } public void wu() { System.out.println("咕咕叫…"); } } 扩充(extends)父类别 Chicken chicken1 = new Chicken("小克","红色小鸡冠"); Chicken chicken2 = new Chicken(); System.out.printf("小雞1 -名称%s,鸡冠是%s。\n", chicken1.getName(), chicken1.getCrest()); chicken1.wu(); System.out.printf("小雞2 -名称%s,鸡冠是%s。\n", chicken2.getName(), chicken2.getCrest()); chicken2.wu(); 被保护的(protected)成员 • 保护意思表示存取该成员是有条件限制的 • 继承的类别就可以直接使用这些成员,但 这些成员仍然受到保护 • 不同套件(package)的对象不可直接呼叫 使用protected成员 被保护的(protected)成员 public class Rectangle { //受保护的member protected int x; protected int y; protected int width; protected int height; … } public class CubicextendsRectangle { … public int getVolumn() { //可以直接使用父类别中的width、height成员 return length*width*height; } } 重新定义(Override)方法 • 如果父类别中的定义并不符合您的需求, 可以在扩充类别的同时重新定义 • 可以重新定义方法的实作内容、成员的访 问权限,或是成员的返回值型态 重新定义(Override)方法 public class SimpleArray { protected int[] array; public SimpleArray(int i) { array = new int[i]; } public void setElement(int i, int data) { array[i] = data; } .... } public class SafeArrayextends SimpleArray { … //重新定义setElement() public void setElement(int i, int data) { if(i < array.length) super.setElement(i, data); } .... } 重新定义(Override)方法 SimpleArray simpleArray = new SafeArray(); simpleArray.setElement(); • 实际运作的对象是SafeArray的实例,所以 被呼叫执行的会是SafeArray中重新定义过 的setElement()方法 重新定义(Override)方法 • 在衍生类别中想要呼叫基类的建构方法, 可以使用super()方法 • 要在衍生类别中呼叫基类方法,则可以如 使用super.methodName() • 条件限制 – 父类别中的方法或建构方法不能是"private", 也就是不能是私用成员 重新定义(Override)方法 • 您可以增大父类别中的方法权限,但不可 以缩小父类别的方法权限 – 若原来成员是"public"的话,您不可以在父类别 中重新定义它为"private"或"protected" public class SafeArray extends SimpleArray { //不可以缩小父类别中同名方法的权限 private void setElement(int i, int data) { .... } } setElement(int,int) in SafeArray cannot override setElement(int,in t) in SimpleArray; attempting to assign weaker accessprivileges; was publicprivate void setElement(int i, int data) {^1 error 重新定义(Override)方法 • 从J2SE5.0开始在重新定义方法时,您可以 重新定义返回值的型态 public class Bird { protected String name; public Bird(String name) { this.name = name; } public Bird getCopied { return new Bird(name); } } 重新定义(Override)方法 public class Chicken extends Bird { protected String crest; public Chicken(String name, String crest) { super(name); this.crest = crest; } //重新定义返回值型态为Chicken publicChicken getCopied() { return new Chicken(name, crest); } } • 重新定义的返回值型态必须是父类别中同一方法 返回型态的子类别 • 无法重新定义static方法 Object类别 • Object是Java程序中所有类别的父类别 • 每个类别都直接或间接继承自Object类别 public class Foo { //实作 } public class Foo extends Object { //实作 } toString()、equals()、hashCode() • toString()方法是对对象的文字描述 • Object的toString()预设会传回类别名称及 16进位制的编码 • 预设的equals()本身是比较对象的内存地址 是否相同 getClass().getName() + '@' + Integer.toHexString(hashCode()) toString()、equals()、hashCode() • 可以重新定义equals()方法,以定义您自己 的对象在什么条件下可视为相等的对象 • 在重新定义equals()方法时,建议同时重新 定义hashCode()方法 toString()、equals()、hashCode() public boolean equals(Object other) { if (this == other) return true; if (!(other instanceof Cat)) return false; final Cat cat = (Cat) other; if (!getName().equals(cat.getName())) return false; if (!getBirthday().equals(cat.getBirthday())) return false; return true; } public int hashCode() { int result = getName().hashCode(); result = 29 * result + getBirthday().hashCode(); return result; } clone()方法 • 如何复制对象本身 • 最基本的作法:实作java.lang.Cloneable界 面(Interface) public class PointimplementsCloneable { //要实作Cloneable … public Object clone() throws CloneNotSupportedException { //呼叫父类别的clone()来进行复制 return super.clone(); } } clone()方法 public class TableimplementsCloneable { //要实作Cloneable private Point center; // … public Object clone () throws CloneNotSupportedException { //呼叫父类的clone()来复制 Table table = (Table) super.clone(); if(this.center != null) { //复制Point类型的数据成员 table.center = (Point) center.clone(); } return table; } } clone()方法 Table table = new Table(); table.setCenter(new Point(2, 3)); Point originalCenter = table.getCenter(); Table clonedTable = (Table) table.clone(); Point clonedCenter = clonedTable.getCenter(); System.out.printf("原来的Table中心:(%d, %d)\n", originalCenter.getX(), originalCenter.getY()); System.out.printf("复制的Table中心:(%d, %d)\n", clonedCenter.getX(), clonedCenter.getY()); clonedCenter.setX(10); clonedCenter.setY(10); //改变复制品的内容,对原来的对象不会有影响 System.out.printf("原来的Table中心:(%d, %d)\n", originalCenter.getX(), originalCenter.getY()); System.out.printf("复制的Table中心:(%d, %d)\n", clonedCenter.getX(), clonedCenter.getY()); final关键词 • “final”关键词使用在变量宣告时,表示该变 量设定之后,就不可以再改变该变量的值 • 如果在定义方法成员时使用"final",表示该 方法成员在无法被子类别重新定义 final double PI = 3.14159; public class Ball { private double radius; publicfinal double getRadius() { return radius; } // .... } final关键词 • 如果您在宣告类别时加上final关键词,则表 示要终止被扩充 publicfinal class Ball { // .... } 多型导论 • 如果使用不正确类别型态转换对象操作接 口,会发生java.lang.ClassCastException 多型导论 • 定义了两个execute()方法来分别操作 Class1与Class2的实例 • execute()分别依赖了Class1与Class2两个 类别 public void execute(Class1 c1) { c1.doSomething(); } public void execute(Class2 c2) { c2.doSomething(); } 多型导论 • 将程序中的execute()改成 • 只依赖ParentClass,程序对个别对象的依 赖程序降低了,日后在修改、维护或调整 程序时的弹性也增加了 public void execute(ParentClass c) { c.doSomething(); } 多型导论 • 实际上在设计并不依赖于具体类别,而是 依赖于抽象 •Java中在实现多型时,可以让程序依赖于 「抽象类」(Abstractclass)或是「接口」 (Interface) 抽象类(Abstract class) public class ConcreteCircle { private double radius; public void setRedius(int radius) { this.radius = radius; } public double getRadius() { return radius; } public void render() { System.out.printf("画一个半径%f的实心圆\n", getRadius()); } } public class HollowCircle { private double radius; public void setRedius(int radius) { this.radius = radius; } public double getRadius() { return radius; } public void render() { System.out.printf("画一个半径%f的空心圆\n", getRadius()); } } 抽象类(Abstractclass) • 要宣告抽象方法与抽象类,您要使用 "abstract"关键词 publicabstract class AbstractCircle { protected double radius; public void setRedius(int radius) { this.radius = radius; } public double getRadius() { return radius; } public abstract void render(); } 抽象类(Abstract class) public class ConcreteCircle extends AbstractCircle { public ConcreteCircle() {} public ConcreteCircle(double radius) { this.radius = radius; } public void render() { System.out.printf("画一个半径%f的实心圆\n", getRadius()); } } public class HollowCircle extends AbstractCircle { public HollowCircle() {} public HollowCircle(double radius) { this.radius = radius; } public void render() { System.out.printf("画一个半径%f的空心圆\n", getRadius()); } } 抽象类(Abstract class) public class CircleDemo { public static void main(String[] args) { renderCircle(new ConcreteCircle(3.33)); renderCircle(new HollowCircle(10.2)); } public static void renderCircle(AbstractCircle circle) { circle.render(); } } 抽象类应用 publicabstract class AbstractGuessGame { … public void start() { showMessage("欢迎"); int guess = 0; do { guess =getUserInput(); if(guess > number) { showMessage("输入的数字较大"); } else if(guess < number) { showMessage("输入的数字较小"); } else { showMessage("猜中了"); } } while(guess != number); } protectedabstractvoid showMessage(String message); protectedabstract int getUserInput(); } 抽象类应用 public class TextModeGameextends AbstractGuessGame { private Scanner scanner; public TextModeGame() { scanner = new Scanner(System.in); } protected void showMessage(String message) { for(int i = 0; i < message.length()*2; i++) { System.out.print("*"); } System.out.println("\n"+ message); for(int i = 0; i < message.length()*2; i++) { System.out.print("*"); } } protected int getUserInput() { System.out.print("\n输入数字:"); return scanner.nextInt(); } } 抽象类应用 • 藉由在抽象类中先定义好程序的执行流 程,并将某些相依方法留待子类别中执行 AbstractGuessGame guessGame = new TextModeGame(); guessGame.setNumber(50); guessGame.start(); 界面(Interface) • 继承某抽象类的类别必定是该抽象类的一 个子类 • 实作某接口的类别并不被归属于哪一类 – 一个对象上可以实作多个接口 • 接口的宣告是使用"interface"关键词 [public] interface接口名称{ 权限设定 传回型态 方法(参数列); 权限设定 传回型态 方法(参数列); // .... } 界面(Interface) • 在宣告接口时方法上的权限设定可以省 略,如果省略的话,预设是"public public interface IRequest { public void execute(); } 界面(Interface) public class HelloRequestimplements IRequest { private String name; public HelloRequest(String name) { this.name = name; } public void execute() { System.out.printf("哈啰%s!%n", name); } } public class WelcomeRequestimplements IRequest { private String place; public WelcomeRequest(String place) { this.place = place; } public void execute() { System.out.printf("欢迎来到%s!%n", place); } } 界面(Interface) public static void main(String[] args) { for(int i = 0; i < 10; i++) { int n = (int) (Math.random() * 10) % 2; //随机产生 switch (n) { case 0: doRequest(new HelloRequest("良葛格")); break; case 1: doRequest(new WelcomeRequest("Wiki网站")); } } } public static voiddoRequest(IRequestrequest) { request.execute(); } 界面(Interface) • 在Java中您可以一次实作多个接口 • 必要时必须作「接口转换」,如此程序才 知道如何正确的操作对象 public class类别名称implements界面1,界面2,界面3 { //界面实作 } ISomeInterface1 obj1 = (ISomeInterface1) someObject; obj1.doSomeMethodOfISomeInterface1(); ISomeInterface2 obj2 = (ISomeInterface2) someObject;obj2.doSomeMethodOfISomeInterface2(); 界面(Interface) • 接口也可以进行继承的动作,同样也是使 用“extends”关键词来继承父接口 • 一个接口可以同时继承多个父接口,实作 子接口的类别必须将所有在父接口和子接 口中定义的方法实作出来 public interface名称extends界面1,界面2 { // ... } 第9章 • 管理类别档案 – 内部类别 – package与import 成员内部类别、区域内部类别 • 成员内部类别,基本上是在一个类别中直 接宣告另一个类别 • 所产生的文件名为「外部类别名称$内部类 别名称.class」 public class OuterClass { //内部类别 private class InnerClass { // .... } } 成员内部类别、区域内部类别 • 区域内部类别定义于一个方法中,类别的 可视范围与生成之对象仅止于该方法之中 • 内部类别还可以被宣告为"static“ • 由于是“static”,它不能存取外部类别的方 法,而必须透过外部类别所生成的对象来 进行呼叫 成员内部类别、区域内部类别 • 被宣告为static的内部类别,事实上也可以 看作是另一种名称空间的管理方式 public class Outer { public static class Inner { .... } .... } Outer.Inner inner = new Outer.Inner(); 匿名内部类别 • 内部匿名类别可以是继承某个类别或是实 作某个接口 new[类别或接口()] { //实作 } Object obj = new Object() { public String toString() { //重新定义toString() return"匿名类别物件"; } }; System.out.println(obj); 匿名内部类别 • 注意如果要在内部匿名类别中使用外部的 局部变量,变量在宣告时必须為"final" .... public void someMethod() { finalint x = 10; //宣告final Object obj = new Object() { public String toString() { return String.valueOf(x); //x可在匿名类别中使用 } }; System.out.println(obj); } .... 匿名内部类别 • 局部变量x并不是真正被拿来于内部匿名类 别中使用 •x会被匿名类别复制作为数据成员来使用 • 编译程序会要求您加上“final”关键词,这样 您就知道不能在内部匿名类别中改变x的值 • 内部匿名类别在编译完成之后会产生「外 部类别名称$编号.class」,编号为1、2、 3...n,每个编号n的档案对应于第n个匿名 类别 设定套件(package) • 套件被设计与文件系统结构相对应 • 为了要能建立与套件相对应的文件系统结 构,您在编译时可以加入"-d"参数,并指定 产生的类别档案要储存在哪一个目录之下 package onlyfun.caterpillar; public class PackageDemo { public static void main(String[] args) { System.out.println("Hello! World!"); } } 设定套件(package) • javac -d . UsePackage.java • 在目前的工作位置中会出现onlyfun目录, 之下会有个caterpillar目录,而当中有個 PackageDemo.class档案 • “package”的设定会成为类别名称的一部份 – 完整类别名onlyfun.caterpillar.PackageDemo – java onlyfun.caterpillar.PackageDemo 设定套件(package) • 「完全描述」(Fullyqualified)名称 – 完整的指出「套件加类别」名称 • 最后编译完成的.class档案必须放在onlyfun 目录的caterpillar目录下 onlyfun.caterpillar.Point2D p1 = new onlyfun.caterpillar.Point2D(10, 20); bad class file: .\Point2D.classclass file contains wrong class: onlyfun.caterpillar.Point2DPlease remove or make sure it appears in the correct subdirectory of the classpath. Point2D p1 = new Point2D(10, 20); ^1 error import的意义 • 您可以使用"import"关键词,告知编译程序 您所要使用的类别是位于哪一个套件 import onlyfun.caterpillar.Point2D; public class Point2DDemo2 { public static void main(String[] args) { Point2D p1 = new Point2D(10, 20); System.out.printf("p1: (x, y) = (%d, %d)%n", p1.getX(), p1.getY()); } } import的意义 • 使用"import"指定时,可于套件指定加上'*' import onlyfun.caterpillar.*; public class Point2DDemo3 { public static void main(String[] args) { Point2D p1 = new Point2D(10, 20); System.out.printf("p1: (x, y) = (%d, %d)%n", p1.getX(), p1.getY()); } } import的意义 • 可能出现以下的错误讯息 • 将原始码与编译完成的档案放在一起并不 是一个好的管理方式 • 指定Classpath的方式如下执行程序 bad class file: .\Point2D.java file does not contain class Point2D Please remove or make sure it appears in the correct subdirectory of the classpath. javac -d ./classes ./src/*.java java -cp ./classes Point2DDemo3 import的意义 • 同名冲突 import java.util.Arrays; import onlyfun.caterpillar.Arrays; public class SomeClass { .... } java.util.Arrays is already defined in a single-type import import onlyfun.caterpillar.Arrays; ^1 error public与套件 • 没有被宣告为“public”的类别只能被同一个套件中 的类别之实例呼叫使用 • 类别成员也可以宣告为"public",宣告为"public" 的类别成员可以被其它对象呼叫使用 • 如果宣告类别时不使用"public"、"protected"或 "private"设定权限,则预设为「套件存取范围」 Point2DDemo.java:3: onlyfun.caterpillar.Point2D is not public in onlyfun.caterpillar; cannot be accessed from outside package onlyfun.caterpillar.Point2D p1 = new public与套件 Point2DDemo.java:7: getX() is not public in onlyfun.caterpillar.Point2D; cannot be accessed from outside package p1.getX(), p1.getY()); ^ public与套件 • 类别上的权限设定会约束类别成员上的权 限设定 • 效果等同于 package onlyfun.caterpillar; class SomeClass { // ... public void someMethod() { // .... } } package onlyfun.caterpillar; class SomeClass { // ... void someMethod() { // .... } } public与套件 • 定义一个类别,但没有定义建构方法时, 编译程序会自动帮您产生一个预设建构方 法package onlyfun.caterpillar; public class Test { .... } package onlyfun.caterpillar; public class Test { public Test() { } .... } public与套件 • 如果您自行定义建构方法,则编译程序就 不会帮您加上预设建构方法 • 在建构时,就必须指明使用哪个建构方法 package onlyfun.caterpillar; public class Test { public Test(int i) { ... } .... } public与套件 • 建议即使没有用到,在定义自己的建构方法的同 时,也加上个没有参数的建构方法 • 没有使用super()指定要使用父类别的哪个建构方 法,则预设会寻找父类别中无参数的建构方法 package onlyfun.caterpillar; public class Test { public Test() { //即使没用到,也先建立一个空的建构方法 } public Test(int i) { ... } .... } public与套件 • 预设建构方法的访问权限是跟随着类别的 访问权限而设定 • 由于类别宣告为public,所以预设建构方法 访问权限為public package onlyfun.caterpillar; public class Test {} public与套件 • 如果是以下的话 • 则预设建构方法访问权限为套件访问权 限,也就是编译程序会自动为您扩展为 package onlyfun.caterpillar; class Test {} package onlyfun.caterpillar; class Test { Test() { } } public与套件 存取修饰 同一类别 同一套件 子类别 全局 private OK (default) OK OK protected OK OK OK public OK OK OK OK import静态成员 import static java.lang.System.out; public class HelloWorld { public static void main(String[] args) { out.println("Hello! World!"); } } import static java.lang.System.out; import static java.util.Arrays.sort; public class ImportStaticDemo { public static void main(String[] args) { int[] array = {2, 5, 3, 1, 7, 6, 8}; sort(array); for(int i : array) { out.print(i + " "); } } } import静态成员 • 如果您想要“import”类别下所有的静态成 员,也可以使用 ‘*’ 字符 • 对于名称冲突编译程序可能透过以下的几 个方法来解决 – 成员覆盖 – 局部变量覆盖 – 重载(Overload)方法上的比对 import static java.util.Arrays.*; 第10章 • 例外处理 – 例外处理入门 – 受检、执行时期例外 – throw、throws – 例外的继承架构 例外处理入门 • 想尝试捕捉例外,可以使用"try"、"catch"、 "finally"三个关键词组合的语法来达到 try { //陈述句 } catch(例外型态 名称) { //例外处理 } finally { //一定会处理的区块 } 例外处理入门 public class CheckArgsDemo { public static void main(String[] args) { try { System.out.printf("执行%s功能%n", args[0]); } catch(ArrayIndexOutOfBoundsException e) { System.out.println("没有指定自变量"); e.printStackTrace(); } } } 例外处理入门 • 例外处理最好只用于错误处理,而不应是 用于程序业务逻辑的一部份,因为例外的 产生要消耗资源 例外处理入门 • 以下应用例外处理的方式就不适当 • 下面的方式才是正确的 while(true) { try { System.out.println(args[i]); i++; } catch(ArrayIndexOutOfBoundsException e) { // .... } } for(int i = 0; i < args.length; i++) { System.out.println(args[i]); } 受检例外、执行时期例外 • 在某些情况下例外的发生是可预期的 – 例如使用输入输出功能 • 错误是可预期发生的这类例外称之为「受 检例外」(Checked Exception) • 受检例外编译程序会要求您进行例外处理 – 在使用java.io.BufferedReader的readLine()方 法取得使用者输入时,编译程序会要求您于程 序代码中明确告知如何处理 java.io.IOException 受检例外、执行时期例外 • 如果您不在程序中处理的话,例如将 IOException的"catch"区块拿掉 CheckedExceptionDemo.java:9: unreported exception java.io.IOException; must be caught or declared to be thrown 受检例外、执行时期例外 try { BufferedReader buf = new BufferedReader( new InputStreamReader(System.in)); System.out.print("请输入整數: "); int input = Integer.parseInt(buf.readLine()); System.out.println("input x 10 = " + (input*10)); } catch(IOException e) { // Checked Exception System.out.println("I/O错誤"); } catch(NumberFormatException e) { // Runtime Exception System.out.println("输入必须为整數"); } 受检例外、执行时期例外 • 像NumberFortmatException例外是「执行 时期例外」(Runtime exception) • 例外是发生在程序执行期间,并不一定可 预期它的发生,编译程序不要求您一定要 处理 • 对于执行时期例外若没有处理,则例外会 一直往外丢,最后由JVM来处理例外, JVM所作的就是显示例外堆栈讯息 throw、throws • 想要自行丢出例外,可以使用"throw"关键 词,并生成指定的例外对象 try { double data = 100 / 0.0; System.out.println("浮点数零除:" + data); if(String.valueOf(data).equals("Infinity")) throw new ArithmeticException("除零例外"); } catch(ArithmeticException e) { System.out.println(e); } • 在巢状的try...catch结构时,必须注意该例 外是由何者引发并由何者捕捉 try { try { throw new ArrayIndexOutOfBoundsException(); } catch(ArrayIndexOutOfBoundsException e) { …. } throw new ArithmeticException(); } catch(ArithmeticException e) { …. } catch(ArrayIndexOutOfBoundsException e) { …. } throw、throws • 在方法中会有例外的发生,而您并不想在 方法中直接处理,而想要由呼叫方法的呼 叫者来处理 – 使用“throws”关键词 – java.ioBufferedReader的readLine()方法就声 明会丢出java.io.IOException private void someMethod(int[] arr) throws ArrayIndexOutOfBoundsException, ArithmeticException { //实作 } 例外的继承架构 Throwable Error LinkageError ThreadDeath VirtualMachineError .... Exception ClassNotFoundException CloneNotSupportedException IllegalAccessException .... RuntimeException ArithmeticException ArrayStoreException ClassCastException .... 继承自 Throwable 继承自 Throwable 严重的系统错 误,不用处理也 无法处理 Checkedexceptio n,编译程序要求 您要处理 Uncheckedexcep tion,编译程序不 要求您要处理 例外的继承架构 • Throwable类别拥有几个取得相关例外讯息的方 法。 – getLocalizedMessage() • 取得例外对象的区域化讯息描述 – getMessage() • 取得例外对象的讯息描述 – printStackTrace() • 显示例外的堆栈讯息,这个方法在追踪例外发生的根源时相当 的有用,简单的说若A方法中呼叫了B方法,而B方法中呼叫了 C方法,C方法产生了例外,则在处理这个例外时呼叫 printStackTrace()可以得知整个方法呼叫的过程,由此得知例 外是如何被层层丢出的。 例外的继承架构 • 了解例外处理的继承架构是必要的 • 如果父类别例外对象在子类别例外对象之 前被捕捉,则“catch”子类别例外对象的区 块将永远不会被执行 • 编译程序也会帮您检查这个错误 例外的继承架构 try { throw new ArithmeticException("例外测試"); } catch(Exceptione) { System.out.println(e.toString()); } catch(ArithmeticException e) { System.out.println(e.toString()); } 例外的继承架构 try { throw new ArithmeticException("例外测試"); } catch(ArithmeticException e) { System.out.println(e.toString()); } catch(Exception e) { System.out.println(e.toString()); } 第11章 • 列举型态 – 常数设置与列举型态 – 定义列举型态 常数设置 • 可使用接口来定义操作时所需的共享常数 public interface ActionConstants { public static final int TURN_LEFT = 1; public static final int TURN_RIGHT = 2; public static final int SHOT = 3; } 常数设置 public void someMethod() { .... doAction(ActionConstants.TURN_RIGHT); .... } public void doAction(int action) { switch(action) { case ActionConstants.TURN_LEFT: System.out.println("向左转"); break; case ActionConstants.TURN_RIGHT: System.out.println("向右转"); break; case ActionConstants.SHOOT: System.out.println("射击"); break; } } 常数设置 • 使用类别来宣告的话 • 如果常数只是在类别内部使用的话,就宣 告其为“private”或是“protected”就可以了 – 宣告为类别外可取用的常数,通常是与类别功 能相依的常数 public class CommandTool { public static final String ADMIN = "onlyfun.caterpillar.admin"; public static final String DEVELOPER = "onlyfun.caterpillar.developer"; public void someMethod() { // .... } } 列举型态入门 • 要定义列举型态是使用“enum”关键词 • 列举型态骨子里就是一个类别,所以您编 译完成后,会产生一个Action.class档案 public enum Action { TURN_LEFT, TURN_RIGHT, SHOOT } 列举型态入门 public class EnumDemo { public static void main(String[] args) { doAction(Action.TURN_RIGHT); } public static void doAction(Action action) { switch(action) { case TURN_LEFT: System.out.println("向左轉"); break; case TURN_RIGHT: System.out.println("向右轉"); break; case SHOOT: System.out.println("射擊"); break; } } } 列举型态入门 • doAction()参数列的型态是Action • 如果对doAction()方法输入其它型态的自变 量,编译程序会回报错误 • 如果您在"switch"中加入了不属于Action中 列举的值,编译程序也会回报错误 列举型态入门... public static void doAction(Action action) { switch(action) { case TURN_LEFT: System.out.println("向左转"); break; case TURN_RIGHT: System.out.println("向右转"); break; case SHOOT: System.out.println("射击"); break; case STOP: //Action中没有列举这个值 System.out.println("停止"); break; } } ... unqualified enumeration constant name required case STOP: 列举型态入门 • 可以在一个独立的档案中宣告列举值,或 是在某个类别中宣告列举成员 private enum InnerAction {TURN_LEFT, TURN_RIGHT, SHOOT}; public static void main(String[] args) { doAction(InnerAction.TURN_RIGHT); } public static void doAction(InnerAction action) { … } 列举型态入门 • 列举型态本质上还是个类别 • 范例11.5的列举宣告方式有些像在宣告「内 部类别」(Innerclass) • 编译产生EnumDemo2$InnerAction.class 与EnumDemo2$1.class 深入列举型态 • 定义列举型态时其实就是在定义一个类别 • 只不过很多细节由编译程序帮您补齐了 • 某些程度上"enum"关键词的作用就像是 "class"或"interface" • 定义出来的型态是继承自java.lang.Enum类 别 深入列举型态 • 列举的成员 – 预设为“final”,所以无法改变常数名称所设定的 值 – 也是“public”且“static”的成员,可以透过类别名 称直接使用它们 深入列举型态 • Object继承下来 – toString()方法被重新定义了,可以让您直接取 得列举值的字符串描述 – values()方法可以让您取得所有的列举成员实 例,并以数组方式传回 – 静态valueOf()方法可以让您将指定的字符串尝 试转换为列举实例 – 可以使用compareTo()方法来比较两个列举对 象在列举时的顺序 深入列举型态 • 定义列举型态时也可以定义方法 public enum DetailAction { TURN_LEFT, TURN_RIGHT, SHOOT; public String getDescription() { switch(this.ordinal()) { case 0: return"向左转"; case 1: return"向右转"; case 2: return"射击"; default: return null; } } } 深入列举型态 • 可以为列举加上建构方法(Constructor) – 不得为公开的(public)建构方法 – 避免粗心的程序人员直接对列举型态实例化 public enum DetailAction2 { TURN_LEFT("向左转"), TURN_RIGHT("向右转"), SHOOT("射擊"); private String description; //不公开的建构方法 private DetailAction2(String description) { this.description = description; } public String getDescription() { return description; } } 深入列举型态 • 在定义列举值时也可以一并实作接口 public interface IDescription { public String getDescription(); } public enum DetailAction3implements IDescription { TURN_LEFT("向左转"), TURN_RIGHT("向右转"), SHOOT("射击"); private String description; //不公开的建构方法 private DetailAction3(String description) { this.description = description; } public String getDescription() { return description; } } Value-Specific Class Bodies public enum MoreActionimplements IDescription { TURN_LEFT { //实作接口上的方法 public String getDescription() { return"向左转"; } }, //记得这边的列举值分隔使用, TURN_RIGHT { //实作接口上的方法 public String getDescription() { return"向右转"; } }, //记得这边的列举值分隔使用, SHOOT { //实作接口上的方法 public String getDescription() { return"射击"; } }; //记得这边的列举值结束使用; } Value-Specific Class Bodies public enum MoreAction2 { TURN_LEFT { //实作抽象方法 public String getDescription() { return"向左转"; } }, //记得这边的列举值分隔使用, TURN_RIGHT { //实作抽象方法 public String getDescription() { return"向右转"; } }, //记得这边的列举值分隔使用, SHOOT { //实作抽象方法 public String getDescription() { return"射击"; } }; //记得这边的列举值结束使用; public abstract String getDescription(); } 第12章 • 泛型(Generics) – 泛型入门 – 泛型进阶语法 没有泛型之前 public class BooleanFoo { privateBoolean foo; public void setFoo(Boolean foo) { this.foo = foo; } publicBoolean getFoo() { return foo; } } public class IntegerFoo { privateInteger foo; public void setFoo(Integer foo) { this.foo = foo; } publicInteger getFoo() { return foo; } } 没有泛型之前 public class ObjectFoo { privateObject foo; public void setFoo(Object foo) { this.foo = foo; } publicObjectgetFoo() { return foo; } } • Object为最上层的父类别,所以用它来实现 泛型(Generics)功能 没有泛型之前 ObjectFoo foo1 = new ObjectFoo(); ObjectFoo foo2 = new ObjectFoo(); foo1.setFoo(new Boolean(true)); //记得转换操作型态 Boolean b =(Boolean) foo1.getFoo(); foo2.setFoo(new Integer(10)); //记得转换操作型态 Integer i =(Integer) foo2.getFoo(); • 转换型态时用错了型态 ObjectFoo foo1 = new ObjectFoo(); foo1.setFoo(new Boolean(true)); String s = (String) foo1.getFoo(); ClassCastException 定义泛型类别 • J2SE5.0之后,针对泛型(Generics)设计 的解决方案 • 使用用来宣告一个型态持有者名称T public class GenericFoo { privateT foo; public void setFoo(T foo) { this.foo = foo; } publicT getFoo() { return foo; } } 定义泛型类别 • 可以使用角括号一并指定泛型类别型态持 有者T真正的型态 GenericFoo foo1 = new GenericFoo(); GenericFoo foo2 = new GenericFoo(); foo1.setFoo(new Boolean(true)); Boolean b = foo1.getFoo(); //不需要再转换型态 System.out.println(b); foo2.setFoo(new Integer(10)); Integer i = foo2.getFoo(); //不需要再转换型态 System.out.println(i); 定义泛型类别 • 型态或接口转换不再需要 – 省去恼人的ClassCastException发生 • 编译程序可以帮您作第一层防线 GenericFoo foo1 = new GenericFoo(); foo1.setFoo(new Boolean(true)); Integer i = foo1.getFoo(); //传回的是Boolean型态 GenericFooDemo.java:7: incompatible types found : java.lang.Boolean required: java.lang.Integer Integer i = foo1.getFoo(); 定义泛型类别 • 宣告及配置对象时不一并指定型态,默认 会使用Object型态 • 编译时编译程序会提出警讯 GenericFoo foo3 = new GenericFoo(); foo3.setFoo(new Boolean(false)); Note: GenericFooDemo.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 定义泛型类别 • GenericFoo< Boolean>宣告的foo1与 GenericFoo< Integer>宣告的foo2是不同的 • 不可以将foo1所参考的实例指定给foo2,或 是将foo2所参考的实例指定給foo1 GenericFoo foo1 = new GenericFoo(); GenericFoo foo2 = new GenericFoo(); incompatible types found : GenericFoo required: GenericFoo foo1 = foo2; 几个定义泛型的例子 • 类别上宣告两个型态持有者T1与T2 public class GenericFoo2 { private T1 foo1; private T2 foo2; … } GenericFoo foo = new GenericFoo(); 几个定义泛型的例子 • 可以用于宣告数组型态 public class GenericFoo3 { private T[] fooArray; public void setFooArray(T[] fooArray) { this.fooArray = fooArray; } public T[] getFooArray() { return fooArray; } } String[] strs = {"caterpillar", "momor", "bush"}; GenericFoo3 foo = new GenericFoo3(); foo.setFooArray(strs); strs = foo.getFooArray(); 几个定义泛型的例子 • 可以使用泛型机制来宣告一个数组 • 不可以使用泛型来建立数组的实例 public class GenericFoo { private T[] fooArray; // ... } public class GenericFoo { private T[] fooArray = new T[10]; //不可以使用泛型建立数组实例 // ... } 几个定义泛型的例子 • 想要设计一个新的类别,当中包括了范例 12.4的类别实例作为其成员 public class WrapperFoo { private GenericFoo foo; public void setFoo(GenericFoo foo) { this.foo = foo; } public GenericFoo getFoo() { return foo; } } 几个定义泛型的例子 GenericFoo foo = new GenericFoo(); foo.setFoo(new Integer(10)); WrapperFoo wrapper = new WrapperFoo(); wrapper.setFoo(foo); 限制泛型可用类型 • 一并使用"extends"指定这个型态持有者实 例化时,实例化的对象必须是扩充自某个 类型或实作某接口 import java.util.List; public class ListGenericFoo { private T[] fooArray; public void setFooArray(T[] fooArray) { this.fooArray = fooArray; } public T[] getFooArray() { return fooArray; } } 限制泛型可用类型 • 在限定持有者时,无论是要限定的对象是 接口或类别,都是使用"extends"关键词 ListGenericFoo foo1 = new ListGenericFoo(); ListGenericFoo foo2 = new ListGenericFoo(); 限制泛型可用类型 • 如果不是实作List的类别,编译时就会发生 错误 ListGenericFoo foo3 = new ListGenericFoo(); type parameter java.util.HashMap is not within its bound ListGenericFoo foo3 = new ListGenericFoo(); 限制泛型可用类型 • 您定义泛型类别时如果只写以下的话 • 相当于以下的定义方式 public class GenericFoo { //.... } public class GenericFoo { //.... } 型态通配字符(Wildcard) • 假设使用GenericFoo类别来如下宣告名称 • 下面的方式是可行的 GenericFoo foo1 = null; GenericFoo foo2 = null; foo1 = new GenericFoo(); foo2 = new GenericFoo(); 型态通配字符(Wildcard) • 可以使用‘?’「通配字符」(Wildcard),‘?’ 代表未知型态,并使用“extends”关键词来 作限定 • 以下这行无法通过编译 GenericFoo foo = null; foo = new GenericFoo(); ..... foo = new GenericFoo(); .... GenericFoo foo = new GenericFoo(); 型态通配字符(Wildcard) • 编译程序会回报以下的错误 • 如果您不希望任何的型态都可以传入 showFoo()方法中 incompatible types found : GenericFoo required: GenericFoo GenericFoo foo = new GenericFoo(); public void showFoo(GenericFoo foo) { //针对String或其子类而制定的内容,例如下面这行 System.out.println(foo.getFoo()); } 型态通配字符(Wildcard) • 透过使用通配字符宣告的名称所参考的对 象,您没办法再对它加入新的信息,您只 能取得它当中的信息或是移除当中的信息 GenericFoo foo = new GenericFoo(); foo.setFoo("caterpillar"); GenericFoo immutableFoo = foo; //可以取得信息 System.out.println(immutableFoo.getFoo()); //可透过immutableFoo来移去foo所参考实例内的信息 immutableFoo.setFoo(null); //不可透过immutableFoo来设定新的信息给foo所参考的实例 //所以下面这行无法通过编译 // immutableFoo.setFoo("良葛格"); 型态通配字符(Wildcard) • 因为您不知道或是宣告的参考名称,实际上参考 的对象,当中确实储存的是什么类型的信 息 • 基于泛型的设计理念,当然也就没有理由 能加入新的信息了 • 因为若能加入,被加入的对象同样也会有 失去型态信息的问题 型态通配字符(Wildcard) • 也可以向上限制,只要使用"super"关键词 GenericFoo foo = null; 扩充泛型类别、实作泛型接口 public class SubGenericFoo4 extends GenericFoo4 { private T3 foo3; public void setFoo3(T3 foo3) { this.foo3 = foo3; } public T3 getFoo3() { return foo3; } } • 可以扩充一个泛型类别,保留其型态持有 者,并新增自己的型态持有者 扩充泛型类别、实作泛型接口 • 如果不保留型态持有者,则继承下来的T1 与T2自动变为Object – 建议是父类别的型态持有者都要保留 • 界面实作 public class ConcreteFoo implements IFoo { private T1 foo1; private T2 foo2; … } 第13章 • 对象容器 – Collection类 –Map类 简介List界面 •List界面是java.util.Collection接口的子接口 • Collection界面是java.lang.Iterable子界面 • 在Java SE的API中找不到任何实作Iterator的类别 – Iterator会根据实际的容器数据结构来迭代元素 – 而容器的数据结构实作方式对外界是隐藏的 package java.lang; import java.util.Iterator; public interface Iterable { Iterator iterator(); } 简介List界面 • Collection界面继承了Iterator界面 package java.util; public interface Collection extends Iterable { int size(); boolean isEmpty(); boolean contains(Object o); Iterator iterator(); T[] toArray(T[] a); boolean add(E o); boolean remove(Object o); boolean containsAll(Collection c); boolean addAll(Collection c); boolean removeAll(Collection c); boolean retainAll(Collection c); void clear(); boolean equals(Object o); int hashCode(); } 简介List界面 • 每个加入List中的元素是循序加入的,并可 指定索引来存取元素 package java.util; public interface List extends Collection { .... boolean addAll(int index, Collection c); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); List subList(int fromIndex, int toIndex); .... } 简介List界面 • List可以使用数组(Array)或是链结串行 (LinkedList)来实作这个特性 • 对于循序加入与存取,使用ArrayList的效率 比较好 • 对于经常变动元素排列顺序的需求,使用 LinkedList会比较好 ArrayList • 使用数组结构实作List数据结构 • 可以使用索引来快速指定对象的位置 • 于快速的随机取得对象来说,使用ArrayList 可以得到较好的效能 • 若要从中间作移除或插入对象的动作,会 需要搬动后段的数组元素以重新调整索引 顺序,所以速度上就会慢的多 ArrayList Scanner scanner = new Scanner(System.in); List list = new ArrayList(); System.out.println("输入名称(使用quit结束)"); while(true) { System.out.print("# "); String input = scanner.next(); if(input.equals("quit")) break; list.add(input); } System.out.print("显示输入: "); for(int i = 0; i < list.size(); i++) System.out.print(list.get(i) + " "); System.out.println(); ArrayList • 如果您的目的是要循序取出容器中所有的 对象,则您可以使用Iterator • Iterator的实例是在ArrayList中根据数组的 结构而实作的,但您不用理会实作细节 Iterator iterator = list.iterator(); while(iterator.hasNext()) { //还有下一个元素吗? //使用next()取得下一个元素 System.out.print(iterator.next() + " "); } ArrayList • 使用「增强的for循环」(Enhanced forloop)来直接遍访List的所有元素 //使用foreach来遍访List中的元素 for(String s : list) { System.out.print(s + " "); } LinkedList • 如果经常从容器中作移除或插入对象的动 作,使用LinkedList会获得较好的效能 • LinkedList使用链结串行(Linkedlist)实作 了List界面 • addFirst()、addLast()、getFirst()、 getLast()、removeFirst( )、removeLast() 等 LinkedList private LinkedList linkedList; public StringStack() { linkedList = new LinkedList(); } public void push(String name) { //将元素加入串行前端 linkedList.addFirst(name); } public String top() { //取得串行第一个元素 return linkedList.getFirst(); } public String pop() { //移出第一个元素 return linkedList.removeFirst(); } public boolean isEmpty() { //串行是否为空 return linkedList.isEmpty(); } LinkedList private LinkedList linkedList; public StringQueue() { linkedList = new LinkedList(); } public void put(String name) { linkedList.addFirst(name); } public String get() { return linkedList.removeLast(); } public boolean isEmpty() { return linkedList.isEmpty(); } HashSet • 实作了java.util.Set界面,Set界面继承了 Collection界面 • List容器中的对象允许重复,但Set容器中 的对象都是唯一的 •Set容器有自己的一套排序规则 • HashSet容器中的对象是否相同时,会先比 较hashCode()方法传回的值是否相同,如 果相同,则再使用equals()方法比较,如果 两者都相同,则视为相同的对象 HashSet Set set = new HashSet(); set.add("caterpillar"); set.add("momor"); set.add("bush"); //故意加入重复的对象 set.add("caterpillar"); //使用Iterator显示对象 Iterator iterator = set.iterator(); while(iterator.hasNext()) { System.out.print(iterator.next() + " "); } System.out.println(); HashSet Set set = new LinkedHashSet(); set.add("caterpillar"); set.add("momor"); set.add("bush"); //使用enhanced for loop显示对象 for(String name : set) { System.out.print(name + " "); } System.out.println(); TreeSet • TreeSet实作Set界面与java.util.SortedSet 界面 • TreeSet是JavaSE中唯一实作SortedSet接 口的类别 • 自动依字典顺序进行排列的动作 TreeSet Set set = new TreeSet(); set.add("justin"); set.add("caterpillar"); set.add("momor"); //使用enhanced for loop显示对象 for(String name : set) { System.out.print(name + " "); } System.out.println(); TreeSet • 自定义一个实作Comparator接口的类别 public class CustomComparator implements Comparator { public int compare(T o1, T o2) { if (((T) o1).equals(o2)) return 0; return ((Comparable) o1).compareTo((T) o2) * -1; } } Comparator comparator = new CustomComparator(); Set set = new TreeSet(comparator); HashMap •Map的特性即「键-值」(Key-Value)匹配 • java.util.HashMap实作了Map界面, • HashMap在内部实作使用哈希(Hash), 很快的时间内可以寻得「键-值」匹配 HashMap Map map = new HashMap(); String key1 = "caterpillar"; String key2 = "justin"; map.put(key1, "caterpillar的讯息"); map.put(key2, "justin的讯息"); System.out.println(map.get(key1)); System.out.println(map.get(key2)); HashMap • 可以使用values()方法返回一个实作 Collection的对象,当中包括所有的「值」 对象 Map map = new HashMap(); map.put("justin", "justin的讯息"); map.put("momor", "momor的讯息"); map.put("caterpillar", "caterpillar的讯息"); Collection collection = map.values(); Iterator iterator = collection.iterator(); while(iterator.hasNext()) { System.out.println(iterator.next()); } System.out.println(); HashMap Map map = new LinkedHashMap(); map.put("justin", "justin的讯息"); map.put("momor", "momor的讯息"); map.put("caterpillar", "caterpillar的讯息"); for(String value : map.values()) { System.out.println(value); } TreeMap • java.util.TreeMap实作Map界面与 java.util.SortedMap界面 • SortedMap提供相关的方法让您有序的取出 对应位置的对象,像是firstKey()、lastKey() 等方法 • TreeMap是JavaSE中唯一实作SortedMap 接口的类别 TreeMap Map map = new TreeMap(); map.put("justin", "justin的讯息"); map.put("momor", "momor的讯息"); map.put("caterpillar", "caterpillar的讯息"); for(String value : map.values()) { System.out.println(value); } TreeMap • 如果对对象有一套排列顺序,要定义一个 实作java.util.Comparator接口的对象 CustomComparator comparator = new CustomComparator(); Map map = new TreeMap(comparator); 第14章 • 输入 输出 – 档案 – 位串流 – 字符串流 File类别 • 不同的操作系统对于文件系统路径的设定 各有差别 • Windows • Linux "C:\\Workspace\\CH14\\" "/home/justin/workspace/ch14" File类别 • File实例用作一个档案或目录的抽象表示 File file = new File(args[0]); if(file.isFile()) { //是否为档案 System.out.println(args[0] + "檔案"); System.out.print( file.canRead() ?"可读" :"不可读"); System.out.print( file.canWrite() ?"可写" :"不可写"); System.out.println( file.length() +"位組"); } File类别 else { //列出所有的档案及目录 File[] files = file.listFiles(); ArrayList fileList = new ArrayList(); for(int i = 0; i < files.length; i++) { //先列出目录 if(files[i].isDirectory()) {//是否为目录 //取得路径名 System.out.println("[" + files[i].getPath() + "]"); } else { //档案先存入fileList,待会再列出 fileList.add(files[i]); } } File类别 //列出档案 for(File f: fileList) { System.out.println(f.toString()); } System.out.println(); } RandomAccessFile类别 File file = new File(args[0]); //建立RandomAccessFile实例并以读写模式开启档案 RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); for(int i = 0; i < students.length; i++) { //使用对应的write方法写入数据 randomAccessFile.writeChars(students[i].getName()); randomAccessFile.writeInt(students[i].getScore()); } RandomAccessFile类别 //使用seek()方法操作存取位置 randomAccessFile.seek((num-1) * Student.size()); Student student = new Student(); //使用对应的read方法读出数据 student.setName(readName(randomAccessFile)); student.setScore(randomAccessFile.readInt()); System.out.println("姓名:" + student.getName()); System.out.println("分数:" + student.getScore()); //设定关闭档案 randomAccessFile.close(); RandomAccessFile类别 private static String readName(RandomAccessFile randomAccessfile) throws IOException { char[] name = new char[15]; for(int i = 0; i < name.length; i++) name[i] = randomAccessfile.readChar(); //将空字符取代为空格符并传回 return new String(name).replace('\0', ' '); } RandomAccessFile类别 • 读写档案时几个必要的流程 – 开启档案并指定读写方式 – 使用对应的写入方法 – 使用对应的读出方法 – 关闭档案 InputStream、OutputStream • 数据流动抽象化为一个串流(Stream) InputStream、OutputStream • InputStream是所有表示位输入串流的类别 之父类别 – System中的标准输入串流in对象就是一个 InputStream类型的实例 • OutputStream是所有表示位输出串流的类 别之父类别 – System中的标准输出串流对象out其类型是 java.io.PrintStream,OutputStream的子类别 InputStream、OutputStream • 很少直接操作InputStream或OutputStream 上的方法,这些方法比较低阶 • 通常会操作它们的子类别 try { System.out.print("输入字元: "); System.out.println("输入字符十进制表示: " + System.in.read()); } catch(IOException e) { e.printStackTrace(); } FileInputStream、FileOutputStream • 建立FileInputStream或FileOutputStream的 实例时,必须指定档案位置及文件名,实 例被建立时档案的串流就会开启 • 不使用串流时,您必须关闭档案串流,以 释放与串流相依的系统资源 FileInputStream fileInputStream = new FileInputStream(new File(args[0])); FileOutputStream fileOutputStream = new FileOutputStream(new File(args[1])); … fileInputStream.close(); fileOutputStream.close(); FileInputStream、FileOutputStream while(true) { if(fileInputStream.available() < 1024) { //剩余的资料比1024字节少 //一位一位读出再写入目标文件 int remain = -1; while((remain = fileInputStream.read()) != -1) { fileOutputStream.write(remain); } break; } else { //从来源档案读取数据至缓冲区 fileInputStream.read(buffer); //将数组数据写入目标文件 fileOutputStream.write(buffer); } } FileInputStream、FileOutputStream • 以附加的模式来写入档案 FileOutputStream fileOutputStream = new FileOutputStream(args[1], true); BufferedInputStream、BufferedOutputStream • BufferedInputStream的资料成员buf是个位 数组,默认为2048字节 • BufferedOutputStream的资料成员buf是个 位数组,默认为512个字节 BufferedInputStream、BufferedOutputStream BufferedInputStream bufferedInputStream = new BufferedInputStream( new FileInputStream(srcFile)); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream( new FileOutputStream(desFile)); System.out.println("复制档案:" + srcFile.length() +"字节"); while(bufferedInputStream.read(data) != -1) { bufferedOutputStream.write(data); } //将缓冲区中的数据全部写出 bufferedOutputStream.flush(); //关闭串流 bufferedInputStream.close(); bufferedOutputStream.close(); BufferedInputStream、BufferedOutputStream • BufferedInputStream、 BufferedOutputStream并没有改变 InputStream或OutputStream的行为 • 只是在操作对应的方法之前,动态的为它 们加上一些是缓冲区功能 DataInputStream、DataOutputStream DataOutputStream dataOutputStream = new DataOutputStream( new FileOutputStream(args[0])); for(Member member : members) { //写入UTF字符串 dataOutputStream.writeUTF(member.getName()); //写入int资料 dataOutputStream.writeInt(member.getAge()); } //出清所有数据至目的地 dataOutputStream.flush(); //关闭串流 dataOutputStream.close(); • 提供一些对Java基本数据型态写入的方法 DataInputStream、DataOutputStream DataInputStream dataInputStream = new DataInputStream( new FileInputStream(args[0])); //读出数据并还原为对象 for(int i = 0; i < members.length; i++) { //读出UTF字符串 String name = dataInputStream.readUTF(); //读出int资料 int score = dataInputStream.readInt(); members[i] = new Member(name, score); } //关闭串流 dataInputStream.close(); ObjectInputStream、ObjectOutputStream • 要直接储存对象,定义该对象的类别必须 实作java.io.Serializable界面 • serialVersionUID代表了可串行化对象版本 • 从档案读回对象时两个对象的 serialVersionUID不相同的话,就会丢出 java.io.InvalidClassException public class User implements Serializable { private static final long serialVersionUID = 1L; … } ObjectInputStream、ObjectOutputStream • 在写入对象时,您要使用writeObject()方法 • 读出对象时则使用readObject()方法,被读 出的对象都是以Object的型态传回 ObjectInputStream、ObjectOutputStream public static void writeObjectsToFile( Object[] objs, String filename) { File file = new File(filename); try { ObjectOutputStream objOutputStream = new ObjectOutputStream( new FileOutputStream(file)); for(Object obj : objs) { //将对象写入档案 objOutputStream.writeObject(obj); } //关闭串流 objOutputStream.close(); } catch(IOException e) { e.printStackTrace(); } } ObjectInputStream、ObjectOutputStream FileInputStream fileInputStream = new FileInputStream(file); ObjectInputStream objInputStream = new ObjectInputStream(fileInputStream); while(fileInputStream.available() > 0) { list.add((User) objInputStream.readObject()); } objInputStream.close(); ObjectInputStream、ObjectOutputStream //附加模式 ObjectOutputStream objOutputStream = new ObjectOutputStream( new FileOutputStream(file, true)) { //如果要附加对象至档案后 //必须重新定义这个方法 protected void writeStreamHeader() throws IOException {} }; for(Object obj : objs) { //将对象写入档案 objOutputStream.writeObject(obj); } objOutputStream.close(); SequenceInputStream • 可以看作是数个InputStream对象的组合 • 当一个InputStream对象的内容读取完毕 后,它就会取出下一个InputStream对象, 直到所有的InputStream物件都读取完毕 SequenceInputStream //建立SequenceInputStream //并使用BufferedInputStream BufferedInputStream bufInputStream = new BufferedInputStream( new SequenceInputStream(enumation), 8192); BufferedOutputStream bufOutputStream = new BufferedOutputStream( new FileOutputStream(filename), 8192); byte[] data = new byte[1]; //读取所有档案数据并写入目的地档案 while(bufInputStream.read(data) != -1) bufOutputStream.write(data); bufInputStream.close(); bufOutputStream.flush(); bufOutputStream.close(); PrintStream • 使用java.io.PrintStream可以自动为您进行 字符转换的动作 • 默认会使用操作系统的编码来处理对应的 字符转换动作 PrintStream printStream = new PrintStream( new FileOutputStream( new File("test.txt"))); printStream.println(1); printStream.close(); ByteArrayInputStream、ByteArrayOutputStream • ByteArrayInputStream可以将一个数组当作 串流输入的来源 • ByteArrayOutputStream则可以将一个位数 组当作串流输出的目的地 PushbackInputStream • 拥有一个PushBack缓冲区 • 从PushbackInputStream读出数据后,只要 PushBack缓冲区没有满,就可以使用 unread()将资料推回串流的前端 Reader、Writer • 在处理串流数据时,会根据系统默认的字 符编码来进行字符转换 • Reader、Writer是抽象类,在进行文本文件 的字符读写时真正会使用其子类别 • 可以直接在建构Reader的实例时,自行指 定读取时的编码 InputStreamReader reader = new InputStreamReader(byteArrayStream, "Big5"); InputStreamReader、OutputStreamWriter • 要对InputStream、OutputStream进行字符 处理,可以使用InputStreamReader、 OutputStreamWriter为加上字符处理的功能 FileInputStream fileInputStream = new FileInputStream(args[0]); //为FileInputStream加上字符处理功能 InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream); FileOutputStream fileOutputStream = new FileOutputStream("backup_" + args[0]); //为FileOutputStream加上字符处理功能 OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream); InputStreamReader、OutputStreamWriter int ch = 0; //以字符方式显示档案内容 while((ch = inputStreamReader.read()) != -1) { System.out.print((char) ch); outputStreamWriter.write(ch); } System.out.println(); inputStreamReader.close(); outputStreamWriter.close(); • 可以自行指定字符编码 InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream, "Big5"); FileReader、FileWriter • 想要存取的是一个文本文件,可直接使用 java.io.FileReader、java.io.FileWriter类别 FileReader fileReader = new FileReader(args[0]); FileWriter fileWriter = new FileWriter(args[0] + ".txt"); int in = 0; char[] wlnChar = {'\r', '\n'}; while((in = fileReader.read()) != -1) { if(in == '\n') { //写入"\r\n" fileWriter.write(wlnChar); } else fileWriter.write(in); } fileReader.close(); fileWriter.close(); BufferedReader、BufferedWriter • System.in是个位串流,为了转换为字符串 流,可使用InputStreamReader为其进行字 符转换,然后再使用BufferedReader为其 增加缓冲功能 BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); BufferedReader、BufferedWriter //缓冲FileWriter字符输出串流 BufferedWriter bufWriter = new BufferedWriter(new FileWriter(args[0])); String input = null; //每读一行进行一次写入动作 while(!(input = bufReader.readLine()).equals("quit")) { bufWriter.write(keyin); // newLine()方法写入与操作系统相依的换行字符 bufWriter.newLine(); } PrintWriter • 除了接受OutputStream实例作为自变量之 外,PrintWriter还可以接受Writer对象作为 输出的对象 CharArrayReader、CharArrayWriter • 可以将字符数组当作字符数据输出或输入 的来源 PushbackReader • 拥有一个PushBack缓冲区,只不过 PushbackReader所处理的是字符 • 只要PushBack缓冲区没有满,就可以使用 unread()将资料回推回串流的前端 第15章 • 线程 – 线程入门 – 同步化议题 – concurrent套件新增类别 继承Thread • 继承java.lang.Thread类别,并重新定义 run()方法 • 实例化您自定义的Thread类别 • 使用start()方法启动线程 继承Thread public class EraserThreadextends Thread { private boolean active; private String mask; … //重新定义run()方法 public void run () { while(isActive()) { System.out.print(mask); try { //暂停目前的线程50毫秒 Thread.currentThread().sleep(50); } catch(InterruptedException e) { e.printStackTrace(); } } } } 继承Thread //启动Eraser线程 EraserThread eraserThread = new EraserThread('#'); eraserThread.start(); String password = scanner.next(); eraserThread.setActive(false); • 在Java SE 6中可以使用System.console() 来取得java.io.Console物件 • 使用Console物件的readPassword()方法, 就可以避免输入的密码被窥视的问题 实作Runnable界面 • 如果您的类别已经要继承某个类别,那么 您就不能继承Thread类别 • 继承了Thread类别,您就不能再继承其它 类别 • 实作java.lang.Runnable接口来定义具线程 功能的类别 • Runnable接口中定义一个run()方法要实作 • 在实例化一个Thread对象时,可以传入一 个实作Runnable接口的对象作为自变量 实作Runnable界面 public class EraserimplementsRunnable { //实作Runnable private boolean active; private String mask; … //重新定义run()方法 public void run () { while(isActive()) { System.out.print(mask); try { //暂停目前的线程50毫秒 Thread.currentThread().sleep(50); } catch(InterruptedException e) { e.printStackTrace(); } } } } 实作Runnable界面 //Eraser实作Runnable界面 Eraser eraser = new Eraser('#'); //启动Eraser线程 Thread eraserThread = new Thread(eraser); eraserThread.start(); String password = scanner.next(); eraser.setActive(false); Daemon线程 • 一个Daemon线程是一个在背景执行服务的 线程 • 如果所有的非Daemon的线程都结束了,则 Daemon线程自动就会终止 • 从Main方法开始的是一个非Daemon线程 • 如果希望某个线程在产生它的线程结束后 跟着终止,要将它设为Daemon线程 Daemon线程 Thread thread = new Thread( //这是匿名类别的写法 new Runnable() { public void run() { while(true) { System.out.print("T"); } } }); //设定为Daemon线程 thread.setDaemon(true); thread.start(); • 使用setDaemon()方法来设定一个线程是否 为Daemon线程 • 预设所有从Daemon线程产生的线程也是 Daemon线程 线程生命周期 • 执行start()之后,线程进入Runnable状态, 此时线程尚未真正开始执行 • 必须等待排班器(Scheduler)的排班 线程生命周期 • 线程有其优先权,由1 (Thread.MIN_PRIORITY)到10 (Thread.MAX_PRIORITY) • 优先权越高,排班器越优先排入执行,如 果优先权相同,则输流执行(Round-robin 方式) 线程生命周期 • 如果您想要让目前线程礼让一下其它线 程,让它们有机会取得执行权,您可以呼 叫绪行绪的yield()方法 // ..... Thread thread = new Thread(new Runnable() { public void run() { // .... while(true) { // .... yield(); //暂时让出执行权 } } }); thread.start(); // .... 线程生命周期 • 有几种状况会让线程进入Blocked状态 – 等待输入输出完成 – 呼叫sleep()方法 – 尝试取得对象锁定 – 呼叫wait()方法 线程生命周期 • 进入Blocked状态,以下的几个对应情况让 线程回到Runnable状态 – 输入输出完成 – 呼叫interrupt() – 取得对象锁定 – 呼叫notify()或notifyAll() 线程生命周期 Thread thread = new Thread(new Runnable() { public void run() { try { //暂停99999毫秒 Thread.sleep(99999); } catch(InterruptedException e) { System.out.println("I'm interrupted!!"); } } }); thread.start(); thread.interrupt(); // interrupt it right now 线程的加入(join) • 当线程使用join()加入至另一个线程时,另 一个线程会等待这个被加入的线程工作完 毕,然后再继续它的动作 • join()的意思表示将线程加入成为另一个线 程的流程之一 线程的加入(join) Thread threadB = new Thread(new Runnable() { public void run() { try { … } catch(InterruptedException e) { e.printStackTrace(); } } }); threadB.start(); try { // Thread B加入Thread A threadB.join(); } catch(InterruptedException e) { e.printStackTrace(); } 线程的停止 • 不建议使用stop()来停止一个线程的运行 public class SomeThread implements Runnable { private boolean isContinue = true; public void terminate() { isContinue = false; } public void run() { while(isContinue) { // ... some statements } } } 线程的停止 • 不建议使用stop()来停止一个线程的运行 Thread thread = new Thread(new SomeThread()); thread.start(); thread.interrupt(); ThreadGroup • 每一个线程产生时,都会被归入某个线程 群组 • 如果没有指定,则归入产生该子线程的线 程群组中 • 可以自行指定线程群组,线程一但归入某 个群组,就无法更换群组 ThreadGroup • java.lang.ThreadGroup类别正如其名,可以统一 管理整个群组中的线程 • ThreadGroup中的某些方法,可以对所有的线程 产生作用 – interrupt()方法可以interrupt群组中所有的线程 – setMaxPriority()方法可以设定群组中线程所能拥有的 最大优先权 ThreadGroup threadGroup1 = new ThreadGroup("group1"); ThreadGroup threadGroup2 = new ThreadGroup("group2"); Thread thread1 = new Thread(threadGroup1, "group1's member"); Thread thread2 = new Thread(threadGroup2, "group2's member"); ThreadGroup • 想要一次取得群组中所有的线程来进行某 种操作,可以使用enumerate()方法 Thread[] threads = new Thread[threadGroup1.activeCount()]; threadGroup1.enumerate(threads); ThreadGroup • uncaughtException()方法是当群组中某个 线程发生非受检例外 (Uncheckedexception)时,由执行环境 呼叫进行处理ThreadGroup threadGroup1 = //这是匿名类别写法 new ThreadGroup("group1") { //继承ThreadGroup并重新定义以下方法 //在线程成员丢出unchecked exception //会执行此方法 public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName() + ": " + e.getMessage()); } }; UncaughtExceptionHandler • 可以让您的例外处理类别实作 Thread.UncaughtExceptionHandler界面, 并实现其uncaughtException()方法 public class ThreadExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName() + ": " + e.getMessage()); } } thread1.setUncaughtExceptionHandler(handler); 同步化 • 如果一个对象所持有的数据可以被多线程 同时共享存取时,您必须考虑到「数据同 步」的问题 • 数据同步指的是两份数据整体性、一致性 同步化 同步化 • 数据的不同步而可能引发的错误通常不易 察觉 • 可能是在您程序执行了几千几万次之后, 才会发生错误 • 这通常会发生在您的产品已经上线之后, 甚至是程序已经执行了几年之后 同步化 public void setNameAndID(String name, String id) { this.name = name; this.id = id; if(!checkNameAndIDEqual()) { System.out.println(count + ") illegal name or ID....."); } count++; } 同步化 • 使用"synchronized"关键词 publicsynchronized void setNameAndID(String name, String id) { this.name = name; this.id = id; if(!checkNameAndIDEqual()) { System.out.println(count + ") illegal name or ID....."); } count++; } 同步化 • 物件的锁定(lock)观念 同步化 • 使用"synchronized"关键词 public void setNameAndID(String name, String id) { synchronized(this) { this.name = name; this.id = id; if(!checkNameAndIDEqual()) { System.out.println(count + ") illegal name or ID....."); } count++; } } //arraylist参考至一个ArrayList的一个实例 synchronized(arraylist) { arraylist.add(new SomeClass() ); 同步化 • 同步化确保数据的同步,但所牺性的就是 在于一个线程取得对象锁定而占据同步化 区块,而其它线程等待它释放锁定时的延 迟 wait()、notify() • wait()、notify()与notifyAll()是由Object类别 所提供的方法 • 宣告為"final" • 在同步化的方法或区块中呼叫wait()方法 • 当物件的wait()方法被调用,目前的线程会 被放入对象的等待池中,线程归还对象的 锁定 • 其它的线程可竞争对象的锁定 wait()、notify() wait()、notify() • 当物件的notify()被调用,它会从目前对象 的等待池中通知「一个」线程加入回到锁 定池的Blocked状态 • 被通知的线程是随机的,被通知的线程会 与其它线程共同竞争对象的锁定 • 如果您呼叫notifyAll(),则「所有」在等待 池中的线程都会被通知回到锁定池的 Blocked状态 wait()、notify() • 当线程呼叫到对象的wait()方法时,表示它 要先让出对象的锁定并等待通知,或是等 待一段指定的时间 • 被通知或时间到时再与其它线程竞争对象 的锁定 • 如果取得锁定了,就从等待点开始执行 wait()、notify() public synchronized void setProduct(int product) { if(this.product != -1) { try { //目前店员没有空间收产品,请稍候! wait(); } catch(InterruptedException e) { e.printStackTrace(); } } this.product = product; System.out.printf("生产者设定(%d)%n", this.product); //通知等待区中的一个消费者可以继续工作了 notify(); } wait()、notify() public synchronized int getProduct() { if(this.product == -1) { try { //缺货了,请稍候! wait(); } catch(InterruptedException e) { e.printStackTrace(); } } int p = this.product; System.out.printf("消费者取走(%d)%n", this.product); this.product = -1; //取走产品,-1表示目前店员手上无产品 //通知等待区中的一个生产者可以继续工作了 notify(); return p; } 容器类的线程安全 • 可以使用java.util.Collections的 synchronizedXXX()等方法来传回一个同步 化的容器对象 • 使用Iterator遍访对象时,您仍必须实作同 步化 List list = Collections.synchronizedList(new ArrayList()); List list = Collections.synchronizedList(new ArrayList()); ... synchronized(list) { Iterator i = list.iterator(); while (i.hasNext()) { foo(i.next()); } } ThreadLocal类别 • 尝试从另一个角度来思考多线程共享资源 的问题 • 共享资源这么困难,那么就干脆不要共享 • 使用java.lang.ThreadLocal,为每个线程创 造一个资源的复本 ThreadLocal类别 public T get() { //取得目前执行get()方法的线程 Thread current = Thread.currentThread(); //根据线程取得线程自有的资源 T t = storage.get(current); //如果还没有线程专用的资源空间 //则建立一个新的空间 if(t == null && !storage.containsKey(current)) { t = initialValue(); storage.put(current, t); } return t; } public void set(T t) { storage.put(Thread.currentThread(), t); } ThreadLocal类别 public static SomeResource getResource() { //根据目前线程取得专属资源 SomeResource resource = threadLocal.get(); //如果没有取得目前专属资源 if(resource == null) { //建立一个新的资源并存入ThreadLocal中 resource = new SomeResource(); threadLocal.set(resource); } return resource; } BlockingQueue • 如果BlockingQueue的内容为空,而有个线 程试图从Queue中取出元素,则该线程会 被Block,直到Queue有元素时才解除Block • 如果BlockingQueue满了,而有个线程试图 再把资料填入Queue中,则该线程会被 Block,直到Queue中有元素被取走后解除 Block BlockingQueue 方法 说明 add() 加入元素,如果队列是满的,则丢出IllegalStateException remove() 传回并从队列移除元素,如果队列是空的,则丢出 NoSuchElementException element() 传回元素,如果队列是空的,则丢出 NoSuchElementException offer() 加入元素并传回true,如果队列是满的,则传回false poll() 传回并从队列移除元素,如果队列是空的,则传回null peek() 传回元素,如果队列是空的,则传回null put() 加入元素,如果队列是满,就block take() 传回并移除元素,如果队列是空的,就block BlockingQueue • ArrayBlockingQueue指定容量大小来建构 • LinkedBlockingQueue默认没有容量上限, 但也可以指定容量上限 • PriorityBlockingQueue严格来说不是 Queue,因为它是根据优先权(Priority) 来移除元素 Callable与Future • 可以协助您完成Future模式 – http://caterpillar.onlyfun.net/Gossip/DesignPat tern/FuturePattern.htm Executors • 可以使用Executors来建立线程池 方法 说明 newCachedThreadPool() 建立可以快取的线程,每个线程预设可 idle的时间为60秒 newFixedThreadPool() 包括固定数量的线程 newSingleThreadExecutor() 只有一个线程,循序的执行指定给它的 每个任务 newScheduledThreadPool() 可排程的线程 newSingleThreadScheduledExecutor() 单一可排程的线程 第16章 • 反射 – 类别载入与检视 – 使用反射生成与操作对象 简介Class与类别载入 • 真正需要使用一个类别时才会加以加载 • java.lang.Class对象代表了Java应用程序在 运行时所加载的类别或接口实例 • 可以透过Object的getClass()方法来取得每 一个对象对应的Class对象,或者是透过 "class"常量(Classliteral) 简介Class与类别载入 String name = "caterpillar"; Class stringClass = name.getClass(); System.out.println("类别名称:" + stringClass.getName()); System.out.println("是否为接口:" + stringClass.isInterface()); System.out.println("是否为基本型态:" + stringClass.isPrimitive()); System.out.println("是否为数组对象:" + stringClass.isArray()); System.out.println("父类别名称:" + stringClass.getSuperclass().getName()); Class stringClass = String.class; 简介Class与类别载入 • 所谓「真正需要」通常指的是要使用指定 的类别生成对象 • 例如使用Class.forName()加载类别,或是 使用ClassLoader的loadClass()载入类别 public class TestClass { static { System.out.println("类别被载入"); } } TestClass test = null; System.out.println("宣告TestClass参考名称"); test = new TestClass(); System.out.println("生成TestClass实例"); 简介Class与类别载入 •Class的讯息是在编译时期就被加入 至.class档案中 • 执行时期JVM在使用某类别时,会先检查 对应的Class对象是否已经加载 • 如果没有加载,则会寻找对应的.class档案 并载入 简介Class与类别载入 • 一个类别在JVM中只会有一个Class实例 • 每个类别的实例都会记得自己是由哪个 Class实例所生成 • 可使用getClass()或.class来取得Class实例 简介Class与类别载入 • 数组是一个对象,也有其对应的Class实例 System.out.println(boolean.class); System.out.println(void.class); int[] iarr = new int[10]; System.out.println(iarr.getClass().toString()); double[] darr = new double[10]; System.out.println(darr.getClass().toString()); boolean void class [I class [D 从Class中获取信息 •Class对象表示所加载的类别,取得Class 对象之后,您就可以取得与类别相关联的 信息 – 套件的对应型态是java.lang.Package – 建构方法的对应型态是 java.lang.reflect.Constructor – 方法成员的对应型态是 java.lang.reflect.Method – 数据成员的对应型态是java.lang.reflect.Field 从Class中获取信息 try { Class c = Class.forName(args[0]); Package p = c.getPackage(); System.out.println(p.getName()); } catch(ArrayIndexOutOfBoundsException e) { System.out.println("没有指定类别"); } catch(ClassNotFoundException e) { System.out.println("找不到指定类别"); } 从Class中获取信息 Class c = Class.forName(args[0]); //取得套件代表对象 Package p = c.getPackage(); System.out.printf("package %s;%n", p.getName()); //取得型态修饰,像是class、interface int m = c.getModifiers(); System.out.print(Modifier.toString(m) + " "); //如果是接口 if(Modifier.isInterface(m)) { System.out.print("interface "); } else { System.out.print("class "); } System.out.println(c.getName() + " {"); 从Class中获取信息 //取得宣告的数据成员代表对象 Field[] fields = c.getDeclaredFields(); for(Field field : fields) { //显示权限修饰,像是public、protected、private System.out.print("\t" + Modifier.toString(field.getModifiers())); //显示型态名称 System.out.print(" " + field.getType().getName() + " "); //显示数据成员名称 System.out.println(field.getName() + ";"); } 从Class中获取信息 //取得宣告的建构方法代表对象 Constructor[] constructors = c.getDeclaredConstructors(); for(Constructor constructor : constructors) { //显示权限修饰,像是public、protected、private System.out.print("\t" + Modifier.toString( constructor.getModifiers())); //显示建构方法名称 System.out.println(" " + constructor.getName() + "();"); } 从Class中获取信息 //取得宣告的方法成员代表对象 Method[] methods = c.getDeclaredMethods(); for(Method method : methods) { //显示权限修饰,像是public、protected、private System.out.print("\t" + Modifier.toString( method.getModifiers())); //显示返回值型态名称 System.out.print(" " + method.getReturnType().getName() + " "); //显示方法名称 System.out.println(method.getName() + "();"); } 简介类别加载器 • Bootstrap Loader通常由C撰写而成 • Extended Loader是由Java所撰写而成,实 对应sun.misc.Launcher$ExtClassLoader (Launcher中的内部类别) • System Loader是由Java撰写而成,实际对 应于sun.misc. Launcher$AppClassLoader (Launcher中的内部类别) 简介类别加载器 简介类别加载器 • BootstrapLoader会搜寻系统参数 sun.boot.class.path中指定位置的类别 • 预设是JRE所在目录的classes下之.class档 案,或lib目录下.jar档案中(例如rt.jar)的 类别 • System.getProperty("sun.boot.class.path") 显示sun.boot.class.path中指定的路径 简介类别加载器 • Extended Loader (sun.misc.Launcher$ExtClassLoader) 是由Java撰写而成,会搜寻系统参数 java.ext.dirs中指定位置的类别 • 预设是JRE目录下的lib\ext\classes目录下 的.class档案,或lib\ext目录下的.jar档案中 (例如rt.jar)的类别 • System.getProperty("java.ext.dirs")陈述来 显示java.ext.dirs中指定的路径 简介类别加载器 • System Loader (sun.misc.Launcher$AppClassLoader) 是由Java撰写而成,会搜寻系统参数 java.class.path中指定位置的类别,也就是 Classpath所指定的路径 • 默认是目前工作路径下的.class档案 • System.getProperty("java.class.path")陈述 来显示java.class.path中指定的路径 简介类别加载器 • ootstrapLoader会在JVM启动之后产生,之后它 会载入ExtendedLoader并将其parent设为 Bootstrap Loader,然後BootstrapLoader再载入 SystemLoader并将其parent设定为 ExtClassLoader • 每个类别加载器会先将加载类别的任务交由其 parent,如果parent找不到,才由自己负责载入 • 载入类别时,会以Bootstrap Loader→Extended Loader→SystemLoader的顺序来寻找类别 – 都找不到,就会丢出NoClassDefFoundError 简介类别加载器 • 类别加载器在Java中是以 java.lang.ClassLoader型态存在 • 每一个类别被载入后,都会有一个Class的 实例来代表,而每个Class的实例都会记得 自己是由哪个ClassLoader载入 • 可以由Class的getClassLoader()取得载入 该类别的ClassLoader,而从ClassLoader 的getParent()方法可以取得自己的parent 简介类别加载器 简介类别加载器 //建立SomeClass实例 SomeClass some = new SomeClass(); //取得SomeClass的Class实例 Class c = some.getClass(); //取得ClassLoader ClassLoader loader = c.getClassLoader(); System.out.println(loader); //取得父ClassLoader System.out.println(loader.getParent()); //再取得父ClassLoader System.out.println(loader.getParent().getParent()); 简介类别加载器 • 取得ClassLoader的实例之后,您可以使用 它的loadClass()方法来加载类别 • 使用loadClass()方法加载类别时,不会执 行静态区块 • 静态区块的执行会等到真正使用类别来建 立实例时 简介类别加载器 try { System.out.println("载入TestClass2"); ClassLoader loader = ForNameDemoV3.class.getClassLoader(); Class c = loader.loadClass("onlyfun.caterpillar.TestClass2"); System.out.println("使用TestClass2宣告参考名称"); TestClass2 test = null; System.out.println("使用TestClass2建立对象"); test = new TestClass2(); } catch(ClassNotFoundException e) { System.out.println("找不到指定的类别"); } 使用自己的ClassLoader • 可以在使用java启动程序时,使用以下的指 令来指定ExtClassLoader的搜寻路径 • 可以在使用java启动程序时,使用- classpath或-cp来指定AppClassLoader的 搜寻路径,也就是设定Classpath java -Djava.ext.dirs=c:\workspace\ YourClass java -classpath c:\workspace\ YourClass 使用自己的ClassLoader • ExtClassLoader与AppClassLoader在程序 启动后会在虚拟机中存在一份 • 在程序运行过程中就无法再改变它的搜寻 路径,如果在程序运行过程中 • 打算动态决定从其它的路径加载类别,就 要产生新的类别加载器 使用自己的ClassLoader • 可以使用URLClassLoader来产生新的类别 加载器 • 搜寻SomeClass类别时,会一路往上委托 至BootstrapLoader先开始搜寻,接着是 ExtClassLoader、AppClassLoader,如果 都找不到,才使用新建的ClassLoader搜寻 URL url = new URL("file:/d:/workspace/"); ClassLoader urlClassLoader = new URLClassLoader(new URL[] {url}); Class c = urlClassLoader.loadClass("SomeClass"); 使用自己的ClassLoader • 每次寻找类别时都是委托parent开始寻找 • 除非有人可以侵入您的计算机,置换掉标 準Java SEAPI与您自己安装的延伸套件, 否则是不可能藉由撰写自己的类别加载器 来载入恶意类别 使用自己的ClassLoader 使用自己的ClassLoader • 由同一个ClassLoader载入的类别档案,会只有一 份Class实例 • 如果同一个类别档案是由两个不同的ClassLoader 载入,则会有两份不同的Class实例 • 如果有两个不同的ClassLoader搜寻同一个类别, 而在parent的AppClassLoader搜寻路径中就可以 找到指定类别的话,则Class实例就只会有一个 • 如果父ClassLoader找不到,而是由各自的 ClassLoader搜寻到,则Class的实例会有两份 使用自己的ClassLoader //测试路径 String classPath = args[0]; //测试类别 String className = args[1]; URL url1 = new URL(classPath); //建立ClassLoader ClassLoader loader1 = new URLClassLoader(new URL[] {url1}); //加载指定类别 Class c1 = loader1.loadClass(className); //显示类别描述 System.out.println(c1); URL url2 = new URL(classPath); ClassLoader loader2 = new URLClassLoader(new URL[] {url2}); Class c2 = loader2.loadClass(className); System.out.println(c2); System.out.println("c1与c1为同一实例?" + (c1 == c2)); 生成物件 • 使用Class的newInstance()方法来实例化一 个对象 Class c = Class.forName(args[0]); List list = (List) c.newInstance(); for(int i = 0; i < 5; i++) { list.add("element " + i); } for(Object o: list.toArray()) { System.out.println(o); } 生成物件 • 如果您要在动态加载及生成对象时指定对 象的初始化自变量 – 要先指定参数型态 – 取得Constructor物件 – 使用Constructor的newInstance()并指定参数的 接受值 生成物件 Class c = Class.forName(args[0]); //指定参数型态 Class[] params = new Class[2]; //第一个参数是String params[0] = String.class; //第二个参数是int params[1] = Integer.TYPE; //取得对应参数列的建构方法 Constructor constructor = c.getConstructor(params); //指定自变量内容 Object[] argObjs = new Object[2]; argObjs[0] = "caterpillar"; argObjs[1] = new Integer(90); //给定自变量并实例化 Object obj = constructor.newInstance(argObjs); 呼叫方法 • 方法的对象代表是java.lang.reflect.Method 的实例 • 使用invoke()方法来动态呼叫指定的方法 呼叫方法 Class c = Class.forName(args[0]); //使用无参数建构方法建立对象 Object targetObj = c.newInstance(); //设定参数型态 Class[] param1 = {String.class}; //根据参数型态取回方法对象 Method setNameMethod = c.getMethod("setName", param1); //设定自变量值 Object[] argObjs1 = {"caterpillar"}; //给定自变量呼叫指定对象之方法 setNameMethod.invoke(targetObj, argObjs1); Class[] param2 = {Integer.TYPE}; Method setScoreMethod = c.getMethod("setScore", param2); Object[] argObjs2 = {new Integer(90)}; setScoreMethod.invoke(targetObj, argObjs2); //显示对象描述 System.out.println(targetObj); 呼叫方法 • 一个存取私有方法的例子 Method privateMethod = c.getDeclaredMethod("somePrivateMethod", new Class[0]); privateMethod.setAccessible(true); privateMethod.invoke(targetObj, argObjs); 修改成员值 Class c = Class.forName(args[0]); Object targetObj = c.newInstance(); Field testInt = c.getField("testInt"); testInt.setInt(targetObj, 99); Field testString = c.getField("testString"); testString.set(targetObj, "caterpillar"); System.out.println(targetObj); Field privateField = c.getDeclaredField("privateField"); privateField.setAccessible(true); privateField.setInt(targetObj, 99); 再看数组对象 System.out.println("short数组类别:" + sArr.getClass()); System.out.println("int数组类别:" + iArr.getClass()); System.out.println("long数组类别:" + lArr.getClass()); System.out.println("float数组类别:" + fArr.getClass()); System.out.println("double数组类别:" + dArr.getClass()); System.out.println("byte数组类别:" + bArr.getClass()); System.out.println("boolean数组类别:" + zArr.getClass()); System.out.println("String数组类别:" + strArr.getClass()); short数组类别:class [S int数组类别:class [I long数组类别:class [J float数组类别:class [F double数组类别:class [D byte数组类别:class [B boolean数组类别:class [Z String数组类别:class [Ljava.lang.String; 再看数组对象 • 使用反射机制动态生成数组 Class c = String.class; Object objArr = Array.newInstance(c, 5); for(int i = 0; i < 5; i++) { Array.set(objArr, i, i+""); } for(int i = 0; i < 5; i++) { System.out.print(Array.get(objArr, i) + " "); } System.out.println(); String[] strs = (String[]) objArr; for(String s : strs) { System.out.print(s + " "); } 再看数组对象 Class c = String.class; //打算建立一个3*4数组 int[] dim = new int[]{3, 4}; Object objArr = Array.newInstance(c, dim); for(int i = 0; i < 3; i++) { Object row = Array.get(objArr, i); for(int j = 0; j < 4; j++) { Array.set(row, j, "" + (i+1)*(j+1)); } } for(int i = 0; i < 3; i++) { Object row = Array.get(objArr, i); for(int j = 0; j < 4; j++) { System.out.print(Array.get(row, j) + " "); } System.out.println(); } Proxy类别 • java.lang.reflect.Proxy类别,可协助您实现 动态代理功能 第17章 • Annotation – Annotation – meta-annotation 限定Override父类方法@Override • 对编译程序说明某个方法必须是重新定义 父类别中的方法 public class CustomClass { @Override public StringToString() { return "customObject"; } } CustomClass.java:4: method does not override a method from its superclass @Override ^1 error 限定Override父类方法@Override • java.lang.Override是个Marker annotation • 用于标示的Annotation,Annotation名称本 身即表示了要给工具程序的信息 标示方法為Deprecated @Deprectated • 对编译程序说明某个方法已经不建议使用 public class Something { @Deprecated public Something getSomething() { return new Something(); } } Something some = new Something(); //呼叫被@Deprecated标示的方法 some.getSomething(); javac -Xlint:deprecation -d . SomethingDemo.java SomethingDemo.java:6: warning: [deprecation] getSomething() in onlyfun.caterpillar.Something has been deprecated some.getSomething(); ^1 warning 标示方法为Deprecated @Deprectated • java.lang.Deprecated也是個Marker annotation • Deprecated这个名称在告知编译程序,被 @Deprecated标示的方法是一个不建议被 使用的方法 抑制编译程序警讯@SuppressWarnings • 对编译程序说明某个方法中若有警示讯 息,则加以抑制 import java.util.*; public class SomeClass { public void doSomething() { Map map = new HashMap(); map.put("some", "thing"); } } Note: SomeClass.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. javac-Xlint:unchecked -d . SomeClass.java SomeClass.java:8: warning: [unchecked] unchecked call to put(K,V) as a member of the raw type java.util.Map map.put("some", "thing"); ^1 warning 抑制编译程序警讯@SuppressWarnings import java.util.*; public class SomeClass2 { @SuppressWarnings(value={"unchecked"}) public void doSomething() { Map map = new HashMap(); map.put("some", "thing"); } } @SuppressWarnings(value={"unchecked", "deprecation"}) 自定义Annotation型态 • 定义Marker Annotation,也就是Annotation 名称本身即提供信息 • 对于程序分析工具来说,主要是检查是否 有MarkerAnnotation的出现,并作出对应的 动作 public@interface Debug {} public class SomeObject { @Debug public void doSomething() { // .... } } 自定义Annotation型态 • Single-value annotation public@interface UnitTest { String value(); } public class MathTool { @UnitTest("GCD") public static int gcdOf(int num1, int num2) { // .... } } 自定义Annotation型态 public@interface FunctionTest { String[] value(); } @FunctionTest({"method1", "method2"}) • 简便形式 • 详细形式 • value成员设定默认值,用"default"关键词 @FunctionTest(value={"method1", "method2"}) public @interface UnitTest2 { String value() default "noMethod"; } 自定义Annotation型态 public @interface Process { public enum Current {NONE, REQUIRE, ANALYSIS, DESIGN, SYSTEM}; Current current() default Current.NONE; String tester(); boolean ok(); } public class Application { @Process( current =Process.Current.ANALYSIS, tester = "Justin Lin", ok = true ) public void doSomething() { // .... } } 自定义Annotation型态 • 使用@interface自行定义Annotation型态 时,实际上是自动继承了 java.lang.annotation.Annotation界面 • 由编译程序自动为您完成其它产生的细节 • 在定义Annotation型态时,不能继承其它的 Annotation型态或是接口 自定义Annotation型态 • 定义Annotation型态时也可以使用套件机制 来管理类别 import onlyfun.caterpillar.Debug; public class Test { @Debug public void doTest() { } } public class Test { @onlyfun.caterpillar.Debug public void doTest() { } } 告知编译程序如何处理@Retention • java.lang.annotation.Retention型态可以在 您定义Annotation型态时,指示编译程序该 如何对待您的自定义的Annotation型态 • 预设上编译程序会将Annotation信息留 在.class档案中,但不被虚拟机读取,而仅 用于编译程序或工具程序运行时提供信息 告知编译程序如何处理@Retention • 在使用Retention型态时,需要提供 java.lang.annotation.RetentionPolicy的列 举型态 package java.lang.annotation; public enum RetentionPolicy { SOURCE, //编译程序处理完Annotation信息后就没事了 CLASS, //编译程序将Annotation储存于class档中,预设 RUNTIME //编译程序将Annotation储存于class檔中,可由VM读入 } 告知编译程序如何处理@Retention • RetentionPolicy为SOURCE的例子是 @SuppressWarnings • 仅在编译时期告知编译程序来抑制警讯,所以不 必将这个信息储存于.class档案 • RetentionPolicy为RUNTIME的时机,可以像是您 使用Java设计一个程序代码分析工具,您必须让 VM能读出Annotation信息,以便在分析程序时使 用 • 搭配反射(Reflection)机制,就可以达到这个目 的 告知编译程序如何处理@Retention • java.lang.reflect.AnnotatedElement界面 •Class、Constructor、Field、Method、 Package等类别,都实作了 AnnotatedElement界面 public Annotation getAnnotation(Class annotationType); public Annotation[] getAnnotations(); public Annotation[] getDeclaredAnnotations(); public boolean isAnnotationPresent(Class annotationType); 告知编译程序如何处理@Retention • 定义Annotation时必须设定RetentionPolicy 为RUNTIME,也就是可以在VM中读取 Annotation信息 import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface SomeAnnotation { String value(); String name(); } public class SomeClass3 { @SomeAnnotation( value = "annotation value1", name = "annotation name1" ) public void doSomething() { } } 告知编译程序如何处理@Retention Class c = SomeClass3.class; //因为SomeAnnotation标示于doSomething()方法上 //所以要取得doSomething()方法的Method实例 Method method = c.getMethod("doSomething"); //如果SomeAnnotation存在的话 if(method.isAnnotationPresent(SomeAnnotation.class)) { System.out.println("找到@SomeAnnotation"); //取得SomeAnnotation SomeAnnotation annotation = method.getAnnotation(SomeAnnotation.class); //取得value成员值 System.out.println("\tvalue = " + annotation.value()); //取得name成员值 System.out.println("\tname = " + annotation.name()); } 告知编译程序如何处理@Retention else { System.out.println("找不到@SomeAnnotation"); } //取得doSomething()方法上所有的Annotation Annotation[] annotations = method.getAnnotations(); //显示Annotation名称 for(Annotation annotation : annotations) { System.out.println("Annotation名称:" + annotation.annotationType().getName()); } 找到@SomeAnnotation value = annotation value1 name = annotation name1 Annotation名称:onlyfun.caterpillar.SomeAnnotation 限定annotation使用对象@Target • 使用java.lang.annotation.Target可以定义 其适用之时机 • 在定义时要指定 java.lang.annotation.ElementType的列举 值之一 限定annotation使用对象@Target package java.lang.annotation; public enum ElementType { TYPE, //适用class, interface, enum FIELD, //适用field METHOD, //适用method PARAMETER, //适用method上之parameter CONSTRUCTOR, //适用constructor LOCAL_VARIABLE, //适用局部变量 ANNOTATION_TYPE, //适用annotation型态 PACKAGE //适用package } 限定annotation使用对象@Target • 限定它只能适用于建构方法与方法成员 import java.lang.annotation.Target; import java.lang.annotation.ElementType; @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface MethodAnnotation {} 限定annotation使用对象@Target • 尝试将MethodAnnotation标示于类别之上 @onlyfun.caterpillar.MethodAnnotation public class SomeoneClass { public void doSomething() { // .... } } SomeObject.java:1: annotation type not applicable to this kind of declaration @onlyfun.caterpillar.MethodAnnotation^1 error 要求为API文件@Documented • 想要在使用者制作JavaDoc文件的同时,也 一并将Annotation的讯息加入至API文件中 • 使用java.lang.annotation.Documented import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Documented @Retention(RetentionPolicy.RUNTIME) public @interface TwoAnnotation {} 子类是否继承父類@Inherited • 预设上父类别中的Annotation并不会被继承 至子类别中 • 可以在定义Annotation型态时加上 java.lang.annotation.Inherited型态的 Annotation import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Inherited; @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface ThreeAnnotation { String value(); String name(); } 第18章 • 舍遗补缺 – 日期、时间 – 日志(Logging) – 讯息绑定 使用Date • 取得系统的时间,可以使用 System.currentTimeMillis()方法 • 从1970年1月1日0时0分0秒开始,到程序 执行取得系统时间为止所经过的毫秒数 public class CurrentTime { public static void main(String[] args) { System.out.println("现在时间" + System.currentTimeMillis()); } } 使用Date Date date = new Date(); System.out.println("现在时间" + date.toString()); System.out.println("自1970/1/1至今的毫秒数" + date.getTime()); 现在时间Mon Jun 06 22:03:52 GMT+08:00 2005 自1970/1/1至今的毫秒数1118066632890 使用Date • 对日期时间作格式设定,则可以使用 java.text.DateFormat来作格式化 Date date = new Date(); DateFormat dateFormat = new SimpleDateFormat("EE-MM-dd-yyyy"); System.out.println(dateFormat.format(date)); 星期一-06-06-2005 使用Date • 直接使用DateFormat上的静态 getDateTimeInstance()方法来指定格式 Date date = new Date(); //简短信息格式 DateFormat shortFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT); //中等信息格式 DateFormat mediumFormat = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.MEDIUM); //长信息格式 DateFormat longFormat = DateFormat.getDateTimeInstance( DateFormat.LONG, DateFormat.LONG); 使用Date //详细信息格式 DateFormat fullFormat = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.FULL); System.out.println("简短信息格式:" + shortFormat.format(date)); System.out.println("中等信息格式:" + mediumFormat.format(date)); System.out.println("长信息格式:" + longFormat.format(date)); System.out.println("详细信息格式:" + fullFormat.format(date)); 简短信息格式:2005/6/6下午10:19 中等信息格式:2005/6/6下午10:19:13 长信息格式:2005年6月6日下午10时19分13秒 详细信息格式:2005年6月6日 星期一 下午10时19分13秒GMT+08:00 使用Date • 指定日期的区域显示方式,指定时要使用 一个java.util.Locale实例作为自变量 //取得目前时间 Date date = new Date(); // en:英语系US:美国 Locale locale = new Locale("en", "US"); //简短信息格式 DateFormat shortFormat = DateFormat.getDateTimeInstance( DateFormat.SHORT, DateFormat.SHORT, locale); 使用Calendar • Calendar的一些方法会取回int型态数字 • 取回的数字对应于Calendar中定义的常数 • 传回的4并不是代表目前时间是4月份,而 是对应于Calendar.MAY常数的值 Calendar rightNow = Calendar.getInstance(); System.out.println(rightNow.get(Calendar.YEAR)); System.out.println(rightNow.get(Calendar.MONTH)); 使用Calendar • 显示传回值的真正意涵 String[] months = {"一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"}; Calendar rightNow = Calendar.getInstance(); int monthConstant = rightNow.get(Calendar.MONTH); System.out.println(months[monthConstant]); 使用Calendar • 显示传回值的真正意涵 String[] dayOfWeek = {"", "日", "一", "二", "三", "四", "五", "六"}; Calendar rightNow = Calendar.getInstance(); int dayOfWeekConstant = rightNow.get(Calendar.DAY_OF_WEEK); System.out.println(dayOfWeek[dayOfWeekConstant]); 简介日志 • 在Java SE中的java.util.logging套件提供了 一系列的日志工具类别 • 首先要取得java.util.logging.Logger实例 Logger logger = Logger.getLogger("LoggingDemo"); try { System.out.println(args[0]); } catch(ArrayIndexOutOfBoundsException e) { logger.warning("没有提供执行时的自变量!"); } 日志的等级 • 在进行讯息的日志记录时,依讯息程度的 不同,您会设定不同等级的讯息输出 Logger logger = Logger.getLogger("loggingLevelDemo"); logger.severe("严重讯息"); logger.warning("警示讯息"); logger.info("一般讯息"); logger.config("设定方面的讯息"); logger.fine("细微的讯息"); logger.finer("更细微的讯息"); logger.finest("最细微的讯息"); 日志的等级 • Logger的默认等级是INFO • 默认等级是定义在执行环境的属性文件 logging.properties中 •JRE安装目录的lib目录下 • Logger预设的处理者(Handler)是 java.util.logging.ConsolerHandler 日志的等级 Logger logger = Logger.getLogger("loggingLevelDemo2"); //显示所有等级的讯息 logger.setLevel(Level.ALL); ConsoleHandler consoleHandler = new ConsoleHandler(); //显示所有等级的讯息 consoleHandler.setLevel(Level.ALL); //设定处理者为ConsoleHandler logger.addHandler(consoleHandler); logger.severe("严重讯息"); logger.warning("警示讯息"); logger.info("一般讯息"); logger.config("设定方面的讯息"); logger.fine("细微的讯息"); logger.finer("更细微的讯息"); logger.finest("最细微的讯息"); 日志的等级 • 想要关闭所有的讯息,可以设定為 Level.OFF • Logger的server()、warning()、info()等方 法,实际上是个便捷的方法 • 可以直接使用log()方法并指定等级来执行 相同的作用 日志的等级 Logger logger = Logger.getLogger("loggingLevelDemo3"); logger.setLevel(Level.ALL); ConsoleHandler consoleHandler = new ConsoleHandler(); consoleHandler.setLevel(Level.ALL); logger.addHandler(consoleHandler); logger.log(Level.SEVERE,"严重讯息"); logger.log(Level.WARNING,"警示讯息"); logger.log(Level.INFO,"一般讯息"); logger.log(Level.CONFIG,"设定方面的讯息"); logger.log(Level.FINE,"细微的讯息"); logger.log(Level.FINER,"更细微的讯息"); logger.log(Level.FINEST,"最细微的讯息"); Handler、Formatter • Logger预设的输出处理者(Handler)是 ConsolerHandler • ConsolerHandler的输出是System.err物件 • 讯息的默认等级是INFO • 可以在JRE安装目录下lib目录的 logging.properties中看到 handlers= java.util.logging.ConsoleHandler java.util.logging.ConsoleHandler.level = INFO Handler、Formatter • Java SE提供了五个预设的Handler – java.util.logging.ConsoleHandler – java.util.logging.FileHandler – java.util.logging.StreamHandler – java.util.logging.SocketHandler – java.util.logging.MemoryHandler Handler、Formatter Logger logger = Logger.getLogger("handlerDemo"); try { FileHandler fileHandler = new FileHandler("%h/myLogger.log"); logger.addHandler(fileHandler); logger.info("测试讯息"); } catch (SecurityException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } 自定义Formatter • 继承抽象类Formatter,并重新定义其 format()方法 public class TableFormatter extends Formatter { public String format(LogRecord logRecord) { return "LogRecord info: " + logRecord.getSourceClassName() + "\n" + "Level\t|\tLoggerName\t|\tMessage\t|\n" + logRecord.getLevel() + "\t|\t" + logRecord.getLoggerName() + "\t|\t" + logRecord.getMessage() + "\t|\n\n"; } } 自定义Formatter Logger logger = Logger.getLogger("tableFormatter"); try { for(Handler h : logger.getParent().getHandlers()) { if(h instanceof ConsoleHandler) { h.setFormatter(new TableFormatter()); } } logger.info("讯息1"); logger.warning("讯息2"); } catch (SecurityException e) { e.printStackTrace(); } Logger阶层关系 • 给getLogger()方法的名称是有意义的 • 给定“onlyfun”,实际上您将从根(Root) logger继承一些特性 • 再取得一个Logger实例,并给定名称 "onlyfun.caterpillar",则这次取得的Logger 将继承"onlyfun"这个Logger的特性 使用ResourceBundle • 用java.util.ResourceBundle来作讯息绑定 • messages.properties • .properties档案必须放置在Classpath的路 径设定下 onlyfun.caterpillar.welcome=Hello onlyfun.caterpillar.name=World 使用ResourceBundle //绑定messages.properties ResourceBundle resource = ResourceBundle.getBundle("messages"); //取得对应讯息 System.out.print(resource.getString( "onlyfun.caterpillar.welcome") + "!"); System.out.println(resource.getString( "onlyfun.caterpillar.name") + "!"); 格式化讯息 String message = "Hello! {0}! This is your first {1}!"; Object[] params = new Object[] {"caterpillar", "Java"}; MessageFormat formatter = new MessageFormat(message); //显示格式化后的讯息 System.out.println(formatter.format(params)); 格式化讯息 onlyfun.caterpillar.greeting=Hello! {0}! This is your first {1}! //绑定messages.properties ResourceBundle resource = ResourceBundle.getBundle("messages2"); String message = resource.getString( "onlyfun.caterpillar.greeting"); Object[] params = new Object[] {args[0], args[1]}; MessageFormat formatter = new MessageFormat(message); //显示格式化后的讯息 System.out.println(formatter.format(params)); 国际化讯息 • Internationalization、I18N – basename.properties(预设) – basename_en.properties – basename_zh_TW.properties Locale locale = new Locale("zh", "TW"); 国际化讯息 onlyfun.caterpillar.welcome=哈啰 onlyfun.caterpillar.name=世界 native2ascii -encoding Big5 messages3_zh_TW.txt messages3_zh_TW.properties onlyfun.caterpillar.welcome=\u54c8\u56c9 onlyfun.caterpillar.name=\u4e16\u754c 国际化讯息 • 想提供messages_en_US.properties档案, 并想要ResourceBundle.getBundle()取得这 个档案的内容 Locale locale = new Locale("en", "US"); ResourceBundle resource = ResourceBundle.getBundle("messages", locale); 第19章 • 文本编辑器 – 产品生命周期 –Swing入门 – 事件处理 – 文字编辑与储存 – ExecutableJar的制作 分析(Analysis) • 具备窗口接口 分析(Analysis) 分析(Analysis) 分析(Analysis) • 档案的开启与储存 – 开启旧档 – 储存档案 – 另存新檔 • 离开应用程序 • 编辑文字 – 剪下 – 复制 – 贴上 设计(Design) • 开始为应用程序规划蓝图 • 根据需求将应用程序切割出许多模块 • 设计出需求中所发掘出来的对象 • 为这些对象设计出交互行为,以完成应用 程序所必须达成的功能 • 还不会考虑到该使用何种语言与技术 开发(Development) • 决定使用何种语言及技术来开发应用程序 – 运用JavaSE技术来开发文本编辑器 – 使用Swing窗口组件来开发 测试(Testing) • 将完成的应用程序进行测试 – 验收其是否完成所期许的需求 – 程序中是否存在臭虫(Bug) – 效能方面等的问题 完成(Implementation) • 交付程序给客户 • 产品上线 • 交给教授打分数XD 维护(Maintenance) • 程序臭虫 • 需求改变 • 需求增加 • 效能、安全问题 •… 结束生命周期(End-of-life,EOL) • 产品不再符合使用的效益 • 版本更迭而造成应用程序不再适合 Swing简介 • Component继承体系 java.awt.Component java.awt.Container java.awt.Panel java.applet.Applet java.awt.Window java.awt.Dialog java.awt.Frame java.awt.Button java.awt.Checkbox java.awt.Label java.awt.List java.awt.TextComponent java.awt.MenuComponent java.awt.MenuBar java.awt.MennItem java.awt.Menu Swing简介 •Swing继承体系 java.awt.Component java.awt.Container javax.swing.JComponent javax.swing.JPanel javax.swing.JTextComponent java.awt.Window javax.swing.JWindow java.awt.Dialog javax.swing.JDialog java.awt.Frame javax.swing.JFrame 设计主窗口与选单列 import javax.swing.JFrame; public class JNotePadUI extends JFrame { public JNotePadUI() { super("新增文本文件"); setUpUIComponent(); setUpEventListener(); setVisible(true); } private void setUpUIComponent() { setSize(640, 480); } private void setUpEventListener() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static void main(String[] args) { new JNotePadUI(); } } 设计主窗口与选单列 设计主窗口与选单列 • 选单列 java.awt.Component java.awt.Container javax.swing.JComponent javax.swing.JMenuBar javax.swing.AbstractButton javax.swing.JMenuItem javax.swing.JMenu 设计主窗口与选单列 • 建立选单列、选单与选单项目 //选单列 JMenuBar menuBar = new JMenuBar() //选单; JMenu fileMenu = new JMenu("檔案"); //选单项目 JMenuItem menuOpen = new JMenuItem("开启旧档"); //在JMenu中加入JMenuItem fileMenu.add(menuOpen) //将JMenu加入JMenuBar; menuBar.add(fileMenu); //使用JFrame的setMenuBar设置选单列 setMenuBar(menuBar); 设计主窗口与选单列 • 快捷键的设置 • 加入分隔线 menuOpen.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_MASK)); fileMenu.addSeparator(); 设计主窗口与选单列 版面管理 • 在Container中的组件的位置跟大小是由版 面管理员(Layoutmanager)来决定 • 当Container需要决定它当中的组件的大小 或位置时,就会呼叫版面管理员来为其执 行 版面管理 • 结合JTextArea、JScrollPane以建立文字编 辑区域 JTextArea textArea = new JTextArea(); textArea.setFont(new Font("细明体", Font.PLAIN, 16)); textArea.setLineWrap(true); JScrollPane panel = new JScrollPane(textArea, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); 版面管理 •Swing窗口包括了几个层次 – RootPane – LayoutPane – ContentPane – MenuBar –GlassPane • 最深层的是RootPane,最上层的是 GlassPane 版面管理 • 在JFrame中要取得ContentPane,可以使 用继承下来的getContentPane()方法 Container contentPane = getContentPane(); contentPane.add(panel,BorderLayout.CENTER); 版面管理 • 制作JLabel并加入至ContentPane中 //状态栏 JLabel stateBar = new JLabel("未修改"); stateBar.setHorizontalAlignment(SwingConstants.LEFT); stateBar.setBorder( BorderFactory.createEtchedBorder()); contentPane.add(stateBar, BorderLayout.SOUTH); 版面管理 Java事件模型 • 委托事件模型(Delegation eventmodel) • 组件会将事件传播至每一个事件倾听者 (Eventlistener) • 事件倾听者中定义了与不同事件相对应的 事件处理者(Eventhandler) • 事件发生时是委托事件处理者进行处理, 事件处理者与组件的设计可以分别独立 Java事件模型 • 事件倾听者都实作了java.util.EventListener 界面 – 标示接口(Markerinterface) • 相应的事件倾听者主要位于java.awt.event 与javax.swing.event套件之下 – EventListener的子界面 文本编辑器的事件处理 • 选单项目被按下时的事件处理 //开启旧文件选单项目的事件处理 menuOpen.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { openFile(); } } ); 文本编辑器的事件处理 • JTextArea的事件方面 • 也可以继承java.awt.event.KeyAdapter package java.awt.event; public interface KeyListener { public void keyPressed(KeyEvent e) public void keyReleased(KeyEvent e) public void keyTyped(KeyEvent e) } 文本编辑器的事件处理 • 使用JTextArea的addKeyListener()方法加 入事件倾听者 //编辑区键盘事件 textArea.addKeyListener( new KeyAdapter() { public void keyTyped(KeyEvent e) { processTextArea(); } } ); 文本编辑器的事件处理 • 鼠标事件倾听者是实作 java.awt.event.MouseListener界面 • 可以继承java.awt.event.MouseAdapter //编辑区鼠标事件 textArea.addMouseListener( new MouseAdapter() { public void mouseReleased(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON3) popUpMenu.show(editMenu, e.getX(), e.getY()); } public void mouseClicked(MouseEvent e) { if(e.getButton() == MouseEvent.BUTTON1) popUpMenu.setVisible(false); } } ); 文本编辑器的事件处理 • popUpMenu参考至 javax.swing.JPopupMenu的实例 • 按下窗口右上角的X按钮 JPopupMenu popUpMenu = editMenu.getPopupMenu(); //按下窗口关闭钮事件处理 addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { closeFile(); } } ); 文本编辑器的事件处理 开启档案的流程处理 private void openFile() { if(isCurrentFileSaved()) { //文件是否为储存状态 open(); //开启旧档 } else { //显示对话框 int option = JOptionPane.showConfirmDialog( null,"档案已修改,是否储存?", "储存档案?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null); switch(option) { //确认档案储存 case JOptionPane.YES_OPTION: saveFile(); //储存档案 break; //放弃档案储存 case JOptionPane.NO_OPTION: open(); break; } } } 开启档案的流程处理 • 在判断档案是否储存的方法上,主要是根 据状态栏来进行判断 private boolean isCurrentFileSaved() { if(stateBar.getText().equals("未修改")) { return false; } else { return true; } } 开启档案的流程处理 • 开启档案时则是使用 javax.swing.JFileChooser来显示档案选取 的对话框 private void open() { // fileChooser是JFileChooser的实例 //显示档案选取的对话框 int option = fileChooser.showDialog(null, null); //使用者按下确认键 if(option == JFileChooser.APPROVE_OPTION) { try { //开启选取的档案 BufferedReader buf = new BufferedReader( new FileReader( fileChooser.getSelectedFile())); 开启档案的流程处理 //设定状态栏 stateBar.setText("未修改"); //取得系统相依的换行字符 String lineSeparator = System.getProperty("line.separator"); //读取档案并附加至文字编辑区 String text; while((text = buf.readLine()) != null) { textArea.append(text); textArea.append(lineSeparator); } buf.close(); } catch(IOException e) { JOptionPane.showMessageDialog(null, e.toString(), "开启档案失败", JOptionPane.ERROR_MESSAGE); } } } 开启档案的流程处理 储存档案的流程处理 private void saveFile() { //从标题栏取得文件名 File file = new File(getTitle()); //若指定的档案不存在 if(!file.exists()) { //执行另存新档 saveFileAs(); } else { try { //开启指定的档案 BufferedWriter buf = new BufferedWriter( new FileWriter(file)); //将文字编辑区的文字写入档案 buf.write(textArea.getText()); buf.close(); //设定状态栏为未修改 stateBar.setText("未修改"); } 储存档案的流程处理 catch(IOException e) { JOptionPane.showMessageDialog(null, e.toString(), "写入档案失败", JOptionPane.ERROR_MESSAGE); } } } private void saveFileAs() { //显示档案对话框 int option = fileChooser.showDialog(null, null); //如果确认选取档案 if(option == JFileChooser.APPROVE_OPTION) { //取得选择的档案 File file = fileChooser.getSelectedFile(); //在标题栏上设定文件名 setTitle(file.toString()); try { //建立档案 file.createNewFile(); //进行档案储存 saveFile(); } catch(IOException e) { JOptionPane.showMessageDialog(null, e.toString(), "无法建立新档", JOptionPane.ERROR_MESSAGE); } } } 储存档案的流程处理 关闭档案的流程处理 private void closeFile() { //是否已储存文件 if(isCurrentFileSaved()) { //释放窗口资源,而后关闭程序 dispose(); } else { int option = JOptionPane.showConfirmDialog( null,"档案已修改,是否储存?", "储存档案?", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null); switch(option) { case JOptionPane.YES_OPTION: saveFile(); break; case JOptionPane.NO_OPTION: dispose(); } } } 文字区的编辑、剪下、复制、贴上 private void cut() { textArea.cut(); stateBar.setText("已修改"); popUpMenu.setVisible(false); } private void copy() { textArea.copy(); popUpMenu.setVisible(false); } private void paste() { textArea.paste(); stateBar.setText("已修改"); popUpMenu.setVisible(false); } 文字区的编辑、剪下、复制、贴上 ExecutableJar的制作 ExecutableJar的制作 ExecutableJar的制作 ExecutableJar的制作 第20章 • JDBC入门 – 使用JDBC连接数据库 – 使用JDBC进行数据操作 简介JDBC 简介JDBC •JDBC数据库驱动程序依实作方式可以分为 四个类型 – Type 1:JDBC-ODBC Bridge – Type 2:Native-API Bridge – Type 3:JDBC-middleware – Type 4:Pure Java Driver 连接数据库 • 载入JDBC驱动程序 try { Class.forName("com.mysql.jdbc.Driver"); } catch(ClassNotFoundException e) { System.out.println("找不到驱动程序类别"); } 连接数据库 • 提供JDBC URL – 协定:子协定:数据源识别 jdbc:mysql://主机名:端口/数据库名称?参数=值&参数=值 jdbc:mysql://localhost:3306/demo?user=root&password=123 jdbc:mysql://localhost:3306/demo?user=root&password=123& useUnicode=true&characterEncoding=Big5 连接数据库 • 取得Connection try { String url = "jdbc:mysql://localhost:3306/demo?" + "user=root&password=123"; Connection conn = DriverManager.getConnection(url); .... } catch(SQLException e) { .... } String url = "jdbc:mysql://localhost:3306/demo"; String user = "root"; String password = "123"; Connection conn = DriverManager.getConnection(url, user, password); 简单的Connection工具类别 • 取得Connection的方式,依所使用的环境 及程序需求而有所不同 • 设计一个DBSource界面 package onlyfun.caterpillar; import java.sql.Connection; import java.sql.SQLException; public interface DBSource { public Connection getConnection() throws SQLException; public void closeConnection(Connection conn) throws SQLException; } 简单的Connection工具类别 public class SimpleDBSourceimplementsDBSource { … public SimpleDBSource(String configFile) throws IOException, ClassNotFoundException { props = new Properties(); props.load(new FileInputStream(configFile)); url = props.getProperty("onlyfun.caterpillar.url"); user = props.getProperty("onlyfun.caterpillar.user"); passwd = props.getProperty("onlyfun.caterpillar.password"); Class.forName( props.getProperty("onlyfun.caterpillar.driver")); } public Connection getConnection() throws SQLException { return DriverManager.getConnection(url, user, passwd); } public void closeConnection(Connection conn) throws SQLException { conn.close(); } } 简单的Connection工具类别 onlyfun.caterpillar.driver=com.mysql.jdbc.Driver onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo onlyfun.caterpillar.user=root onlyfun.caterpillar.password=123456 DBSource dbsource = new SimpleDBSource(); Connection conn = dbsource.getConnection(); if(!conn.isClosed()) { System.out.println("数据库连接已开启…"); } dbsource.closeConnection(conn); if(conn.isClosed()) { System.out.println("数据库连接已关闭…"); } 简单的连接池(Connectionpool) • 数据库连接的取得是一个耗费时间与资源 的动作 – 建立Socket connection – 交换数据(用户密码验证、相关参数) – 数据库初始会话(Session) – 日志(Logging) – 分配行程(Process) –… 简单的连接池(Connectionpool) public synchronized Connection getConnection() throws SQLException { if(connections.size() == 0) { return DriverManager.getConnection(url, user, passwd); } else { int lastIndex = connections.size() - 1; return connections.remove(lastIndex); } } public synchronized void closeConnection(Connection conn) throws SQLException { if(connections.size() == max) { conn.close(); } else { connections.add(conn); } } 简单的连接池(Connectionpool) DBSource dbsource = newBasicDBSource("jdbc2.properties"); Connection conn1 = dbsource.getConnection(); dbsource.closeConnection(conn1); Connection conn2 = dbsource.getConnection(); System.out.println(conn1 == conn2); onlyfun.caterpillar.driver=com.mysql.jdbc.Driver onlyfun.caterpillar.url=jdbc:mysql://localhost:3306/demo onlyfun.caterpillar.user=root onlyfun.caterpillar.password=123456 onlyfun.caterpillar.poolmax=10 简单的连接池(Connectionpool) • 初始的Connection数量 • Connection最大idle的数量 • 如果超过多久时间,要回收多少数量的 Connection • Proxool – http://proxool.sourceforge.net/index.html • Apache Jakarta的Common DBCP – http://jakarta.apache.org/commons/dbcp/ Statement、ResultSet • 要执行SQL的话,必须取得 java.sql.Statement物件,它是Java当中一 个SQL叙述的具体代表对象 • 插入一笔数据,可以如下使用Statement的 executeUpdate()方法 Statement stmt = conn.createStatement(); stmt.executeUpdate("INSERT INTO t_message VALUES(1, 'justin', " + "'justin@mail.com', 'mesage...')"); Statement、ResultSet • executeUpdate()会传回int结果,表示资料变动的 笔数 • executeQuery()方法则是用于SELECT等查询数 据库的SQL • executeQuery()会传回java.sql.ResultSet对象, 代表查询的结果 • 可以使用ResultSet的next()来移动至下一笔数 据,它会传回true或false表示是否有下一笔资料 • 使用getXXX()来取得资料 Statement、ResultSet • 指定域名来取得数据 ResultSet result = stmt.executeQuery("SELECT * FROM t_message"); while(result.next()) { System.out.print(result.getInt("id") + "\t"); System.out.print(result.getString("name") + "\t"); System.out.print(result.getString("email") + "\t"); System.out.print(result.getString("msg") + "\t"); } Statement、ResultSet • 使用查询结果的字段顺序来显示结果 ResultSet result = stmt.executeQuery("SELECT * FROM t_message"); while(result.next()) { System.out.print(result.getInt(1) + "\t"); System.out.print(result.getString(2) + "\t"); System.out.print(result.getString(3) + "\t"); System.out.print(result.getString(4) + "\t"); } Statement、ResultSet •Statement的execute()可以用来执行SQL,并可 以测试所执行的SQL是执行查询或是更新 • 传回true的话表示SQL执行将传回ResultSet表示 查询结果,此时可以使用getResultSet()取得 ResultSet物件 • 如果execute()传回false,表示SQL执行会传回更 新笔数或没有结果,此时可以使用 getUpdateCount()取得更新笔数 • 如果事先无法得知是进行查询或是更新,就可以 使用execute() Statement、ResultSet finally { if(stmt != null) { try { stmt.close(); } catch(SQLException e) { e.printStackTrace(); } } if(conn != null) { try { dbsource.closeConnection(conn); } catch(SQLException e) { e.printStackTrace(); } } } Statement、ResultSet • Connection对象默认为「自动认可」 (autocommit) • getAutoCommit()可以测试是否设定为自动 认可 • 无论是否有无执行commit()方法,只要SQL 没有错,在关闭Statement或Connection 前,都会执行认可动作 PreparedStatement • preparedStatement()方法建立好一个预先 编译(precompile)的SQL语句 • 当中参数会变动的部份,先指定"?"这个占 位字符 PreparedStatement stmt = conn.prepareStatement( "INSERT INTO t_message VALUES(?, ?, ?, ?)"); PreparedStatement • 需要真正指定参数执行时,再使用相对应 的setInt()、setString()等方法,指定"?"处 真正应该有的参数 stmt.setInt(1, 2); stmt.setString(2, "momor"); stmt.setString(3, "momor@mail.com"); stmt.setString(4, "message2..."); LOB读写 •BLOB全名Binary LargeObject,用于储存 大量的二进制数据 •CLOB全名Character LargeObject,用于储 存大量的文字数据 • 在JDBC中也提供了java.sql.Blob与 java.sql.Clob两个类别分别代表BLOB与 CLOB资料 LOB读写 • 取得一个档案,并将之存入数据库中 File file = new File("./logo_phpbb.jpg"); int length = (int) file.length(); InputStream fin = new FileInputStream(file); //填入数据库 PreparedStatement pstmt = conn.prepareStatement( "INSERT INTO files VALUES(?, ?, ?)"); pstmt.setInt(1, 1); pstmt.setString(2, "filename"); pstmt.setBinaryStream (3, fin, length); pstmt.executeUpdate(); pstmt.clearParameters(); pstmt.close(); fin.close(); LOB读写 • 从数据库中取得BLOB或CLOB资料 Blob blob = result.getBlob(2); //取得BLOB Clob clob = result.getClob(2) //取得CLOB 交易(Transaction) • 可以操作Connection的setAutoCommit()方 法,给它false自变量 • 在下达一连串的SQL语句后,自行呼叫 Connection的commit()来送出变更 交易(Transaction) try { … conn.setAutoCommit(false); //设定autocommit为false stmt = conn.createStatement(); stmt.execute("...."); // SQL stmt.execute("...."); stmt.execute("...."); conn.commit(); //正确无误,确定送出 } catch(SQLException e) { //喔喔!在commit()前发生错误 try { conn.rollback(); //撤消操作 } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); } 交易(Transaction) • 设定储存点(savepoint) conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.executeUpdate("...."); stmt.executeUpdate("...."); Savepoint savepoint = conn.setSavepoint(); //设定 savepoint stmt.executeUpdate("...."); //如果因故rollback conn.rollback(savepoint); . . . conn.commit(); //记得释放savepoint stmt.releaseSavepoint(savepoint); 批处理 • 使用addBatch()方法将要执行的SQL叙述加 入,然后执行executeBatch() conn.setAutoCommit(false); Statement stmt = conn.createStatement(); stmt.addBatch("..."); // SQL stmt.addBatch("..."); stmt.addBatch("..."); ... stmt.executeBatch(); conn.commit(); 批处理 • 使用PreparedStatement可以进行批处理 PreparedStatement stmt = conn.prepareStatement( "INSERT INTO t_message VALUES(?, ?, ?, ?)"); Message[] messages = ...; for(int i = 0; i < messages.length; i++) { stmt.setInt(1, messages[i].getID()); stmt.setString(2, messages[i].getName()); stmt.setString(3, messages[i].getEmail()); stmt.setString(4, messages[i].getMsg()); stmt.addBatch(); } stmt.executeBatch(); ResultSet光标控制 • 可以在建立Statement对象时指定resultSetType – ResultSet.TYPE_FORWARD_ONLY – ResultSet.TYPE_SCROLL_INSENSITIVE – ResultSet.TYPE_SCROLL_SENSITIVE • 预设是第一个,也就是只能使用next()来逐笔取得 资料 • 指定第二个或第三个时,则可以使用ResultSet的 afterLast()、previous()、absolute()、relative()等 方法 ResultSet光标控制 • 还必须指定resultSetConcurrency – ResultSet.CONCUR_READ_ONLY – ResultSet.CONCUR_UPDATABLE • createStatement()不给定参数时,预设是 TYPE_FORWARD_ONLY、 CONCUR_READ_ONLY ResultSet光标控制 dbsource = new SimpleDBSource(); conn = dbsource.getConnection(); stmt = conn.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); ResultSet result = stmt.executeQuery( "SELECT * FROM t_message"); result.afterLast(); while(result.previous()) { System.out.print(result.getInt("id") + "\t"); System.out.print(result.getString("name") + "\t"); System.out.print(result.getString("email") + "\t"); System.out.println(result.getString("msg")); } ResultSet新增、更新、删除数据 • 建立Statement时必须在createStatement() 上指定TYPE_SCROLL_SENSITIVE(或 TYPE_SCROLL_INSENSITIVE,如果不想 取得更新后的数据的话)与 CONCUR_UPDATABLE Statement stmt = conn.createStatement( ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet新增、更新、删除数据 • 针对查询到的数据进行更新的动作 ResultSet result = stmt.executeQuery( "SELECT * FROM t_message WHERE name='justin'"); result.last(); result.updateString("name", "caterpillar"); result.updateString("email", "caterpillar@mail.com"); result.updateRow(); ResultSet新增、更新、删除数据 • 如果想要新增数据 ResultSet result = stmt.executeQuery( "SELECT * FROM t_message WHERE name='caterpillar'"); result.moveToInsertRow(); result.updateInt("id", 4); result.updateString("name", "jazz"); result.updateString("email", "jazz@mail.com"); result.updateString("msg", "message4..."); result.insertRow(); ResultSet新增、更新、删除数据 • 要删除查询到的某笔数据 ResultSet result = stmt.executeQuery( "SELECT * FROM t_message WHERE name='caterpillar'"); result.last(); result.deleteRow(); ResultSetMetaData • MetaData即「数据的数据」(Data aboutdata) • ResultSet用来表示查询到的数据,而 ResultSet数据的数据,即描述所查询到的 数据背后的数据描述,即用来表示表格名 称、域名、字段型态 • 可以透过ResultSetMetaData来取得 ResultSetMetaData dbsource = new SimpleDBSource(); conn = dbsource.getConnection(); stmt = conn.createStatement(); ResultSet result = stmt.executeQuery( "SELECT * FROM t_message"); ResultSetMetaData metadata = result.getMetaData(); for(int i = 1; i <= metadata.getColumnCount(); i++) { System.out.print( metadata.getTableName(i) + "."); System.out.print( metadata.getColumnName(i) + "\t|\t"); System.out.println( metadata.getColumnTypeName(i)); } 第21章 • Java SE6新功能简介 – Java SE6基本新功能 – Apache Derby、JDBC 4.0 java.lang套件 • 在Java SE 6中,String类别上新增了 isEmpty()方法 String str = ""; if(str.isEmpty()) { … } java.util套件 • 在Java SE 6中,Arrays类别新增了copyOf() 方法 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = Arrays.copyOf(arr1, arr1.length); for(int i = 0; i < arr2.length; i++) System.out.print(arr2[i] + " "); System.out.println(); int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = Arrays.copyOf(arr1, 10); for(int i = 0; i < arr2.length; i++) System.out.print(arr2[i] + " "); System.out.println(); java.util套件 • copyOfRange()方法 • binarySearch()方法 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = Arrays.copyOf(arr1, 1, 4); int[] arr1 = {10, 20, 30, 40, 50, 60, 70, 80, 90}; int result = Arrays.binarySearch(arr1, 6, 9, 85); if(result > -1) { System.out.printf("索引%d处找到数据%n", result); } else { System.out.printf("插入点%d %n", (result + 1) * -1); } java.util套件 • 在Java SE 6中,您可以直接使用 getDisplayNames()或getDisplayName()方 法取得区域化的日期格式显示 Calendar rightNow = Calendar.getInstance(); Locale locale = Locale.getDefault(); System.out.println("现在时间是:"); System.out.printf("%s:%d %n", rightNow.getDisplayName(ERA, LONG, locale), rightNow.get(YEAR)); System.out.println( rightNow.getDisplayName(MONTH, LONG, locale)); System.out.printf("%d日%n", rightNow.get(DAY_OF_MONTH)); System.out.println( rightNow.getDisplayName(DAY_OF_WEEK, LONG, locale)); java.io套件 • 使用System类别上新增的console()方法 • 使用Console物件的readLine()方法 System.out.print("输入名称:"); String name = System.console().readLine(); System.out.print("输入密码:"); char[] passwd = System.console().readPassword(); String password = new String(passwd); if("caterpillar".equals(name) && "123456".equals(password)) { System.out.println("欢迎caterpillar "); break; } else { System.out.printf("%s,名称或密码错误,请重新输入!%n", name); } java.io套件 String name = console.readLine("[%s] ","输入名称…"); char[] passwd = console.readPassword("[%s]","输入密码…"); String password = new String(passwd); if("caterpillar".equals(name) && "123456".equals(password)) { System.out.println("欢迎caterpillar "); break; } else { System.out.printf("%s,名称或密码错误,请重新输入!%n", name); } java.io套件 • 在Java SE6中,对于File类别新增了几个方 法 File[] roots = File.listRoots(); for(File root : roots) { System.out.printf("%s总容量%d,可用容量%d %n", root.getPath(), root.getTotalSpace(), root.getUsableSpace()); } java.awt套件 • 指定启动屏幕的图片 • manifest档案的写法 java-splash:caterpillar.jpg -jar JNotePad.jar Manifest-Version: 1.0 Main-Class: onlyfun.caterpillar.JNotePad SplashScreen-Image: caterpillar.jpg java.awt套件 • 系统工具栏图标的支持 if(SystemTray.isSupported()) { SystemTray tray = SystemTray.getSystemTray(); Image image = Toolkit.getDefaultToolkit() .getImage("musical_note_smile.gif"); TrayIcon trayIcon = new TrayIcon(image, "JNotePad 1.0"); try { tray.add(trayIcon); } catch (AWTException e) { System.err.println("无法加入系统工具栏图标"); e.printStackTrace(); } } else { System.err.println("无法取得系统工具栏"); } java.awt套件 • 系统工具栏图标的支持 if(SystemTray.isSupported()) { SystemTray tray = SystemTray.getSystemTray(); Image image = Toolkit.getDefaultToolkit() .getImage("musical_note_smile.gif"); PopupMenu popup = new PopupMenu(); MenuItem item = new MenuItem("开启JNotePad 1.0"); popup.add(item); TrayIcon trayIcon = new TrayIcon(image, "JNotePad 1.0", popup); try { tray.add(trayIcon); } catch (AWTException e) { System.err.println("无法加入系统工具栏图标"); e.printStackTrace(); } } else { System.err.println("无法取得系统工具栏"); } java.awt套件 • 系统工具栏图标上主动显示讯息 • 移除系统工具栏中的图标 trayIcon.displayMessage("哈囉","该休息了吗?", TrayIcon.MessageType.WARNING); tray.remove(trayIcon); Classpath简化设定 • 在Java SE 6中,您可以使用'*'来指定某个 目录下的所有.jar档案 java –cp .;c:\jars\* onlyfun.caterpillar.JNotePad 使用Apache Derby • 在JDK6中捆绑了ApacheDerby数据库 (http://db.apache.org/derby/) • 纯Java撰写的数据库,支援JDBC 4.0 • 可以在JDK6包装目录的db目录下找到 ApacheDerby的相关档案 加载驱动程序 • JDBC 4.0之中,不需要再呼叫Class.forName()并 指定驱动程序 •JVM会自动在Classpath中寻找适当的驱动程序 • 在包装有JDBC驱动程序的JAR档案中,必须有一 个"META-INF/services/java.sql.Driver"档案,当 中撰写驱动程序类别名称 Connection conn = DriverManager.getConnection( url, username, password); 改进的例外处理 • 在JDBC 4.0之中,SQLException新增了几 个建构函式,可以接受Throwable实例进行 SQLException的建构 • SQLException并实作了Iterable界面 改进的例外处理 try { … } catch(SQLException ex) { for(Throwable t : ex) { System.err.println(t); Throwable cause = t.getCause(); while(cause != null) { System.err.println("Cause: " + cause); cause = cause.getCause(); } } } BLOB、CLOB的改进 • 在JDBC 4.0中,PreparedStatement新增了 接受InputStream实例的setBlob()等方法 • 会将资料以BLOB的方式送至数据库 • 对于CLOB,PreparedStatement也增加有 接受Reader实例的setClob()等方法 • Connection上,也增加有createBlob()与 createClob()等方法
还剩618页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

boyika

贡献于2013-10-27

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf