Java从入门到精通 -阅读摘要与实践
第1篇 基础知识
java语言基础
基本数据类型
在Java中有8中基本数据类型来存储数值、字符和布尔值
基本数据类型 |
数值型 |
整数类型 |
byte、short、int、long |
浮点类型 |
float、double |
||
字符型 |
char |
||
布尔型 |
booblean |
默认值如下:
byte |
0 |
short |
0 |
int |
0 |
long |
0L |
float |
0.0F |
double |
0.0 |
char |
‘\u0000’空格 |
booblean |
false |
整型数据类型
数据类型 |
内存空间(8位等于1字节) |
取值范围 |
byte |
8位 |
-128-127 |
short |
16位 |
-32768-32767 |
int |
32位 |
- 2147483648- 2147483647 |
long |
64位 |
-9223372036854775808- 9223372036854775807 |
浮点型数据类型
数据类型 |
内存空间(8位等于1字节) |
取值范围 |
float |
32位 |
1.4E-45至3.4028235E38 |
double |
64位 |
4.9E-324至1.7976931348623157E308 |
变量与常量
标识符
标识符可以简单理解为一个名字,用来标识类名、变量名、方法名、数组名、文件名的有效字符序列。
Java语言规定标识符由任意顺序的字母、下划线(_)、美元符号($)和数字组成,并且第一个字符不能是数字。标识符不能是Java中的保留关键字。
关键字
int |
public |
this |
finally |
boolean |
abstract |
continue |
float |
long |
short |
throw |
throws |
retuan |
break |
for |
static |
new |
interface |
if |
goto |
default |
byte |
do |
case |
strictfp |
package |
super |
void |
try |
switch |
else |
catch |
implements |
private |
final |
class |
extends |
volatile |
while |
synchronized |
instanceof |
char |
protecte |
importd |
transient |
implements |
dafaule |
double |
声明变量
定义变量就是要告诉编译器这个变量的数据类型。
在程序运行过程中,空间内的值是变化的,这个内存空间就称之为变量。为了便于操作,给这个空间取个名字,称为变量名。内存空间内的值就是变量值。
声明常量
在程序运行过程中一直不会改变的量称为常量,通常也被称为“final变量”。常量在整个程序中只能被赋值一次。在为所有的对象共享值时,常量是非常有用的。
在Java语言中声明一个常量,除了要指定数据类型外,还需要通过final关键字进行限定。声明常量的标准语法如下:
final 数据类型 常量名称[=值]
当定义的final变量属于“成员变量”时,必须在定义就设定它的初值,否则将会产生编译错误。
变量的有效范围
l 成员变量
在类体中所定义的变量被称之为成员变量,成员变量在整个类中都有效。类的成员变量又可分为静态变量和实例变量。静态变量的有效范围可以跨类,甚至可达到整个应用程序之内。
l 局部变量
在类的方法体中定义的变量称为局部变量,局部变量只在当前代码块中有效。
运算符
数据类型转换
类型转换是一个值从一种类型更改为另一种类型的过程。
如果从低精度数据类型向高精度数据类型转换,则永远不会溢出,并且总是成功的;而把高精度数据类型向低精度数据类型转换则必然会有信息丢失,有可能失败。
数据类型转换有两种方式,及隐式转换与显式转换。
l 隐式转换:从低级类型向高级类型的转换,系统将自动执行,程序员无须进行任何操作。
操作数1的数据类型 |
操作数2的数据类型 |
转换后的数据类型 |
byte、short、char |
int |
int |
byte、short、char、int |
long |
long |
byte、short、char、int、long |
float |
float |
byte、short、char、int、long、float |
double |
double |
l 显示转换:把高精度的变量赋值给低精度的变量,也称为“强制转换”。
流程控制
l 复合语句:Java语言整个块区为单位的语句,又称为块语句,由开括号“{”开始和闭括号“}”结束。
l 条件语句:if|if else|if else if;switch
l 循环语句:while|do while|for|foreach
字符串
String类
l 声明字符串
String str=[null]
l 创建字符串
String(char a[]) String(char a[],int offset,int length) String(char[] value)
l 连接字符串
String s=’!’+’’+’b’;
l 获取字符串长度
str.length();
l 字符串查找
str.indexOf(substr);//返回参数字符串s在指定字符串中首次出现的索引位置。无则返回-1。 str.latIndexOf(substr);//返回指定字符串最后一次出现的索引位置。无则返回-1。
l 获取指定索引位置的字符
str.charAt(int index);
l 获取子字符串
str.substring(int beginIndex);//返回从指定的索引位置开始截取直到该字符串结尾的字串 str.substring(int beginIndex,endIndex)//返回从字符串某一索引位置开始截取至某一索引位置结束的子串。
l 去除空格
str.trim()
l 字符串替换
srt.replace(char oldChar,char newChar);
l 判断字符串的开始与结尾
str.startsWith(String prefix);
str.endsWith(String suffix);
l 判断字符串是否相等
str.equals(String otherstr)
l 按字典顺序比较两个字符串
//String对象位于参数字符串之前,负数;之后,正整数;字符串相等,返回0 str.compareTO(String ohtersstr);//compareTO()方法只有在equals(Object)方法返回true时才返回0.
l 字符大小写转换
str.toLowerCase();
str.toUpperCase();
l 字符串分割
str.split(String sign); str.split(String sign,int limit);//限定拆分次数
l 格式化字符串
str.format(String format,Object…args);
l 正则判断字符串
String regex=”xxxxxx”; str1.matches(regex);//判断字符串变量是否与正则表达式匹配
l 字符串生成器
StringBuilder
数组
一维数组
声明
数组元素类型 数组名字[];
数组元素类型[] 数组名字;
例
int attr[]; String str[];
初始化
int attr[]=new in[{1,2,3,4,5}]; int attr2[]={1,2,3}
二维数组
声明
数组元素类型 数组名字[][];
数组元素类型[][] 数组名字;
例
int myarr[][];
初始化
type arrayname[][]={value1,value2…valuen};
例
int myarr[][]=[{12,0},{45,10}];
数组的基本操作
l 填充替换数组元素
fill(int[] a,int value); fill(int[] a,int fromIndex,int toIndex,intvalue);//fromIndex包含,toIndex不包含
l 排序
Arrays.sort(object)
l 复制
copyOf(arr,int newlength) copyOfRange(arr,int fromIndex,toIndex);
l 查询
binarySearch(Object[] a,Object key) binarySearch(Object[] a,int fromIndex,int toIndex,Object key)
数组排序算法
l 冒泡排序
对比相邻的元素值,如果满足条件就交换元素值,把较小的元素移动到数组前面,把大的元素移动到数组后面(也就是交换两个元素的位置),这样较小的元素就像气泡一样从底部上升到顶部。
l 直接选择排序
排序速度比冒泡排序快,将指定排序位置与其他数组元素分别比对,如果满足条件就减缓元素值,这里这里区别冒泡排序,不是交换相邻元素,而是把满足条件的元素与指定的排序位置交换。
例:
初始数组资源【63 4 24 1 3 15】 第一趟排序后【15 4 24 1 3】63 第二趟排序后【15 4 3 1】24 63 第三趟排序后【1 4 3】15 24 63 第四趟排序后【1 3】4 15 24 63 第五趟排序后【1】3 4 15 24 63
l 反转排序
把数组最后一个元素与第一个元素替换,倒数第二个元素与第二个元素替换,依此类推,知道把所有元素反转替换。
初始数组资源【10 20 30 40 50 60】 第一趟排序后60 【20 30 40 50】10- 第二趟排序后60 50【30 40】20 10- 第三趟排序后60 50 40 30 20 10-
类和对象
面向对象
l 对象
l 类
l 封装
l 继承
l 多态
类
l 成员变量:在Java中对象的属性也称为成员变量。
l 成员方法:在Java语言中使用成员方法对应于类对象的行为。
l 权限修饰符:private、protected、public
l 局部变量:如果在成员方法内定义一个变量,那么这个变量被称为局部变量。
l 局部变量的有效范围:可以将局部变量的有效范围称为变量的作用域,局部变量的有效范围从该范围从该变量的声明开始到该变量的结束位置。
l this关键字:在Java语言中规定使用this关键字来代替本类对象的引用,this关键字被隐式地用于引用对象的成员变量和方法。
类的构造方法
在类中除了成员方法外,还存在一种特殊类型的方法,那就是构造方法。构造方法是一个与类同名的方法,对象的创建就是通过构造方法完成的。每当类实例化一个对象时,类都会自动调用构造方法。
构造方法的特点如下:
l 构造方法没有返回值。
l 构造方法的名称要与本类的名称相同。
静态变量、常量和方法
被声明为static的变量、常量和方法被称为静态成员。静态成员属于类所有,区别于个别对象,可以在本类或其他类使用类名和“.”运算符调用静态成员。
在Java语言中对静态方法有两点规定:
l 在静态方法中不可以使用this关键字。
l 在静态方法中不可以直接调用非静态方法。
类的主方法
主方法是类的入口点,它定义了程序从何出开始:主方法提供对程序流向的控制,Java编译器通过主方法来执行程序。
public static void main(String[] args){ //方法体 }
在主方法的定义可以看到主方法具有以下特性:
l 主方法是静态的,所以如要直接在主方法调用其他方法,则该方法必须也是静态的。
l 主方法没有返回值。
l 主方法的形参为数组。其中args[0]~args[n]分别代表程序的第一个参数到第n个参数,可以使args.length获取参数的个数。
对象
l 对象的创建
Test test=new Test();
l 访问对象的属性和行为
用户使用了new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。
l 对象的引用
类名 对象引用名称
l 对象的比较
equals()比较两个对象引用所指的内容是否相等;“==”运算符比较的是两个对象引用的地址是否相等。
l 对象的销毁
以下两种情况会被Java虚拟机视为垃圾:对象引用超过其作用范围;将对象赋值未null。
虽然垃圾回收机制已经很完善,但垃圾回收器只能回收那些由new操作符创建的对象,如果某些对象不是通过new操作符在内存中获取一块内存区域,这种对象可能不能被垃圾回收机制所识别,所以在java中提供了一个finalize()方法。这个方法是object类的方法,它被声明为protected,用户可以在自己的类中定义这个方法。如果用户在类中定义了finalize()方法,在垃圾回收时会首先调用该方法,在下一次垃圾回收动作发生时,才能真正回收被对象占用的内存。java提供了system.gc()方法强制启动垃圾回收器。
包装类
Java是一种面向对象语言,Java中的类把方法与数据连接在一起,构成了自包含式的处理单元。但在Java中不能定于基本类型对象,为了能将基本类型视为对象进行处理,并能连接相关的方法,Java为每个基本类型都提供了包装类。
Integer
l 构造方法
Integer(int number) Integer(String str)
l 常用方法
方法 |
返回类 |
功能描述 |
byteValue |
byte |
以byte类型返回该integer的值 |
compareTo(Integer anotherInteger) |
int |
在数字上比较两个Integer对象。如果这两个值相等,则返回0;如果调用对象的数值小于anotherInteger的数值,则返回负数;如果调用对象的数值大于anotherInteger的数值,则返回正值。 |
equals(Object IntegerObj) |
boolean |
比较此对象与指定的对象是否相等 |
intValue() |
int |
以int型返回此Integer对象 |
shortValue() |
short |
以short型返回此Integer对象 |
toString() |
String |
返回一个表示该Integer值的String对象 |
valueOf(String str) |
Integer |
返回保存指定的String值的Integer对象 |
pareInt(String str) |
int |
返回包含在由str指定的字符串中的数字的等价整数值 |
l integer类提供了以下四个常量
MAX_VALUE:表示int类型可取的最大值
MIN_VALUE:标识int类型可取的最小值
SIZE:用来以二进制补码形式标识int值的位数。
TYPE:标识基本类型int的class实例。
Boolean
Boolean默认值是null,booblean默认值是false。
l 构造方法
Boolean b=new Boolean(true) Boolean b=new Boolean(ok)
l 常用方法
方法 |
返回类 |
功能描述 |
booleanValue |
boolean |
将Boolean对象的值以对应的boolean值返回 |
equals(Object obj) |
boolean |
判断调用该方法的对象与obj是否相对。当且仅当参数不是null,而且与调用该方法的对象一样都表示同一个boolean值的Boolean对象时,才返回true |
pareBoolean(String s) |
boolean |
将字符串参数解析为boolean值 |
toString() |
String |
返回标识该boolean值的string对象 |
valueOf(String s) |
boolean |
返回一个用指定的字符串表示值的boolean值 |
l Boolean提供了以下3个常量
TRUE:对应基值true的Boolean对象
FALSE:对应基值false的Boolean对象
TYPE:基本类型boolean的class对象
Byte
l 构造方法
Byte(byte value) Byte(String str)
l 常用方法
方法 |
返回类 |
功能描述 |
byteValue() |
byte |
以一个byte值返回byte对象 |
compareTo(Byte anotherByte) |
int |
在数字上比较两个byte对象 |
doubleValue() |
double |
以一个double值返回此byte的值 |
intValue() |
Int |
以一个int值返回此byte的值 |
pareByte(String s) |
byte |
将string型参数解析成等价的字节(byte)形式 |
toString() |
String |
返回表示此byte的值的string对象 |
valueOf(String str) |
Byte |
返回一个保持指定string所给出的值的byte对象 |
equals(Object obj) |
boolean |
将此对象与指定对象比较,如果调用该方法的对象与obj相等,则返回true,否则返回false |
l Byte类提供了如下4个常量
MIN_VALUE:byte类型可取的最小值
MAX_VALUE:byte类型可取的最大值
SIZE:用于以二进制补码形式表示byte值的位数
TYPE:表示基本类型byte的class示例。
Character
l 构造方法
Character(char value) Character mychar=new Character(‘s’)
l 常用方法
方法 |
返回类 |
功能描述 |
charvalue |
char |
返回此Character对象的值 |
compareTo(Character anotherCharacter) |
int |
根据数字比较两个Character对象,若这两个对象相等则返回0 |
equals(Object obj) |
Boolean |
将调用该方法的对象与指定的对象相比较 |
toUpperCase(char ch) |
char |
将字符串参数转换为大写 |
toLowCase(char ch) |
char |
将字符串参数转换为小写 |
toString() |
String |
返回一个表示指定char值的String对象 |
charValue() |
char |
返回此Character对象的值 |
isUpperCase(char ch) |
boolean |
判断指定字符是否是大写字符 |
isLowerCase(char ch) |
boolean |
判断指定字符是否是小写字符 |
l Character类提供了大量表示特定字符的常量
CONNECTOR_PUNCTUATION:返回byte型值,表示Unicode规范中的常规类别“Pc”
UNASSIGNED:返回byte型值,表示Unicode规范中的常规类别“Cn”
TITLECASE_LETTER:返回byte型值,表示Unicode规范中的常规类别“Lt”
Double
Double和Float包装类是double、float基本类型的封装,它们都是Number类的子类,又都是对小数进行操作,所以常用方法基本相同。
l 构造方法
Double(double value) Double(String str)
l 常用方法
方法 |
返回类 |
功能描述 |
byteValue() |
byte |
以byte形式返回Double对象值(通过强制转换) |
compareTo(Double d) |
int |
对两个Double对象进行数值比较。如果两个值相等,则返回0;如果调用对象的数值小于d的数值,则返回负值;如果调用对象的数值大于d的值,则返回值 |
equals(Object obj) |
boolean |
将此对象与指定的对象相比较 |
intValue() |
int |
以int形式返回double值 |
isNaN() |
boolean |
如果此double值是非数字(NaN)值,则返回true;否则返回false |
toString() |
String |
返回此Double对象的字符串表示形式 |
valueOf(String str) |
Double |
返回保存用参数字符串str表示的double值的Double对象 |
doubleValue() |
double |
以double形式返回此Double对象 |
longValue() |
long |
以long形式返回此double的值(通过强制转换为long类型) |
l Double类提供了以下的常量
MAX_EXPONENT:返回int值,表示有限double变量可能具有的最大指数。
MIN_EXPONENT:返回int值,表示标准化double变量可能具有的最小指数。
NEGATIVE_INFINITY:返回double值,表示保存double类型的负无穷大值的常量。
POSITIVE_INFINITY:返回double值,表示保存double类型的正无穷大值的常量。
Number
l 常用方法
方法 |
返回类 |
功能描述 |
byteValue() |
byte |
以byte形式返回指定的数值 |
intValue() |
int |
以int形式返回指定的数值 |
floatValue() |
float |
以float形式返回指定的数值 |
shortValue() |
short |
以short形式返回指定的数值 |
longValue() |
long |
以long形式返回指定的数值 |
doubleValue() |
double |
以double形式返回指定的数值 |
数字处理类
数字格式化
在Java中没有格式化的数据遵循以下原则:
l 如果数据绝对值大于0.001并且小于10000000,Java将以常规小鼠形式表示。
l 如果数据绝对值小于0.001或者大于10000000,使用科学计数法表示。
Decimal Format类中特殊字符说明
字符 |
说明 |
0 |
代表阿拉伯数字,使用特殊字符“0”表示数字的一位阿拉伯数字,如果该位不存在数字,则显示0 |
# |
代表阿拉伯数字,使用特殊字符“#”表示数字的一位阿拉伯数字,如果该位存在数字,则显示字符;如果该位不存在数字,则不现实 |
. |
小数分隔符或货币小数分隔符 |
- |
负号 |
, |
分组分隔符 |
E |
分割科学计数法中的尾数和指数 |
% |
本符号放置在数字的前缀或后缀,将数字乘以100显示为百分数 |
\u2030 |
本符号防止在数值的前缀或后缀,将数字乘以1000显示为千分数 |
\u00A4 |
本符号放置在数字的前缀或后缀,作为货币记号 |
‘ |
本符号为单引号,当上述特殊字符出现在数字中时,应为特殊符号添加单引号,系统会将此符号视为普通符号处理。 |
数学运算
Math类中包含的三角函数方法如下:
public static double sin(double a):返回角的三角正弦 public static double cos(double a):返回角的三角余弦 public static double tan(double a):返回角的三角正切 public static double asin(double a):返回一个值的反正弦 public static double acos(double a):返回一个值的反余弦 public static double atan(double a):返回一个值的反正切 public static double toRadians(double a):将角度转换为弧度 public static double toDegrees(double a):将弧度转换为角度
Math类中与指数相关的函数方法如下:
public static double exp(double a):用于获取e的a次方 public static double log(double a):用于取自然对数,即取lna的值 public static double log10(double a):用于取底数为10的对数 public static double sqrt(double a):用于取a的平方根,其中a的值不能为负数。 public static double cbrt(double a):用于取a的立方根 public static double pow(double a):用于取a的b次方
Math类中主要包括以下几种取整方法:
l public static double ceil(double a):返回大于等于参数的最小整数 l public static double floor(double a):返回小于等于参数的最大整数 l public static double rint(double a):返回与参数最接近的整数,如果两个同为整数且同样接近,则结果取偶数。 l public static int round(float a):将参数加上0.5后返回与参数最近的整数。 l public static long round(double a):将参数加上0.5后返回与参数最近的整数,然后强制转换为长类型。
Math类中包括的取最大值、最小值、绝对值等方法如下:
l public static double max(double a):取a与b之间的最大值 l public static int min(int a,int b):取a与b之间的最小值,参数为整型 l public static long min(long a,long b):取a与b之间的最小值,参数为长类型 l public static float min(float a,float b):取a与b之间的最小值,参数为浮点型 l public static double min(double a,double b):取a与b之间的最小值,参数为双精度型。 l public static int abs(int a):返回整型参数的绝对值 l public static long abs(long a):返回长整型参数的绝对值 l public static float abs(float a):返回浮点型参数的绝对值 l public static double abs(double a):返回双精度型参数的绝对值
随机数
在Math类中存在一个random()方法,用于产生随机数字,这个方法默认生成大于等于0.0小于1.0的double型随机数,即0<=Math.random()<1.0,虽然Math.random()方法只可以产生0至1之间的double型数字,其实只要稍加处理,就可以使用这个方法产生任意范围的随机数。
大数字运算
BigInteger支持任意精度的整数,也就是说在运算中BigInteger类型可以准确地表示任何大小地整数值而不会丢失任何信息。
Biginteger类中的一些方法进行运算操作,包括基本的数学运算和位运算以及一些取相反数、绝对值等操作。
public BigInteger add (BigInteger val)//做加法运算 public BigInteger subtract (BigInteger val)//做减法运算 public BigInteger multiply (BigInteger val)//做乘法运算 public BigInteger divide (BigInteger val)//做除法运算 public BigInteger remainder (BigInteger val)//做取余操作 public BigInteger[] divideAndRemainder (BigInteger val)//用数组返回余数和商,结果数组中第一个值为商,第二个值位余数 public BigInteger pow ( int exponent)//进行取参数的exponent次方操作 public BigInteger negate ()//取相反数 public BigInteger shiftLeft ( int n)//将数字左移n位,如果n为负数,做右移操作 public BigInteger shiftRight ( int n)//将数字右移n位,如果n为负数,做左移操作 public BigInteger and(BigInteger val)//做与操作 public BigInteger or(BigInteger val)//做或操作 public int compareTo(BigInteger val)//做数字比较操作 public boolean equals(Object x)//当参数x是BigInteger类型的数字并且数值相等时,返回true public BigInteger min(BigInteger val)//返回较小的数值 public BigInteger max(BigInteger val)//返回较大的数值
BigDEcimal支持任何精度的定点数,可以用它来精确计算货币值。
l 常用两种构造方法
public BigDecimal(double val)//实例化时将双精度型转换为BigDecimal类型 public BigDecimal(String val)//实例化时将字符串形式转换为BigDecimal类型
l 常用方法
public BigDecimal add(BigDecimal augend) //做加法运算 public BigDecimal subtract(BigDecimal subtrahend)//做减法运算 public BigDecimal multiply(BigDecimal multiplicand)//做乘法运算 public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)//做除法操作,方法中三个参数分别代表除数、商的小数点后的位数、近似处理模式
BigDecimal类中的divide()方法的多种处理模式
模式 |
含义 |
BigDecimal.ROUND_UP |
商的最后一位如果大于0,则向前进位,正负数都是如此 |
BigDecimal.ROUND_DOWN |
商的最后一位无论是什么数字都省略 |
BigDecimal.ROUND_CEILING |
商如果是正数,按照ROUND_UP模式处理;如果是负数,按照ROUND_DOWN模式处理。这种模式的处理都会使近似值大于等于实际值 |
BigDecimal.ROUND_FLOOR |
与ROUND_CEILING模式相反,商如果是整数,按照ROUND_DOWN模式处理;商如果是负数,则按照ROUND_UP模式处理,这种模式小于等于实际值 |
BigDecimal.ROUND_HALF_DOWN |
对商进行四舍五入操作,如果商最后一位小于等于5,则做舍弃操作;如果最后一位大于5,进行进位操作,如7.5≈7 |
BigDecimal.ROUND_HALF_UP |
对商进行四舍五入操作,如果商最后一位小于5,则做舍弃操作;如果最后一位大于等于5,进行进位操作,如7.5≈8 |
BigDecimal.ROUND_HALF_EVEN |
如果商的倒数第二位数为奇数,则按照ROUND_HALF_UP处理; 如果为偶数,则按照ROUND_HALF_DOWN处理,如7.5≈8,8.5≈8 |
第2篇 核心技术
接口、继承与多态
类的继承
基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原来的属性与方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
Test与Test2类之间的继承关系
继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或者修改成员方法的返回值类型。
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种重写方式被称为重构。
注意:当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变。
在继承机制中,创建一个子类对象,将包含一个父类子对象,这个对象与父类创建的对象是一样的。两者的区别在于后者来自外部,而前者来自子类对象的内部。
Object类
所有类的父类,是Java类层中的最高层。
定义类时可以省略extends Object关键字
几个重要方法如下:
getClass()方法:是Object类定义的方法,它会返回对象执行的Class实例,然后使用此示例调用getName()方法可以取得类的名称。
toString()方法:将一个对象返回为字符串形式,它会放回一个String实例。
equals()方法:比较两个对象的实际内容,注意,在自定义类中使用equals()方法进行比较时,将返回false,因为equals()方法默认实现是使用“==”运算符比较两个对象的引用地址,而不是比较对象的内容。所以要想真正做到比较两个对象的内容,需要在自定义类中重写equals()方法。
对象类型的转换
l 向上转型
子类对象赋值给父类类型的变量,这种技术被称为“向上转型”。
由于向上转型是一个较具体的类到较抽象的类的转换,所以总是安全的。
演示了平行四边形类继承四边形类的关系。
l 向下转型
将较抽象类转换为比较具体的类,这样的转型通常会出问题。因为父类不一定是子类的示例。
四边形与具体的四边形的关系
使用instanceof操作符判断对象类型
当在程序中执行向下转型操作时,如果父类对象不是子类对象的实例,就会发生ClassCastException异常。可以使用instanceof操作符判断一个类实现了某个接口,也可以用它来判断一个实例对象是否属于一个类,语法如下:
某类的对象引用 instanceof 某个类
方法的重载
方法的重载就是在同一个类中允许同时存在一个以上的同名方法,只要这些方法的参数个数或类型不同即可。
构成方法重载的条件
编译器是利用方法名、方法各参数类型、参数的个数以及参数的顺序来确定类中的方法是否唯一。
在谈到参数个数可以确定两个方法是否具有重载关系时,会想到定义不定长参数方法,语法如下:
返回值 方法名(参数数据类型…参数名称)
多态
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。
抽象类与接口
l 抽象类
抽象类继承关系
在Java语言设置抽象类不可以实例化对象,语法如下:
public abstract class Test(){ //定义抽象方法 abstract void testAbstract(); }
abstract是定义抽象类的关键字。
抽象类除了被继承之外没有任何意义。
在Java中规定,类不能同时继承多个父类。
l 接口
使用接口继承关系
接口是抽象的延申,可以将它看作是纯粹的抽象类,接口中的所有方法都没有方法体,语法如下:
public interface Test(){ //接口内的方法,省略abstract关键字 void test(); }
一个类实现一个接口可以使用implements关键字,代码如下:
public class A extends B implements C{ }
注意:
在接口中定义的方法必须被指定为public或abstract形式,其他修饰权限不被Java编译器认可,即使不将该方法声明public形式,它也是public。(所以idea会提示直接去除public和abstract)。
说明:
在接口中定义的任何字段都自动是static和final的。在Java中无论是将一个类向上转型为父类对象,还是向上转型为抽象父类对象,或者向上转型为该类实现接口,都是没有问题的。
接口与继承:
接口可以实现多重继承,语法如下:
class 类名 implements 接口1,接口2,接口3,…,接口n
类的高级特性
Java类包
Java中每个接口或类都来自不同的类包,无论是Java API中的类与接口还是自定义的类与接口都需要隶属于某一个类包,这个类包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。
使用import关键字导入包:
import com.lzp.*; //指定com.lzp包中的所有类在程序中都可以使用(根据Java开发规范,日常使用不推荐直接导入*)
使用import导入静态成员
import static 静态成员;
final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。
一旦一个对象引用被修饰为final后,它只能恒定指向一个对象,无法将其改变以指向另一个对象。一个即是statci又是final的字段只占据一段不能改变的存储空间。
可以将方法的参数定义为final类型,这预示着无法在方法中更改参数引用所指向的对象。
程序中可以定义为final的数据
final方法
final的方法不能被重写。将方法定义为final类型可以防止子类修改该类的定义与实现方法,同时定义为final的方法的执行效率要高于非final方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为final类型,这样无需将一个定义为private的方法再定义为final类型。
final类
定义为final的类不能被继承。
如果将某个类设置为final形式,则类中的所有方法都被隐式设置为final形式,但是final类中的成员变量可以被定义为final或非final形式。
内部类
如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可分为成员内部类、局部内部类以及匿名类。
l 成员内部类
n 成员内部类简介
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量,尽管这些类成员被修饰为private,内部类可以访问它的外部类成员,但内部类的成员只有在内部类的范围之内是可知道,不能被外部类使用。
语法如下:
public class A{ //外部类 private class B{ //内部类 //… } }
内部类对象与外部类对象的关系
内部类对象与外部类对象的关系
n 内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常被应用在Swing编程中,可以在一个类中做出多个不同的响应时间。
例:
interface OutInterface{ //定义一个接口 publlic void f(); } public class InterfaceInner{ public static void main(String args[]){ OuterClass2 out=new OuterClass2();//实例化一个OuterClass2duixiang OutInterface outinter=out.doit();//调用doit方法,返回一个OutInterface接口 outinter.f(); //调用f()方法 } } class OuterClass2{ //定义一个内部类实现OutInterface接口 private class InnerClass implements OutInterface{ InnerClass(String s){ //内部类构造方法 System.out.println(s); } public void f(){ //实现接口中的f()方法 System.out.println(“访问内部类中的f()方法”); } } public OutInterface doit(){ return new InnerClass(“访问内部类构造方法”) } }
输出结果如下:
访问内部类构造方法
访问内部类中的f()方法
n 使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。
l 局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。
l 匿名内部类
匿名类的所有代码都需要在大括号之间进行编写,语法如下:
return new A(){ …//内部类体 };
l 静态内部类
在内部类前添加修饰符static,这个内部类就变成了静态内部类。一个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为static,就会有更多的限制。
静态内部类具有以下两个特点:如果创建静态内部类的对象,不需要其外部类的对象;不能从静态内部类的对象中方位非静态外部类的对象。
l 内部类的继承
内部类和其他普通类一样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成。
public class C extend ClassA.ClassB{//继承内部类ClassB public C(ClassA a){ a.supper(); } } class ClassA{ class ClassB{ } }
在某个类继承内部类时,必须硬性给与这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法中使用a.supper()语句,这样才为继承提供了必要的对象引用。
异常处理
异常概述
在Java中这种在程序运行时可能出现的一些错误称为异常。异常是一个在程序执行期间发生的事件,它中断了正在执行的程序的正常指令流。
Java语言是一门面向对象的编程语言,因此,异常在Java语言中也是作为类的实例的形式出现的。当某一方法中发生错误时,这个方法会创建一个对象,并且把它传递给正在运行的系统。这个对象就是异常对象。通过异常处理机制,可以将非正常情况下的处理代码与程序的主逻辑分离,即在编写代码主流程的同时在其他地方处理异常。
处理程序异常错误
为了保证程序有效地执行,需要对发生的异常进行相应的处理。在Java中,如果某个方法抛出异常,既可以在当前方法中进行捕捉,然后处理该异常,也可以将异常向上抛出,由方法调用者来处理。
错误
异常产生后,如果不做任何处理,程序就会被终止。
捕捉异常
Java语言的异常捕获结构由try、catch和finally 3部分组成。其中,try语句块存放的是可能发生异常的Java语句;catch程序块在try语句块之后,用来激发被捕获的异常;finally语句块是异常处理结构的最后执行部分,无论try语句块中的代码如何退出,都将执行finally语句块。
try{ //程序代码块 } catch(Exceptiontype1){ // 对Exceptiontype1的处理 } catch(Exceptiontype2){ // 对Exceptiontype2的处理 } … finally{ //程序块 }
Java的异常处理是结构化的,不会因为一个异常影响整个程序的执行。
注意:Exception是try代码块传递给catch代码块的变量类型,e是变量名。通常,异常处理常用以下3个函数来获取异常的有关信息。
l getMessage()函数:输出错误性质。
l toString()函数:给出异常的类型与性质。
l printStackTrace()函数:指出异常的类型、性质、栈层次及出现在程序中的位置。
完整的异常处理语句一定要包含finally语句,无论程序中有无异常发生,并且无论之间的try-catch是否顺利执行完毕,都会执行finally语句。在以下4种特殊情况下,finally块不会被执行:
l 在finally语句块中发生了异常。
l 在前面的代码中使用了System.exit()退出程序。
l 程序所在的线程死亡。
l 关闭CPU。
Java常见异常
自定义异常
使用Java内置的异常类可以描述在编程时出现的大部分异常情况。除此之外,用户只需继承Exception类即可自定义异常类。
在程序中使用自定义异常类,大体可分为以下几个步骤:
(1) 创建自定义异常类。
(2) 在方法中通过throw关键字抛出异常对象。
(3) 如果在当前抛出异常的方法中处理异常,可以使用try-catch语句块捕获并处理,否则在方法的声明处通过throws关键字指明要抛出给方法调用者的异常,继续进行下一步操作。
(4) 在出现异常方法的调用者中捕获并处理异常。
例:
public class SystemException extends Exception { public SystemException(message) { super(message); } public SystemException(Throwable cause) { super(cause); } public SystemException(String message, Throwable cause) { super(message, cause); } }
在方法中抛出异常
若某个方法可能会发生异常,但不想在当前方法中处理这个异常,则可以使用throws、throw关键字在方法中抛出异常。
l 使用throws关键字抛出异常
throws关键字通常被应用在声明方法时,用来指定方法可能抛出的异常。多个异常可使用逗号分隔。
使用throws关键字将异常抛给上一级后,如果不想处理该异常,可以继续向上抛出,但最终要有能够处理该异常的代码。
说明:如果是Error、RuntimeException或它们的子类,可以不使用throws关键字来声明要抛出的异常,编译仍能顺利通过,但在运行时会被系统抛出。
l 使用throw关键字抛出异常
throw关键字通常用于方法体中,并且抛出一个异常对象。程序在执行到throw语句时立即终止,它后面的语句都不执行。通过throw抛出异常后,如果想在上一级代码中来捕获并处理异常,则需要在抛出异常的方法中使用throws关键字在方法的声明中指明要抛出的异常;
如果要捕捉throw抛出的异常,则必须使用try-catch语句块。throw通常用来抛出用户自定义异常。
运行时异常
RuntimeException异常是程序运行过程中产生的异常。Java类库的每个包中都定义了异常类,所有这些类都是Throwable类的子类。Throwable类派生了两个子类,分别是Exception和Error类。Error类及其子类用来描述Java运行系统中的内部错误以及资源耗尽的错误,这类错误比较严重。Exception类称为非致命性类,可以通过捕捉处理使程序继续执行。
Java异常类结构
RuntimeException异常的种类
异常的使用原则
Java异常强制用户去考虑程序的强健性和安全性。异常处理不应用来控制程序的正常流程,其主要作用是捕获程序在运行时发生的异常并进行相应的处理。编写代码处理某个方法可能出现的异常时,可遵循以下几条原则:
l 在当前方法声明中使用try-catch语句捕获异常。
l 一个方法被覆盖时,覆盖它的方法必须抛出相同的异常或异常的子类。
l 如果父类抛出多个异常,则覆盖方法必须抛出那些异常的一个子集,不能抛出新异常。
Swing程序设计
Swing概述
GUI(图形用户界面)为程序提供图形界面,它最初的设计目的是为程序员构建一个通用的GUI,使其能够在所有的平台上运行,但Java 1.0中基础类AWT(抽象窗口工具箱)并没有达到这个要求,于是Swing出现了,它是AWT组件的增强组件,但是它并不能完全替代AWT组件,这两种组件需要同时出现在一个图形用户界面中。
Swing特点
Swing组件允许编程人员在跨平台时指定统一的外观和风格。
Swing组件通常被称为“轻量级组件”,完全由Java语言编写,它可以在任何平台上运行
Swing包
在Swing组件中最重要的父类是Container类,而Container类有两个最重要的子类,分别为java.awt.Window与java.awt.Frame,除了以往的AWT类组件会继承这两个类之外,现在的Swing组件也扩展了这两个类。
Swing组件的类的层次和继承关系
常用Swing组件
常用窗体
窗体作为Swing应用程序中组件的承载体,处于非常重要的位置。Swing中常用的窗体包括JFrame和JDialog
JFrame窗体
JFrame窗体是一个容器,它是Swing程序中各个组件的载体,可以将JFrame看作是承载这些Swing组件的容器。
JFrame在程序中的语法格式如下:
JFrame jf = new JFrame(title); Container container = jf.getContentPane();
容器添加组件
container.add(new JButton("按钮"));
容器移除组件
container.remove(new JButton("按钮"));
示例:
import javax.swing.*; import java.awt.*; public class Example extends JFrame { public void createjFrame(String title) { JFrame jf = new JFrame(title); Container container = jf.getContentPane(); JLabel jl = new JLabel("这是一个JFrame窗体"); //使标签上的文字居中 jl.setHorizontalAlignment(SwingConstants.CENTER); //将标签添加到容器中 container.add(jl); //设置容器的背景色 container.setBackground(Color.white); //使窗体可视 jf.setVisible(true); //设置窗体大小 jf.setSize(200, 150); //设置窗体关闭方式 jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new Example().createjFrame("创建一个JFrame载体"); } }
运行结果
JFrame类的常用构造方法包括以下两种形式:public JFrame()和 public JFrame(String title)。
创建窗体后,需要给予窗体一个关闭方式,可以调用setDefaultCloseOperation()方法关闭窗体。Java为窗体关闭提供了多种方式,常用的有以下4种:
DO_NOTHING_ON_CLOSE:退出方式代表什么都不做就将窗体关闭
DISPOSE_ON_CLOSE:退出方式则代表任何注册监听程序对象后会自动隐藏并释放窗体
HIDE_ON_CLOSE:隐藏窗口的默认窗口关闭
EXIT_ON_CLOSE:退出应用程序默认窗口关闭
这几种操作实质上是将一个int类型的常量封装在javax.swing.WindowConstants接口中。
JDialog窗体
JDialog窗体是Swing组件中的对话框,功能是从一个窗体中弹出另一个窗体。
通常使用以下几个JDialog类的构造方法:
l public JDialog():创建一个没有标题和父窗体的对话框。 l public JDialog(Frame f):创建一个指定父窗体的对话框,但该窗体没有标题。 l public JDialog(Frame f,boolean model):创建一个指定类型的对话框,并指定父窗体,但该窗体没有指定标题。 l public JDialog(Frame f,String title):创建一个指定标题和父窗体的对话框。 l public JDialog(Frame f,String title,boolean model):创建一个指定标题、窗体和模式的对话框。
示例:
public class JDialogExample extends JFrame { public static void main(String[] args) { new JDialogExample(); } public static class MyDialog extends JDialog { public MyDialog(JDialogExample owner) { super(owner, "第一个JDiglog窗体", true); Container container = getContentPane(); JLabel l = new JLabel("这是一个对话框"); l.setVisible(true); container.add(l); setBounds(120, 120, 100, 100); setVisible(true); } } public JDialogExample() { Container container = getContentPane(); container.setLayout(null); JLabel jl = new JLabel("这是一个JFrame窗体"); //使标签上的文字居中 jl.setHorizontalAlignment(SwingConstants.CENTER); //将标签添加到容器中 container.add(jl); //定义一个按钮 JButton bl = new JButton("弹出对话框"); bl.setBounds(10, 10, 100, 20); bl.addActionListener(e -> new MyDialog(JDialogExample.this).setVisible(true)); //将按钮添加到容器中 container.add(bl); //设置容器的背景色 container.setBackground(Color.white); container.setLayout(null); //使窗体可视 setVisible(true); //设置窗体大小 setSize(1000, 500); //设置窗体关闭方式 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } }
运行结果
标签组件与图标
标签组件
标签由JLabel类定义,它的父类为JComponent类。
JLabel类常用的几个构造方法如下:
public JLabel():创建一个不带图标和文本的JLabel对象。 public JLabel(Icon icon):创建一个带图标的JLabel对象。 public JLabel(Icon icon,int aligment):创建一个带图标的JLabel对象,并设置图标水平对齐方式。 public JLabel(String text,int aligment):创建一个带文本的JLabel对象,并设置文字水平对齐方式。 public JLabel(String text,Icon icon,int aligment):创建一个带文本、带图标的JLabel对象,并设置标签内容的水平对齐方式。
图标
在Swing中通过Icon接口来创建图标,可以在创建时给定图标的大小、颜色等特性。如果使用Icon接口,必须实现Icon接口中的3个方法:
public int getIconHeight()。 public int getIconWidth()。 public void paintIcon(Component arg0, Graphics arg1, int arg2, int arg3)。
getIconHeigth()与getIconWidth()方法用于获取图标的长与宽,paintIcon()方法用于实现在指定坐标位置画图。
实例如下:
public class IconExample implements Icon { private final int width; private final int height; public IconExample(int width, int height) { this.width = width; this.height = height; } @Override public void paintIcon(Component c, Graphics g, int x, int y) { //绘制一个原型 g.fillOval(x, y, width, height); } @Override public int getIconWidth() { return this.width; } @Override public int getIconHeight() { return this.height; } public static void main(String[] args) { IconExample icon = new IconExample(15, 15); JFrame jf = new JFrame(); Container container = jf.getContentPane(); JLabel jl = new JLabel("测试", icon, SwingConstants.CENTER); jl.setHorizontalAlignment(SwingConstants.CENTER); //使标签上的文字居中 container.add(jl); //将标签添加到容器中 container.setBackground(Color.white); //设置容器的背景色 jf.setVisible(true); //使窗体可视 jf.setSize(200, 150); //设置窗体大小 jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); //设置窗体关闭方式 } }
实现Icon接口创建图标
图片图标
ImageIcon类有多个构造方法,下面是其中几个常用的构造方法。
public ImageIcon():该构造方法创建一个通用的ImageIcon对象,当真正需要设置图片时再使用ImageIcon对象调用setImage(Image image)方法来操作。 public ImageIcon(Image image):可以直接从图片源创建图标。 public ImageIcon(Image image,String description):除了可以从图片源创建图标之外,还可以为这个图标添加简短的描述,但这个描述不会在图标上显示,可以使用getDescription()方法获取这个描述。 public ImageIcon(URL url):该构造方法利用位于计算机网络上的图像文件创建图标。
实例如下:
public class ImageIconExample extends JFrame { public ImageIconExample() { Container container = getContentPane(); JLabel jl = new JLabel("这是一个JFrame窗体", JLabel.CENTER); URL url = ImageIconExample.class.getResource("a.jpg");//创建一个标签 Icon icon = new ImageIcon(url); //获取图片所在的URL jl.setIcon(icon); jl.setHorizontalAlignment(SwingConstants.CENTER); //使标签上的文字居中 jl.setOpaque(true); //设置标签为不透明状态 container.add(jl); //将标签添加到容器中 container.setBackground(Color.white); //设置容器的背景色 setVisible(true); //使窗体可视 setSize(200, 150); //设置窗体大小 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); //设置窗体关闭方式 } public static void main(String[] args) { new ImageIconExample(); } }
结果如下
常用布局管理器
绝对布局
绝对布局,就是硬性指定组件在容器中的位置和大小,可以使用绝对坐标的方式来指定组件的位置。步骤如下:
(1)使用Container.setLayout(null)方法取消布局管理器。
(2)使用Component.setBounds()方法设置每个组件的大小与位置。
绝对布局的例子:
public class AbsolutePosition extends JFrame { public AbsolutePosition() { setTitle("本窗体使用绝对布局");//设置窗体标题 setLayout(null);//使该窗体取消布局管理器设置 setBounds(0, 0, 200, 150);//绝对定位窗体的位置与大小 Container c = getContentPane();//创建容器对象 JButton b1 = new JButton("按钮1"); JButton b2 = new JButton("按钮2"); b1.setBounds(10, 30, 80, 30);//设置按钮的位置与大小 b2.setBounds(60, 70, 100, 20); c.add(b1); c.add(b2); setVisible(true);//使窗体可见 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//设置窗体关闭方式 } public static void main(String[] args) { new AbsolutePosition(); } }
绝对布局效果
注意:在使用绝对布局之前需要调用setLayout(null)方法告知编译器,这里不再使用布局管理器。
流布局管理器
流(FlowLayout)布局管理器是最基本的布局管理器,在整个容器中的布局正如其名,像“流”一样从左到右摆放组件,直到占据了这一行的所有空间,然后再向下移动一行。默认情况下,组件在每一行都是居中排列的,但是通过设置也可以更改组件在每一行上的排列位置。FlowLayout类中具有以下常用的构造方法:
public FlowLayout()。 public FlowLayout(int alignment)。 public FlowLayout(int alignment,int horizGap,int vertGap)。
构造方法中的alignment参数表示使用流布局管理器后组件在每一行的具体摆放位置。它可以被赋予以下3个值之一:
FlowLayout.LEFT=0。 FlowLayout.CENTER=1。 FlowLayout.RIGHT=2。
在public FlowLayout(int alignment,int horizGap,int vertGap)构造方法中还存在horizGap与vertGap两个参数,分别以像素为单位指定组件之间的水平间隔与垂直间隔。
流布局管理器的例子如下:
public class FlowLayoutPosition extends JFrame { public FlowLayoutPosition() { setTitle("本窗体使用流布局管理器");//设置窗体标题 //设置窗体使用流布局管理器,组件右对齐,设置组件之间的水平间隔与垂直间隔 setLayout(new FlowLayout(2, 10, 10)); Container c = getContentPane();//创建容器对象 for (int i = 0; i < 10; i++) {//容器中循环添加10个按钮 c.add(new Button("button" + i)); } setSize(300, 200);//设置窗体大小 setVisible(true);//使窗体可见 setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);//设置窗体关闭方式 } public static void main(String[] args) { new FlowLayoutPosition(); } }
在应用程序使用流布局管理器
边界布局管理器
在默认不指定窗体布局的情况下,Swing组件的布局模式是边界(BorderLayout)布局管理器。边界布局管理器可将容器划分为东、南、西、北、中5个区域,可以将组件加入到这5个区域中。容器调用Container类的add()方法添加组件时可以设置此组件在边界布局管理器中的区域,区域的控制可以由BorderLayout类中的成员变量来决定,这些成员变量的具体含义如表:
BorderLayout类的主要成员变量
实例如下:
public class BorderLayoutPosition extends JFrame { static final String[] border = {BorderLayout.CENTER, BorderLayout.NORTH, BorderLayout.SOUTH, BorderLayout.WEST, BorderLayout.EAST}; static final String[] buttonName = {"Center button", "north button", "south button", "west button", "east button"}; public BorderLayoutPosition() { setTitle("这个窗体使用边界布局管理器"); setLayout(new BorderLayout());//设置容器为边界布局管理器 Container c = getContentPane(); for (int i = 0; i < border.length; i++) {//在容器中添加按钮,并设置按钮布局 c.add(border[i], new JButton(buttonName[i])); } setSize(350, 200); setVisible(true); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); } public static void main(String[] args) { new BorderLayoutPosition(); } }
运行结果
网格布局管理器
网格(GridLayout)布局管理器将容器划分为网格,所以组件可以按行和列进行排列。在网格布局管理器中,每一个组件的大小都相同,并且网格中空格的个数由网格的行数和列数决定。组件从网格的左上角开始,按照从左到右、从上到下的顺序加入到网格中,而且每一个组件都会填满整个网格,改变窗体的大小,组件的大小也会随之改变。网格布局管理器主要有以下两个常用的构造方法:
public GridLayout(int rows,int columns)。 public GridLayout(int rows,int columns,int horizGap,int vertGap)。
在上述构造方法中,rows与columns参数代表网格的行数与列数,这两个参数只有一个参数可以为0,代表一行或一列可以排列任意多个组件;参数horizGap与vertGap指定网格之间的距离,其中horizGap参数指定网格之间的水平距离,vertGap参数指定网格之间的垂直距离。
实例如下:
public class GridLayoutPosition extends JFrame { public GridLayoutPosition() { Container c = getContentPane(); for (int i = 0; i < 20; i++) {//在容器中添加按钮,并设置按钮布局 c.add(new JButton("button" + i)); } setTitle("这是一个使用网格布局管理器的窗体"); setLayout(new GridLayout(7, 3, 5, 5));//设置容器为网格布局管理器,设置7行3列的网格 setSize(300, 300); setVisible(true); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new GridLayoutPosition(); } }
在应用程序中使用网格布局管理器
常用面板
JPanel面板:JPanel面板可以聚集一些组件来布局。
实例如下:
public class JPanelTest extends JFrame { public JPanelTest() { Container c = getContentPane(); JPanel p1 = new JPanel(new GridLayout(1, 3, 10, 10)); JPanel p2 = new JPanel(new GridLayout(1, 2, 10, 10)); JPanel p3 = new JPanel(new GridLayout(1, 2, 10, 10)); JPanel p4 = new JPanel(new GridLayout(2, 1, 10, 10)); p1.add(new JButton("1")); p2.add(new JButton("2")); p3.add(new JButton("3")); p4.add(new JButton("4")); c.add(p1); c.add(p2); c.add(p3); c.add(p4); setLayout(new GridLayout(2, 1, 10, 10)); setVisible(true);//使窗体可见 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//设置窗体关闭方式 } public static void main(String[] args) { new JPanelTest(); } }
运行结果
JPanel面板:在设置界面时,可能会遇到在一个较小的容器窗体中显示一个较大部分的内容的情况,这时可以使用JScrollPane面板。JScrollPane面板是带滚动条的面板,它也是一种容器,但是JScrollPane只能放置一个组件,并且不可以使用布局管理器。如果需要在JScrollPane面板中放置多个组件,需要将多个组件放置在JPanel面板上,然后将JPanel面板作为一个整体组件添加在JScrollPane组件上。
实例如下:
public class JScrollPanelTest extends JFrame { public JScrollPanelTest() { Container c = getContentPane(); JTextArea ta = new JTextArea(20, 50);//创建文本区域组件 JScrollPane sp = new JScrollPane(ta);//创建JScrollPane面板对象 c.add(sp); setTitle("带滚动条的文字编译器"); setSize(200, 200); setVisible(true);//使窗体可见 setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new JScrollPanelTest(); } }
运行结果
按钮组件
按钮在Swing中是较为常见的组件,用于触发特定动作。Swing中提供多种按钮,这些按钮都是从AbstractButton类中继承而来的。
提交按钮组件
Swing中的提交按钮(JButton)由JButton对象表示,其构造方法主要有以下几种形式:
public JButton():生成不带任何文本组件的对象和图标,可以在以后使用相应方法为按钮设置指定的文本和图标 public JButton(String text)。 public JButton(Icon icon)。 public JButton(String text,Icon icon)。
实例如下:
public class JButtonTest extends JFrame { public JButtonTest() { Icon icon = new ImageIcon(JButtonTest.class.getResource("a.png")); setLayout(new GridLayout(3, 2, 5, 5)); Container c = getContentPane(); for (int i = 0; i < 5; i++) { JButton j = new JButton("button" + i, icon); c.add(j); if (i % 2 == 0) { j.setEnabled(false);//设置其中一些按钮不可见 } } JButton jb = new JButton(); jb.setMaximumSize(new Dimension(90, 30));//设置按钮与图片相同大小 jb.setIcon(icon); jb.setHideActionText(true); jb.setToolTipText("图片按钮");//设置按钮提示为文字 jb.setBorderPainted(false);//设置按钮边界不显示 jb.addActionListener(e -> JOptionPane.showConfirmDialog(null, "弹出对话框")); c.add(jb); setVisible(true); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new JButtonTest(); } }
运行结果
单选按钮组件
在默认情况下,单选按钮(JRadioButton)显示一个圆形图标,并且通常在该图标旁放置一些说明性文字,而在应用程序中,一般将多个单选按钮放置在按钮组中,使这些单选按钮表现出某种功能,当用户选中某个单选按钮后,按钮组中其他按钮将被自动取消。单选按钮是Swing组件中JRadioButton类的对象,该类是JToggleButton的子类,而JToggleButton类又是AbstractButton类的子类,所以控制单选按钮的诸多方法都是AbstractButton类中的方法。
单选按钮
可以使用JRadioButton类中的构造方法创建单选按钮对象。JRadioButton类的常用构造方法主要有以下几种形式:
public JRadioButton() public JRadioButton(Icon icon) public JRadioButton(Icon icon,boolean selected) public JRadioButton(String text) public JRadioButton(String text,Icon icon) public JRadioButton(String text,Icon icon,boolean selected)
根据上述构造方法的形式,可以知道在初始化单选按钮时,可以同时设置单选按钮的图标、文字以及默认是否被选中等属性。
按钮组
在Swing中存在一个ButtonGroup类,用于产生按钮组,如果希望将所有的单选按钮放置在按钮组中,需要实例化一个JRadioButton对象,并使用该对象调用add()方法添加单选按钮。
JRadioButton jr1=new JRadioButton(); JRadioButton jr2=new JRadioButton(); JRadioButton jr3=new JRadioButton(); ButtonGroup group=new ButtonGroup(); group.add(jr1); group.add(jr2); group.add(jr3);
单选按钮与提交按钮的用法基本类似,只是实例化单选按钮对象后需要将其添加至按钮组中。
复选框组件
复选框具有一个方块图标,外加一段描述性文字。与单选按钮唯一不同的是,复选框可以进行多选设置,每一个复选框都提供“选中”与“不选中”两种状态。复选框用JCheckBox类的对象表示,它同样继承于AbstractButton类,所以复选框组件的属性设置也来源于AbstractButton类。JCheckBox的常用构造方法如下:
l public JCheckBox()。 l public JCheckBox(Icon icon,boolean checked)。 l public JCheckBox(String text,boolean checked)。
实例如下:
public class JCheckBoxExample extends JFrame { public JCheckBoxExample() { Container c = getContentPane(); c.setLayout(new BorderLayout()); JPanel p1 = new JPanel(); c.add(p1, BorderLayout.NORTH); final JScrollPane scrollpane = new JScrollPane(); p1.add(scrollpane); JPanel p2 = new JPanel(); p2.add(new JLabel("2")); c.add(p2, BorderLayout.SOUTH); JCheckBox jc1 = new JCheckBox(); JTextArea jt = new JTextArea(); p2.add(jc1); jc1.addActionListener(e -> jt.append("复选框1被选中\n")); c.add(jt); setVisible(true); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new JCheckBoxExample(); } }
运行结果
列表组件
Swing中提供两种列表组件,分别为下拉列表框与列表框。下拉列表框与列表框都是带有一系列项目的组件,用户可以从中选择需要的项目。列表框较下拉列表框更直观,它将所有的项目罗列在列表框中;但下拉列表框较列表框更为便捷、美观,它将所有的项目隐藏起来,当用户选用其中的项目时才会显现出来。
下拉列表框组件
l JComboBox类
Swing中的下拉列表框使用JComboBox类对象来表示,它是javax.swing.JComponent类的子类。它的常用构造方法如下:
public JComboBox() public JComboBox(ComboBoxModel dataModel) public JComboBox(Object[] arrayData) public JComboBox(Vector vector)
在初始化下拉列表框时,可以选择同时指定下拉列表框中的项目内容,也可以在程序中使用其他方法设置下拉列表框中的内容,下拉列表框中的内容可以被封装在ComboBoxModel类型、数组或Vector类型中。
l JComboBox模型
在开发程序中,一般将下拉列表框中的项目封装为ComboBoxModel的情况比较多。ComboBoxModel为接口,它代表一般模型,可以自定义一个类实现该接口,然后在初始化JComboBox对象时向上转型为ComboBoxModel接口类型,但是必须实现以下两种方法:
public void setSelectedItem(Object item) public Object getSelectedItem()
其中,setSelectedItem()方法用于设置下拉列表框中的选中项,getSelectedItem()方法用于返回下拉列表框中的选中项,有了这两个方法,就可以轻松地对下拉列表框中的项目进行操作。自定义这个类除了实现该接口之外,还可以继承AbstractListModel类,在该类中也有两个操作下拉列表框的重要方法:
getSize():返回列表的长度 getElementAt(int index):返回指定索引处的值。
具体实例
public class JComboBoxTest extends JFrame { JComboBox<String> jc = new JComboBox<>(new MyComboBox()); JLabel jl = new JLabel("请选择证件"); public JComboBoxTest() { Container container = getContentPane(); container.setLayout(new FlowLayout()); container.add(jl); container.add(jc); setVisible(true); setSize(200, 150); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } class MyComboBox extends AbstractListModel<String> implements ComboBoxModel<String> { String selecteditem = null; String[] test = {"身份证", "军人证", "学生证", "工作证"}; @Override public void setSelectedItem(Object anItem) { selecteditem = (String) anItem; } @Override public Object getSelectedItem() { return selecteditem; } @Override public int getSize() { return test.length; } @Override public String getElementAt(int index) { return test[index]; } } public static void main(String[] args) { new JComboBoxTest(); } }
运行结果
列表框组件
列表框(JList)与下拉列表框的区别不仅表现在外观上,当激活下拉列表框时,还会出现下拉列表框中的内容;但列表框只是在窗体上占据固定的大小,如果需要列表框具有滚动效果,可以将列表框放入滚动面板中。用户在选择列表框中的某一项时,按住Shift键并选择列表框中的其他项目,则当前选项和其他项目之间的选项将全部被选中;也可以按住Ctrl键并单击列表框中的单个项目,这样可以使列表框中被单击的项目反复切换非选择状态或选择状态。
Swing中使用JList类对象来表示列表框,下面列举几个常用的构造方法。
public void JList() public void JList(Object[] listData) public void JList(Vector listData) public void JList(ListModel dataModel)
在上述构造方法中,存在一个没有参数的构造方法,可以通过在初始化列表框后使用setListData()方法对列表框进行设置,也可以在初始化的过程中对列表框中的项目进行设置。设置的方式有3种类型,包括数组、Vector类型和ListModel模型。
当使用数组作为构造方法的参数时,首先需要创建列表项目的数组,然后再利用构造方法来初始化列表框。
具体实例:
public class JListExample extends JFrame { public JListExample() { Container container = getContentPane(); container.setLayout(null); JList<String> jl = new JList<>(new MyListModel()); JScrollPane js = new JScrollPane(jl); js.setBounds(10, 10, 100, 100); container.add(js); setVisible(true); setSize(200, 150); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } class MyListModel extends AbstractListModel<String> { private final String[] contents = {"列表1", "列表2", "列表3", "列表4", "列表5", "列表6",}; @Override public int getSize() { return contents.length; } @Override public String getElementAt(int index) { return contents[index]; } } public static void main(String[] args) { new JListExample(); } }
运行效果
文本组件
文本框组件
文本框(JTextField)用来显示或编辑一个单行文本,在Swing中通过javax.swing.JTextField类对象创建,该类继承了javax.swing.text.JTextComponent类。下面列举了一些创建文本框常用的构造方法:
public JTextField() public JTextField(String text) public JTextField(int fieldwidth) public JTextField(String text,int fieldwidth) public JTextField(Document docModel,String text,int fieldWidth)
具体实例:
public class JTextFieldExample extends JFrame { public JTextFieldExample() { Container container = getContentPane(); container.setLayout(new FlowLayout()); JTextField jt = new JTextField("aaa", 20); JButton jb = new JButton("清除"); jt.addActionListener(e -> jt.setText("触发事件")); jb.addActionListener(e -> { jt.setText(""); jt.requestFocus(); }); container.add(jt); container.add(jb); setVisible(true); setSize(300, 150); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new JTextFieldExample(); } }
运行结果
密码框组件
密码框(JPasswordField)与文本框的定义与用法基本相同,唯一不同的是密码框将用户输入的字符串以某种符号进行加密。密码框对象是通过javax.swing.JPasswordField类来创建的,JPasswordField类的构造方法与JTextField类的构造方法非常相似。下面列举几个常用的构造方法:
public JPasswordField() public JPasswordFiled(String text) public JPasswordField(int fieldwidth) public JPasswordField(String text,int fieldwidth) public JPasswordField(Document docModel,String text,int fieldWidth)
文本域组件
Swing中任何一个文本区域都是JTextArea类型的对象。JTextArea常用的构造方法如下:
public JTextArea() public JTextArea(String text) public JTextArea(int rows,int columns) public JTextArea(Document doc) public JTextArea(Document doc,String Text,int rows,int columns)
上述构造方法可以在初始化文本域时提供默认文本以及文本域的长与宽。
具体实例:
public class JTextFAreaExample extends JFrame { public JTextFAreaExample() { Container container = getContentPane(); JTextArea jt = new JTextArea("文本域", 6, 6); jt.setLineWrap(true); //设置自动换行 container.add(jt); setTitle("定义自动换行的文本域"); setVisible(true); setSize(200, 100); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); } public static void main(String[] args) { new JTextFAreaExample(); } }
运行结果
常用事件监听器
l 动作事件监听器
动作事件(ActionEvent)监听器是Swing中比较常用的事件监听器,很多组件的动作都会使用它监听,如按钮被单击。
动作事件监听器
l 焦点事件监听器
焦点事件(FocusEvent)监听器在实际项目开发中应用也比较广泛,如将光标焦点离开一个文本框时需要弹出一个对话框,或者将焦点返回给该文本框等。
焦点事件监听器
具体实例
public class FocusEventExample extends JFrame { public FocusEventExample() { Container container = getContentPane(); container.setLayout(new FlowLayout()); JTextField jt = new JTextField("文本框1", 10); JTextField jt2 = new JTextField("文本框2", 10); container.add(jt); container.add(jt2); jt.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { } @Override public void focusLost(FocusEvent e) { JOptionPane.showMessageDialog(null, "文本框1失去焦点"); } }); setVisible(true); setSize(300, 150); setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); } public static void main(String[] args) { new FocusEventExample(); } }
运行结果
集合类
集合类概述
java.util包中提供了一些集合类,这些集合类又被称为容器。提到容器不难想到数组,集合类与数组的不同之处是:数组的长度是固定的,集合的长度是可变的;数组用来存放基本类型的数据,集合用来存放对象的引用。
常用的集合有List集合、Set集合和Map集合,其中List与Set继承了Collection接口,各接口还提供了不同的实现类。
集合类的继承关系
Collection接口
Collection接口是层次结构中的根接口。构成Collection的单位称为元素。Collection接口通常不能直接使用,但该接口提供了添加元素、删除元素、管理数据的方法。由于List接口与Set接口都继承了Collection接口,因此这些方法对List集合与Set集合是通用的。
Collection接口的常用方法
通常遍历集合,都是通过迭代器(Iterator)来实现。Collection接口中的iterator()方法可返回在此Collection进行迭代的迭代器。实例如下:
public class Muster { public static void main(String[] args) { Collection<String> list = new ArrayList<>(); list.add("a"); list.add("b"); list.add("c"); Iterator<String> it = list.iterator(); while (it.hasNext()) { System.out.println(it.next()); } } }
List集合
List集合包括List接口以及List接口的所有实现类。List集合中的元素允许重复,各元素的顺序就是对象插入的顺序。类似Java数组,用户可通过使用索引(元素在集合中的位置)来访问集合中的元素。
List接口
List接口继承了Collection接口,因此包含Collection中的所有方法。此外,List接口还定义了以下两个非常重要的方法:
l get(int index):获得指定索引位置的元素。 l set(int index,Object obj):将集合中指定索引位置的对象修改为指定的对象。
List接口的实现类
List接口的常用实现类有ArrayList与LinkedList。
ArrayList类实现了可变的数组,允许保存所有元素,包括null,并可以根据索引位置对集合进行快速的随机访问;缺点是向指定的索引位置插入对象或删除对象的速度较慢。
LinkedList类采用链表结构保存对象。优点是便于向集合中插入和删除对象,需要向集合中插入、删除对象时,使用LinkedList类实现的List集合的效率较高;但对于随机访问集合中的对象,使用LinkedList类实现List集合的效率较低。
使用List集合时通常声明为List类型,可通过不同的实现类来实例化集合。
Set集合
Set集合中的对象不按特定的方式排序,只是简单地把对象加入集合中,但Set集合中不能包含重复对象。Set集合由Set接口和Set接口的实现类组成。Set接口继承了Collection接口,因此包含Collection接口的所有方法。
Set接口常用的实现类有HashSet类与TreeSet类:
HashSet类实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证Set的迭代顺序,特别是它不保证该顺序恒久不变。此类允许使用null元素。
TreeSet类不仅实现了Set接口,还实现了java.util.SortedSet接口,因此,TreeSet类实现的Set集合在遍历集合时按照自然顺序递增排序,也可以按照指定比较器递增排序,即可以通过比较器对用TreeSet类实现的Set集合中的对象进行排序。
TreeSet类增加的方法
实例如下:
public class UpdaStu implements Comparable<Object> { String name; long id; @Override public int compareTo(Object o) { UpdaStu upstu = (UpdaStu) o; return Long.compare(id, upstu.id); } public UpdaStu(String name, long id) { this.name = name; this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public long getId() { return id; } public void setId(long id) { this.id = id; } public static void main(String[] args) { UpdaStu stu1 = new UpdaStu("李同学", 521); UpdaStu stu2 = new UpdaStu("陈同学", 529); UpdaStu stu3 = new UpdaStu("王同学", 553); UpdaStu stu4 = new UpdaStu("马同学", 523); TreeSet<UpdaStu> tree = new TreeSet<>(); tree.add(stu1); tree.add(stu2); tree.add(stu3); tree.add(stu4); Iterator<UpdaStu> it = tree.iterator(); System.out.println("Set集合中的所有元素:"); while (it.hasNext()) { UpdaStu stu = it.next(); System.out.println(stu.id + "" + stu.name); } it = tree.headSet(stu2).iterator(); System.out.println("截取排在stu2对象之前的对象:"); while (it.hasNext()) { UpdaStu stu = it.next(); System.out.println(stu.id + "" + stu.name); } it = tree.subSet(stu2, stu3).iterator(); System.out.println("截取排在stu2与stu3之间的对象:"); while (it.hasNext()) { UpdaStu stu = it.next(); System.out.println(stu.id + "" + stu.name); } } }
运行结果如下:
代码说明:存入TreeSet类实现的Set集合必须实现Comparable接口,该接口中的compareTo(Object o)方法比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、0或正整数。
Map集合
Map集合没有继承Collection接口,其提供的是key到value的映射。Map中不能包含相同的key,每个key只能映射一个value。key还决定了存储对象在映射中的存储位置,但不是由key对象本身决定的,而是通过一种“散列技术”进行处理,产生一个散列码的整数值。散列码通常用作一个偏移量,该偏移量对应分配给映射的内存区域的起始位置,从而确定存储对象在映射中的存储位置。Map集合包括Map接口以及Map接口的所有实现类。
Map接口
Map接口提供了将key映射到值的对象。一个映射不能包含重复的key,每个key最多只能映射到一个值。Map接口中同样提供了集合的常用方法,常用方法如下:
Map接口中的常用方法
说明Map集合中允许值对象是null,而且没有个数限制。
Map接口的实现类
Map接口常用的实现类有HashMap和TreeMap。建议使用HashMap类实现Map集合,因为由HashMap类实现的Map集合添加和删除映射关系效率更高。HashMap是基于哈希表的Map接口的实现,HashMap通过哈希码对其内部的映射关系进行快速查找;而TreeMap中的映射关系存在一定的顺序,如果希望Map集合中的对象也存在一定的顺序,应该使用TreeMap类实现Map集合。
l HashMap类是基于哈希表的Map接口的实现,此实现提供所有可选的映射操作,并允许使用null值和null键,但必须保证键的唯一性。HashMap通过哈希表对其内部的映射关系进行快速查找。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
l TreeMap类不仅实现了Map接口,还实现了java.util.SortedMap接口,因此,集合中的映射关系具有一定的顺序。但在添加、删除和定位映射关系时,TreeMap类比HashMap类性能稍差。由于TreeMap类实现的Map集合中的映射关系是根据键对象按照一定的顺序排列的,因此不允许键对象是null。
I/O(输入/输出)
Java的I/O技术可以将数据保存到文本文件、二进制文件甚至是ZIP压缩文件中,以达到永久性保存数据的要求。
流概述
流是一组有序的数据序列,根据操作的类型,可分为输入流和输出流两种。I/O(Input/Output,输入/输出)流提供了一条通道程序,可以使用这条通道把源中的字节序列送到目的地。
输入模式
输出模式
输入/输出流
Java语言定义了许多类专门负责各种方式的输入/输出,这些类都被放在java.io包中:
l 所有输入流类都是抽象类InputStream(字节输入流)或抽象类Reader(字符输入流)的子类
l 所有输出流都是抽象类OutputStream(字节输出流)或抽象类Writer(字符输出流)的子类。
输入流
InputStream类是字节输入流的抽象类,是所有字节输入流的父类。
InputStream类的层次结构
该类中所有方法遇到错误时都会引发IOException异常。
常用方法:
read()方法:从输入流中读取数据的下一个字节。返回0~255范围内的int字节值。如果因为已经到达流末尾而没有可用的字节,则返回值为-1。 read(byte[] b):从输入流中读入一定长度的字节,并以整数的形式返回字节数。 mark(int readlimit)方法:在输入流的当前位置放置一个标记,readlimit参数告知此输入流在标记位置失效之前允许读取的字节数。 reset()方法:将输入指针返回到当前所做的标记处。 skip(long n)方法:跳过输入流上的n个字节并返回实际跳过的字节数。 markSupported()方法:如果当前流支持mark()/reset()操作就返回true。 close方法:关闭此输入流并释放与该流关联的所有系统资源。
Java中的字符是Unicode编码,是双字节的。InputStream是用来处理字节的,并不适合处理字符文本。Java为字符文本的输入专门提供了一套单独的类Reader,但Reader类并不是InputStream类的替换者,只是在处理字符串时简化了编程。Reader类是字符输入流的抽象类,所有字符输入流的实现都是它的子类。
Reader类的层次结构
输出流
OutputStream类是字节输出流的抽象类,此抽象类是表示输出字节流的所有类的超类。
OutputStream类的层次结构
OutputStream类中的所有方法均返回void,在遇到错误时会引发IOException异常。
常用方法:
write(int b)方法:将指定的字节写入此输出流。 write(byte[] b)方法:将b个字节从指定的byte数组写入此输出流。 write(byte[] b,int off,int len)方法:将指定byte数组中从偏移量off开始的len个字节写入此输出流。 flush()方法:彻底完成输出并清空缓存区。 close()方法:关闭输出流。
Writer类是字符输出流的抽象类,所有字符输出类的实现都是它的子类。
Writer类的层次结构
File类
File类是java.io包中唯一代表磁盘文件本身的对象。File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中的方法,实现创建、删除、重命名文件等操作。File类的对象主要用来获取文件本身的一些信息,如文件所在的目录、文件的长度、文件读写权限等。数据流可以将数据写入到文件中,文件也是数据流最常用的数据媒体。
文件的创建与删除
通常使用以下3种构造方法来创建文件对象:
l File(String pathname):通过将给定路径名字符串转换为抽象路径名来创建一个新File实例。
例如:
File file = new File("E:\\java从入门到精通.docx");
l File(String parent,String child):根据定义的父路径和子路径字符串(包含文件名)创建一个新的File对象。
例如:
File file2 = new File("E:\\","java从入门到精通.docx");
l File(File f , String child):根据parent抽象路径名和child路径名字符串创建一个新File实例。
例如:
File file3 = new File(file,"letter.txt");
具体实例:
public class FileTest { public static void main(String[] args) { File file = new File("letter.txt"); if (file.exists()) { System.out.println("文件已存在,删除结果:" + file.delete()); } else { try { System.out.println("文件不存在,创建结果:" + file.createNewFile()); } catch (IOException e) { e.printStackTrace(); } } } }
运行结果
获取文件信息
File类的常用方法
具体实例:
public static void main(String[] args) { File file = new File( "E:\\iDataLight_Share\\xxxxx\\004_经验分享\\009_书籍阅读分享", "java从入门到精通.docx"); if (file.exists()) { System.out.println("文件名称:" + file.getName()); System.out.println("文件是否可读:" + file.canRead()); System.out.println("文件是否可被写入:" + file.canWrite()); System.out.println("文件长度:" + file.length()); System.out.println("文件的绝对路径:" + file.getAbsolutePath()); System.out.println("文件的父路径:" + file.getAbsolutePath()); System.out.println("文件的最后修改时间:" + file.lastModified()); System.out.println("判断是否是文件:" + file.isFile()); System.out.println("判断是否是文件夹:" + file.isDirectory()); System.out.println("判断是否是文件夹:" + file.isHidden()); } }
运行结果
文件输入/输出流
程序运行期间,大部分数据都在内存中进行操作,当程序结束或关闭时,这些数据将消失。如果需要将数据永久保存,可使用文件输入/输出流与指定的文件建立连接,将需要的数据永久保存到文件中。
FileInputStream与FileOutputStream类
FileInputStream类常用的构造方法如下:
FileInputStream(String name):给定的文件名name创建一个FileInputStream对象
FileInputStream(File file):使用File对象创建FileInputStream对象,允许在把文件连接输入流之前对文件作进一步分析。
实例:
public static void fileTestThree() { File file = new File("letter.txt"); try { FileOutputStream out = new FileOutputStream(file); byte[] buy = "我有一只小毛驴,我从来也不骑。".getBytes(); out.write(buy); out.close(); } catch (IOException e) { e.printStackTrace(); } try { FileInputStream in = new FileInputStream(file); byte[] byt = new byte[1024]; int len = in.read(byt); System.out.println("文本中的信息是:" + new String(byt, 0, len)); in.close(); } catch (IOException e) { e.printStackTrace(); } }
运行效果
FileReader和FileWriter类
使用FileOutputStream类向文件中写入数据与使用FileInputStream类从文件中将内容读出来,都存在一点不足,即这两个类都只提供了对字节或字节数组的读取方法。由于汉字在文件中占用两个字节,如果使用字节流,读取不好可能会出现乱码现象,此时采用字符流Reader或Writer类即可避免这种现象。
FileReader流顺序地读取文件,只要不关闭流,每次调用read()方法就顺序地读取源中其余的内容,直到源的末尾或流被关闭。
带缓存的输入/输出流
缓存是I/O的一种性能优化。缓存流为I/O流增加了内存缓存区。有了缓存区,使得在流上执行skip()、mark()和reset()方法都成为可能。
BufferedInputStream与BufferedOutputStream类
BufferedInputStream类可以对所有InputStream类进行带缓存区的包装以达到性能的优化。BufferedInputStream类有两个构造方法:
BufferedInputStream(InputStream in):创建了一个带有32个字节的缓存流 BufferedInputStream(InputStream in,int size):按指定的大小来创建缓存区
一个最优的缓存区的大小,取决于它所在的操作系统、可用的内存空间以及机器配置。
BufferedInputStream读取文件过程
使用BufferedOutputStream输出信息和用OutputStream输出信息完全一样,只不过BufferedOutputStream有一个flush()方法用来将缓存区的数据强制输出完。
注意:flush()方法就是用于即使在缓存区没有满的情况下,也将缓存区的内容强制写入到外设,习惯上称这个过程为刷新。flush()方法只对使用缓存区的OutputStream类的子类有效。当调用close()方法时,系统在关闭流之前,也会将缓存区中的信息刷新到磁盘文件中。
BufferedReader与BufferedWriter类
BufferedReader类与BufferedWriter类分别继承Reader类与Writer类。这两个类同样具有内部缓存机制,并可以以行为单位进行输入/输出。
BufferedReader类读取文件的过程
BufferedReader类常用的方法如下:
read()方法:读取单个字符。
readLine()方法:读取一个文本行,并将其返回为字符串。若无数据可读,则返回null。
BufferedWriter类中的方法都返回void。常用的方法如下:
write(String s, int off, int len)方法:写入字符串的某一部分。 flush()方法:刷新该流的缓存。 newLine()方法:写入一个行分隔符。
在使用BufferedWriter类的Write()方法时,数据并没有立刻被写入输出流,而是首先进入缓存区中。如果想立刻将缓存区中的数据写入输出流,一定要调用flush()方法。
具体实例:
public class BufferedReaderTest { public static void main(String[] args) { String[] content = {"好久不见", "最近好吗", "常联系"}; File file = new File("word.text"); try { FileWriter fw = new FileWriter(file); BufferedWriter bufw = new BufferedWriter(fw); for (String s : content) { bufw.write(s); bufw.newLine(); } bufw.close(); fw.close(); } catch (IOException e) { e.printStackTrace(); } try { FileReader fr = new FileReader(file); BufferedReader bufr = new BufferedReader(fr); String s; int i = 0; while ((s = bufr.readLine()) != null) { i++; System.out.println("第" + i + "行:" + s); } bufr.close(); fr.close(); } catch (IOException e) { e.printStackTrace(); } } }
运行效果
数据输入/输出流
数据输入/输出流(DataInputStream类与DataOutputStream类)允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型。也就是说,当读取一个数据时,不必再关心这个数值应当是哪种字节。
DataInputStream类与DataOutputStream类的构造方法如下。
DataInputStream(InputStream in):使用指定的基础InputStream创建一个DataInputStream
DataOutputStream(OutputStream out):创建一个新的数据输出流,将数据写入指定基础输出流。
DataOutputStream类提供了如下3种写入字符串的方法:
writeBytes(String s)
writeChars(String s)
writeUTF(String s)
由于Java中的字符是Unicode编码,是双字节的,writeBytes只是将字符串中的每一个字符的低字节内容写入目标设备中;而writeChars将字符串中的每一个字符的两个字节的内容都写到目标设备中;writeUTF将字符串按照UTF编码后的字节长度写入目标设备,然后才是每一个字节的UTF编码。
DataInputStream类只提供了一个readUTF()方法返回字符串。这是因为要在一个连续的字节流读取一个字符串,如果没有特殊的标记作为一个字符串的结尾,并且不知道这个字符串的长度,就无法知道读取到什么位置才是这个字符串的结束。DataOutputStream类中只有writeUTF()方法向目标设备中写入字符串的长度,所以也能准确地读回写入字符串。
具体实例:
public class DataOutputStreamTest { public static void main(String[] args) { try { FileOutputStream fs = new FileOutputStream("word.text"); DataOutputStream ds = new DataOutputStream(fs); ds.writeUTF("使用UTF()方法写入数据"); ds.writeChars("使用writeChar()方法写入数据"); ds.writeBytes("使用writeBytes()方法写入数据"); ds.close(); FileInputStream fis = new FileInputStream("word.text"); DataInputStream dis = new DataInputStream(fis); System.out.println(dis.readUTF()); } catch (IOException e) { e.printStackTrace(); } } }
使用记事本程序将word.txt打开。尽管在记事本程序中看不出writeUTF()写入的字符串是“使用writeUFT()方法写入数据”,但程序通过readUTF()读回后显示在屏幕上的仍是“使用writeUFT()方法写入数据”。但如果使用writeChars()和writeBytes()方法写入字符串后,再读取回来就不容易了。
运行结果
word.txt文件内容
ZIP压缩输入/输出流
Java实现了I/O数据流与网络数据流的单一接口,因此数据的压缩、网络传输和解压缩的实现比较容易。ZipEntry类产生的对象,是用来代表一个ZIP压缩文件内的进入点(entry)。
ZipInputStream类用来读取ZIP压缩格式的文件,所支持的包括已压缩及未压缩的进入点(entry)。
ZipOutputStream类用来写出ZIP压缩格式的文件,而且所支持的包括已压缩及未压缩的进入点(entry)。
压缩文件
利用ZipOutputStream类对象,可将文件压缩为.zip文件。
ZipOutputStream类的构造方法如下:
ZipOutputStream(OutputStream out)
ZipOutputStream类的常用方法
解压缩ZIP文件
ZipInputStream类可读取ZIP压缩格式的文件,包括已压缩和未压缩的条目(entry)。ZipInputStream类的构造方法如下:
ZipInputStream(InputStream in)
ZipInputStream类的常用方法
反射
通过Java的反射机制,程序员可以更深入地控制程序的运行过程,如在程序运行时对用户输入的信息进行验证,还可以逆向控制程序的执行过程。
Class类与Java反射
通过Java反射机制,可以在程序中访问已经装载到JVM中的Java对象的描述,实现访问、检测和修改描述Java对象本身信息的功能。Java反射机制的功能十分强大,在java.lang.reflect包中提供了对该功能的支持。
所有Java类均继承了Object类,在Object类中定义了一个getClass()方法,该方法返回一个类型为Class的对象。
Class testFieIdC = testFieldC.getClass();//testFieldC为JTexFieId类的对象
利用Class类的对象textFieldC,可以访问用来返回该对象的textField对象的描述信息。可以访问的主要描述信息如表
通过反射可访问的主要描述信息
说明
在通过getFields()和getMethods()方法依次获得权限为public的成员变量和方法时,将包含从超类中继承到的成员变量和方法;而通过方法getDeclaredFields()和getDeclaredMethods()只是获得在本类中定义的所有成员变量和方法。
访问构造方法
在通过下列一组方法访问构造方法时,将返回Constructor类型的对象或数组。每个Constructor对象代表一个构造方法,利用Constructor对象可以操纵相应的构造方法:
getConstructors() getConstructor(Class<?>…parameterTypes) getDeclaredConstructors() getDeclaredConstructor(Class<?>…parameterTypes)
如果是访问指定的构造方法,需要根据该构造方法的入口参数的类型来访问。例如,访问一个入口参数类型依次为String和int型的构造方法,通过下面两种方式均可实现。
Constructor类的常用方法
通过java.lang.reflect.Modifier类可以解析出getModifiers()方法的返回值所表示的修饰符信息,在该类中提供了一系列用来解析的静态方法,既可以查看是否被指定的修饰符修饰,还可以以字符串的形式获得所有修饰符。
Modifier类中的常用解析方法
例如,判断对象constructor所代表的构造方法是否被private修饰,以及以字符串形式获得该构造方法的所有修饰符的典型代码如下:
访问成员变量
在通过下列一组方法访问成员变量时,将返回Field类型的对象或数组。每个Field对象代表一个成员变量,利用Field对象可以操纵相应的成员变量。
getFields()
getField(String name)
getDeclaredFields()
getDeclaredField(String name)
Field类的常用方法
访问方法
在通过下列一组方法访问方法时,将返回Method类型的对象或数组。每个Method对象代表一个方法,利用Method对象可以操纵相应的方法。
getMethods() getMethod(String name, Class<?>…parameterTypes) getDeclaredMethods() getDeclaredMethod(String name, Class<?>…parameterTypes)
如果是访问指定的方法,需要根据该方法的名称和入口参数的类型来访问。例如,访问一个名称为print、入口参数类型依次为String和int型的方法,通过下面两种方式均可实现:
objectClass.getDeclaredMethod("print", String.class, int.class); objectClass.getDeclaredMethod("print", new Class[] {String.class,int.class });
Method类的常用方法
使用Annotation功能
Java中提供了Annotation功能,该功能可用于类、构造方法、成员变量、方法、参数等的声明中。该功能并不影响程序的运行,但是会对编译器警告等辅助工具产生影响
定义Annotation类型
在定义Annotation类型时,也需要用到用来定义接口的interface关键字,但需要在interface关键字前加一个“@”符号,即定义Annotation类型的关键字为@interface,这个关键字的隐含意思是继承了java.lang.annotation.Annotation接口。
在定义Annotation类型时,还可以通过Annotation类型@Target来设置Annotation类型适用的程序元素种类。如果未设置@Target,则表示适用于所有程序元素。枚举类ElementType中的枚举常量用来设置@Targer
枚举类ElementType中的枚举常量
通过Annotation类型@Retention可以设置Annotation的有效范围。枚举类RetentionPolicy中的枚举常量用来设置@Retention。如果未设置@Retention,Annotation的有效范围为枚举常量CLASS表示的范围。
枚举类RetentionPolicy中的枚举常量
访问Annotation信息
如果在定义Annotation类型时将@Retention设置为RetentionPolicy.RUNTIME,那么在运行程序时通过反射就可以获取到相关的Annotation信息,如获取构造方法、字段和方法的Annotation信息。
类Constructor、Field和Method均继承了AccessibleObject类,在AccessibleObject中定义了3个关于Annotation的方法,其中方法isAnnotationPresent(Class<? extends Annotation> annotationClass)用来查看是否添加了指定类型的Annotation,如果是则返回true,否则返回false;方法getAnnotation(Class<T>annotationClass)用来获得指定类型的Annotation,如果存在则返回相应的对象,否则返回null;方法getAnnotations()用来获得所有的Annotation,该方法将返回一个Annotation数组。
在类Constructor和Method中还定义了方法getParameterAnnotations(),用来获得为所有参数添加的Annotation,将以Annotation类型的二维数组返回,在数组中的顺序与声明的顺序相同,如果没有参数则返回一个长度为0的数组;如果存在未添加Annotation的参数,将用一个长度为0的嵌套数组占位。
枚举类型与泛型
枚举类型可以取代以往常量的定义方式,即将常量封装在类或接口中,此外,它还提供了安全检查功能。枚举类型本质上还是以类的形式存在。泛型的出现不仅可以让程序员少写某些代码,主要的作用是解决类型安全问题,它提供编译时的安全检查,不会因为将对象置于某个容器中而失去其类型。
枚举类型
使用枚举类型可以取代以往定义常量的方式,同时枚举类型还赋予程序在编译时进行检查的功能。
1. 操作枚举类型成员的方法
枚举类型较传统定义常量的方式,具有以下优势
l 参数类型检测的优势
l 还可以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员;
l 由于枚举类型对象继承于java.lang.Enum类,所以该类中一些操作枚举类型的方法都可以应用到枚举类型中
枚举类型的常用方法
2. 枚举类型中的构造方法
在枚举类型中,可以添加构造方法,但是规定这个构造方法必须为private修饰符所修饰。2
3. 使用枚举类型的优势
枚举类型声明提供了一种用户友好的变量定义方法,枚举了某种数据类型所有可能出现的值。总结枚举类型,它具有以下特点:
l 类型安全。
l 紧凑有效的数据定义。
l 可以和程序其他部分完美交互。
l 运行效率高。
泛型
泛型实质上就是使程序员定义安全的类型。在没有出现泛型之前,Java也提供了对Object的引用“任意化”操作,这种“任意化”操作就是对Object引用进行向下转型及向上转型操作,但某些强制类型转换的错误也许不会被编译器捕捉,而在运行后出现异常,可见强制类型转换存在安全隐患,所以在此提供了泛型机制。
一般向下转型操作通常会出现问题,执行时出现ClassCastException异常。
定义泛型类
在JDK 1.5版本以后,提出了泛型机制,语法如下:
类名<T> //T代表一个类型的名称
泛型的常规用法
常用的被泛型化的集合类
泛型的高级用法
l 限制泛型可用类型:默认可以使用任何类型来实例化一个泛型类对象,但Java中也对泛型类实例的类型作了限制。语法如下
class 类名称<T extends anyClass> //anyClass指某个接口或类。
使用泛型限制后,泛型类的类型必须实现或继承了anyClass这个接口或类。无论anyClass是接口还是类,在进行泛型限制时都必须使用extends关键字。
两个等价的泛型类
l 使用类型通配符:在泛型机制中,提供了类型通配符,其主要作用是在创建一个泛型类对象时限制这个泛型类的类型实现或继承某个接口或类的子类。要声明这样一个对象可以使用“?”通配符来表示,同时使用extends关键字来对泛型加以限制。使用泛型类型通配符的语法如下:
泛型类名称<? extends List> a=null; //<? extends List>表示类型未知,当需要使用该泛型对象时,可以单独实例化。
l 继承泛型类与实现泛型接口:定义为泛型的类和接口也可以被继承与实现。
泛型使用方法总结
l 泛型的类型参数只能是类类型,不可以是简单类型,如A<int>这种泛型定义就是错误的。
l 泛型的类型个数可以是多个。
l 可以使用extends关键字限制泛型的类型。
l 可以使用通配符限制泛型的类型。
多线程
线程简介
世间万物都可以同时完成很多工作,例如,人体可以同时进行呼吸、血液循环、思考问题等活动,用户既可以使用计算机听歌,也可以使用它打印文件,而这些活动完全可以同时进行,这种思想放在Java中被称为并发,而将并发完成的每一件事情称为线程。
在Java中,并发机制非常重要,但并不是所有的程序语言都支持线程。在以往的程序中,多以一个任务完成后再进行下一个项目的模式进行开发,这样下一个任务的开始必须等待前一个任务的结束。Java语言提供了并发机制,程序员可以在程序中执行多个线程,每一个线程完成一个功能,并与其他线程并发执行,这种机制被称为多线程。
多线程是非常复杂的机制,比如同时阅读3本书,首先阅读第1本书第1章,然后再阅读第2本书第1章,再阅读第3本书第1章,回过头再阅读第1本书第2章,依此类推,就体现了多线程的复杂性。
既然多线程这样复杂,那么它在操作系统中是怎样工作的呢?其实Java中的多线程在每个操作系统中的运行方式也存在差异,在此着重说明多线程在Windows操作系统中的运行模式。Windows操作系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。系统可以分配给每个进程一段有限的使用CPU的时间(也可以称为CPU时间片),CPU在这段时间中执行某个进程,然后下一个时间片又跳至另一个进程中去执行。由于CPU转换较快,所以使得每个进程好像是同时执行一样。
Windows操作系统的执行模式
一个线程则是进程中的执行流程,一个进程中可以同时包括多个线程,每个线程也可以得到一小段程序的执行时间,这样一个进程就可以具有多个并发执行的线程。在单线程中,程序代码按调用顺序依次往下执行,如果需要一个进程同时完成多段代码的操作,就需要产生多线程。
实现线程的两种方式
在Java中主要提供两种方式实现线程,分别为继承java.lang.Thread类与实现java.lang.Runnable接口。
继承Thread类
Thread类是java.lang包中的一个类,从这个类中实例化的对象代表线程,程序员启动一个新线程需要建立Thread实例。Thread类中常用的两个构造方法如下:
l public Thread():创建一个新的线程对象。
l public Thread(String threadName):创建一个名称为threadName的线程对象。
继承Thread类创建一个新的线程的语法如下:
public class ThreadTest extends Thread {}
完成线程真正功能的代码放在类的run()方法中,当一个类继承Thread类后,就可以在该类中覆盖run()方法,将实现该线程功能的代码写入run()方法中,然后同时调用Thread类中的start()方法执行线程,也就是调用run()方法。
Thread对象需要一个任务来执行,任务是指线程在启动时执行的工作,该工作的功能代码被写在run()方法中。run()方法必须使用以下语法格式:
public void run() {}
注意:如果start()方法调用一个已经启动的线程,系统将抛出IllegalThreadStateException异常。
具体实例:
public class ThreadTest extends Thread { private int i = 10; public void run() { while (true) { System.out.println(i); if (--i == 0) { return; } } } public static void main(String[] args) { new ThreadTest().start(); } }
运行结果
在上述实例中,继承了Thread类,然后在类中覆盖了run()方法。通常在run()方法中使用无限循环的形式,使得线程一直运行下去,所以要指定一个跳出循环的条件,如本实例中使用变量count递减为0作为跳出循环的条件。
在main方法中,使线程执行需要调用Thread类中的start()方法,start()方法调用被覆盖的run()方法,如果不调用start()方法,线程永远都不会启动,在主方法没有调用start()方法之前,Thread对象只是一个实例,而不是一个真正的线程。
实现Runnable接口
到目前为止,线程都是通过扩展Thread类来创建的,如果程序员需要继承其他类(非Thread类),而且还要使当前类实现多线程,那么可以通过Runnable接口来实现。例如,一个扩展JFrame类的GUI程序不可能再继承Thread类,因为Java语言中不支持多继承,这时该类就需要实现Runnable接口使其具有使用线程的功能。
实现Runnable接口的语法如下:
public class Thread extends Object implements Runnable
说明:实质上Thread类实现了Runnable接口,其中的run()方法正是对Runnable接口中的run()方法的具体实现。
实现Runnable接口的程序会创建一个Thread对象,并将Runnable对象与Thread对象相关联。Thread类中有以下两个构造方法:
l public Thread(Runnable target)。 l public Thread(Runnable target,String name)。
这两个构造方法的参数中都存在Runnable实例,使用以上构造方法就可以将Runnable实例与Thread实例相关联。
使用Runnable接口启动新的线程的步骤如下:
(1)建立Runnable对象。
(2)使用参数为Runnable对象的构造方法创建Thread实例。
(3)调用start()方法启动线程。
实现Runnable接口创建线程的流程
通过Runnable接口创建线程时程序员首先需要编写一个实现Runnable接口的类,然后实例化该类的对象,这样就建立了Runnable对象;接下来使用相应的构造方法创建Thread实例;最后使用该实例调用Thread类中的start()方法启动线程。
注意:启动一个新的线程,不是直接调用Thread子类对象的run()方法,而是调用Thread子类的start()方法,Thread类的start()方法产生一个新的线程,该线程运行Thread子类的run()方法。
线程的生命周期
线程具有生命周期,其中包含7种状态,分别为出生状态、就绪状态、运行状态、等待状态、休眠状态、阻塞状态和死亡状态。
l 出生状态就是线程被创建时处于的状态,在用户使用该线程实例调用start()方法之前线程都处于出生状态;
l 当用户调用start()方法后,线程处于就绪状态(又被称为可执行状态);
l 当线程得到系统资源后就进入运行状态。
一旦线程进入可执行状态,它会在就绪与运行状态下转换,同时也有可能进入等待、休眠、阻塞或死亡状态。
当处于运行状态下的线程调用Thread类中的wait()方法时,该线程便进入等待状态,进入等待状态的线程必须调用Thread类中的notify()方法才能被唤醒,而notifyAll()方法是将所有处于等待状态下的线程唤醒;
当线程调用Thread类中的sleep()方法时,则会进入休眠状态。
如果一个线程在运行状态下发出输入/输出请求,该线程将进入阻塞状态,在其等待输入/输出结束时线程进入就绪状态,对于阻塞的线程来说,即使系统资源空闲,线程依然不能回到运行状态。
当线程的run()方法执行完毕时,线程进入死亡状态。
线程的生命周期状态图
虽然多线程看起来像同时执行,但事实上在同一时间点上只有一个线程被执行,只是线程之间切换较快,所以才会使人产生线程是同时进行的假象。在Windows操作系统中,系统会为每个线程分配一小段CPU时间片,一旦CPU时间片结束就会将当前线程换为下一个线程,即使该线程没有结束。
使线程处于就绪状态有以下几种方法:
l 调用sleep()方法。
l 调用wait()方法。
l 等待输入/输出完成。
当线程处于就绪状态后,可以用以下几种方法使线程再次进入运行状态。
l 线程调用notify()方法。
l 线程调用notifyAll()方法。
l 线程调用interrupt()方法。
l 线程的休眠时间结束。
l 输入/输出结束。
操作线程的方法
线程的休眠
一种能控制线程行为的方法是调用sleep()方法,sleep()方法需要一个参数用于指定该线程休眠的时间,该时间以毫秒为单位。它通常是在run()方法内的循环中被使用。
sleep()方法的语法如下:
try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
上述代码会使线程在2秒之内不会进入就绪状态。由于sleep()方法的执行有可能抛出InterruptedException异常,所以将sleep()方法的调用放在try-catch块中。虽然使用了sleep()方法的线程在一段时间内会醒来,但是并不能保证它醒来后进入运行状态,只能保证它进入就绪状态。
线程的加入
如果当前某程序为多线程程序,假如存在一个线程A,现在需要插入线程B,并要求线程B先执行完毕,然后再继续执行线程A,此时可以使用Thread类中的join()方法来完成。这就好比此时读者正在看电视,突然有人上门收水费,读者必须付完水费后才能继续看电视。
当某个线程使用join()方法加入到另外一个线程时,另一个线程会等待该线程执行完毕后再继续执行。
线程的中断
以往有的时候会使用stop()方法停止线程,但当前版本的JDK早已废除了stop()方法,不建议使用stop()方法来停止一个线程的运行。现在提倡在run()方法中使用无限循环的形式,然后使用一个布尔型标记控制循环的停止。
具体实例
public class InterruptedTest implements Runnable { private boolean isContinue = false; @Override public void run() { while (true) { //... if (isContinue) { break; } } } public void setContinue(boolean aContinue) { isContinue = aContinue; } }
如果线程是因为使用了sleep()或wait()方法进入了就绪状态,可以使用Thread类中interrupt()方法使线程离开run()方法,同时结束线程,但程序会抛出InterruptedException异常,用户可以在处理该异常时完成线程的中断业务处理,如终止while循环。
线程的礼让
Thread类中提供了一种礼让方法,使用yield()方法表示,它只是给当前正处于运行状态的线程一个提醒,告知它可以将资源礼让给其他线程,但这仅是一种暗示,没有任何一种机制保证当前线程会将资源礼让。
yield()方法使具有同样优先级的线程有进入可执行状态的机会,当当前线程放弃执行权时会再度回到就绪状态。对于支持多任务的操作系统来说,不需要调用yield()方法,因为操作系统会为线程自动分配CPU时间片来执行。
线程的优先级
每个线程都具有各自的优先级,线程的优先级可以表明在程序中该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这并不意味着低优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收线程的优先级就较低。
Thread类中包含的成员变量代表了线程的某些优先级,如Thread.MIN_PRIORITY(常数1)、Thread.MAX_PRIORITY(常数10)、Thread.NORM_PRIORITY(常数5)。其中每个线程的优先级都在Thread.MIN_PRIORITY~Thread.MAX_PRIORITY之间,在默认情况下其优先级都是Thread.NORM_PRIORITY。每个新产生的线程都继承了父线程的优先级。
在多任务操作系统中,每个线程都会得到一小段CPU时间片运行,在时间结束时,将轮换另一个线程进入运行状态,这时系统会选择与当前线程优先级相同的线程予以运行。系统始终选择就绪状态下优先级较高的线程进入运行状态。
处于各个优先级状态下的线程的运行顺序
优先级为5的线程A首先得到CPU时间片;当该时间结束后,轮换到与线程A相同优先级的线程B;当线程B的运行时间结束后,会继续轮换到线程A,直到线程A与线程B都执行完毕,才会轮换到线程C;当线程C结束后,才会轮换到线程D。
线程的优先级可以使用setPriority()方法调整,如果使用该方法设置的优先级不在1~10之内,将产生IllegalArgumentException异常。
线程同步
在单线程程序中,每次只能做一件事情,后面的事情需要等待前面的事情完成后才可以进行,但是如果使用多线程程序,就会发生两个线程抢占资源的问题,如两个人同时说话、两个人同时过同一个独木桥等。所以在多线程编程中需要防止这些资源访问的冲突。Java提供了线程同步的机制来防止资源访问的冲突。
线程安全
实质上线程安全问题来源于两个线程同时存取单一对象的数据。
线程同步机制
那么该如何解决资源共享的问题呢?基本上所有解决多线程资源冲突问题的方法都是采用给定时间只允许一个线程访问共享资源,这时就需要给共享资源上一道锁。
1. 同步块
在Java中提供了同步机制,可以有效地防止资源冲突。同步机制使用synchronized关键字。
具体实例
public class ThreadSafeTest implements Runnable { private int num = 10; @Override public void run() { while (true) { synchronized ("") { if (num > 0) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } System.out.println("tickets" + --num); } else { System.err.println("tickets==0"); break; } } } } public static void main(String[] args) { ThreadSafeTest t = new ThreadSafeTest(); Thread tA = new Thread(t); Thread tB = new Thread(t); Thread tC = new Thread(t); Thread tD = new Thread(t); tA.start(); tB.start(); tC.start(); tD.start(); } }
synchronized关键字语法如下:
synchronized ("") {}
通常将共享资源的操作放置在synchronized定义的区域内,这样当其他线程也获取到这个锁时,必须等待锁被释放时才能进入该区域。Object为任意一个对象,每个对象都存在一个标志位,并具有两个值,分别为0和1。一个线程运行到同步块时首先检查该对象的标志位,如果为0状态,表明此同步块中存在其他线程在运行。这时该线程处于就绪状态,直到处于同步块中的线程执行完同步块中的代码为止。这时该对象的标志位被设置为1,该线程才能执行同步块中的代码,并将Object对象的标志位设置为0,防止其他线程执行同步块中的代码。
2. 同步方法
同步方法就是在方法前面修饰synchronized关键字的方法,其语法如下:
synchronized void f() {}
当某个对象调用了同步方法时,该对象上的其他同步方法必须等待该同步方法执行完毕后才能被执行。必须将每个能访问共享资源的方法修饰为synchronized,否则就会出错。
具体实例
public class ThreadSafeTwoTest implements Runnable { private int num = 10; @Override public void run() { doit(); } public synchronized void doit() { if (num > 0) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("tickets" + --num); } } public static void main(String[] args) { ThreadSafeTwoTest t = new ThreadSafeTwoTest(); Thread tA = new Thread(t); Thread tB = new Thread(t); Thread tC = new Thread(t); Thread tD = new Thread(t); tA.start(); tB.start(); tC.start(); tD.start(); } }
网络通信
Internet提供了大量、多样的信息,很少有人能在接触过Internet后拒绝它的诱惑。计算机网络实现了多个计算机互连系统,相互连接的计算机之间彼此能够进行数据交流。网络应用程序就是在已连接的不同计算机上运行的程序,这些程序相互之间可以交换数据。而编写网络应用程序,首先必须明确网络应用程序所要使用的网络协议,TCP/IP协议是网络应用程序的首选。
网络程序设计基础
网络程序设计是指编写与其他计算机进行通信的程序。
局域网与因特网
为了实现两台计算机的通信,必须用一个网络线路连接两台计算机。
服务器、客户机和网络
服务器是指提供信息的计算机或程序,客户机是指请求信息的计算机或程序,而网络用于连接服务器与客户机,实现两者相互通信。但有时在某个网络中很难将服务器与客户机区分开。我们通常所说的局域网(Local Area Network,LAN),就是一群通过一定形式连接起来的计算机。它可以由两台计算机组成,也可以由同一区域内的上千台计算机组成。由LAN延伸到更大的范围,这样的网络称为广域网(Wide Area Network,WAN)。我们熟悉的因特网(Internet),就是由无数的LAN和WAN组成的。
网络协议
网络协议规定了计算机之间连接的物理、机械(网线与网卡的连接规定)、电气(有效的电平范围)等特征以及计算机之间的相互寻址规则、数据发送冲突的解决、长的数据如何分段传送与接收等。就像不同的国家有不同的法律一样,目前网络协议也有多种,下面简单地介绍几个常用的网络协议。
1. IP协议
IP是Internet Protocol的简称,它是一种网络协议。Internet网络采用的协议是TCP/IP协议,其全称是Transmission Control Protocol/Internet Protocol。Internet依靠TCP/IP协议,在全球范围内实现不同硬件结构、不同操作系统、不同网络系统的互联。在Internet网络上存在数以亿计的主机,每一台主机在网络上用为其分配的Internet地址代表自己,这个地址就是IP地址。到目前为止,IP地址用4个字节,也就是32位的二进制数来表示,称为IPv4。为了便于使用,通常取用每个字节的十进制数,并且每个字节之间用圆点隔开来表示IP地址,如192.168.1.1。现在人们正在试验使用16个字节来表示IP地址,这就是IPv6,但IPv6还没有投入使用。
TCP/IP层次结构
TCP/IP模式是一种层次结构,共分为4层,分别为应用层、传输层、互联网层和网络层。各层实现特定的功能,提供特定的服务和访问接口,并具有相对的独立性。
2. TCP与UDP协议
在TCP/IP协议栈中,有两个高级协议是网络应用程序编写者应该了解的,即传输控制协议(Transmission Control Protocol, TCP)与用户数据报协议(User Datagram Protocol, UDP)。
TCP协议是一种以固接连线为基础的协议,它提供两台计算机间可靠的数据传送。TCP可以保证从一端数据送至连接的另一端时,数据能够确实送达,而且抵达的数据的排列顺序和送出时的顺序相同,因此,TCP协议适合可靠性要求比较高的场合。就像拨打电话,必须先拨号给对方,等两端确定连接后,相互才能听到对方说话,也知道对方回应的是什么。
HTTP、FTP和Telnet等都需要使用可靠的通信频道。例如,HTTP从某个URL读取数据时,如果收到的数据顺序与发送时不相同,可能就会出现一个混乱的HTML文件或是一些无效的信息。
UDP是无连接通信协议,不保证可靠数据的传输,但能够向若干个目标发送数据,接收发自若干个源的数据。UDP是以独立发送数据包的方式进行。这种方式就像邮递员送信给收信人,可以寄出很多信给同一个人,而每一封信都是相对独立的,各封信送达的顺序并不重要,收信人接收信件的顺序也不能保证与寄出信件的顺序相同。
UDP协议适合于一些对数据准确性要求不高的场合,如网络聊天室、在线影片等。这是由于TCP协议在认证上存在额外耗费,可能使传输速度减慢,而UDP协议可能会更适合这些对传输速度和时效要求非常高的网站,即使有一小部分数据包遗失或传送顺序有所不同,也不会严重危害该项通信。
端口和套接字
一般而言,一台计算机只有单一的连到网络的物理连接(Physical Connection),所有的数据都通过此连接对内、对外送达特定的计算机,这就是端口。网络程序设计中的端口(port)并非真实的物理存在,而是一个假想的连接装置。端口被规定为一个在0~65535之间的整数。HTTP服务一般使用80端口,FTP服务使用21端口。假如一台计算机提供了HTTP、FTP等多种服务,那么客户机会通过不同的端口来确定连接到服务器的哪项服务上。
端口
套接字
通常,0~1023之间的端口数用于一些知名的网络服务和应用,用户的普通网络应用程序应该使用1024以上的端口数,以避免端口号与另一个应用或系统服务所用端口冲突。
网络程序中的套接字(Socket)用于将应用程序与端口连接起来。套接字是一个假想的连接装置,就像插插头的设备“插座”用于连接电器与电线一样,如图19.4所示。Java将套接字抽象化为类,程序设计者只需创建Socket类对象,即可使用套接字。
数据库操作
数据库系统是由数据库、数据库管理系统和应用系统、数据库管理员构成的。数据库管理系统简称DBMS,是数据库系统的关键组成部分,包括数据库定义、数据查询、数据维护等。JDBC技术是连接数据库与应用程序的纽带。
数据库基础知识
什么是数据库
数据库是一种存储结构,它允许使用各种格式输入、处理和检索数据,不必在每次需要数据时重新输入。
主要特点:
l 实现数据共享。数据共享包含所有用户可同时存取数据库中的数据,也包括用户可以用各种方式通过接口使用数据库,并提供数据共享。
l 减少数据的冗余度。同文件系统相比,数据库实现了数据共享,从而避免了用户各自建立应用文件,减少了大量重复数据,减少了数据冗余,维护了数据的一致性。
l 数据的独立性。数据的独立性包括数据库中数据库的逻辑结构和应用程序相互独立,也包括数据物理结构的变化不影响数据的逻辑结构。
l 数据实现集中控制。文件管理方式中,数据处于一种分散的状态,不同的用户或同一用户在不同处理中其文件之间毫无关系。利用数据库可对数据进行集中控制和管理,并通过数据模型表示各种数据的组织以及数据间的联系。
l 数据的一致性和可维护性,以确保数据的安全性和可靠性。
数据库的基本结构分为3个层次:
l 物理数据层:它是数据库的最内层,是物理存储设备上实际存储的数据集合。这些数据是原始数据,是用户加工的对象,由内部模式描述的指令操作处理的字符和字组成。
l 概念数据层:它是数据库的中间一层,是数据库的整体逻辑表示,指出了每个数据的逻辑定义及数据间的逻辑联系,是存储记录的集合。它所涉及的是数据库所有对象的逻辑关系,而不是它们的物理情况,是数据库管理员概念下的数据库。
l 逻辑数据层:它是用户所看到和使用的数据库,是一个或一些特定用户使用的数据集合,即逻辑记录的集合。
数据库的种类及功能
数据库系统一般基于某种数据模型,可以分为层次型、网状型、关系型及面向对象型等。
l 层次型数据库:层次型数据库类似于树结构,是一组通过链接而相互联系在一起的记录。层次模型的特点是记录之间的联系通过指针实现。由于层次模型层次顺序严格而且复杂,因此对数据进行各项操作都很困难。
层次型数据库
l 网状型数据库:网络模型是使用网络结构表示实体类型、实体间联系的数据模型。网络模型容易实现多对多的联系。但在编写应用程序时,程序员必须熟悉数据库的逻辑结构。
网状型数据库
l 面向对象型数据库:建立在面向对象模型基础上。
l 关系型数据库:关系型数据库是目前最流行的数据库,是基于关系模型建立的数据库,关系模型是由一系列表格组成的。后面会详细地讲解它。
SQL语言
SQL(Structure Query Language,结构化查询语言)被广泛地应用于大多数数据库中,使用SQL语言可以方便地查询、操作、定义和控制数据库中的数据。SQL语言主要由以下几部分组成。
l 数据定义语言(Data Definition Language,DDL),如create、alter、drop等。
l 数据操纵语言(Data Manipulation Language,DML),如select、insert、update、delete等。
l 数据控制语言(Data Control Language,DCL),如grant、revoke等。
l 事务控制语言(Transaction Control Language),如commit、rollback等。
JDBC概述
JDBC是一种可用于执行SQL语句的Java API(Application Programming Interface,应用程序设计接口),是连接数据库和Java应用程序的纽带。
JDBC-ODBC桥
JDBC-ODBC桥是一个JDBC驱动程序,完成了从JDBC操作到ODBC操作之间的转换工作,允许JDBC驱动程序被用作ODBC的驱动程序。JDBC-ODBC桥作为连接数据库的过渡性技术,现在已经不被Java广泛应用了,现在被广泛应用的是JDBC技术。
JDBC技术
JDBC的全称是Java DataBase Connectivity,是一套面向对象的应用程序接口,指定了统一的访问各种关系型数据库的标准接口。JDBC是一种底层的API,因此访问数据库时需要在业务逻辑层中嵌入SQL语句。SQL语句是面向关系的,依赖于关系模型,所以通过JDBC技术访问数据库也是面向关系的。JDBC技术主要完成以下几个任务:
l 与数据库建立一个连接。
l 向数据库发送SQL语句。
l 处理从数据库返回的结果。
JDBC驱动程序的类型
JDBC的总体结构由4个组件—应用程序、驱动程序管理器、驱动程序和数据源组成。JDBC驱动基本上分为以下4种。JDBC驱动基本上分为以下4种。
l JDBC-ODBC桥:依靠ODBC驱动器和数据库通信。这种连接方式必须将ODBC二进制代码加载到使用该驱动程序的每台客户机上。这种类型的驱动程序最适合于企业网或者用Java编写的三层结构的应用程序服务器代码。
l 本地API一部分用Java编写的驱动程序:这类驱动程序把客户机的API上的JDBC调用转换为Oracle、DB2、Sybase或其他DBMS的调用。这种驱动程序也需要将某些二进制代码加载到每台客户机上。
l JDBC网络驱动:这种驱动程序将JDBC转换为与DBMS无关的网络协议,又被某个服务器转换为一种DBMS协议,是一种利用Java编写的JDBC驱动程序,也是最为灵活的JDBC驱动程序。这种方案的提供者提供了适合于企业内部互联网(Intranet)用的产品。为使这种产品支持Internet访问,需要处理Web提出的安全性、通过防火墙的访问等额外的要求。
l 本地协议驱动:这是一种纯Java的驱动程序。这种驱动程序将JDBC调用直接转换为DBMS所使用的网络协议,允许从客户机上直接调用DBMS服务器,是一种很实用的访问Intranet的解决方法。
JDBC网络驱动和本地协议驱动是JDBC访问数据库的首选,这两类驱动程序提供了Java的所有优点。
JDBC中常用的类和接口
Connection接口:代表与特定的数据库的连接,在连接上下文中执行SQL语句并返回结果。
Connection接口的常用方法
Statement接口:用于在已经建立连接的基础上向数据库发送SQL语句。
Statement接口中常用的方法
PreparedStatement接口:用来动态地执行SQL语句。
PreparedStatement接口提供的常用方法
DriverManager类:用来管理数据库中的所有驱动程序。
DriverManager类的常用方法
ResultSet接口
ResultSet接口类似于一个临时表,用来暂时存放数据库查询操作所获得的结果集。ResultSet实例具有指向当前数据行的指针,指针开始的位置在第一条记录的前面,通过next()方法可将指针向下移。
在JDBC 2.0(JDK 1.2)之后,该接口添加了一组更新方法updateXXX(),该方法有两个重载方法,可根据列的索引号和列的名称来更新指定列。但该方法并没有将对数据进行的操作同步到数据库中,需要执行updateRow()或insertRow()方法更新数据库。
ResultSet接口提供的常用方法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix