3.2. Java代码中的“;”、“{}”、“ ”. 3-5
7.2.2. 父类(SuperClass)和 子类(SubClass)的关系... 7-17
7.3.1. 方法的覆盖(overridding)... 7-18
7.12.3. 五个最常用的集合类之间的区别和联系... 7-33
10. 九·AWT(Abstract Window Toolkit) 事件模型... 10-41
11. 十·The AWT Component Library. 11-41
12. 十一·JFC(Java Foundation Classes)... 12-41
17.3.1. 在基本数据类型和封装类之间的自动转换... 17-69
17.7. Building Strings(StringBuilder类) 17-73
17.9. JAVA5.0 的注释 (Annotation) 17-73
17.10. Callable 和 Future接口... 17-74
JAVA数据库连接:JDBC
1. 前言
1.1. JAVA特点
1) 简单(Java语法是C++语法的一个“纯净”版本);
2) 可移植性 (一次编译到处运行)
3) 面向对象
4) 分布式(Java把打开套接字连接等繁琐的网络任务变得非常容易)
5) 健壮性(Java编译器会检查出很多其他语言在运行时刻才显示出来的错误;Java采用的指针模型可以消除重写内存和数据崩溃的可能)
6) 多线程(多线程编程的简单性是Java成为流行的服务器端开发语言的主要原因之一)
7)安全(用Java可以构建防病毒和防篡改的系统)
9) 动态(Java可随意增加新的方法以及实例变量,而客户端却不需做任何的更改)
10)体系结构中立(字节码与计算机体系结构无关,只要存在运行时系统,可在多种处理器上执行)
1.2. 运行原理
先编译 *.java文件――――>*.class文件
运行 *.class ――加载――> JVM(JAVA虚拟机)
1.3. JAVA目录
JRE―――――――运行环境
SRC――――――-类库
BIN―――――――应用程序
2. 一·基础知识
2.1. 配置环境
LINUX系统(修改环境配置文件)
1 打开shell
2 vi .bash_profile
3 JAVA_HOME=JAVA目录路径
4 PATH=$JAVA_HOME/bin:其他路径
5 CLASSPATH=.
6 export JAVA_HOME CLASSPATH
Windows系统
我的电脑属性―――>环境变量
设置环境变量:
JAVA_HOME=路径
PATH = %PATH%;c:/j2sdk
CLASSPATH = .;
2.2. Java中基本概念
1) 源文件
在最顶层只包括一个public类型的类/接口,文件名与类/接口名同并以.java作为文件后缀。
2) 包(package ,在源文件中this identify只能放在第一行,且最多只能是一行)
一个将类和接口组织在一块的实体,在文件系统中以目录/文件夹型式呈现。
3. 二·定义,关键字和类型
3.1. 注释的三种形式
// 单行注释
/* 一或多行注释 */
/** 文档注释 */
3.2. Java代码中的“;”、“{}”、“ ”
Java语句以分号分隔;
Java代码块包含在大括号内;
忽略空格。
3.3. 标识符
1) 用以命名类、方法和变量、以及包;
遵守JAVA的命名规范
类以每个单词都以大写字母开头。
方法和变量第一个字母不大写,其他依旧
2) 以字符、“_”或“$”开头;
3) 无长度限制。
3.4. 数据类型
1) 整型
byte 1B 8位 -128到127
short 2B 16位 -2^15到2^15-1
int 4B 32位 -2^31到2^31-1
long 8B 64位 -2^63到2^63-1
2) 浮点类型
float 4B 32位
double 8B 64位
3) 字符类型
char 2B 16位
4) 布尔型
boolean false/true
注:1) char是无符号的16位整数,字面值必须用单引号括起来; ‘a’
2) String 是类,非原始数据类型;
3) 长整型数字有一个后缀为“L”或“l”,八进制前缀为“
4) 黙认浮点类型为double;
5) float数据类型有一个后缀为“f”或“F”,Double数据类型后可跟后缀“D”或“d“
3.5. 命名规则
1) 类/接口名首字母大写;
2) 方法、变量名第一个字母小写,其余首字母大写;
3) 常量名称全部大写;
4) 包名全部小写。
4. 三·表达式和控制流
4.1. 变量和作用域
1) 局部变量
定义在方法内部,其作用域为所在代码块,也称为临时变量、栈变量。
存在于栈中。
2) 实例变量
定义在类内部方法之外,其作用域为整个类。如未定义初值,系统会自动为其赋黙认值。存在于堆中
默认数值
类型 黙认值
byte 0
short 0
int 0
long
float
double 0.0d
char '/u0000' 空格
boolean false
*All reference types null
4.2. 操作符
System.out.println(3/2) 按整型计算 得1
1) >> 前面是零补零,前面是一补一;
2) >>> 无符号右移;
>>和>>>对于负数不一样
正数:>>等于除以X的2次方
负数:正数的结果+1
3) && 短路与,前面为假,表达式为假,后面不须计算;
4) || 短路或,前面为真,表达式为真,后面不计算;
例:
if(a<3&(b=a)==0) b赋值
if(a<3&&(b=a)==0) b不赋值
4.3. 数字类型之间的转换
1) byte ——→ short ——→ int ——→ long
2) char ——→ int - - - → float
3) float ——→ double
4) long - - - → float
5) long - - - → double
6) int ——→ double
注:1)实箭头表示无信息损失的转换,虚箭头表示转换可能引起损失;
2)int和float同为32位,但float要有几位表示幂的位数,在精度位数上要比int要小,所以有可能会有损失。long转为double同理;
3)char和short同为16位,但char属无符号数,其范围为0~2^16, short的范围为-2^15~2^15-1 , 所以char和short不能相互转换;
4)byte、short、char属child型,在计算时会自动转为int型,然后转为更大范围类型(long、short、double)。
4.4. 强制类型转换
1) 语法:圆括号中给出要转换的目标类型,随后是待转换的变量名。例:doublc x=9.997;int nx = (int)x;
2) 如果试图强制转换的类型超出了目标类型的范围,结果是一个被截取的不同的数值;
3) 不能在布尔值和任何数字类型间强制类型转换,如确要转换,可使用条件表达式,例:b?1:0。
4.5. 转换的二种类型
1) 赋值
double a =
int = ‘j’;
2) 方法调用
double converValue(int value){
return value;
}
3) 数值计算转换 -9.232e20+1;
4.6. 控制流
if()
if()….else
if()…..else if()….else
switch(){
case variable:……..
case variable:……..
default:
…………
}
注解:switch()内数据类型为child类型 byte short char 自动转换为int
case块中不加break时顺序执行下面的语句。
4.7. 循环语句
for(int i=0;i<n;i++){}
while(){}
do{} while();-----------à加分号
例子:
loop:for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(3==j){
break loop;//---------------àloop为标签 只能用在循环语句中,循环//嵌套中用于跳到外层循环
}
}
}
辨析:
int x,a=6,b=7;
x=a++ + b++; //----------a=7,b=8,x=13
int x=6;x=~x;//---------------- 6的二进制0110 取反得11001 再转成补码(取反加一)10111 = -7
5. 四·数组
5.1. 声明数组
1) 一组相同类型(可以是类)数据的集合;
2) 一个数组是一个对象;
3) 声明一个数组没有创建一个对象;
4) 数组能以下列形式声明:
int[] i 或 int i[]
Car[] c 或 Car c[]
*C++中只能 Car c[]
*JAVA中推荐用 Car[] c;
5.2. 创建数组
1) 创建基本数据类型数组 int[] i = new int[2];
2) 创建引用数据类型数组 Car[] c = new Car[100];
3) 数组创建后有初始值。
数字类型为0,布尔类型为false,引用类型为null
5.3. 初始化数组
1) 初始化、创建、和声明分开
int[] i;
i = new int[2];
i[0] = 0;
i[1] = 1;
2) 初始化、创建、和声明在同一时间
int[] i = {0,1};
Car[] c = {new Car(),new Car()};
5.4. 多维数组
1) 有效
int[][] i1 = new int[2][3];
int[][] i2 = new int[2][];
i2[0] = new int[2],i2[1] = new int[3];
*C++中 int[][] =new int[][3];有效
2) 无效
int[][] i1 = new int[][3];
3) 数组长度 ------------à数组的属性length
int[] i = new int[5];
int len = i.length;//len = 5;
Student[][] st = new Student[4][6];
len = st.length;//len = 4;
len = st[0].length;//len = 6;
请问以下哪段代码哪个可正确执行?(a,c)
1. a char[] i = {'a','b'}; i = new char[]{'b','c'};
b char[] i = {'a','b'}; i = {'b','c'};
c char[] i = new char[2]; i = new char[]{'b','c'};
d char[] i = new char[2]; i = {'b','c'};
5.5. 数组拷贝
System.arrayCopy(Object src, int srcPos, Object dest, int destPos, int length);
拷贝一个数组到另一个数组。
6. 五·对象和类
6.1. 面向对象与面向过程
面向对象:
Everything is an Object
为什么面向对象?
符合我们看待客观世界的思维方式
各司其职 各尽所能
可重用性
可移植性
可插入性
可扩展性
弱耦合性
面向过程:先有算法,后有数据结构
面向对象:先有数据结构,后有算法
定义类
定义属性:实例变量
定义方法:声明 实现
Overload:方法名相同,参数表不同
编译器根据参数,选择一个方法,允许自动类型提升,就近匹配原则
定义构造方法:1.分配空间 2.初始化属性 3.调用某一个构造方法
ClassName cn=new ClassName();
对象变量:引用、句柄 存储的是对象的地址 (栈空间)
参数传递:简单类型参数:值传递 对象类型参数:地址传递
this:当前对象
调用本类其他的构造方法,必须放在构造方法的第一行
封装:该隐藏的隐藏,该公开的公开
属性:隐藏 private
方法:该公开的公开 public
方法声明:可以公开
方法实现:不可以公开 实现细节的改变对架构的影响最小化
6.2. 对象的概念
什么是对象:EVERYTHING IS OBJECT(万物皆对象)
所有的事物都有两个方面:
1.有什么(属性):用来描述对象。
2.能够做什么(方法):告诉外界对象有那些功能。后者以前者为基础。
*一个对象的属性也可以是一个的对象。这是一种对象的关联(associate)
public class Student{
private String name;---------对象
private int age;---------------基本类型
private gender;
public void study(){}---------方法
}
6.3. 成员变量和局部变量
1.实例变量:定义在类中但在任何方法之外。(New出来的均有初值)
实例变量中对象默认值为null。
实例变量的作用域在本类中完全有效,当被其他的类调用的时候也可能有效。
2.局部变量:定义在方法之中的变量。
局部变量要先赋值,再进行运算,而实例变量均已经赋初值。这是局部变量和实例变量的一大区别。
局部变量不允许范围内定义两个同名变量。实例变量和局部变量允许命名冲突,但在局部变量的作用域内局部变量优先,访问实例变量是使用this.variableName。
对于引用型类型变量
Car cart = new Car();
变量cart存在在栈中,而cart对象实际存在在堆中。
如果没有引用
New Cart();
垃圾回收会很快回收这个匿名对象。
简单的垃圾收集器原理
每个对象都包含了一个引用计数器,Garbage Collection 运行时检查对象的这个计数器,垃圾收集器会在整个对象列表中移动巡视,一旦它发现其中一个引用计数成为0,就释放它占据的存储空间。
6.4. 成员方法
方法定义
1) 格式 <modifiers><return_type><name>([argument_list>])[throws <exception>]{<block>}
例如:public String getName(){ return name; }
2) 当没有返回值时,返回类型必须被定义为void。
3) 构造方法没有返回类型。
4) 返回类型必须与方法相邻,其他修饰符号可以调换位置。
参数传递
Java语言总是使用传值调用。这意味着方法得到的只是所有参数值的拷贝。因此,方法不能修改传递给它的任何参数变量的内容。对于对象类型的参数传递的也是该对象的引用值,方法中并不能改变对象变量,但能通过该变量调用对象的方法或修改对象的成员。
***方法与函数的区别,方法的内容是和对象的属性紧密相连的,函数只为了解决一个功能。
6.5. This关键字
1) this是个隐式参数,代表被构造的对象;
publie class Person{
private String name;
public void setName(String name){
this.name=name;-------------àthis.name为成员变量
}
}
2) 如果构造器的第一个语句具有形式this(...),那么这个构造器将调用同一类中的其他构造器。
3)在构造器中this()必须放在方法的第一行。
*Super关键字也是个隐形参数,代表被构造对象的父类。
同样也必须在构造方法的第一行
6.6. 访问控制符
权限高
public 全部可见
protected 本类可见,同包可见,子类可见
default 本类可见,同包可见
private 本类可见
权限低
6.7. 构造方法
构造方法是在生成对象的过程中调用的方法,但构造方法并不能创建对象。
其特点为:
1.构造方法没有返回值。
2.构造方法的方法名与类名相同。
格式为:public ClassName(){}
构造方法也可以是其他的限制符――private protected default private 一般用在singleton模式中。
在一个对象的生成周期中构造方法只用一次,一旦这个对象生成,那么这个构造方法失效。
*接口不能创建实例,因为没有构造方法
可以构造多个构造方法,但多个构造方法的参数表一定不同,参数顺序不同即属于不同的构造方法:-----------------------à构造方法的重载
public student(string name,int a){
}
public student(int a,string name){
}
为两个不同的构造方法。
如果我们未给系统提供一个构造方法,那么系统会自动提供一个为空的构造方法。
如果我们提供了有参的构造方法,那么系统不会再提供无参的构造方法了。这样当被子类继承时,如果子类构造方法不人为调用父类的有参构造方法就会出现异常。
6.8. 数据和方法的隐藏―――封装
public class Person{
private String name;----------------------à数据的隐藏
private int age;
public String getName(){-------------------à方法尽量公开
return name;
}
public int getAge(){
return age;
}
}
有些方法也需要隐藏起来,比如:
Class Singleton{
Private Singleton(){
……
}
Public static Singleton getInstance(){
…..
…..
If(…..){
Singleton=new Singleton();
}
//对生产对象的条件进行限制
Return singleton;
}
}
6.9. 方法的重载
Overloading在一个类中可以定义多个同名方法,各个方法的参数表一定不同。但修饰词可能相同,返回值也可能相同。
在程序的编译过程中根据变量类型来找相应的方法。Overloading被认为是编译时的多态。Overloading 只是为方便程序员编程的解决方法,编译之后的方法名实际加上了各参数类型成为唯一的名字。
普通方法
public void aa(int a,double b) throws IOException{}
private int aa(double a,int b){}
protected double aa(String a,String b){}
构造方法也可以实现overloading。
例:
public void teach(){};
public void teach(int a){};
public void teach(String a){}为三种不同的方法。
Overloading方法对于不匹配的参数是从低向高转换的。
Byte—short—float—int—long—double。
System.out.println(“%
7. 六·高级语言特性
7.1. 封装 (encapsulation)
1.事物的内部实现细节隐藏起来
2.对外提供一致的公共的接口――间接访问隐藏数据
3.可维护性
7.2. 继承 (inherit)
7.2.1. JAVA继承特点
继承:父类的成员能否继承到子类?
子类能否访问到父类的成员
private:本类内部可以访问 不能继承到子类
(default):本类内部可以访问,同包其他类也可以访问;
能否继承到子类? 不一定
protected:本类内部可以访问,不同包的子类也可以访问, 同包其他类也可以访问能继承到子类
public:任何地方都可以访问 能继承到子类
从严 到宽
覆盖:
方法名:相同
参数表:相同
访问限制符:相同或者更宽
返回值类型:相同 或者 子类返回的类型是父类返回的类型的子类
对象的构造过程:
1.递归的构造父类对象
2.分配空间
3.初始化属性
4.调用本类的某一个构造方法
super:调用父类的某一个构造方法
父类对象
多态:
1. 对象不变
2. 只能对对象调用编译时类型中定义的方法
3. 运行时,根据对象的运行时类型,找覆盖过的方法来调用(运行时动态类型判定)
强制类型转换 instanceof
屏蔽子类差异,利用父类共性做出通用编程
属性的遮盖(shadow)没有多态
方法的重载看参数的编译时类型
7.2.2. 父类(SuperClass)和 子类(SubClass)的关系
父类的非私有化属性(不同包的子类无法访问default修饰符)和方法可以默认继承到子类。
Class Son extends Father{
}
而如果父类中的私有方法被子类调用的话,则编译报错。
父类的构造方法子类不可以继承,更不存在覆盖的问题。
所以子类构造方法默认调用父类的无参构造方法。(所以养成写无参构造的习惯)
如果子类访问父类的有参构造方法,必须在子类构造方法第一行使用super(参数)
当构造一个对象的时候,系统先构造父类对象,再构造子类对象。
Public class BMWcar extends Car{
Public BMWcar(){
Super(int alength); //显式的调用父类的构造,默认调用无参构造
//所以父类没有无参构造的话,子类如果不加显示调用其他构造就会报错。这里的super是一个对父类的引用
}
}
7.2.3. 系统构造一个对象的顺序
1先为最里层类成员属性赋初值;
2再构造该类对象;
3返回外层,重复1(上一层类)、2步骤直到完成最外层类的构造。
最外层类 |
最里层基类 |
super() this() 不会同时出现
A(){
super();
}
A(int a){
this();
}
7.3. 多态(polymorphism)
多态:一个对象变量可以指向多种实际类型的现象。
7.3.1. 方法的覆盖(overridding)
当子类从父类继承一个无参方法,而又定义了一个同样的无参方法,则子类新写的方法覆盖父类的方法,称为覆盖。(注意返回值类型也必须相同,否则编译出错。)
如果方法参数表不同,则成重载。
特点:
1.对于方法的访问限制修饰词,子类方法要比父类的访问权限更高。
父类为public,那么子类为private则出现错误。
2.子类抛出的异常应该是父类抛出的异常或其子类。
7.3.2. 多态的分类
多态分两种:
1编译时多态:编译时动态重载;
2运行时多态:指一个对象可以具有多个类型,方法的覆盖
这样对于对象而言分为:
理解运行时多态:
Car c = new Bus();
Car编译时类型 编译时检查变量类型是否存在,是否有调用的方法
Bus运行时类型 实际运行是访问heep中的对象,调用实际的方法。
运行时多态是由运行时类型决定的
编译时多态是由编译时类型决定的
猫,小鸟,狗 都是动物,都可以安上动物的标签。
Interface Animal{}
Class Car implements Animal{}
Class Bird implements Animal{}
Class Dog implements Animal{}
方法中
Animal a = new Car();
Animal b = new Bird();
Animal c = new Dog();
*方法重载看的是参数的编译时类型
public class Animal{
public static void main(String[] args){
}
}
(1) 是覆盖吗?不能多态了
abstract class MyClass{
priavate void m();
}
class Sub extends MyClass(){
public void m();
}
(2) 错误的修饰符组合
abstract class MyClass{
priavate abstract void m();
}
class Sub extends MyClass(){
public void m();
}
(3) 5.0 新 非覆盖
abstract class MyClass{
private final void m();
}
class Sub extends MyClass(){
public void m();
}
7.3.3. 运行时多态的三原则
1.对象不变;(改变的是主观认识)
2.对于对象的调用只能限于编译时类型的方法,如调用运行时类型方法报错。
在上面的例子中:Animal a=new Dog();对象a的编译时类型为Animal,运行时类型为dog。
注意:编译时类型一定要为运行时类型的父类或者同类型。
对于语句:Dog d=(Dog)a。将d强制声明为a类型,此时d为Dog(),此时d就可以调用运行时类型。注意:a和d指向同一对象。
3.动态类型判定实际调用的方法。即它调用覆盖后的方法。
7.3.4. 关系运算符:instanceof
instanceof Animal;(这个式子的结果是一个布尔表达式)
上面语句是判定a是否可以贴Animal标签。如果可以贴则返回true,否则返回false。
在上面的题目中: a instanceof Animal返回 True,
a instanceof Dog也返回 True,
用于判定前面的对象是否是后边的类或者子类。
Animal a = new Car();
If(a instanceof Dog){
Dog b =(Dog)a;
}
else if(a instanceof Car){
Car c =(Car)a
}
不会错。
7.4. 静态变量,方法和类
静态变量
Static int data语句说明data为类变量,为一个类的共享变量,属于整个类。
例:
Class M{
static int data;
}
M m1=new M(); M m2=new M();
m1.data=0;
m1.data++的结果为1,此时m2.data的结果也为1。
Static定义的是一块为整个类共有的一块存储区域。
其变量可以通过类名去访问:类名.变量名。与通过对象引用访问变量是等价的。
2) 静态方法
Public static void printData(){}
表明此类方法为类方法(静态方法)
静态方法不需要有对象,可以使用类名调用。
静态方法中不允许访问类的非静态成员,包括成员的变量和方法,因为此时是通过类调用的,没有对象的概念。方法中this.data和super.data是不可用的。
原因:从根本来说,是静态变量不管类是否实例化都会存在,而实例变量只有类实例化了才存在。直接调用静态方法时并不确定实例变量是否存在。
一般情况下,主方法是静态方法,所以JVM可以直接调用它,主方法为静态方法是因为它是整个软件系统的入口,而进入入口时系统中没有任何对象,只能使用类调用。
猜想:JVM在代码中有这样的语句:
ClassName.main(arg); ClassName 通过命令行的”java 类名”取得,所以类名不用加.class 扩展名
*覆盖不适用于静态方法。
静态方法不可被覆盖。
如果子类中有和父类重名的静态方法,虽然编译通过,但它并不能实现多态,所以不能称作覆盖。
public class Test {
public static void main(String[] arg) {
Super s = new Sub();
s.show();
}
}
class Super
{
static public void show(){System.out.println("in Super");}
}
class Sub extends Super
{
static public void show(){System.out.println("in Sub");}
}
执行结果是: in Super
3) 静态内部类----à只能是成员内部类
class Out{
public static class
}
4) 初始化块
1. 只被执行一次;
2. 初始化块在类被加载后首先被运行,不管类是否实例化
3.一般用来初始化静态变量
Public static void main(String[] args){
System.out.println(Car.name);//这时加载Car Class进入JVM并执行静态代//码块
}
7.5. Singleton模式
Static通常用于Singleton模式开发:
Singleton是一种设计模式,高于语法,可以保证一个类在整个系统中仅有一个对象。
特点:
1.有一个静态属性
2.私有的构造―――singleton不能new
3.公共的静态方法来得到静态属性
实现单例模式的原理:
利用类属性(静态变量)在系统中唯一的特性,建立这样一个唯一的引用并控制这个引用所指的空间是不变的。
public class
private static
private
if(conn==null)
conn = new
}
public
return conn;
}
}
实现2
public class
private static
static{
conn = new
}
public static
return conn;
}
}
7.6. final关键字
7.6.1. final变量不能被改变;
当利用final修饰一个属性(变量)的时候,此时的属性成为常量。
注意JAVA命名规范中常量全部字母大写:
Final int AGE=10;
常量的地址不可改变,但在地址中保存的值(即对象的属性)是可以改变的。
在JAVA中利用public static final的组合方式对常量进行标识(固定格式)。
Final变量是在整个类被创建时候被赋值,之后就不能改变了。
对于final变量,如果在声明的时候和构造的时候均不进行赋值,编译出错。
对于利用构造方法对final变量进行赋值的时候,此时在构造之前系统设置的默认值被覆盖。
常量(这里的常量指的是实例常量:即成员变量)赋值:
①在初始化的时候通过显式声明赋值。Final int x=3;
②在构造的时候赋值。
Class A{
Final int x=3;
Public A(){
x=4;
}
}
7.6.2. final方法不能被改写;
利用final定义方法:这样的方法为一个不可覆盖的方法。
Public final void print(){};
为了保证方法的一致性(即不被改变),可将方法用final定义。
如果在父类中有final定义的方法,那么在子类中继承同一个方法。
如果一个方法前有修饰词private或static,则系统会自动在前面加上final。即private和static方法默认均为final方法。
注:final并不涉及继承,继承取决于类的修饰符是否为private、default、protected还是public。也就是说,是否继承取决于这个类对于子类是否可见。
Final和abstract永远不会同时出现。
7.6.3. final类不能被继承;
final修饰类的时候,此类不可被继承,即final类没有子类。这样可以用final保证用户调用时动作的一致性,可以防止子类覆盖情况的发生。
String 类数据final类 ,目的是提供效率保证安全。
7.6.4. String 类
String 的声明:public final class String 无法继承,强不变模式
字符串池―――――――――――――>池化思想 数据库连接池,EJB池
public class TestString {
public static void main(String[] args){
String s1=new String(“abc”);
String s2=s1;
s1+=”d”;
System.out.println( s1 );
System.out.println( s1==s2 );
}
}
便于实例重用
不要轻易在heap里创建空间
intern() 返回池地址
对于字符串连接
str=”
产生:
123456
123456789
123456789123
产生多余对象
应该使用 StringBuffer(线程安全的) 或者 StringBuilder(线程不安全的)
String str=”hello:nihao:happy”;
StringTokenizer st=new StringTokenizer(s,”:”);
while(st.hasMoreTokens()){
String str=st.nextToken();
System.out.println(str);
}
7.7. 抽象类
1) Abstract(抽象)可以修饰类、方法
如果将一个类声明为abstract,此类不能生成对象,只能被继承使用。
Abstract类的设计是将子类的共性最大限度的抽取出来,以提高程序的统一性。
2) 一个类中包含有抽象方法必须声明为抽象类;
如果一个类中有一个抽象方法,那么这个类一定为一个抽象类。
反之,如果一个类为抽象类,那么其中可能有非抽象的方法。
3) 抽象类不能实例化,但仍可以声明;
Abstract类可以作为编译时类型,但不能作为运行时类型。
4) 子类继承抽象类必须实现其中抽象方法
当abstract用于修饰方法时,此时该方法为抽象方法,此时方法不需要实现,实现留给子类覆盖,子类覆盖该方法之后方法才能够生效。
注意比较:
private void print(){};此语句表示方法的空实现。
Abstract void print(); 此语句表示方法的抽象,无实现
7.8. 接口 (模板方法模式)
1) 接口是抽象类的另外一种形式(没有实例变量的抽象类);
2) 在一个接口中所有方法都是抽象方法;
3) 接口中所有变量都必须被定义为final static;
4) 接口可以继承多个接口。
5) 可插入性的保障――工程模式
6) 是规范的制订者和规范的实现者分开。――JDBC
注:1) 接口中的方法自动被置为public, 因经,在接口中声明方法并不需要提供public关键字。但在实现接口时,必须把方法声明为public。
接口与抽象类的区别:
接口 抽象类
无实现方法 可以有非抽象方法――实现代码
可以把子类的共有代码
提取出来放在抽象类中
在JAVA中的接口分类:
普通 -----有方法和属性
常量 -----存常量
标记 -----没有方法,属性,只为了做编译类型的标记
实例:
简单工厂模式(接口和多态的实际使用)
public interface Car {
public void brake();
public void grade();
public void engineer();
}
public class BMW implements Car{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void brake() {
// TODO Auto-generated method stub
System.out.println("BMWcar's brake()");
}
public void engineer() {
// TODO Auto-generated method stub
System.out.println("BMWcar's engieer()");
}
public void grade() {
// TODO Auto-generated method stub
System.out.println("BMWcar's greade()");
}
/**
* @param args
*/
}
Public class Banz implements Car{
……
}
Public class Factory{
Public static Car factory(String name){
If(name.equals(“BMW”){
Return new BMWCar(); //返回一个引用
}
If(name.equals(“Banz”){
Return new BanzCar();
}
}
}
注意:
1 这个问题不是对象有没有toString方法的问题,而是用接口作为编译时类型能否调用的问题.
2 接口就是接口,尽管本质上是抽象类,但是总归不是类,接口没有父类,Object类绝不是接口的父类,这种理解大错而特错!!!
3.我们来看看虚拟机规范是怎么说的
JVM会解析类型为CONSTANT_InterfaceMethodref_info的常量池的入口,会按照如下步骤执行接口方法解析:
1) 检查接口是否具有用户调用的方法
2) 检查接口的父接口是否具有用户调用的方法
3) 检查java.lang.Object是否具有用户调用的方法
4) 抛出NoSuchMethodException
因此,不是接口类型有toString方法,用反射是看不到这个方法的,而是虚拟机在作方法调用连接时,会自动去找Object类中的方法.虚拟机就是这么工作的.
所以,对以接口作为编译时类型的方法调用,根据以上说的第三点,Object类中的方法都可以得到调用
7.9.
Ob
ject 类
JAVA中有一个特殊的类: Object。它是JAVA体系中所有类的父类(直接父类或者间接父类)。
此类中的方法可以使所的类均继承。
以下介绍的三种属于Object的方法:
(1)finalize方法:当一个对象被垃圾回收的时候调用的方法。
(2)toString():是利用字符串来表示对象。
当我们直接打印定义的对象的时候,隐含的是打印toString()的返回值。
可以通过子类作为一个toString()来覆盖父类的toString()。
以取得我们想得到的表现形式,即当我们想利用一个自定义的方式描述对象的时候,我们应该覆盖toString()。
(3)equal
首先试比较下例:
String A=new String(“hello”);
String B=new String(“hello”);
A==B(此时程序返回为FALSE)
因为此时AB中存的是不同的对象引用。
附加知识:
字符串类为JAVA中的特殊类,String中为final类,一个字符串的值不可重复。因此在JAVA VM(虚拟机)中有一个字符串池,专门用来存储字符串。如果遇到String a=”hello”时(注意没有NEW,不是创建新串),系统在字符串池中寻找是否有”hello”,此时字符串池中没有”hello”,那么系统将此字符串存到字符串池中,然后将”hello”在字符串池中的地址返回a。如果系统再遇到String b=”hello”,此时系统可以在字符串池中找到 “hello”。则会把地址返回b,此时a与b为相同。
String a=”hello”;
System.out.println(a==”hello”);
系统的返回值为true。
故如果要比较两个字符串是否相同(而不是他们的地址是否相同)。可以对a调用equal:
System.out.println(a.equal(b));
equal用来比较两个对象中字符串的顺序。
a.equal(b)是a与b的值的比较。
注意下面程序:
student a=new student(“LUCY”,20);
student b=new student(“LUCY”,20);
System.out.println(a==b);
System.out.println(a.equal(b));
此时返回的结果均为false。
因为Student继承的是Object的equals()方法,此时toString()等于==
为了实现对象的比较需要覆盖equals(加上这个定义,返回ture或false)
以下为实现标准equals的流程:
public boolean equals(Object o){
if (this==o) return trun; //此时两者相同
if (o==null) return false;
if (! o instanceof strudent) return false; //不同类
studeng s=(student)o; //强制转换
if (s.name.equals(this.name)&&s.age==this.age) return true;
else return false;
}
7.10. 封装类
JAVA为每一个简单数据类型提供了一个封装类,使每个简单数据类型可以被Object来装载。
除了int和char,其余类型首字母大写即成封装类。
注:
“==”在任何时候都是比较地址,这种比较永远不会被覆盖。
程序员自己编写的类和JDK类是一种合作关系。(因为多态的存在,可能存在我们调用JDK类的情况,也可能存在JDK自动调用我们的类的情况。)
注意:类型转换中double/interger/string之间的转换最多。
封装类·字符串·基本类型
Interger--------------------(Double(a.toString))------------>Double
String -----------------(Integer.valueOf() )------------------>Integer
Integer-----------------(String.valueOf() )-------------------> String
Int----------------------(100+””)------------------------------->String
String------------------(Integer.parseInt() )----------------->int
Integer-----------------(Integer.intValue() )----------------->int
7.11. 内部类
(注:所有使用内部类的地方都可以不用内部类,但使用内部类可以使程序更加的简洁,便于命名规范和划分层次结构)。
内部类是指在一个外部类的内部再定义一个类。
*内部类可为静态,可用PROTECTED和PRIVATE修饰。(而外部类不可以:*类只能使用PUBLIC和DEFAULT)。
*JAVA文件中没有publie class 可以类名和文件不同名。
7.11.1. 内部类的分类
成员内部类、
局部内部类、
静态内部类、
匿名内部类(图形是要用到,必须掌握)。
7.11.2. 成员内部类
作为外部类的一个成员存在,与外部类的属性、方法并列。
内部类和外部类的实例变量可以共存。
在内部类中访问实例变量:this.属性
在内部类访问外部类的实例变量:外部类名.this.属性。
在外部类的外部访问内部类,使用out.inner.
成员内部类的特点:
1.内部类作为外部类的成员,可以访问外部类的私有成员或属性。(即使将外部类声明为PRIVATE,但是对于处于其内部的内部类还是可见的。)
2.用内部类定义在外部类中不可访问的属性。这样就在外部类中实现了比外部类的private还要小的访问权限。
注意:内部类是一个编译时的概念,一旦编译成功,就会成为完全不同的两类。
对于一个名为outer的外部类和其内部定义的名为inner的内部类。编译完成后出现outer.class和outer$inner.class两类。
3.成员内部类不能有静态属性
建立内部类对象时应注意:
在外部类的内部可以直接使用inner s=new inner();(因为外部类知道inner是哪个类,所以可以生成对象。)
而在外部类的外部,要生成(new)一个内部类对象,需要首先建立一个外部类对象(外部类可用),然后在生成一个内部类对象。
Outer o=new Outer();
Outer.Inner in=o.new.Inner()。
7.11.3. 局部内部类
在方法中定义的内部类称为局部内部类。
与局部变量类似,在局部内部类前不加修饰符public和private,其范围为定义它的代码块。
注意:
局部内部类不仅可以访问外部类实例变量,但可以访问外部类的局部常量
在类外不可直接访问局部内部类(保证局部内部类对外是不可见的)。
在方法中才能调用其局部内部类。
7.11.4. 静态内部类
(注意:前三种内部类与变量类似,所以可以对照参考变量)
静态内部类定义在类中,任何方法外,用static定义。
静态内部类只能访问外部类的静态成员。
生成(new)一个静态内部类不需要外部类成员:这是静态内部类和成员内部类的区别。静态内部类的对象可以直接生成:
Outer.Inner in=new Outer.Inner();
而不需要通过生成外部类对象来生成。这样实际上使静态内部类成为了一个*类。静态内部类不可用private来进行定义。
注意:当类与接口(或者是接口与接口)发生方法命名冲突的时候,此时必须使用内部类来实现。
用接口不能完全地实现多继承,用接口配合内部类才能实现真正的多继承。
例子:
对于两个类,拥有相同的方法:
class People
{
run();
}
interface Machine{
run();
}
此时有一个robot类:
class Robot extends People implement Machine.
此时run()不可直接实现。
interface Machine
{
void run();
}
class Person
{
void run(){System.out.println("run");}
}
class Robot extends Person
{
private class MachineHeart implements Machine
{
public void run(){System.out.println("heart run");}
}
public void run(){System.out.println("Robot run");}
Machine getMachine(){return new MachineHeart();}
}
class Test
{
public static void main(String[] args)
{
Robot robot=new Robot();
Machine m=robot.getMachine();
m.run();
robot.run();
}
}
7.11.5. 匿名内部类
匿名内部类是一种特殊的局部内部类,它是通过匿名类实现接口。
IA被定义为接口。
IA I=new IA(){};
注:一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类,没有类名,根据多态,我们使用其父类名。
因其为局部内部类,那么局部内部类的所有限制都对其生效。
匿名内部类是唯一一种无构造方法类。
匿名内部类在编译的时候由系统自动起名Out$1.class。
如果一个对象编译时的类型是接口,那么其运行的类型为实现这个接口的类。
因匿名内部类无构造方法,所以其使用范围非常的有限。
7.12. 集合
集合对象:用于管理其他若干对象的对象
数组:长度不可变
List: 有顺序的,元素可以重复
遍历:for 迭代
排序:Comparable Comparator Collections.sort()
ArrayList:底层用数组实现的List
特点:查询效率高,增删效率低 轻量级 线程不安全
LinkedList:底层用双向循环链表 实现的List
特点:查询效率低,增删效率高
Vector: 底层用数组实现List接口的另一个类
特点:重量级,占据更多的系统开销 线程安全
Set:无顺序的,元素不可重复(值不相同)
遍历:迭代
排序:SortedSet
HashSet:采用哈希算法来实现Set接口
唯一性保证:重复对象equals方法返回为true
重复对象hashCode方法返回相同的整数
不同对象 哈希码 尽量保证不同(提高效率)
SortedSet:对一个Set排序
TreeSet:在元素添加的同时,进行排序。也要给出排序规则
唯一性保证:根据排序规则,compareTo方法返回为0,就可以认定两个对象中有一个是重复对象。
Map:元素是键值对 key:唯一,不可重复 value:可重复
遍历:先迭代遍历key的集合,再根据key得到value
HashMap:轻量级 线程不安全 允许key或者value是null
Hashtable:重量级 线程安全 不允许key或者value是null
Properties:Hashtable的子类,key和value都是String
SortedMap:元素自动对key排序
TreeMap:
集合是指一个对象可以容纳了多个对象(不是引用),这个集合对象主要用来管理维护一系列相似的对象。
7.12.1. 集合接口类层次
位于package java.util.*;
Collection
↑
|ˉˉˉˉˉˉ|
Set List Map
↑ ↑
| |
SortedSet SortedMap
1) Set: 集合类中不允许有重复对象;
2) SortedSet: 和Set接口同,但元素按升序排列;
3) List: 元素加载和移出时按照顺序,可以保存重复对象。
4) Map: (key-value对)存储了唯一关键字辨识和对应的值。
5) SortedMap: 和Map类同,但对象按他们关键字的升序排列。
7.12.2. 集合类层次
(注:JAVA1.5对JAVA1.4的最大改进就是增加了对范型的支持)
Collection
↑
|ˉˉˉˉˉˉ|
HashSet LinkedList Hashtable
(Set) Vector, ArrayList Hashmap
(List) (Map)
↑ ↑
| |
TreeSet TreeMap
(SortedSet) (SortedMap)
add(Object o)
addAll(
contains(
containsAll(
remove(
removeAll(
clear()
equals(
isEmpty()
iterator()
size()
toArray()
toArray(
7.12.3. 五个最常用的集合类之间的区别和联系
1.ArrayList: 元素单个,效率高,多用于查询
2.Vector: 元素单个,线程安全,多用于查询
3.LinkedList:元素单个,多用于插入和删除
4.HashMap: 元素成对,元素可为空
5.HashTable: 元素成对,线程安全,元素不可为空
ArrayList
底层是Object数组,所以ArrayList具有数组的查询速度快的优点以及增删速度慢的缺点。
而在LinkedList的底层是一种双向循环链表。在此链表上每一个数据节点都由三部分组成:前指针(指向前面的节点的位置),数据,后指针(指向后面的节点的位置)。最后一个节点的后指针指向第一个节点的前指针,形成一个循环。
双向循环链表的查询效率低但是增删效率高。
ArrayList和LinkedList在用法上没有区别,但是在功能上还是有区别的。
LinkedList
经常用在增删操作较多而查询操作很少的情况下:队列和堆栈。
队列:先进先出的数据结构。
栈:后进先出的数据结构。
注意:使用栈的时候一定不能提供方法让不是最后一个元素的元素获得出栈的机会。
Vector
(与ArrayList相似,区别是Vector是重量级的组件,使用使消耗的资源比较多。)
结论:在考虑并发的情况下用Vector(保证线程的安全)。
在不考虑并发的情况下用ArrayList(不能保证线程的安全)。
面试经验(知识点):
java.util.stack(stack即为堆栈)的父类为Vector。可是stack的父类是最不应该为Vector的。因为Vector的底层是数组,且Vector有get方法(意味着它可能访问到并不属于最后一个位置元素的其他元素,很不安全)。
对于堆栈和队列只能用push类和get类。
Stack类以后不要轻易使用。
实现栈一定要用LinkedList。
(在JAVA1.5中,collection有queue来实现队列。)
Set-HashSet实现类:
遍历一个Set的方法只有一个:迭代器(interator)。
HashSet中元素是无序的(这个无序指的是数据的添加顺序和后来的排列顺序不同),而且元素不可重复。
在Object中除了有finalize(),toString(),equals(),还有hashCode()。
HashSet底层用的也是数组。
当向数组中利用add(Object o)添加对象的时候,系统先找对象的hashCode:
int hc=o.hashCode(); 返回的hashCode为整数值。
Int I=hc%n;(n为数组的长度),取得余数后,利用余数向数组中相应的位置添加数据,以n为6为例,如果I=0则放在数组a[0]位置,如果I=1,则放在数组a[1]位置。如果equals()返回的值为true,则说明数据重复。如果equals()返回的值为false,则再找其他的位置进行比较。这样的机制就导致两个相同的对象有可能重复地添加到数组中,因为他们的hashCode不同。
如果我们能够使两个相同的对象具有相同hashcode,才能在equals()返回为真。
在实例中,定义student对象时覆盖它的hashcode。
因为String类是自动覆盖的,所以当比较String类的对象的时候,就不会出现有两个相同的string对象的情况。
现在,在大部分的JDK中,都已经要求覆盖了hashCode。
结论:如将自定义类用hashSet来添加对象,一定要覆盖hashcode()和equals(),覆盖的原则是保证当两个对象hashcode返回相同的整数,而且equals()返回值为True。
如果偷懒,没有设定equals(),就会造成返回hashCode虽然结果相同,但在程序执行的过程中会多次地调用equals(),从而影响程序执行的效率。
我们要保证相同对象的返回的hashCode一定相同,也要保证不相同的对象的hashCode尽可能不同(因为数组的边界性,hashCode还是可能相同的)。
例子:
public int hashCode(){
return name.hashcode()+age;
}
这个例子保证了相同姓名和年龄的记录返回的hashCode是相同的。
使用hashSet的优点:
hashSet的底层是数组,其查询效率非常高。而且在增加和删除的时候由于运用的hashCode的比较开确定添加元素的位置,所以不存在元素的偏移,所以效率也非常高。因为hashSet查询和删除和增加元素的效率都非常高。
但是hashSet增删的高效率是通过花费大量的空间换来的:因为空间越大,取余数相同的情况就越小。HashSet这种算法会建立许多无用的空间。
使用hashSet类时要注意,如果发生冲突,就会出现遍历整个数组的情况,这样就使得效率非常的低。
7.12.4. 比较
Public static int binarySearch(List list,
Public static void Sort(List list,Comparator com)
Public static void sort(List list)
方法一:
Comparator接口
Int compare(Object a,Object b)
Boolean equals(Object o)
例子:
import java.util.*;
public class Test {
public static void main(String[] arg) {
ArrayList al = new ArrayList();
Person p1 = new Person("dudi");
Person p2 = new Person("cony");
Person p3 = new Person("aihao");
al.add(p1);
al.add(p2);
al.add(p3);
Collections.sort(al,p1);
for(Iterator it = al.iterator();it.hasNext();){
Person p = (Person)it.next();
System.out.println(p.name);
}
}
}
class Person implements java.util.Comparator
{
public String name;
public Person(String name){
this.name = name;
}
public int compare(Object a,Object b){
if(a instanceof Person&&b instanceof Person){
Person pa = (Person)a;
Person pb = (Person)b;
return pa.name.compareTo(pb.name);
}
return 0;
}
public boolean equals(Object a){return true;}
}
方法二
Java.lang.Comparable
Public int compareTo(Object o)
Class Person implements java.lang.Comparable{
Public int compareTo(
Comparable c1=(Comparable)this;
Comparable c2=(Comparable)o;
Return c1.name.compareTo(c2.name );
}
}
……………………………….
}
Collections.sort( list );
*注意:程序员和类库之间是平等的关系,而不是上下级关系,以前的类可以是通过接口调用后来的类 。
集合的最大缺点是无法进行类型判定(这个缺点在JAVA1.5中已经解决),这样就可能出现因为类型不同而出现类型错误。
解决的方法是添加类型的判断。
7.13. 反射
用于工具,架构,动态开发等开发工程
三种得到类对象的途径:
Class.forName(“name”) //输入全类名
object.getClass() //得到该对象的类对象
object.class
无参构造一个对象
newInstance()
有参的构造一个对象
Constructor con=c.getConstructor(String.class)
Object o=con.newInstance(“liucy”);
调用对象方法
Class newClass=Student.class; //得到一个类对象
Object o = newClass.newInstance(); //产生对象
Method newMethod = newClass.getDeclaredMethod("study");//得到这个类的方法类
newMethod.invoke(o);//调用方法,需要提供是哪个对象调用和参数
1) 确定一个对象的类;
2) 获得一个类的修改符、变量、方法、构器函数、和父类的相类信息;
3) 找出哪些常量和方法是从一个接口声明的;
4) 创建一个在运行时才知道名称的类;
5) 调用对象的方法;
8. 七·异常
8.1. 异常的基本概念
1) 异常事件改变程序流程;
2) 当一个异常事件发生时,一个异常被抛出;
3) 响应处理异常的代码被称为exception handler;
4) exception handler捕获异常;
5) 异常处理能让你集中精力在一个地方解决问题,然后将处理错误的代码分开来放在另一个地方。
8.2. 捕获异常
1) 设置一个try/catch的代码块;
2) 如果try块内的任何代码抛出了由catch子句指定的异常,则
a. 程序跳过try块中的其他代码;
b. 程序执行catch从句中的处理器代码。
3) 如try块内没有抛出异常,直接跳过catch从句内容。
4) 如try块内抛出的异常没有在catch从句中指定, 则该方法会立即退出。
8.3. 处理异常
1.如何控制try的范围:根据操作的连动性和相关性,如果前面的程序代码块抛出的错误影响了后面程序代码的运行,那么这个我们就说这两个程序代码存在关联,应该放在同一个try中。
对已经查出来的例外,有throw(消极)和try catch(积极)两种处理方法。
对于try catch放在能够很好地处理例外的位置(即放在具备对例外进行处理的能力的位置)。如果没有处理能力就继续上抛。
当我们自己定义一个例外类的时候必须使其继承excepiton或者RuntimeException。
3) 对子类方法抛出的异常不能超出父类方法throws指令的范围。如父类方法不抛出任何异常,在子类方法中必须捕捉每一个“已检查异常”。
8.4. 捕捉多个异常
1) 每个异常类型使用一个catch从句;
2) 如前面catch从句捕获异常,将直接跳过后面catch从句内容;
3) 建议按异常类型的子类->超类的顺序排列catch从句的先后顺序。
8.5. finally 声明
无论是否捕获异常,都会执行finally从句中的代码;
例子:
finally{ con.close();}
8.6. 异常调用栈
1. 哪个方法调用的代码发生异常,返回到调用方法的地方;
2. main方法调用的代码发生异常,返回到虚拟机。
8.7. 异常层次
答:1) 起源于Error的类代表不常用的环境(通常是硬件层面);
2) 应用程序不能够从Error中恢复正常;
3) 所有的Java异常都起源于Exception;
4) RuntimeExcepiton也称为未检查异常;
5) 未检查异常无须捕获;
6) 其它异常,也称为检查异常,必须处理
Object
↑
Throwable
↑
|ˉˉˉˉˉˉˉˉ|
Error Exception
| ↑
| |ˉˉˉˉˉˉˉ|
RuntimeException
8.8. 一些未检查的异常
答:1) java.lang.ArithmeticException 如:除0;
2) java.lang.NullPointerException 如:没初始化一个References便使用;
3) java.lang.ArrayIndexoutofBoundsException 如:调用一个有十个元素的Array的第十一个元素的内容;
4) java.lang.NumberFORMatException 如:Integer.parseInt("a");
8.9. 写你自己的异常
答:1) 要做的仅仅是从Exception继承或者是从Exception的一个子类衍生出自己需要的类就可;
2) 习惯为每一个异常类提供一个默认的构造器以及一个包含详细信息的构造器。
8.10. 抛出你自己的异常
答:1) 在方法的定义中增加一个throws修饰符,以通知调用者可能会抛出一个异常;
2) 在方法中构造一个该类的实例,然后抛出该实例。
9. 八·图形用户接口
构造GUI的步骤
1.选择容器
2.设置布局管理器
3.添加组件
4.设置事件监听
布局管理器
1. FlowLayout 流式布局 Panel的默认
2. BorderLayout 东西南北中 Frame的默认
3. GridLayout 网格布局
4. CardLayout 卡片布局
5. GridBagLayout 复杂的网格布局
JButton :按钮
JTextField:单行文本域
JTextArea:多行文本区
JScrollPane:滚动窗体
JComboBox:下拉选择框
JRadioButton:单选按钮
JCheckBox:多选按钮
JList:多行列表
JLabel:标签
JPasswordField:密码输入框
JEditorPane:显示结构化文档
Border:边框
JMenuBar:菜单条
JMenu:菜单
JMenuItem:菜单项
JPopupMenu:弹出式菜单
JSlider:滑动条
JProgressBar:进度条
JTabbedPane:分层面板
JSplitPane:分隔面板
JToolBar:工具条
JFileChooser:文件选择器
JColorChooser:颜色选择器
显示对话框
JoptionPane 里面有很多静态方法可以弹出对话框
10. 九·AWT(Abstract Window Toolkit) 事件模型
Jbutton ---->
ActionListener -----------> getAcitonCommand()
Window -------->
11. 十·The AWT Component Library
12. 十一·JFC(Java Foundation Classes)
13. 十二·Applets
14. 十三·线程Thread
14.1. 线程原理
进程是数据独占的
线程是数据共享的(所以需要处理数据并发)
并发原理:宏观并行,微观串行
OS将一段时间分为多个时间片,每个时间片CPU只能运行一个任务。
JVM Thread对象 类似于代理模式,通过操作Thread间接访问线程。 |
OS 线程 |
注意: 凡是访问JVM外部的资源时,都不能直接访问的,都需要一个代理的对象。
14.2. 线程实现的两种形式
继承java.lang.Thread:
class MyThread extends Thread{
public void run(){
需要进行执行的代码,如循环。
}
}
启动线程
public class TestThread{
public static void main(){
Thread t1=new Mythread();
T1.start();
}
}
实现java.lang.Runnable接口:
Class MyThread implements Runnable{
Public void run(){
}
}
这种实现可以再继承其他类。
启动线程时不同前者
public static void main(){
Runnable myThread = new MyThread();
Thread t = new Thread(myThread);
t.start();
}
当调用start方法时,JVM会到OS中产生一个线程。
14.3. 线程的生命周期
下面为线程中的7中非常重要的状态:(有的书上也只有认为前五种状态:而将“锁池”和“等待池”都看成是“阻塞”状态的特殊情况:这种认识也是正确的,但是将“锁池”和“等待池”单独分离出来有利于对程序的理解)
Sleep超时 子线程结束 |
Sleep或join |
synchronized |
yield |
New Thread |
Runnable |
start |
Running |
Blocked in object’s wait pool |
Blocked in object’s lock pool |
Otherwise Blocked |
Dead
|
被调度程序选中 |
Run 方法结束 |
wait |
notify或notifyAll |
锁被释放 |
一条真理: 只有runable 状态的线程才有机会执行代码。
14.4. Thread的方法
public static void sleep(long millis)
throws InterruptedException
括号中以毫秒为单位, 使线程停止一段时间,间隔期满后,线程不一定立即恢复执行。
当main()运行完毕,即使在结束时时间片还没有用完,CPU也放弃此时间片,继续运行其他程序。
Try{Thread.sleep(1000);}
Catch(InterruptedException e){e.printStackTrace(e);}
Public final void join() throws InterruptedException
表示其他运行线程放弃执行权,进入阻塞状态,直到调用线程结束。
实际上是把并发的线程变为串行运行。
线程的优先级:1-10,越大优先级越高,优先级越高被OS选中的可能性就越大。(不建议使用,因为不同操作系统的优先级并不相同,使得程序不具备跨平台性,这种优先级只是粗略地划分)。
注:程序的跨平台性:除了能够运行,还必须保证运行的结果。
Public static void field()
使当前线程马上交出执行权,回到可运行状态,等待OS的再次调用。
Public final Boolean isActive()
验证当前线程是否是活动的,不管它是否正在运行。
14.5. 共享数据的并发处理
两个线程修改共享资源时会出现数据的不一致,为避免这种现象采用对访问的线程做限制的方法。利用每个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。
1.Synchronized修饰代码块
public void push(char c){
synchronized(this){
...
}
}
对括号内的对象加锁,只有拿到锁标记的对象才能执行该代码块
2.Synchronized修饰方法
public synchronized void push(char c) {
...
}
对当前对象的加锁,只有拿到锁标记的对象才能执行该方法
注:方法的Synchronized特性本身不会被继承,只能覆盖。
线程因为未拿到锁标记而发生阻塞进入锁池(lock pool)。每个对象都有自己的一个锁池的空间,用于放置等待运行的线程。由系统决定哪个线程拿到锁标记并运行。
14.6. 使用互斥锁的注意事项
举例:男孩和女孩例子,每个女孩是一个对象,每个男孩是个线程。每个女孩都有自己的锁池。每个男孩可能在锁池里等待。
Class Girl{
Public void hand(){
}
Public syncronized void kiss(){
}
}
Class Boy extends Thread{
Public void run(){
}
}
锁标记如果过多,就会出现线程等待其他线程释放锁标记,而又都不释放自己的锁标记供其他线程运行的状况。就是死锁。
死锁的两种处理方法
统一排列锁顺序(解决不同方法中对多个共享资源的访问)
对象1的方法
synchronized(a)
synchronized(b)
对象2的方法
synchronized(a)
synchronized(b)
2.线程间通信(也就是线程间的相互协调)
线程间通信使用的空间称之为对象的等待池(wait pool),该队列也是属于对象的空间的。
进入等待池
使用Object类中wait()的方法,在运行状态中,线程调用wait(),此时表示线程将释放自己所有的锁标记和CPU的占用,同时进入这个对象的等待池。等待池的状态也是阻塞状态,只不过线程释放自己的锁标记。
退出等待池进入锁池
notify():将从对象的等待池中移走一个任意的线程,并放到锁池中,那里的对象一直在等待,直到可以获得对象的锁标记。
notifyAll(): 将从等待池中移走所有等待那个对象的线程并放到锁池中,只有锁池中的线程能获取对象的锁标记,锁标记允许线程从上次因调用wait()而中断的地方开始继续运行
注意:只能对加锁的资源进行wait()和notify()。
1) wait():交出锁和CPU的占用;
2) notify():将从对象的等待池中移走一个任意的线程,并放到锁池中,那里的对象一直在等待,直到可以获得对象的锁标记。
3) notifyAll(): 将从等待池中移走所有等待那个对象的线程并放到锁池中,只有锁池中的线程能获取对象的锁标记,锁标记允许线程从上次因调用wait()而中断的地方开始继续运行
注:在java.io包中Vector 和 HashTable 之所以是线程安全的,是因为每个方法都有synchronized修饰。Static 方法可以加 synchronized , 锁的是类对象。
但是Vector 是 jdk 1.0 的 ArrayList 是 jdk1.2 所以实际应用还是使用ArrayList
例子:
生产者和消费者
一个柜台一定数量的产品,柜台满时不能生产,空时不能够买。
15. 十四·标准I/O流与文件
15.1. 对文件的操作
1.File类(java.io.File)可表示文件或者目录(在JAVA中文件和目录都属于这个类中,而且区分不是非常的明显)。
File下的方法是对磁盘上的文件进行磁盘操作,但是无法读取文件的内容。
注意:创建一个文件对象和创建一个文件在JAVA中是两个不同的概念。前者是在虚拟机中创建了一个文件,但却并没有将它真正地创建到OS的文件系统中,随着虚拟机的关闭,这个创建的对象也就消失了。而创建一个文件才是在系统中真正地建立一个文件。
例如:File f=new File(“11.txt”);//创建一个名为11.txt的文件对象
f.CreateNewFile(); //真正地创建文件
2.File的方法
Boolean createNewFile() //创建文件
Boolean mkdir() //创建目录
Boolean mkdirs() //创建多个目录
Boolean delete() //删除文件
Boolean deleteOnExit(); //在进程退出的时候删除文件,这样的操作通常用在临时文件的删除。
String[] List():返回当前File对象下所以显文件和目录名(相对路径)
File[] ListFiles():返回当前File对象所有Files对象,可以用getName()来访问到文件名。
isDirectory()和isFile()来判断究竟是目录还是文件。
String getParent() 得到父类文件名
File getParentFile() 。。。
String getPath() 。。。路径
exists() 判断文件是否存在
15.2. 处理跨平台性
对于命令:File f2=new file(“d://abc//789//1.txt”)
这个命令不具备跨平台性,因为不同的OS的文件系统的分隔符是不相同。
使用file类的separtor属性,返回当前平台文件分隔符。
File newD = new File("aa"+File.separator+"bb"+File.separator+"cc");
File newF = new File(newD,"mudi.txt");
try{
newD.mkdirs();
newF.createNewFile();
}catch(Exception e){}
15.3. 对象的序列化接口
Serializable接口没有方法,是标识接口。
序列化的步骤:
1)实现Serializable 接口
2)实例化对象文件输出对象
3)将对象输出到文件里
4)有些临时变量生命周期不需要超过虚拟机的生命周期,需要加上:transient 关键字,这个属性不被序列化。
一个序列化对象内部属性的对象也需要序列化接口。
15.4. I/O流基础
Input/Output:指跨越出了JVM的边界,与外界进行数据交换。
数据源 |
JVM |
输入
注意:输入/输出是针对JVM而言。
15.5. 流的分类
1) 从数据类型分:字节流和字符流
字节流类:
抽象父类: InputStream,OutputStream
实现类:
BufferedInputStream 缓冲流-过虑流
BufferedOutputStream
ByteArrayInputStream 字节数组流-节点流
ByteArrayOutputStream
DataInputStream 处理JAVA标准数据流-过虑流
DataOutputStream
FileInputStream 处理文件IO流-节点流
FileOutputStream
FilterInputStream 实现过虑流-字节过虑流父类
FilterOutputStream
PipedInputStream 管道流
PipedOutputStream
PrintStream 包含print() 和 println()
抽象父类:Reader, Writer
实现类:
BufferedReader
BufferedWriter
PrintWriter
CharArrayReader
CharArrayWriter
FileReader
FileWriter
FilterReader
FilterWriter
InputStreamReader
OutputStreamWriter
PipedReader
PipedWriter
StringReader
StringWriter
2) 从数据方向分:输入流和输出流
InputXXXXX , OutputXXXXX
3) 从流的功能分:节点流和过滤流(使用到油漆工模式)
节点流用来传输数据。
过滤流用来封装节点流或者其他过滤流,从而给节点流或其他的过滤流增加一个功能。
15.6. I/O输入输出
流的标准写法:
OutputStream os=null;
OutputStream os=null;
try{
String a="hello";
byte[] b=a.getBytes();
os=new FileOutputStream("D://aa.txt");
os.write(b);
}catch(IOException ioe){
ioe.printStackTrace();
}finally{
if(os!=null){
try {
os.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
1. InputStream类
所有字节输入流的父类,如:FileInputStream,
1) 三个基本的read()方法
a. int read(): 从流里读出的一个字节或者-1; (实际读了多长)
b. int read(byte[]):将数据读入到字节数组中,并返回所读的字节数; (期望读了多长)
c. int read(byte[], int , int):两个int参数指定了所要填入的数组的子范围。
2) 其它方法
a. void close(): 关闭流,如使用过滤器流,关闭栈顶部的流,会关闭其余的流。
b. int available(): 返回可从流中读取的字节数。
c. skip(long): 丢弃了流中指定数目的字符。
d. boolean markSupported()
e. void mark(int)
f. void rese()
2. OutputStream方法
答:1) 三个基本的read()方法
a. void write():
b. void write(byte[]):
c. void write(byte[], int , int):
写输出流。
2) 其它方法
a. void close(): 关闭流,如使用过滤器流,关闭栈顶部的流,会关闭其余的流。
b. void flush(): 允许你强制执行写操作。
注:在流中close()方法由程序员控制。因为输入输出流已经超越了JVM的边界,所以有时可能无法回收资源。
原则:凡是跨出虚拟机边界的资源都要求程序员自己关闭,不要指望垃圾回收。
3. FileInputStream和FileOutputStream
答:1) 节点流,使用磁盘文件。
2) 要构造一个FileInputStream, 所关联的文件必须存在而且是可读的。
3) 要构造一个FileOutputStream而输出文件已经存在,则它将被覆盖。 FileInputStream infile = new FileInputStream("myfile.dat");
FIleOutputStream outfile = new FileOutputStream("results.dat");
FileOutputStream outfile = new FileOutputStream(“results.dat”,true);
参数为true时输出为添加,为false时为覆盖。
FileOutputStream类代码:(为什么能建文件)
Public FileOutputStream(String name){
This(name!=null?new File(String):null,false);
}
键盘流
PrintWriter : System.in
4.DataInputStream和DataOutputStream
为过滤流。通过流来读写Java基本类,注意DataInputStream和DataOutputStream的方法 是成对的。
过滤流。输出输入各种数据类型。
writeBoolean(boolean b) ------以1bit数据传送
writeByte(int) ------以1 byte数据传送
writeBytes(String s) --------以byte序列数据传送
writeChar(int v) ――――――以 2 byte
writeChars(String s)-------------以 2 byte序列
writeDouble(double d) -------以 8 byte
writeInt(int v)
writeLong(long l)
writeShort(short s)
writeUTF(String)-----------能输出中文!
6.ObjectInputStream和ObjectOutputStream
过滤流。处理对象的持久化
Object o = new Object();
FileOutputStream fos=new FileOutputStream("Object.txt");
ObjectOutputStream oos=new ObjectOutputStream(fos);
oos.writeObject(o);
oos.close();
FileInputStream fis =new FileInputStream(“Object.txt”);
ObjectInputStream ois =new
Object o = (Object)Ois.readObject();
ois.close();
7. BufferInputStream和BufferOutputStream
过滤流,可以提高I/O操作的效率
用于给节点流增加一个缓冲的功能。
在VM的内部建立一个缓冲区,数据先写入缓冲区,等到缓冲区的数据满了之后再一次性写出,效率很高。
使用带缓冲区的输入输出流的速度会大幅提高,缓冲区越大,效率越高。(这是典型的牺牲空间换时间)
切记:使用带缓冲区的流,如果数据数据输入完毕,使用flush方法将缓冲区中的内容一次性写入到外部数据源。用close()也可以达到相同的效果,因为每次close都会使用flush。一定要注意关闭外部的过滤流。
8. PipedInputStream和PipedOutputStream
用来在线程间通信.
PipedOutputStream pos=new PipedOutputStream();
PipedInputStream pis=new PipedInputStream();
try
{
pos.connect(pis);
new Producer(pos).start();
new Consumer(pis).start();
}
catch(Exception e)
{
e.printStackTrace();
}
9.RandomAccessFile 随机访问
可以得到文件指针。
long getFilePointer() 得到从文件开始处到文件指针的位置。
seek(long point) 将文件指针移动到此处。
10 Reader和Writer
1) Java技术使用Unicode来表示字符串和字符,而且提供16位版本的流,以便用类似的方法处理字符。
2) InputStreamReader和OutputStreamWriter作为字节流与字符流中的接口。
3) 如果构造了一个连接到流的Reader和Writer,转换规则会在使用缺省平台所定义的字节编码和Unicode之间切换。
4) 字节流与字符流的区别:
编码是把字符转换成数字存储到计算机中。把数字转换成相应的字符的过程称为解码。
编码方式的分类:
ASCII(数字、英文):1个字符占一个字节(所有的编码集都兼容ASCII)
ISO8859-1(欧洲):1个字符占一个字节
GB-2312/GBK:1个字符占两个字节
Unicode: 1个字符占两个字节(网络传输速度慢)
UTF-8:变长字节,对于英文一个字节,对于汉字两个或三个字节。
10
都是过滤流。
BufferedReader的方法:readLine():String
PrintWriter的方法:println(….String,Object等等)和write()和.intln()erderd,按
11. 随机存取文件
1) 实现了二个接口:DataInput和DataOutput;
2) 只要文件能打开就能读写;
3) 通过文件指针能读写文件指定位置;
4) 可以访问在DataInputStream和DataOutputStream中所有的read()和write()操作;
5) 在文件中移动方法:
a. long getFilePointer(): 返回文件指针的当前位置。
b. void seek(long pos): 设置文件指针到给定的绝对位置。
c. long length(): 返回文件的长度。
12.编码问题:
编码的方式:
每个字符对应一个整数。
不同的国家有不同的编码,当编码方式和解码方式不统一时,产生乱码。
因为美国最早发展软件,所以每种的编码都向上兼容ASCII 所以英文没有乱码。
ISO-8859-1 西方字符
GB2312 。。。。。。镕
GBK
Big5
Unicode
UTF-8
补充:
字节流结束返回 -1
字符流结束返回null
对象流结束返回 EOFException
引申---------〉异常经常被用在流程控制, 异常也是方法的一种返回形式。
16. 十五·网络编程
16.1. 网络基础知识
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。
计算机网络形式多样,内容繁杂。网络上的计算机要互相通信,必须遵循一定的协议。目前使用最广泛的网络协议是Internet上所使用的TCP/IP协议。
IP地址:具有全球唯一性,相对于internet,IP为逻辑地址。
IP地址分类:
1. A类地址
A类地址第1字节为网络地址,其它3个字节为主机地址。另外第1个字节的最高位固定为0。
A类地址范围:
A类地址中的私有地址和保留地址:
10.0.0.0到10.255.255.255是私有地址(所谓的私有地址就是在互联网上不使用,而被用在局域网络中的地址)。
127.0.0.0到127.255.255.255是保留地址,用做循环测试用的。
2. B类地址
B类地址第1字节和第2字节为网络地址,其它2个字节为主机地址。另外第1个字节的前两位固定为10。
B类地址范围:128.0.0.1到191.255.255.254。
B类地址的私有地址和保留地址
172.16.0.0到172.31.255.255是私有地址
169.254.0.0到169.254.255.255是保留地址。如果你的IP地址是自动获取IP地址,而你在网络上又没有找到可用的DHCP服务器,这时你将会从169.254.0.0到169.254.255.255中临得获得一个IP地址。
3. C类地址
C类地址第1字节、第2字节和第3个字节为网络地址,第4个个字节为主机地址。另外第1个字节的前三位固定为110。
C类地址范围:192.0.0.1到223.255.255.254。
C类地址中的私有地址:
192.168.0.0到192.168.255.255是私有地址。
4. D类地址
D类地址不分网络地址和主机地址,它的第1个字节的前四位固定为1110。
D类地址范围:224.0.0.1到239.255.255.254
Mac地址:每个网卡专用地址,也是唯一的。
端口(port):OS中可以有65536(2^16)个端口,进程通过端口交换数据。连线的时候需要输入IP也需要输入端口信息。
计算机通信实际上的主机之间的进程通信,进程的通信就需要在端口进行联系。
192.168.0.23:21
协议:为了进行网络中的数据交换(通信)而建立的规则、标准或约定。
不同层的协议是完全不同的。
网络层:寻址、路由(指如何到达地址的过程)
传输层:端口连接
TCP模型:应用层/传输层/网络层/网络接口
端口是一种抽象的软件结构,与协议相关:TCP23端口和UDT23端口为两个不同的概念。
端口应该用1024以上的端口,以下的端口都已经设定功能。
TCP/IP模型
Application (FTP,HTTP,TELNET,POP3,SMPT) |
Transport (TCP,UDP) |
Network (IP,ICMP,ARP,RARP) |
Link (Device driver,….) |
注:
IP:寻址和路由
ARP(Address Resolution Protocol)地址解析协议:将IP地址转换成Mac地址
RARP(Reflect Address Resolution Protocol)反相地址解析协议:与上相反
ICMP(Internet Control Message Protocol)检测链路连接状况。利用此协议的工具:ping , traceroute
16.2. TCP Socket
TCP是Tranfer Control Protocol的简称,是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输,双方都可以进行发送或接收操作。
1) 服务器分配一个端口号,服务器使用accept()方法等待客户端的信号,信号一到打开socket连接,从socket中取得OutputStream和InputStream。
2) 客户端提供主机地址和端口号使用socket端口建立连接,得到OutputStream和InputStream。
TCP/IP的传输层协议
16.2.1. 建立TCP服务器端
创建一个TCP服务器端程序的步骤:
1). 创建一个ServerSocket
2). 从ServerSocket接受客户连接请求
3). 创建一个服务线程处理新的连接
4). 在服务线程中,从socket中获得I/O流
5). 对I/O流进行读写操作,完成与客户的交互
6). 关闭I/O流
7). 关闭Socket
ServerSocket server = new ServerSocket(post)
Socket connection = server.accept();
ObjectInputStream put=new ObjectInputStream(connection.getInputStream());
ObjectOutputStreamo put=newObjectOutputStream(connection.getOutputStream());
处理输入和输出流;
关闭流和socket。
16.2.2. 建立TCP客户端
创建一个TCP客户端程序的步骤:
1).创建Socket
2). 获得I/O流
3). 对I/O流进行读写操作
4). 关闭I/O流
5). 关闭Socket
Socket connection = new Socket(127.0.0.1, 7777);
ObjectInputStream input=new ObjectInputStream(connection.getInputStream());
ObjectOutputStream utput=new ObjectOutputStream(connection.getOutputStream());
处理输入和输出流;
关闭流和socket。
16.3. 建立UDP连接
UDP是User Datagram Protocol的简称,是一种无连接的协议,每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。
比较:TCP在网络通信上有极强的生命力,例如远程连接(Telnet)和文件传输(FTP)都需要不定长度的数据被可靠地传输;既然有了保证可靠传输的TCP协议,为什么还要非可靠传输的UDP协议呢?主要的原因有两个。一是可靠的传输是要付出代价的,对数据内容正确性的检验必然占用计算机的处理时间和网络的带宽,因此TCP传输的效率不如UDP高。二是在许多应用中并不需要保证严格的传输可靠性,比如视频会议系统,并不要求音频视频数据绝对的正确,只要保证连贯性就可以了,这种情况下显然使用UDP会更合理一些。
如:
http://www.tarena.com.cn:80/teacher/zhuzh.html
协议名://机器名+端口号+文件名
2 . URL类的常见方法
一个URL对象生成后,其属性是不能被改变的,但是我们可以通过类URL所提供的方法来获取这些属性:
public String getProtocol() 获取该URL的协议名。
public String getHost() 获取该URL的主机名。
public int getPort() 获取该URL的端口号,如果没有设置端口,返回-1。
public String getFile() 获取该URL的文件名。
public String getRef() 获取该URL在文件中的相对位置。
public String getQuery() 获取该URL的查询信息。
public String getPath() 获取该URL的路径
public String getAuthority() 获取该URL的权限信息
public String getUserInfo() 获得使用者的信息
public String getRef() 获得该URL的锚
3. 例子,将tarena网站首页拷贝到本机上。
import java.net.*;
import java.io.*;
import java.util.*;
public class TestURL{
public static void main(String[] arg){
System.out.println("http://www.tarena.com.cn:80/index.htm===>");
//System.out.println(getWebContent());
writeWebFile(getWebContent());
}
public static String getWebContent(){
URL url = null;
HttpURLConnection uc = null;
BufferedReader br = null;
final int buffLen = 2048;
byte[] buff = new byte[buffLen];
String message = "";
String tmp = "";
int len = -1;
String urlStr = "http://www.tarena.com.cn:80/index.htm";
try{
url = new URL(urlStr);
//连接到web资源
System.out.println("before openConnection ====>"+new Date());
uc = (HttpURLConnection)url.openConnection();
System.out.println("end openConnection ====>"+new Date());
br = new BufferedReader( new InputStreamReader(uc.getInputStream()));
System.out.println("end getINputStream() ====>"+new Date());
while( ( tmp = br.readLine())!=null){
message += tmp;
}
System.out.println("end set message ====>"+new Date());
}catch(Exception e){e.printStackTrace();System.exit(1);}
finally{
if(br!=null){
try{
br.close();
}catch(Exception ioe){ioe.printStackTrace();}
}
}
return message;
}
public static void writeWebFile(String content){
FileWriter fw = null;
try{
fw = new FileWriter("index.htm");
fw.write(content,0,content.length());
}catch(Exception e){
e.printStackTrace();
}finally{
if(fw!=null){
try{
fw.close();
}catch(Exception e){}
}
}
}
}
16.4. UDP socket
这种信息传输方式相当于传真,信息打包,在接受端准备纸。
特点:
1) 基于UDP无连接协议
2) 不保证消息的可靠传输
3) 它们由Java技术中的DatagramSocket和DatagramPacket类支持
DatagramSocket(邮递员):对应数据报的Socket概念,不需要创建两个socket,不可使用输入输出流。
DatagramPacket(信件):数据包,是UDP下进行传输数据的单位,数据存放在字节数组中,其中包括了目标地址和端口以及传送的信息(所以不用建立点对点的连接)。
DatagramPacket的分类:
用于接收:DatagramPacket(byte[] buf,int length)
DatagramPacket(byte[] buf,int offset,int length)
用于发送:DatagramPacket(byte[] buf,int length, InetAddress address,int port )
DatagramPacket(byte[] buf,int offset,int length,InetAddress address,int port)
注:InetAddress类网址用于封装IP地址
没有构造方法,通过
InetAddress.getByAddress(byte[] addr):InetAddress
InetAddress.getByName(String host):InetAddress
等。
16.4.1. 建立UDP 发送端
创建一个UDP的发送方的程序的步骤:
1). 创建一个DatagramPacket,其中包含发送的数据和接收方的IP地址和端口
号。
2). 创建一个DatagramSocket,其中包含了发送方的IP地址和端口号。
3). 发送数据
4). 关闭DatagramSocket
byte[] buf = new byte[1024];
DatagramSocket datagramSocket = new DatagramSocket(13);// set port
DatagramPacket intputPacket = new DatagramPacket (buf,buf.length);
datagramSocket.receive(inputPacket);
DatagramPacket outputPacket = new DatagramPacket (buf,buf.length,
inetAddress,port);
datagramSocket.send(outputPacket);
没建立流所以不用断开。
16.4.2. 建立UDP 接受端
创建一个UDP的接收方的程序的步骤:
1). 创建一个DatagramPacket,用于存储发送方发送的数据及发送方的IP地址和端口号。
2). 创建一个DatagramSocket,其中指定了接收方的IP地址和端口号。
3). 接收数据
4). 关闭DatagramSocket
byte[] buf = new byte[1024];
DatagramSocket datagramSocket = new DatagramSocket();//不用设端口,因为发送的包中端口
DatagramPacket outputPacket=new DatagramPacket(
Buf, buf.length, serverAddress, serverPort);
DatagramPacket inputPacket=new DatagramPacket(buf, buf.length);
datagramSocket.receive(inputPacket);
URL类:可直接送出或读入网络上的数据。
Socket类:可以想象为两个不同主机的程序通过网络的通信信道。
Datagram类:它把数据的目的记录放在数据包中,然后直接放到网络上。
InetAddress类:用来存储internet地址的类(ip地址,域名)。
1、建立JDBC—ODBC桥接器
try
{ Class.forName(“sun.jdbc.odbc.JdbcOdbcDriver”); } //建立JDBC—ODBC桥接器
catch(ClassNotFoundException e) {}
2、连接到数据库
Connection con = DriverManager.getConnection(“jdbc:odbc:数据源名字”,”数据源的login name”,”数据源的password”);
建立连接时应捕获SQLException异常:……
3、向数据库发送SQL语句
Statement sql = con.createStatement();
创建SQL语句对象,应捕获SQLException异常。
4、
4.1、查询:ResultSet rs = sql.executeQuery(“SQL查询语句”);
4.2、更新(update)、添加(insert)、删除(delete)
调用Statement对象的method:
public int executeUpdate(String sqlStatement);
17. java5.0的新特性
JAVA的又一次革命。
它的特点:
JVM不变
JAVA和C++不断的融合, 5.0里有许多和C++ 类似。留下的只是思想。
程序员的开发工作越来越简单。
17.1. 泛型
17.1.1. 说明
增强了java的类型安全,可以在编译期间对容器内的对象进行类型检查,在运行期不必进行类型的转换。而在j2se5之前必须在运行期动态进行容器内对象的检查及转换,泛型是编译时概念,运行时没有泛型
减少含糊的容器,可以定义什么类型的数据放入容器
ArrayList<Integer> aList = new ArrayList<Integer>();
aList.add(new Integer(1));
// ...
Integer myInteger = aList.get(0);
我们可以看到,在这个简单的例子中,我们在定义aList的时候指明了它是一个直接受Integer类型的ArrayList,当我们调用aList.get(0)时,我们已经不再需要先显式的将结果转换成Integer,然后再赋值给myInteger了。而这一步在早先的Java版本中是必须的。也许你在想,在使用Collection时节约一些类型转换就是Java泛型的全部吗?远不止。单就这个例子而言,泛型至少还有一个更大的好处,那就是使用了泛型的容器类变得更加健壮:早先,Collection接口的get()和Iterator接口的next()方法都只能返回Object类型的结果,我们可以把这个结果强制转换成任何Object的子类,而不会有任何编译期的错误,但这显然很可能带来严重的运行期错误,因为在代码中确定从某个Collection中取出的是什么类型的对象完全是调用者自己说了算,而调用者也许并不清楚放进Collection的对象具体是什么类的;就算知道放进去的对象“应该”是什么类,也不能保证放到Collection的对象就一定是那个类的实例。现在有了泛型,只要我们定义的时候指明该Collection接受哪种类型的对象,编译器可以帮我们避免类似的问题溜到产品中。我们在实际工作中其实已经看到了太多的ClassCastException,不是吗?
17.1.2. 用法
声明及实例化泛型类:
HashMap<String,Float> hm = new HashMap<String,Float>();
编译类型的泛型和运行时类型的泛型一定要一致。没有多态。
不能使用原始类型
GenList<int> nList = new GenList<int>(); //编译错误
J2SE 5.0目前不支持原始类型作为类型参数(type parameter)
定义泛型接口:
public interface GenInterface<T> {
void func(T t);
}
定义泛型类:
public class ArrayList<ItemType> { ... }
public class GenMap<T, V> { ... }
例1:
public class MyList<Element> extends LinkedList<Element>
{
public void swap(int i, int j)
{
Element temp = this.get(i);
this.set(i, this.get(j));
this.set(j, temp);
}
public static void main(String[] args)
{
MyList<String> list = new MyList<String>();
list.add("hi");
list.add("andy");
System.out.println(list.get(0) + " " + list.get(1));
list.swap(0,1);
System.out.println(list.get(0) + " " + list.get(1));
}
}
17.1.3. 泛型的通配符"?"
package day16;
import java.util.*;
import static java.lang.System.*;
public class TestTemplate {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
List<String> l1=new ArrayList<String>();
l1.add("abc");
l1.add("def");
List<Number> l2=new ArrayList<Number>();
l2.add(1.3);
l2.add(11);
List<Integer> l3=new ArrayList<Integer>();
l3.add(123);
l3.add(456);
// print(l1);
print(l2);
print(l3);
}
static void print(List<? extends Number> l){ //所有Number的子类
for(Object o:l){
out.println(o);
}
}
static void print(List<? super Number> l){ //所有Number的父类
for(Object o:l){
out.println(o);
}
}
}
"?"可以用来代替任何类型, 例如使用通配符来实现print方法。
public static void print(GenList<?> list) {})
17.1.4. 泛型方法的定义
<E> void copyArrayToList(E[] os,List<E> lst){
for(E o:os){
lst.add(o);
}
}
static <E extends Number> void copyArrayToList(E[] os,List<E> lst){
for(E o:os){
lst.add(o);
}
}
static<E extends Number & Comparable> void copyArrayToList(E[] os,List<E> lst){
for(E o:os){
lst.add(o);
}
}
受限泛型是指类型参数的取值范围是受到限制的. extends关键字不仅仅可以用来声明类的继承关系, 也可以用来声明类型参数(type parameter)的受限关系.例如, 我们只需要一个存放数字的列表, 包括整数(Long, Integer, Short), 实数(Double, Float), 不能用来存放其他类型, 例如字符串(String), 也就是说, 要把类型参数T的取值泛型限制在Number极其子类中.在这种情况下, 我们就可以使用extends关键字把类型参数(type parameter)限制为数字
只能使用extends 不能使用 super ,只能向下,不能向上。
调用时用 <? > 定义时用 <E>
17.1.5. 泛型类的定义
类的静态方法不能使用泛型,因为泛型类是在创建对象的时候产生的。
class MyClass<E>{
public void show(E a){
System.out.println(a);
}
public E get(){
return null;
}
}
受限泛型
class MyClass <E extends Number>{
public void show(E a){
}
}
17.1.6. 泛型与异常
类型参数在catch块中不允许出现,但是能用在方法的throws之后。例:
import java.io.*;
interface Executor<E extends Exception> {
void execute() throws E;
}
public class GenericExceptionTest {
public static void main(String args[]) {
try {
Executor<IOException> e = new Executor<IOException>() {
public void execute() throws IOException{
// code here that may throw an
// IOException or a subtype of
// IOException
}
};
e.execute();
} catch(IOException ioe) {
System.out.println("IOException: " + ioe);
ioe.printStackTrace();
}
}
}
17.1.7. 泛型的一些局限型
不能实例化泛型
T t = new T(); //error
不能实例化泛型类型的数组
T[] ts= new T[10]; //编译错误
不能实例化泛型参数数
Pair<String>[] table = new Pair<String>(10); // ERROR
类的静态变量不能声明为类型参数类型
public class GenClass<T> {
private static T t; //编译错误
}
泛型类不能继承自Throwable以及其子类
public GenExpection<T> extends Exception{} //编译错误
不能用于基础类型int等
Pair<double> //error
Pair<Double> //right
17.2. 增强的for循环
for in loop
解决遍历数组和遍历集合的不统一。
package com.kuaff.jdk5;
import java.util.*;
import java.util.Collection;
public class Foreach
{
private Collection<String> c = null;
private String[] belle = new String[4];
public Foreach()
{
belle[0] = "西施";
belle[1] = "王昭君";
belle[2] = "貂禅";
belle[3] = "杨贵妃";
c = Arrays.asList(belle);
}
public void testCollection()
{
for (String b : c)
{
System.out.println("曾经的风化绝代:" + b);
}
}
public void testArray()
{
for (String b : belle)
{
System.out.println("曾经的青史留名:" + b);
}
}
public static void main(String[] args)
{
Foreach each = new Foreach();
each.testCollection();
each.testArray();
}
}
对于集合类型和数组类型的,我们都可以通过foreach语法来访问它。上面的例子中,以前我们要依次访问数组,挺麻烦:
for (int i = 0; i < belle.length; i++)
{
String b = belle[i];
System.out.println("曾经的风化绝代:" + b);
}
现在只需下面简单的语句即可:
for (String b : belle)
{
System.out.println("曾经的青史留名:" + b);
}
对集合的访问效果更明显。以前我们访问集合的代码:
for (Iterator it = c.iterator(); it.hasNext();)
{
String name = (String) it.next();
System.out.println("曾经的风化绝代:" + name);
}
现在我们只需下面的语句:
for (String b : c)
{
System.out.println("曾经的风化绝代:" + b);
}
Foreach也不是万能的,它也有以下的缺点:
在以前的代码中,我们可以通过Iterator执行remove操作。
for (Iterator it = c.iterator(); it.hasNext();)
{
itremove()
}
但是,在现在的foreach版中,我们无法删除集合包含的对象。你也不能替换对象。
同时,你也不能并行的foreach多个集合。所以,在我们编写代码时,还得看情况而使用它。
17.3. 自动装箱和自动拆箱
自动封箱解箱只在必要的时候才进行。还有其它选择就用其它的
byte b -128~127
Byte b 多一个null
简单类型和封装类型之间的差别
封装类可以等于null ,避免数字得0时的二义性。
Integer i=null;
int ii=i; 会抛出NullException 异常。
相当于 int ii=i.intValue();
17.3.1. 在基本数据类型和封装类之间的自动转换
5.0之前
Integer i=new Integer(4);
int ii= i.intValue();
5.0之后
Integer i=4;
Long l=4.3;
静态导入(Static Imports)
17.4. 类型安全的枚举
在5.0之前使用模式做出枚举
final class Season{
public static final Season SPRING=new Season();
public static final Season WINTER=new Season();
public static final Season SUMMER=new Season();
public static final Season AUTUMN=new Season();
private Season(){}
}
完全等价于
enum Season2{
SPRING,
SUMMER,
AUTUMN,
WINTER
}
枚举类(Enumeration Classes)和类一样,具有类所有特性。Season2的父类是java.lang.Enum;
隐含方法: Season2[] ss=Season2.values(); 每个枚举类型都有的方法。enum可以switch中使用(不加类名)。
switch( s ){
case SPRING:
…………….
case SUMMER:
…………….
…………..
}
枚举的有参构造
enum Season2{
SPRING(“春”), -------------------------------逗号
SUMMER(“夏), -------------------------------逗号
AUTUMN(“秋”), -------------------------------逗号
WINTER(“冬”); -------------------------------分号
private String name;
Season2(String name){
this.name=name;
}
String getName(){
return name;
}
}
Season2.SPRING.getName() ---------------------à春
枚举的高级用法:
enum Operation{
ADD{
public double calculate(double s1,double s2){
return s1+s2;
}
},
SUBSTRACT{
public double calculate(double s1,double s2){
return s1-s2;
}
},
MULTIPLY{
public double calculate(double s1,double s2){
return s1*s2;
}
},
DIVIDE{
public double calculate(double s1,double s2){
return s1/s2;
}
};
public abstract double calculate(double s1 ,double s2);
}
有抽象方法枚举元素必须实现该方法。
17.5. 静态引入
静态成员的使用,使用import static 引入静态成员。
很简单的东西,看一个例子:
没有静态导入
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
有了静态导入
import static java.lang.Math.*;
sqrt(pow(x, 2) + pow(y, 2));
其中import static java.lang.Math.*;就是静态导入的语法,它的意思是导入Math类中的所有static方法和属性。这样我们在使用这些方法和属性时就不必写类名。
需要注意的是默认包无法用静态导入,另外如果导入的类中有重复的方法和属性则需要写出类名,否则编译时无法通过。
17.6. C风格的格式化输出
格式化I/O(Formatted I/O)
增加了类似C的格式化输入输出,简单的例子:
public class TestFormat{
public static void main(String[] args){
int a = 150000, b = 10;
float c =
System.out.printf("%4d %4d%n", a, b);
System.out.printf("%x %x%n", a, b);
System.out.printf("%
System.out.printf("%1.3e %1.3e%n", c, d*100);
}
}
输出结果为:
150000 10
5.01 3.1
5.010e+00 3.140e+02
17.7. Building Strings(StringBuilder类)
在JDK5.0中引入了StringBuilder类,该类的方法不是同步(synchronized)的,这使得它比StringBuffer更加轻量级和有效。
17.8. 可变长的参数
使用条件:只在必要的时候进行。同时有数组,没变参,有变参,没输组,不能共存。一个方法最多只能有一个变长参数,而且是最后一个参数。
5.0之前
public static void main(String[] args){
}
JVM收到数据封装在数组里,然后传入方法
5.0之后
public static void m(String... s){
System.out.println("m(String)" +s);
}
调用m(String… s)
for(String s2:s){
System.out.println(s2);
}
17.9. JAVA5.0 的注释 (Annotation)
描述代码的代码。给编译器看的代码,作用是规范编译器的语法。
class Student{
@Override
public String toString(){
return “student”;
}
}
类型(接口)
1.标记注释
@Override
2.单值注释
@注释名(prameter=10)
int parameter
特例:
@注释名 (value “
@SuperessWarning({“ddd”,”aaa”,”ccc”}) //JVM还没有实现这个注释
3.普通注释
(key1=value,……)
4.自定义注释
public @interface Test{
}
注释的属性类型可以是
8种基本类型
String
Enum
Annotation
以及它们的数组
三个新加的多线程包
Java 5.0里新加入了三个多线程包:java.util.concurrent, java.util.concurrent.atomic, java.util.concurrent.locks.
java.util.concurrent包含了常用的多线程工具,是新的多线程工具的主体。
java.util.concurrent.atomic包含了不用加锁情况下就能改变值的原子变量,比如说AtomicInteger提供了addAndGet()方法。Add和Get是两个不同的操作,为了保证别的线程不干扰,以往的做法是先锁定共享的变量,然后在锁定的范围内进行两步操作。但用AtomicInteger.addAndGet()就不用担心锁定的事了,其内部实现保证了这两步操作是在原子量级发生的,不会被别的线程干扰。
java.util.concurrent.locks包包含锁定的工具。
17.10. Callable 和 Future接口
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。Callable和Runnable有几点不同:
Callable规定的方法是call(),而Runnable规定的方法是run().
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
call()方法可抛出异常,而run()方法是不能抛出异常的。
运行Callable任务可拿到一个Future对象,通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
以下是Callable的一个例子:
public class DoCallStuff implements Callable{ // *1 private int aInt; public DoCallStuff(int aInt) { this.aInt = aInt; } public String call() throws Exception { //*2 boolean resultOk = false; if(aInt == 0){ resultOk = true; } else if(aInt == 1){ while(true){ //infinite loop System.out.println("looping...."); Thread.sleep(3000); } } else { throw new Exception("Callable terminated with Exception!"); //*3 } if(resultOk){ return "Task done."; } else { return "Task failed"; } } } |
*1: 名为DoCallStuff类实现了Callable,String将是call方法的返回值类型。例子中用了String,但可以是任何Java类。
*2: call方法的返回值类型为String,这是和类的定义相对应的。并且可以抛出异常。
*3: call方法可以抛出异常,如加重的斜体字所示。
以下是调用DoCallStuff的主程序。
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Executor { public static void main(String[] args){ //*1 DoCallStuff call1 = new DoCallStuff(0); DoCallStuff call2 = new DoCallStuff(1); DoCallStuff call3 = new DoCallStuff(2); //*2 ExecutorService es = Executors.newFixedThreadPool(3); //*3 Future future1 = es.submit(call1); Future future2 = es.submit(call2); Future future3 = es.submit(call3); try { //*4 System.out.println(future1.get()); //*5 Thread.sleep(3000); System.out.println("Thread 2 terminated? :" + future2.cancel(true)); //*6 System.out.println(future3.get()); } catch (ExecutionException ex) { ex.printStackTrace(); } catch (InterruptedException ex) { ex.printStackTrace(); } } } |
*1: 定义了几个任务
*2: 初始了任务执行工具。任务的执行框架将会在后面解释。
*3: 执行任务,任务启动时返回了一个Future对象,如果想得到任务执行的结果或者是异常可对这个Future对象进行操作。Future所含的值必须跟Callable所含的值对映,比如说例子中Future对印Callable
*4: 任务1正常执行完毕,future1.get()会返回线程的值
*5: 任务2在进行一个死循环,调用future2.cancel(true)来中止此线程。传入的参数标明是否可打断线程,true表明可以打断。
*6: 任务3抛出异常,调用future3.get()时会引起异常的抛出。
运行Executor会有以下运行结果:
looping.... Task done. //*1 looping.... looping....//*2 looping.... looping.... looping.... looping.... Thread 2 terminated? :true //*3 //*4 java.util.concurrent.ExecutionException: java.lang.Exception: Callable terminated with Exception! at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:205) at java.util.concurrent.FutureTask.get(FutureTask.java:80) at concurrent.Executor.main(Executor.java:43) ……. |
*1: 任务1正常结束
*2: 任务2是个死循环,这是它的打印结果
*3: 指示任务2被取消
*4: 在执行future3.get()时得到任务3抛出的异常