Java Jdk6学习笔记[Ppt]
Upcoming SlideShare
Loading in...5
×
 

Java Jdk6学习笔记[Ppt]

on

  • 5,133 views

 

Statistics

Views

Total Views
5,133
Views on SlideShare
5,085
Embed Views
48

Actions

Likes
1
Downloads
102
Comments
2

2 Embeds 48

http://www.uniquesoft.cn 40
http://www.slideshare.net 8

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • goog
    Are you sure you want to
    Your message goes here
    Processing…
  • thanks!
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Java Jdk6学习笔记[Ppt] Java Jdk6学习笔记[Ppt] Presentation Transcript

  • 第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变数 set Path= C:Program FilesJavajdk1.6.0bin;%Path% • Windows下安装JRE时,会将java.exe复制 至「C:WindowsSystem32」路径之下, 而这个路径在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 类别取得使用者的输入 Scanner scanner = new Scanner(System.in); System.out.print("请输入您的名字:"); System.out.printf("哈啰!%s!n", scanner.next()); • 可以使用这个工具的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("u0048u0065u006Cu006Cu006F");
  • 输出格式控制 • 若是使用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 ~ %dn", Short.MAX_VALUE,Short.MIN_VALUE); System.out.printf("int t数值范围:%d ~ %dn", Integer.MAX_VALUE, Integer.MIN_VALUE); System.out.printf("long t数值范围:%d ~ %dn", Long.MAX_VALUE, Long.MIN_VALUE); System.out.printf("byte t数值范围:%d ~ %dn", Byte.MAX_VALUE, Byte.MIN_VALUE); System.out.printf("float t数值范围:%e ~ %en", Float.MAX_VALUE, Float.MIN_VALUE); System.out.printf("double t数值范围:%e ~ %en", Double.MAX_VALUE, Double.MIN_VALUE);
  • 变数、常数 • 在Java中要使用变量,必须先宣告变量名 称与数据型态 int age; //宣告一个整数变量 double scope; //宣告一个倍精度浮点数变量 • 使用int、float、double、char等关键词来宣 告变量名称并指定其数据型态 – 不可以使用数字作为开头 – 不可以使用一些特殊字符,像是*&^%之类 – 不可以與Java内定的关键词同名
  • 变数、常数 • 鼓励用清楚的名称来表明变量的作用 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("%4dt %4.1ft %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 ? '是' : '否'));
  • 逻辑、位运算 • 「且」(&&)、「或」(||)、「反相」(!) int number = 75; System.out.println((number > 70 && number < 80)); System.out.println((number > 80 || number < 75)); System.out.println(!(number > 80 || number < 75)); • &(AND)、|(OR)、^(XOR)与~(补 码) System.out.println("0 AND 0tt" + (0 & 0)); System.out.println("0 AND 1tt" + (0 & 1)); System.out.println("1 AND 0tt" + (1 & 0)); System.out.println("1 AND 1tt" + (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(条件式一) { if(条件式一) { 陈述句一; 陈述句一; //其它陈述句 //其它陈述句 } else } else if(条件式二) 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物件 Integer integer = new Integer(10); • 在J2SE5.0之后提供了自动装箱的功能 Integer integer = 10;
  • 自动装箱、拆箱 Integer data1 = 10; Integer data2 = 20; //转为double值再除以3 System.out.println(data1.doubleValue() / 3); //进行两个值的比较 System.out.println(data1.compareTo(data2));
  • 自动装箱、拆箱 • 自动装箱运用的方法还可以如下: int i = 10; Integer integer = i; • 更一般化的java.lang.Number类别自动装箱 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 • 自动装箱与拆箱的功能是编译程序来帮忙 Integer i = 100; Integer i = new Integer(100); • 自动装箱与拆箱的功能是所谓的「编译程 序蜜糖」(Compilersugar) Integer i = null; Integer i = null; int j = i; int j = i.intValue(); NullPointerException
  • 小心使用boxing Integer i1 = 100; Integer i2 = 100; if (i1 == i2) System.out.println("i1 == i2"); 显示"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"
  • 小心使用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章 • 阵列 – 一维数组、二维数组 – 进阶数组观念
  • 一维数组对象 • 宣告一个数组并初始数组内容 int[] score = {90, 85, 55, 94, 77}; for(int i = 0; i < score.length; i++) System.out.printf("score[%d] = %dn", i, score[i]); • 指定的索引值不可超出数组范围 – 会发生ArrayIndexOutOfBoundsException • length为数组对象的属性成员
  • 一维数组对象 • 当您宣告一个数组时,其实就是在配置一 个数组对象 • 一个完整的数组宣告方式如下 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] = %dn", 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("nn透过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()方法 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(); • 在JDK6中,也为Arrays类别新增了数组复 制的copyOf()方法
  • 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()nt" + Arrays.deepToString(arr1));
  • foreach与数组 • 加强的for循环(Enhanced forLoop) for(type element : array) { System.out.println(element).... } • J2SE 5.0之前 int[] arr = {1, 2, 3, 4, 5}; for(int i = 0; i < arr.length; i++) System.out.println(arr[i]); • 在J2SE5.0之后 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类别 • 将输入的字符串转换为整数、浮点数等 方 法 说 明 Byte.parseByte(字符串) 将字符串剖析为位 Short.parseShort(字符串) 将字符串剖析为short整数 Integer.parseInt(字符串) 将字符串剖析为int整数 Long.parseLong(字符串) 将字符串剖析为long整数 Float.parseFloat(字符串) 将字符串剖析为float浮点数 Double.parseDouble(字符串) 将字符串剖析为double浮点数 • 指定的字符串无法剖析为指定的数据型态数值, 则会发生NumberFormatException例外
  • 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 str1 = "flyweight"; String str2 = "flyweight"; System.out.println(str1 == str2); • 当您直接在程序中使用""来包括一个字符串 时,该字符串就会在String池中
  • 不可变(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)字符串 • 不可用‘==’比较字符串的字符内容是否相同 String str1 = new String("caterpillar"); String str2 = new String("caterpillar"); System.out.println(str1 == str2); • 要比较两个字符串对象的字符值是否相 同,您要使用equals()方法 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 = { "justint64/5/26t0939002302t5433343", "momort68/7/23t0939100391t5432343" }; 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-100391n" + "momor的手机号码:0939-666888n"; Pattern pattern = Pattern.compile(".*0939-d{6}"); Matcher matcher = pattern.matcher(phones1); while(matcher.find()) { System.out.println(matcher.group()); } String phones2 = "caterpillar的手机号码:0952-600391n" + "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) public static int sum(int... nums) { //使用...宣告参数 int sum = 0; for(int num : nums) { sum += num; } return sum; } • 实际上nums是一个数组
  • 不定长度自变量 • 宣告的参数必须设定在参数列的最后一 个,下面的方式是合法的 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)方法 • 实际运作的对象是SafeArray的实例,所以 被呼叫执行的会是SafeArray中重新定义过 的setElement()方法 SimpleArray simpleArray = new SafeArray(); simpleArray.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进位制的编码 getClass().getName() + '@' + Integer.toHexString(hashCode()) • 预设的equals()本身是比较对象的内存地址 是否相同
  • 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 double PI = 3.14159; • 如果在定义方法成员时使用"final",表示该 方法成员在无法被子类别重新定义 public class Ball { private double radius; publicfinal double getRadius() { return radius; } // .... }
  • final关键词 • 如果您在宣告类别时加上final关键词,则表 示要终止被扩充 publicfinal class Ball { // .... }
  • 多型导论 • 如果使用不正确类别型态转换对象操作接 口,会发生java.lang.ClassCastException
  • 多型导论 • 定义了两个execute()方法来分别操作 Class1与Class2的实例 public void execute(Class1 c1) { c1.doSomething(); } public void execute(Class2 c2) { c2.doSomething(); } • execute()分别依赖了Class1与Class2两个 类别
  • 多型导论 • 将程序中的execute()改成 public void execute(ParentClass c) { c.doSomething(); } • 只依赖ParentClass,程序对个别对象的依 赖程序降低了,日后在修改、维护或调整 程序时的弹性也增加了
  • 多型导论 • 实际上在设计并不依赖于具体类别,而是 依赖于抽象 • 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
  • 成员内部类别、区域内部类别 • 成员内部类别,基本上是在一个类别中直 接宣告另一个类别 public class OuterClass { //内部类别 private class InnerClass { // .... } } • 所产生的文件名为「外部类别名称$内部类 别名称.class」
  • 成员内部类别、区域内部类别 • 区域内部类别定义于一个方法中,类别的 可视范围与生成之对象仅止于该方法之中 • 内部类别还可以被宣告为"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)名称 – 完整的指出「套件加类别」名称 onlyfun.caterpillar.Point2D p1 = new onlyfun.caterpillar.Point2D(10, 20); • 最后编译完成的.class档案必须放在onlyfun 目录的caterpillar目录下 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的意义 • 可能出现以下的错误讯息 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 • 指定Classpath的方式如下执行程序 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”的类别只能被同一个套件中 的类别之实例呼叫使用 Point2DDemo.java:3: onlyfun.caterpillar.Point2D is not public in onlyfun.caterpillar; cannot be accessed from outside package onlyfun.caterpillar.Point2D p1 = new • 类别成员也可以宣告为"public",宣告为"public" 的类别成员可以被其它对象呼叫使用 • 如果宣告类别时不使用"public"、"protected"或 "private"设定权限,则预设为「套件存取范围」
  • 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与套件 • 建议即使没有用到,在定义自己的建构方法的同 时,也加上个没有参数的建构方法 package onlyfun.caterpillar; public class Test { public Test() { //即使没用到,也先建立一个空的建构方法 } public Test(int i) { ... } .... } • 没有使用super()指定要使用父类别的哪个建构方 法,则预设会寻找父类别中无参数的建构方法
  • public与套件 • 预设建构方法的访问权限是跟随着类别的 访问权限而设定 package onlyfun.caterpillar; public class Test {} • 由于类别宣告为public,所以预设建构方法 访问权限為public
  • 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”类别下所有的静态成 员,也可以使用 ‘*’ 字符 import static java.util.Arrays.*; • 对于名称冲突编译程序可能透过以下的几 个方法来解决 – 成员覆盖 – 局部变量覆盖 – 重载(Overload)方法上的比对
  • 第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 Throwable LinkageError 严重的系统错 ThreadDeath 误,不用处理也 VirtualMachineError 无法处理 .... 继承自 Exception Throwable ClassNotFoundException CloneNotSupportedException Checkedexceptio IllegalAccessException n,编译程序要求 .... 您要处理 RuntimeException Uncheckedexcep ArithmeticException tion,编译程序不 ArrayStoreException 要求您要处理 ClassCastException ....
  • 例外的继承架构 • 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; } }
  • 常数设置 • 使用类别来宣告的话 public class CommandTool { public static final String ADMIN = "onlyfun.caterpillar.admin"; public static final String DEVELOPER = "onlyfun.caterpillar.developer"; public void someMethod() { // .... } } • 如果常数只是在类别内部使用的话,就宣 告其为“private”或是“protected”就可以了 – 宣告为类别外可取用的常数,通常是与类别功 能相依的常数
  • 列举型态入门 • 要定义列举型态是使用“enum”关键词 public enum Action { TURN_LEFT, TURN_RIGHT, SHOOT } • 列举型态骨子里就是一个类别,所以您编 译完成后,会产生一个Action.class档案
  • 列举型态入门 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; } }
  • 没有泛型之前 • Object为最上层的父类别,所以用它来实现 泛型(Generics)功能 public class ObjectFoo { privateObject foo; public void setFoo(Object foo) { this.foo = foo; } publicObjectgetFoo() { return foo; } }
  • 没有泛型之前 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>用来宣告一个型态持有者名称T public class GenericFoo<T> { privateT foo; public void setFoo(T foo) { this.foo = foo; } publicT getFoo() { return foo; } }
  • 定义泛型类别 • 可以使用角括号一并指定泛型类别型态持 有者T真正的型态 GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>(); GenericFoo<Integer> foo2 = new GenericFoo<Integer>(); 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<Boolean> foo1 = new GenericFoo<Boolean>(); 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是不同的 GenericFoo<Boolean> foo1 = new GenericFoo<Boolean>(); GenericFoo<Integer> foo2 = new GenericFoo<Integer>(); • 不可以将foo1所参考的实例指定给foo2,或 是将foo2所参考的实例指定給foo1 incompatible types found : GenericFoo<java.lang.Integer> required: GenericFoo<java.lang.Boolean> foo1 = foo2;
  • 几个定义泛型的例子 • 类别上宣告两个型态持有者T1与T2 public class GenericFoo2<T1, T2> { private T1 foo1; private T2 foo2; … } GenericFoo<Integer, Boolean> foo = new GenericFoo<Integer, Boolean>();
  • 几个定义泛型的例子 • 可以用于宣告数组型态 public class GenericFoo3<T> { private T[] fooArray; public void setFooArray(T[] fooArray) { this.fooArray = fooArray; } public T[] getFooArray() { return fooArray; } } String[] strs = {"caterpillar", "momor", "bush"}; GenericFoo3<String> foo = new GenericFoo3<String>(); foo.setFooArray(strs); strs = foo.getFooArray();
  • 几个定义泛型的例子 • 可以使用泛型机制来宣告一个数组 public class GenericFoo<T> { private T[] fooArray; // ... } • 不可以使用泛型来建立数组的实例 public class GenericFoo<T> { private T[] fooArray = new T[10]; //不可以使用泛型建立数组实例 // ... }
  • 几个定义泛型的例子 • 想要设计一个新的类别,当中包括了范例 12.4的类别实例作为其成员 public class WrapperFoo<T> { private GenericFoo<T> foo; public void setFoo(GenericFoo<T> foo) { this.foo = foo; } public GenericFoo<T> getFoo() { return foo; } }
  • 几个定义泛型的例子 GenericFoo<Integer> foo = new GenericFoo<Integer>(); foo.setFoo(new Integer(10)); WrapperFoo<Integer> wrapper = new WrapperFoo<Integer>(); wrapper.setFoo(foo);
  • 限制泛型可用类型 • 一并使用"extends"指定这个型态持有者实 例化时,实例化的对象必须是扩充自某个 类型或实作某接口 import java.util.List; public class ListGenericFoo<T extends List> { private T[] fooArray; public void setFooArray(T[] fooArray) { this.fooArray = fooArray; } public T[] getFooArray() { return fooArray; } }
  • 限制泛型可用类型 • 在限定持有者时,无论是要限定的对象是 接口或类别,都是使用"extends"关键词 ListGenericFoo<LinkedList> foo1 = new ListGenericFoo<LinkedList>(); ListGenericFoo<ArrayList> foo2 = new ListGenericFoo<ArrayList>();
  • 限制泛型可用类型 • 如果不是实作List的类别,编译时就会发生 错误 ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>(); type parameter java.util.HashMap is not within its bound ListGenericFoo<HashMap> foo3 = new ListGenericFoo<HashMap>();
  • 限制泛型可用类型 • 您定义泛型类别时如果只写以下的话 public class GenericFoo<T> { //.... } • 相当于以下的定义方式 public class GenericFoo<T extends Object> { //.... }
  • 型态通配字符(Wildcard) • 假设使用GenericFoo类别来如下宣告名称 GenericFoo<Integer> foo1 = null; GenericFoo<Boolean> foo2 = null; • 下面的方式是可行的 foo1 = new GenericFoo<Integer>(); foo2 = new GenericFoo<Boolean>();
  • 型态通配字符(Wildcard) • 可以使用‘?’「通配字符」(Wildcard),‘?’ 代表未知型态,并使用“extends”关键词来 作限定 GenericFoo<? extends List> foo = null; foo = new GenericFoo<ArrayList>(); ..... foo = new GenericFoo<LinkedList>(); .... • 以下这行无法通过编译 GenericFoo<? extends List> foo = new GenericFoo<HashMap>();
  • 型态通配字符(Wildcard) • 编译程序会回报以下的错误 incompatible types found : GenericFoo<java.util.HashMap> required: GenericFoo<? extends java.util.List> GenericFoo<? extends List> foo = new GenericFoo<HashMap>(); • 如果您不希望任何的型态都可以传入 showFoo()方法中 public void showFoo(GenericFoo<? extends String> foo) { //针对String或其子类而制定的内容,例如下面这行 System.out.println(foo.getFoo()); }
  • 型态通配字符(Wildcard) • 透过使用通配字符宣告的名称所参考的对 象,您没办法再对它加入新的信息,您只 能取得它当中的信息或是移除当中的信息 GenericFoo<String> foo = new GenericFoo<String>(); foo.setFoo("caterpillar"); GenericFoo<?> immutableFoo = foo; //可以取得信息 System.out.println(immutableFoo.getFoo()); //可透过immutableFoo来移去foo所参考实例内的信息 immutableFoo.setFoo(null); //不可透过immutableFoo来设定新的信息给foo所参考的实例 //所以下面这行无法通过编译 // immutableFoo.setFoo("良葛格");
  • 型态通配字符(Wildcard) • 因为您不知道<?>或是<? extends SomeClass>宣告的参考名称,实际上参考 的对象,当中确实储存的是什么类型的信 息 • 基于泛型的设计理念,当然也就没有理由 能加入新的信息了 • 因为若能加入,被加入的对象同样也会有 失去型态信息的问题
  • 型态通配字符(Wildcard) • 也可以向上限制,只要使用"super"关键词 GenericFoo<? super StringBuilder> foo = null;
  • 扩充泛型类别、实作泛型接口 • 可以扩充一个泛型类别,保留其型态持有 者,并新增自己的型态持有者 public class SubGenericFoo4<T1, T2, T3> extends GenericFoo4<T1, T2> { private T3 foo3; public void setFoo3(T3 foo3) { this.foo3 = foo3; } public T3 getFoo3() { return foo3; } }
  • 扩充泛型类别、实作泛型接口 • 如果不保留型态持有者,则继承下来的T1 与T2自动变为Object – 建议是父类别的型态持有者都要保留 • 界面实作 public class ConcreteFoo<T1, T2> implements IFoo<T1, T2> { private T1 foo1; private T2 foo2; … }
  • 第13章 • 对象容器 – Collection类 – Map类
  • 简介List界面 • List界面是java.util.Collection接口的子接口 • Collection界面是java.lang.Iterable子界面 package java.lang; import java.util.Iterator; public interface Iterable<T> { Iterator<T> iterator(); } • 在Java SE的API中找不到任何实作Iterator的类别 – Iterator会根据实际的容器数据结构来迭代元素 – 而容器的数据结构实作方式对外界是隐藏的
  • 简介List界面 • Collection界面继承了Iterator界面 package java.util; public interface Collection<E> extends Iterable<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); <T> T[] toArray(T[] a); boolean add(E o); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> 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<E> extends Collection<E> { .... boolean addAll(int index, Collection<? extends E> 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<E> subList(int fromIndex, int toIndex); .... }
  • 简介List界面 • List可以使用数组(Array)或是链结串行 (LinkedList)来实作这个特性 • 对于循序加入与存取,使用ArrayList的效率 比较好 • 对于经常变动元素排列顺序的需求,使用 LinkedList会比较好
  • ArrayList • 使用数组结构实作List数据结构 • 可以使用索引来快速指定对象的位置 • 于快速的随机取得对象来说,使用ArrayList 可以得到较好的效能 • 若要从中间作移除或插入对象的动作,会 需要搬动后段的数组元素以重新调整索引 顺序,所以速度上就会慢的多
  • ArrayList Scanner scanner = new Scanner(System.in); List<String> list = new ArrayList<String>(); 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 iterator = list.iterator(); while(iterator.hasNext()) { //还有下一个元素吗? //使用next()取得下一个元素 System.out.print(iterator.next() + " "); } • Iterator的实例是在ArrayList中根据数组的
  • 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<String> linkedList; public StringStack() { linkedList = new LinkedList<String>(); } 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<String> linkedList; public StringQueue() { linkedList = new LinkedList<String>(); } 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<String> set = new HashSet<String>(); 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<String> set = new LinkedHashSet<String>(); 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<String> set = new TreeSet<String>(); 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<T> implements Comparator<T> { public int compare(T o1, T o2) { if (((T) o1).equals(o2)) return 0; return ((Comparable<T>) o1).compareTo((T) o2) * -1; } } Comparator<String> comparator = new CustomComparator<String>(); Set<String> set = new TreeSet<String>(comparator);
  • HashMap • Map的特性即「键-值」(Key-Value)匹配 • java.util.HashMap实作了Map界面, • HashMap在内部实作使用哈希(Hash), 很快的时间内可以寻得「键-值」匹配
  • HashMap Map<String, String> map = new HashMap<String, String>(); 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<String, String> map = new HashMap<String, String>(); 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<String, String> map = new LinkedHashMap<String, String>(); 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<String, String> map = new TreeMap<String, String>(); 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<String> comparator = new CustomComparator<String>(); Map<String, String> map = new TreeMap<String, String>(comparator);
  • 第14章 • 输入 输出 – 档案 – 位串流 – 字符串流
  • File类别 • 不同的操作系统对于文件系统路径的设定 各有差别 • Windows "C:WorkspaceCH14" • Linux "/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<File> fileList = new ArrayList<File>(); 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 • 提供一些对Java基本数据型态写入的方法 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();
  • 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界面 public class User implements Serializable { private static final long serialVersionUID = 1L; … } • serialVersionUID代表了可串行化对象版本 • 从档案读回对象时两个对象的 serialVersionUID不相同的话,就会丢出 java.io.InvalidClassException
  • 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') { //写入"rn" 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线程 • 使用setDaemon()方法来设定一个线程是否 为Daemon线程 • 预设所有从Daemon线程产生的线程也是 Daemon线程 Thread thread = new Thread( //这是匿名类别的写法 new Runnable() { public void run() { while(true) { System.out.print("T"); } } }); //设定为Daemon线程 thread.setDaemon(true); thread.start();
  • 线程生命周期 • 执行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 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中的某些方法,可以对所有的线程 产生作用 – interrupt()方法可以interrupt群组中所有的线程 – setMaxPriority()方法可以设定群组中线程所能拥有的 最大优先权
  • 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()等方法来传回一个同步 化的容器对象 List list = Collections.synchronizedList(new ArrayList()); • 使用Iterator遍访对象时,您仍必须实作同 步化 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目录下的libextclasses目录下 的.class档案,或libext目录下的.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 -Djava.ext.dirs=c:workspace YourClass • 可以在使用java启动程序时,使用- classpath或-cp来指定AppClassLoader的 搜寻路径,也就是设定Classpath java -classpath c:workspace YourClass
  • 使用自己的ClassLoader • ExtClassLoader与AppClassLoader在程序 启动后会在虚拟机中存在一份 • 在程序运行过程中就无法再改变它的搜寻 路径,如果在程序运行过程中 • 打算动态决定从其它的路径加载类别,就 要产生新的类别加载器
  • 使用自己的ClassLoader • 可以使用URLClassLoader来产生新的类别 加载器 URL url = new URL("file:/d:/workspace/"); ClassLoader urlClassLoader = new URLClassLoader(new URL[] {url}); Class c = urlClassLoader.loadClass("SomeClass"); • 搜寻SomeClass类别时,会一路往上委托 至BootstrapLoader先开始搜寻,接着是 ExtClassLoader、AppClassLoader,如果 都找不到,才使用新建的ClassLoader搜寻
  • 使用自己的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"}) • 详细形式 @FunctionTest(value={"method1", "method2"}) • value成员设定默认值,用"default"关键词 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界面 public Annotation getAnnotation(Class annotationType); public Annotation[] getAnnotations(); public Annotation[] getDeclaredAnnotations(); public boolean isAnnotationPresent(Class annotationType); • Class、Constructor、Field、Method、 Package等类别,都实作了 AnnotatedElement界面
  • 告知编译程序如何处理@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<SomeClass3> 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()方法 public class CurrentTime { public static void main(String[] args) { System.out.println("现在时间" + System.currentTimeMillis()); } } • 从1970年1月1日0时0分0秒开始,到程序 执行取得系统时间为止所经过的毫秒数
  • 使用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中定义的常数 Calendar rightNow = Calendar.getInstance(); System.out.println(rightNow.get(Calendar.YEAR)); • 传回的4并不是代表目前时间是4月份,而 是对应于Calendar.MAY常数的值 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" + "Levelt|tLoggerNamet|tMessaget|n" + logRecord.getLevel() + "t|t" + logRecord.getLoggerName() + "t|t" + logRecord.getMessage() + "t|nn"; } }
  • 自定义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 onlyfun.caterpillar.welcome=Hello onlyfun.caterpillar.name=World • .properties档案必须放置在Classpath的路 径设定下
  • 使用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 Locale locale = new Locale("zh", "TW"); – basename.properties(预设) – basename_en.properties – basename_zh_TW.properties
  • 国际化讯息 onlyfun.caterpillar.welcome=哈啰 onlyfun.caterpillar.name=世界 native2ascii -encoding Big5 messages3_zh_TW.txt messages3_zh_TW.properties onlyfun.caterpillar.welcome=u54c8u56c9 onlyfun.caterpillar.name=u4e16u754c
  • 国际化讯息 • 想提供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的事件方面 package java.awt.event; public interface KeyListener { public void keyPressed(KeyEvent e) public void keyReleased(KeyEvent e) public void keyTyped(KeyEvent e) } • 也可以继承java.awt.event.KeyAdapter
  • 文本编辑器的事件处理 • 使用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的实例 JPopupMenu popUpMenu = editMenu.getPopupMenu(); • 按下窗口右上角的X按钮 //按下窗口关闭钮事件处理 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 stmt = conn.createStatement(); • 插入一笔数据,可以如下使用Statement的 executeUpdate()方法 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()方法 int[] arr1 = {1, 2, 3, 4, 5}; int[] arr2 = Arrays.copyOf(arr1, 1, 4); • binarySearch()方法 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套件 • 指定启动屏幕的图片 java-splash:caterpillar.jpg -jar JNotePad.jar • manifest档案的写法 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()并 指定驱动程序 Connection conn = DriverManager.getConnection( url, username, password); • JVM会自动在Classpath中寻找适当的驱动程序 • 在包装有JDBC驱动程序的JAR档案中,必须有一 个"META-INF/services/java.sql.Driver"档案,当 中撰写驱动程序类别名称
  • 改进的例外处理 • 在JDBC 4.0之中,SQLException新增了几 个建构函式,可以接受Throwable实例进行 SQLException的建构 • SQLException并实作了Iterable<T>界面
  • 改进的例外处理 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()等方法