此系列主题帖会持续更新,希望大家都贡献自己的智慧!
PS:《21天学通Java6》(人民邮电出版社 作者:Rogers Cadenhead, Laura Lemay 翻译:袁国忠 张劼)
如果用简短几个字来的顶贴,希望在顶完之后能编辑自己的帖子发表自己的经验!同时希望大家“举手之劳”对好的回复给与加分!
如果有需要下载的附件,新人下载的积分不够的话,去多看几篇精华帖然后回复一下,积分自然就上来了,我以前也是这样过来的!
以后还会开展《thinking in java》的学习互动活动!
智慧在于分享!
[ 本帖最后由 冰封之灵 于 2009-6-28 01:44 编辑 ]
第1章 Java基础
希望大家都能贡献贡献经验,争取多补充些知识进来!
这一章没什么特别详细的内容,对Java语言和面向对象进行一个简单的介绍。
这是第三版的目录,涉及的内容比我这本第五版要多http://baike.baidu.com/view/7802.htm(可能新版本觉得有些东西可以暂时不讨论,比如Applet)
唯一需要注意的是类的层次结构问题。
“继承”实现了代码的重(chong)用,我们可以根据需要对其进行扩展(final关键字修饰的类除外)。超类是对其所有子类公有属性和行为的抽象。类的对象可以实现同类事物的多样性,而子类是对其超类的扩展。同一超类的不同分支间的共同属性和行为可以通过接口来进行限定。
覆盖原理:调用方法的时候,Java解释器是从子类开始往上查找,如果子类中没有找到这个方法,就去查看其上层超类,如果其上层超类中没有这个方法,就去更上层的超类中查找。所以在子类中定义一组与超类同名(名称,返回值,参数)的方法之后就会覆盖上层超类的同名方法。
简单的总结:用类来模拟真实的世界的事物(甚至是一些抽象的概念),然后通过传参来实现事物间的交互(你可以在类中定义一些用来接收参数的方法)。所有类和接口的管理(如分类和访问控制)通过包来进行管理。
顺便说下,当使用继承之后,父类对象和子类对象是两个完全的对象,它们在虚拟机上是平等的,没有依赖关系(比如,可以在父类中定义一个私有方法来操作子类。这在逻辑上似乎说不通,因为,儿子还没生出来怎么能去操作它呢?情况恰恰就是
这样,因为我考虑的是对象间的关系而不是类之间的关系,在对象创建完成之后它们就是相互的事物,之间可以进行交互。父亲可以具有一些儿子所没有的能力:如,儿子做错了事,父亲可以给与一定的教育。不过现实中的这种生育关系和面向对象编程(OOP)的继承是有区别的,现实中的父亲和儿子各具特色,是同一层面上的,它们的超类是“人”。在所有类默认的顶层超类“Object”的源代码中就有这样的代码来处理子类。
知识扩展:java.lang包是JVM(Java虚拟机)系统自动引入的包(里面的类可以直接使用而无需用import关键字导入),这个包里有3个特殊的类:Object ,Class ,ClassLoader 。其中Object 是所有类的父类。ClassLoader用来将类加载到JVM中。而Class的对象用来描述对象的类型,属性和行为,它在创建对象的时候由JVM通过ClassLoader的defineClass方法自动构造并与此对象绑定,这样就使得每个对象知道自己是什么和能做什么,并且通过反射机制(此技术大家可以另作讨论)来显示出这些描述并进行一些应用。
例如有个特殊用法:类名·class(如String.class ,Integer.class)用来表示此类的类型。这个特殊用法,我们可以用来比较某一对象是不是我们需要的某类对象,从一系列对象中筛选出这类对象。
以下是一个例子
public boolean compare (Class c,Object o){
boolean bool=false;
//c获取它的名称,o获得其Class对象然后获得它的名称,如果两个名称相同,将bool设置为true
if(c.getName().equals(o.getClass().getName())) bool=true;
return bool;
}
而用上述方法传参的时候可以这样compare(Integer.class,"冲啊") 在此,将返回false,前者为Integer类,后者为字符串
也许有人会问:用关键字instanceof 来判断某对象是不是属于某类不就行了,比如,if(o instanceof String),但是,如要有必要通过传参来实现这种比较呢,所以这种方法有它的灵活性!
二进制名称:在帮助文档中的ClassLoader类中会看到这个名词(大概就是通常的符合语法规定的“字符串”)。网上没找到这个名词的解释,不过在维基百科上有:二进制文件(Binary files)一般指包含ASCII及扩展ASCII字符中编写的数据或程序指令的文件。所以我想,这种叫法跟ASCII编码有关系。
[ 本帖最后由 冰封之灵 于 2009-6-26 22:19 编辑 ]
第2章 Java编程基础
第二章主要内容是基础语法,具体内容大家可以去参考此书目录,在此我不一一列举。仅对一些新手可能遇到的问题和精华内容作一些说明。
一、Java中以分号(;)标志语句结束,如果在一些莫名其妙的报错可能会是这个原因
引起,由于现在IDE(集成开发环境)的语法纠错都比较好,这种错误不容易犯。不过会有这种情况,由于分号缺少导致的其它语句报错可能会让新手百思不得其解,所以应当注意。
除了以分号结束语句以外,“块”(用一对花括号{}表示)语句也可以成为单独的语句,并且具有局部变量范围的(即,块中的变量仅属于此块),也就是说,你在块中定义的变量是不能在外面使用的,所以要在外面使用某个变量,需要在块外表示。块语句可以出现在任何可以使用单条语句的地方。
以下是一个特例(知识扩展):
static{
//内容
}
这个static块是一个特殊用法,它是在使用类(如,Math.random())或者其对象的时候自动执行。如使用类变量或类方法(用static关键字定义)的时候或创建对象。也就是说只要涉及到这个类,这个块就会自动执行。这种用法一般在JDK类库的底层类的源代码中比较常用,用来注册一些本地代码(用native关键字定义,是用其它语言C,C++等编写)。对于我们来说一般不会去使用,在此作个了解。
二、单目运算符:++,-- 的问题,不论是新手还是老鸟,除非你对此运算符非常熟练(自己用代码测试过多次),应尽量避免在复杂的表达式中使用,因为可能会出现意外的结果。如果出现这种情况,解决办法是将这个表达式拆成多个语句(分步运算),以对其进行简化。
三、拼接运算符:+
将数值转换成字符串最简单的办法就是:””+5
将两个数字拼接起来而不要其执行加号运算:5+””+6
四、字面量
数字、字符、字符串和布尔值(true和false)都是字面量,另外八进制和十六进制表示的数字也称为字面量。也就是一眼就能看出它们的内容
指数表示法:double y=19E-95;// 19乘以10的负95次方
小数点的问题: .5 //这个“点5”其实就是0.5,前面的0可以省略
默认类型:整数默认是int,带小数的默认是double。要将一个超过int类型精度范围的数表示成long 就必须在后面加上小l或大L。最好用大写L,把小l和数字1从视觉上区分开。
五、写注释的习惯
这点特别重要,当你程序代码量越来越大的时候,注释尤为重要。这既关系到别人阅读你代码的效率,也关系到你自己阅读自己代码并进行debug的效率(分步骤检查),并且会帮助你理清程序的逻辑思路。在写程序的时候,一般都是将代码进行分步骤书写(比如某段代码合在一起是什么作用),然后把注释写在这段代码的上面。
第3章 对象
这一章讲了对象的基本知识,从创建对象,访问和设置类变量和实例变量,
调用方法,对象的引用,类型转换,到对象的比较。
Java真的是简单的,因为在这个语言里,一切都是类的对象。线程(Thread)是类,数据结构是类(如最流行的Vector)我们日常使用的类库的所有东西都是类和接口。而接口的使用方式在很多方面跟类是相似的。很多Java程序员说到类时,常常指的是“类或接口”。因此,用从一开始就用面向对象的思维方式去学习也是必须掌握的方法。当你创建自己的类时,就要把你的程序抽象成一个个的事物并能与外界交互。当你掌握了类的使用以及对象与对象之间的交互,那么剩下的只是对类库的熟悉而已了。你可以根据自己的方向去选择自己所需要使用的类(它们会给你提供很多方便),然后开始创建自己需要的类,书写自己程序。
一、创建对象
new是一个神奇的关键字,它能创造我们所需要的事物。使用new后会有两个关键步骤:为实例分配内存,实例化完毕之后就会调用构造方法。也就是说构造方法时对象创建过程的最后一步(即使你没有定义构造方法,系统会自动隐式的调用空构造方法,更详细的内容在第5章)
补充:Java中的对象是在堆(Heap)中分配内存空间,而基本数据类型以及对象的“引用”是分配到栈(stack)上。
创建对象其实有很多方法,具体的方法我会在2楼进行整理。
二、内存管理
Java中的内存管理是动态的,自动的。你不必显式的去分配内存,也不需要自己去释放内存。在某些类中(比如流和组件方面),你可以间接的通过调用方法来释放资源,这常常是必要。
三、类变量-类方法和实例变量-实例方法
类变量-类方法是属于整个类的,而不是属于某个对象。它们由关键字static定义,在程序编译时就可以确定它们所需的内存空间,并且它们的内存分配方式也有些不一样。String.valueOf()就是一个调用静态方法的例子。注:修改类变量的值会影响所有该类对象。
实例变量-实例方法是属于对象的,包含在对象独自的内存空间。
调用方法和变量都是用点(.)运算符:有返回值的方法,打点调用之后把它看成一个整体,可以继续在后面打点调用其方法。System.out.println();就是一个典型的例子,其中out是一个静态对象,属于类PrintStream,是一个输出流对象。
四、类型转换
基本数据类型:低精度可以直接向高精度转换,比如int类型赋值给long类型。
基本数据类型可以“封装”成对象,比如Integer k=new Integer(15);
而要拆封装可以调用方法int a=k.intValue();
对于那些自动封装和拆封装的情况将在2楼讨论。
对
象间的转换:要注意的就是两个对象间必须具有父子关系,并且,子类转换成父类之后,对象不变,也就是内存空间不变。这时新的引用不能调用子类专有的方法和变量。而父类转换成子类之后就等于是进行了扩展(对象和接口的转换问题到第6章时讨论)
五、对象的比较,判断其所属类
“==”是用来比较基本数据类型的,所以如果把对象的引用放上去,那木就是比较对象的内存首地址是否一样。从这里可以看出:对象的引用实际上是个“基本类型”,代表对象的内存首地址。
关键字instaceof(如k instaceof Integer)左边是对象的引用,右边是类名。使用这个关键字有两层意思,1:告诉你这个对象是不是属于这个类或其子类。2:如果是,那么就可以进行强制转换。说简单点就是告诉你能不能进行强转。
内存管理和分配,创建对象的多种方法,封装等详细的内容,我会在2楼进行整理。希望大家都去找找资料然后看看有什么需要补充的知识!人多力量大嘛!
第4章 数组、逻辑和循环
基本内容大家自己去找资料看,以下我都是对此书内容的扩展。
数组
数组是一种最简单的容器类。与它绑定的Class对象是由Java根据需要自动创建。如果数组元素为基本类型,那么“此基本数据类型数组没有类加载器(ClassLoader)”。使它在执行上更接近于基本数据类型。这也是数组的执行速率优于其它容器的重要原因之一。
缺点:
1.本身缺少操作其元素的方法(只有一个很简单的接口)
2.长度固定,因此无法在不确定存储数量的范围时定义数组
3.元素容易被更改,无法像其它集合框架那样可以根据需要控制其元素是否允许被替换
优点:
1.其执行速度比java.util包中的任何容器速度更快
2.存入基本数据类型时无需将其包装成对象
利用数组的执行速度:在你不知道要存入多少元素时,可以先用ArrayList(可变数组)进行存储,然后转存到一个数组中:
import java.io.*;
import java.util.*;
public class Test{
public static void mai(String[] args){
//创建File对象,代表当前文件夹中的obj文件夹下的double.dat
File file =new File(“obj\\\\double.dat”);
//创建文件输入流,然后交给数据输入流
DataInputStream input=new DataInputStream(new FileInputStream(file));
//以字节为单位,返回文件的长度(long类型),然后转成int并赋值给length
int length=(int)file.length();
//如果长度不是8字节的整数倍,抛出IO异常
if(lenth%8 != 0)
throw new IOException(“文件损坏!”);
/
/得到文件的字节数
int numRecords=lenth/8;
//创建可变数组对象并转换成List接口(仅以接口方式使用对象)
List list=new ArraList();
//返回输入流的下8个字节的double值→封装成Double对象→加入可变数组
for (int i=0;i
list.add(new Double(input.readDouble()));
//得到可变数组的长度→创建数组
double[] values=new double[list.size()];
//将可变数组将可变数组交给迭代器(接口Iterator)
Iterator itrator=list.iterator();
//初始化下标变量
int index=0;
//如果可变数组还有有元素,就把下个元素转回Double类型并提取其double值,然后赋值给数组元素
while(iterator.hasNext())
values[index++]=((Double)iterator.next()).doubleValue();
}
}
对于对象数组,有必要说明的是,它内存空间里面存储的实际上是一系列到这些对象的“引用”,将这个元素赋值给其它引用,传递的其实只是引用,而不是复制这个对象。
[ 本帖最后由 冰封之灵 于 2009-8-1 22:37 编辑 ]
第5章 创建类和方法
这章讲了类的基本语法,讲的都是基础语法,我暂时没想到该讲什么好。所以,贴点书上代码上来,并附以注释供新手参考。之前我写的一些内容由于写得还比较乱,暂时保留另作它用。
-------------------------------------------------------------------------
class RangeLister{
//此方法的作用是:通过参数传入一个下限值(lower)和一个上限值(upper)
//然后返回 lower<=值<=upper 之间的所有整数的数组
int[] makeRange(int lower, int upper){
int[] range = new int[(upper-lower)+1]; //这里加1是必要的
for(int i = 0; i
range = lower++;
}
return range;
}
//主方法
public static void main(String[] arguments){
int[] range;
RangeLister lister = new RangeLister();
range = lister.makeRange(4,13);
System.out.println("The array:[");
for(int i = 0; i
System.out.print(range+" ");
}
System.out.println("]");
}
}
-------------------------------------------------------------------------
class Passer{
//此方法将字符串数组的所有字符串统一成大写
void toUpperCase(String[] text){
for(int i = 0; i
//这里赋值是必须的,虽然String是对象,但是它是不变的,这个方法将产生新的字符串
text = text.toUpperCase();
}
}
//主方法
public static void main(String[] arguments){
Passer passer = new Passer();
passer.toUpperCase(arguments);//将命令行输入的参数传递给方法
for(in
t i =0; i
System.out.print(arguments+" ");
}
System.out.println();//最后换一次行(这步在这里可以不必)
}
}
-------------------------------------------------------------------------
class Circle{
//坐标和半径(此类没写主方法)
int x,y,radius;
//构造方法一
Circle(int xPoint, int yPoint, int radiusLength){
//由于参数名和实例属性没有同名,所以this可以去掉
this.x = xPoint;
this.y = yPoint;
this.radius = radiusLength;
}
//构造方法二
Circle(int xPoint, int yPoint){
//用this来引用本类的其它构造方法,以提高代码的重用。基础类中的方法重载常用这种方式
//注:this()方法只能用在构造方法中
this(xPoint, yPoint, 1); //这里默认创建一个半径为1的虚拟圆(因为这个类只有圆的属性)
}
}
这里对一些细节的内容作一点说明:
1、tihs和super都是“引用”,分别代表本类对象和超类对象
2、变量的作用域:使用变量时,Java是从作用域内向外查找名称,也就是先在使用这个变量的当前“块”语句(花括号)中查找。这就形成了对“块”外变量的覆盖或者说屏蔽。方法内部:在块里不能定义与块外同名的变量。
3、类的加载顺序和构造方法的执行顺序:JVM为创建对象而加载类的时候,是从父类到子类。而执行构造方法是从子类到父类。构造方法第一句必须是super(); 即使你没有写super();系统也会自动隐式无参数super(); 并且super();只能用在构造方法中。
那么这个过程就是这样:对象模型创建完毕,执行子类构造方法,由于第一句是super();所以开始执行父类构造方法,父类构造方法又super();
一步步执行下来,到最后,其实,虽然构造方法的执行顺序是从子类到父类,但是子类的构造方法是最后执行完毕的!至此,对象创建完毕!
[ 本帖最后由 冰封之灵 于 2009-7-1 20:56 编辑 ]
第6章 包、接口和其他类特性
类用于创建对象。初学者先把类的基本语法了解清楚,然后就是熟练传参。传参是对象间的交流。熟练以上知识之后,接下来,这一章简单讲了如何去组织和管理你的多个类。
Java语言提供了4种访问控制:公有、私有、受保护和默认(不适用限定符)。给变量和方法设定访问控制。这就是封装:对象控制外部世界对它的了解程度,以及如何与它进行交互。
封装的意义(个人总结,高手们多发表下意见)
1、防止类中的变量和方法被其它类以非法的方式使用。
2、减少程序员在开发大型程序中犯错的机率,并缩小dubu
bug的范围。
3、有利于程序员间的分工,提高代码的重用性。
外部接口:public定义的变量和方法称为外部接口 ,通过它们来实现封装后的类与外界的交互。
说明:使用JDK命令行的新手,在你的硬盘中创建文件夹结构:示例D:\\myshop\\org\\cadenhead\\ecommerce\\ 然后将D:\\myshop加入环境变量classpath。将后两个类放入这个目录。含有主方法的类不能放在你import的目录中。
----------------------------------------------------------------------------------------------------------------------------------------------------
复制内容到剪贴板
代码:
import org.cadenhead.ecommerce.*;
public class GiftShop {
public static void main(String[] args) {
//创建商店库
Storefront store = new Storefront();
//添加商品
store.addItem("C01
复制内容到剪贴板
代码:
package org.cadenhead.ecommerce;
import java.util.*;
//由于要将此对象用于Collections.sor()方法排序,所有必须实现Comparable接口
public class Item implements Comparable {
//型号、商品名、零售价、库存量、销售价格(这些信息都被封装——private修饰)
private String id;
private String name;
private double retail;
private int quantity;
private double price;
//构造方法,构造Item对象(用来代表商品个体信息)
Item(String idIn, String nameIn, String retailIn, String quanIn) {
//将信息存储到Item对象
id = idIn;
name = nameIn;
retail = Double.parseDouble(retailIn);
quantity = Integer.parseInt(quanIn);
//根据库存量计算销售价
if (quantity > 400)
price = retail * 0.5;
else if (quantity > 200)
price = retail * 0.6;
else
price = retail * 0.7;
//四舍五入算法(这里对小数点后第3位取舍,6.929→6.93)
price = Math.floor(price * 100 + 0.5) / 100;
}
//覆盖Comparable接口的compareTo方法,用于之后的sort()方法中。这里是对销售价格进行排序
public int compareTo(Object obj) {
Item temp = (Item) obj;
if (this.price < temp.price)
return 1;
else if (this.price > temp.price)
return -1;
return 0;
}
//以下方法是用来获取被封装的商品信息的接口
public String getId() {
return id;
}
public String getName() {
return name;
}
public double getRetail() {
return retail;
}
public int getQuantity() {
return quantity;
}
public double getPrice() {
return price;
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------
[ 本帖最后由 冰封之灵 于 2009-7-11 19:21 编辑 ]
JDK使用细节的说明
1、修改环境变量后需要从新打开命令行
2、主方法别定义包名,也最好别放在某个包中。如果出现问题,将主方法单独分离出来放入外层目录
3、环境变量只需要配置path和classpath。前者代表执行java、javac等命令的程序路径,后者代表你所使用的类的路径。
(此为绝对路径,配%java_home%是相对路径,不必要。另外,环境变量不区分大小写)
path为JDK安装目录下的bin文件夹。
示例:C:\\Program Files\\Java\\jdk1.6.0_13\\bin
classpath为你所要使用的类的路径。如果是你自己创建的类,那么就是你所建包所在的目录。
示
例:.;C:\\Program Files\\Java\\jdk1.6.0_13\\lib\ools.jar;D:\\myshop
//对上面代码中四舍五入算法的简单说明:表达式中乘以100表示将对小数点后第3位进行取舍,然后加0.5就是开始配合Math.floor()进行四舍五入。所以,如果要对第4位取舍就乘以1000再加0.5,依此类推。Math.floor()的具体意思以及之后排序的用法自己去查帮助文档吧。从import可以看出它们在java.lang包或者java.util包中
UpDate1: 在命令行中结束自己程序的的操作是:Ctrl+C
[ 本帖最后由 冰封之灵 于 2009-7-11 15:28 编辑 ]
第7章 异常、断言和线程
这是第一周的最后一章,其内容是很重要的。
异常
保证自己代码足够健壮的同时,需要对可能发生的异常进行妥善的处理。因为可能会出现很多意外的情况。管理好程序可能发生的异常能够保证你的程序能够更稳定的运行。下面对Java中的异常处理进行一些必要的说明。
-----------------------------------------------------------------------
先看下面一个代码片段(原书代码)
int status=loadTextFile();
if(status!=1){
//发生一些通常的状况,把它描述出来
Switch(status){
case 2:
System.out.println(“没找到文件”);
case 3:
System.out.println(“磁盘错误”);
case 4:
System.out.println(“文件损坏”);
default:
System.out.println(“其它错误”);
}
}else{
//文件加载成功,程序继续
}
这是一种简单的异常处理的方式,但是这种方式存在严重的缺点。如果出现的异常情况有更多的类型,那么这就需要更多的if和switch块来对其进行处理。更重要的是,这种处理方式不利于异常的统一管理,并使代码难以阅读和维护(用“返回值”来对异常分类很难形成一套统一的标准,并且这种处理方法会非常复杂)
----------------------------------------------------------------------------------------------------------------------------------------------------
//Java中的异常处理模式
public void demo() throws IOException{
try{
//可能引发IO异常的代码
}catch(IOException e){
//处理IO异常的代码
throw e; //将异常抛出(传递到使用这个方法的代码)
}finally{
//必须执行的后续操作
}
}
说明:将可能发生异常的代码放入try块中。发生异常后就会在catch块中进行一些处理。如果有必要,就用throw e; 传递给方法。由方法执行时抛出,并仍用try-catch结构在使用此方法的代码中捕获(将异常向上传递的目的是为了告诉上层代码这个方法出现异常,需要进行一些必
要的处理)
finally块的说明:使用finally是为了在发生异常后执行一些必要的操作(如关闭流,关闭打开的文件,释放系统资源)。新手可能会问:为什么不放进catch中执行呢,的确可以这样。但是如果没有发生异常,或者有非常多的异常情况(为这些异常使用多个catch块),那么这种必要的操作就需要放进各个“块”中,使代码变得冗余。直接用一个finally就OK了,因为finally始终会按照程序的顺序在最后执行。
finally的另一种用途:
try{
//包含return,break或continue的语句,一旦执行这几个关键字就会跳到finally中
}finally{
//在finally中进行一些操作,一般用来执行一些清理,清空或新的初始化等等
}
异常管理说明:
如果你使用了一个可能抛出异常的方法(由throws关键字定义),那么就必须用try-catch结构对其进行捕捉。
尽量让自己的代码更健壮(逻辑严谨、使用恰当),仅当你无法控制异常的发生时(出现无法控制的意外),才去“引发”异常(用throw关键字抛出)
trow new NotInServiceException( ); //类似于这样,你可以定义自己的异常类
异常的传递:引发的异常种类越多,代码就会越复杂,别人使用你的方法也就会更麻烦。所以异常的引发和传递需要根据情况去考虑。
认真处理影响代码的异常,当你在以后的开发中重用你的类时,将会给你带来非常大的帮助。
[ 本帖最后由 冰封之灵 于 2009-7-5 18:08 编辑 ]
第8章 数据结构
数据结构+算法=程序,你可以暂时不去考虑那些纷繁的算法,但是你必须掌握各种适用的数据结构,因为算法是围绕数据结构开展的,没有好的数据结构,谈论优秀的算法是没有意义的。
这章介绍了Java中的两个接口:Iterator和Map,以及4个类:BitSet、Vector、Stack、Hashtable
Iterator(迭代器接口)
此接口用来为一些数据结构提供统一的“遍历”标准。这就使我们学习起来更加方便。
Iterator只有3个方法,实现这个接口的数据结构将会把这些方法具体化:
public boolean hasNext( ){
//此方法用来确定结构中是否还有元素(配合next()使用)
//方法体
}
public Object next( ){
//提取下一个元素
//方法体
}
public void remove( ){
//移除最后返回的元素
//方法体
}
迭代器原理
一般容器都有一个iterator()方法,它返回一个迭代器,用来对自身元素进行“遍历”。我想有必要说明一下Iterator的迭代原理,这样有助于新手更好的掌握数据结构的使用。
以下内容参考百度百
科(http://baike.baidu.com/view/1413849.htm)我对这些内容进行更详细的说明,以便照顾新手
先简单说下迭代的原理,这涉及到一种“设计模式”(更具体的说法见百科)。
为避免使容器变得更复杂,但同时也为了使容器不至于简单到用外面的方法来“遍历”自身(这样就暴露出容器的内部细节,有违于封装的意义),所以,容器自身的迭代器是通过“内部类”来实现的。iterator()方法返回一个内部类的对象,然后就可以调用上述3种方法来完成对此容器的“遍历”。
需要注意的是,返回的这个对象是把其声明为一个接口:
Vector v = new Vector();
Iterator it = v.iterator();
把这个对象声明成接口的原因就是:这个内部类是为了这个接口而生的,所以它叫什么名字无关紧要,重要的是这样声明后能使用接口的方法就行了。而这样也就使我们在设计这个迭代器的时候不需要为这个类的名字订立一个统一的名称。
请看下面的迭代器代码:
public abstract class AbstractList extends AbstractCollection implements List {
/*
*这个容器超类的其它部分省略,我们主要看这个迭代器。(新手注意:内部类可以直接使用其外部类的方法)
*/
//这个便是负责创建具体迭代器角色的工厂方法
public Iterator iterator() {
return new Itr();
}
//作为内部类的具体迭代器角色
private class Itr implements Iterator { //这个内部类被定义成private,因此它处于一种封装模式
int cursor = 0; //游标,为0代表处于容器第一个元素
int lastRet = -1; //最后一个被迭代的元素索引
int expectedModCount = modCount; //modCount代表容器从结构上被修改的次数(结构:数量,顺序)
public boolean hasNext() {
return cursor != size();//如果游标不等于容器元素的数量就返回true。
} //这其实是一种逻辑判断(初始状态为0,如果游标不等于容器大小,那么可以继续获取元素)
public Object next() {
checkForComodification(); //检查容器结构是否被修改
try {
Object next = get(cursor); //调用容易的get方法获取元素(注:在Vector源代码中这里是声明为泛型)
lastRet = cursor++; //游标先赋值然后移位
return next;
} catch(IndexOutOfBoundsException e) {
checkForComodification(); //捕获越界异常后,检查是否发生过结构修改
throw new NoSuchElementException();
}
}
public void remove() {
if (lastRet == -1) //如果等于-1说明还没开始迭代,然后抛出异常
throw new IllegalStateException();
checkForComodification(); //检查容器结
是否被修改
try {
AbstractList.this.remove(lastRet); //将最后获取的元素从这个容器中移除,此容器之后元素索引左移1位。此时结构修改次数发生改变
if (lastRet < cursor)
cursor--; //将游标也左移1位
lastRet = -1;
expectedModCount = modCount; //改变容器结构后将这个新的次数赋值
} catch(IndexOutOfBoundsException e) { //如果越界,则抛出异常
throw new ConcurrentModificationException();
}
}
}
final void checkForComodification() { //此方法用来检查结构是否被修改
if (modCount != expectedModCount) //如果迭代过程中,次数被修改,则抛出异常
throw new ConcurrentModificationException();
}
}
-----------------------------------------------------------------------------
Vector v = new Vector(0,1);
//用v.add(“”)添加元素
Iterator it = v.iterator();
String[] s=new String[v.size()];
int i=0;
while(it.hasNext()){
s=(String)it.next();
i++;
}
注:游标初始为0并对应容器的0号元素,调用一次next()方法后游标值就会增加。而其它一些集合,如:接口ResultSet的next()方法与此不同,它的游标初始时是在第一行之前,用一次next()只是把光标向下移动一行。
[ 本帖最后由 冰封之灵 于 2009-7-7 00:13 编辑 ]
第9章 使用Swing
这章开始到第14章都是属于GUI(图形用户界面)方面内容了,虽然Java做GUI不太方便,不过大家可以了解一下,也许哪天会有需要或者其中的思想能帮助你更深刻的理解Java。
其实,GUI是更利于大家培养面向对象的思维方式的,因为它有能够展现出直观的界面。所以,在做GUI设计的时候,需要你很好的对你所做的GUI组件进行分类。一切都是对象,从按钮到框架。完全可以把它们想象成是一套组装起来的物品。
在javax.swing,java.awt这些类似的包中有很多可用的组件,你只需要继承这些组件,就可以根据自己的需要进行使用。
一般顺序如下:继承JButton,继承JLabel,继承JPanel,继承JFrame→将JButton和JLabel添加到JPanel,然后将各个JPanel添加到面板(注意添加的顺序)→最后将JPanel加入JFrame
你可以根据需要决定是否继承JButton和JLabel,如果只是利用他们的普通用法,那就没必要继承它们,直接在JPanle里面创建它们的对象就好了。不过,有些时候你需要为它们添加一些新的属性用来存储信息。比如我用JLabel标签做QQ头像,我就在这个标签类里面定义了新的属性来存储用户信息(号码,IP地址,头像编号,昵称等等)。
下面还是用实例来进行说明,帮助新手更快的
掌握GUI的开发。我还是选点书上的例子好了,这些例子都是可以作为模版来使用。到后面的章节再拿一些大型而完整的例子进行讲解。
import javax.swing.JFrame;
public class SimpleFrame extends JFrame{ //继承框架
public SimpleFrame(){
super("Frame Title"); //设置标题文本
setSize(300,100); //设置框架宽300,高100
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //关闭框架时退出程序
setVisible(true); // 设置框架可见
}
public static void main(String[] arguments){
SimpleFrame sf=new SimpleFrame();
}
}
------------------------------------------------------------------------------------------------
import javax.swing.JWindow;
public class SimpleWindow extends JWindow{ //继承窗体(界面比较简单,适合作登陆画面)
public SimpleWindow(){
super(); //为空可以不写,但是如果写了super(); 就必须是构造方法的第一句
setBounds(400,300,10,10); //设置窗体左上角坐标为(400,300)宽10,高10
setVisible(true);
}
public static void main(String[] arguments){
SimpleWindow sw=new SimpleWindow();
for(int i=10;i<400;i++){
sw.setBounds(400-(i/2),300-(i/2),i,i); //窗体尺寸从10增加到399,位置也随i变化
}
}
}
------------------------------------------------------------------------------------------------
import javax.swing.*;
public class Authenticator extends javax.swing.JFrame{
JTextField username=new JTextField(15); //文本框,长度15
JPasswordField password=new JPasswordField(15); //密码框,长度15
JTextArea comments=new JTextArea(4,15); //文本域,4行15列
JButton ok=new JButton("OK"); //按钮
JButton cancel=new JButton("Cancel");
public Authenticator(){
super("Accout Information");
setSize(300,220);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel pane=new JPanel(); //创建面板
JLabel usernameLabel=new JLabel("Username"); //与上面各组件对应的文本标签
JLabel passwordLabel=new JLabel("Password");
JLabel commentsLabel=new JLabel("Comments:");
comments.setLineWrap(true); //为true自动换行
comments.setWrapStyleWord(true); //为true表示按单词换行,false代表按字符换行
pane.add(usernameLabel); //添加各个组件到面板,顺序很重要
pane.add(username);
pane.add(passwordLabel);
pane.add(password);
pane.add(commentsLabel);
pane.add(comments);
pane.add(ok);
pane.add(cancel);
add(pane); //将面板加入框架
setVisible(true);
}
public static void main(String[] arguments){
Au
thenticator auth=new Authenticator();
}
}
说明:为什么这个程序要把一些组件定义在外面,又把另外一些组件定义在构造方法呢?原因是以后你将会使用那些文本框和按钮的组件,所以必须要有它们的引用。而像用来说明文本框作用的标签这些不再会被使用到的组件就可以定义在方法里面了
[ 本帖最后由 冰封之灵 于 2009-7-8 00:54 编辑 ]
知识补充1:组件中有很多方法时类似的,这些方法可以给我们提供方便,如果你没找到可能具有的方法,那么上其超类看看,组件的很多公有方法在超类中可以找到
知识补充2:让框架出现在屏幕的代码如下
double swidth=Toolkit.getDefaultToolkit().getScreenSize().getWidth();
double sheight=Toolkit.getDefaultToolkit().getScreenSize().getHeight();
setLocation((int)((swidth-getWidth())/2),(int)((sheight-getHeight())/2));
把这3句放在你的setSize()后面就OK啦!
swidth和sheight这两个表示屏幕宽和高,这些变量最好是定义在外面,以后可能会需要得到它们的引用
这些方法具体的含义,去查帮助文档哈。在java.awt包中
[ 本帖最后由 冰封之灵 于 2009-7-8 01:28 编辑 ]
21天学通java6之Swing-------模仿篇
Windows 计算器界面模仿
JDKGroup群推出了《21天学通java6》的活动。看到通知的时候,本活动已经进行第8天了。
JDKGroup组成员像我这样的新手居多,为本群贡献出自己的一份微薄之力,在09年7月7日夜完成了计算器界面的全部设计工作。
在本计算器界面中,所用到的布局方式为null布局。所用到的组件分别是:JFrame容器,JPanel面板,JTextField文本框,JButton按钮。
注意:本实例为了方便都将所有的代码放入到了一个java文件中。
计算器界面模仿源码如下:
/*
* 代码已经完成,由于没有考虑太多,可能会有些问题。
* 发现后请指出错误的地方,如果您有好的意件,您可以发送到javatianxia@163.com。
* 尔林将在看到邮件之后马上更正您所提到的错误。谢谢……
*/
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class jisuanqikuangjia{
public static void main(String[] args){
Computer JDKGroupGUI=new Computer();
/**
* 这个部分可以去掉↓↓↓↓↓↓
* */
String lookAndFeelName = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel";
try {
UIManager.setLookAndFeel(lookAndFeelName);//通过UIManager管理器来设置,GUI 的UI界面
SwingUtilities.updateComponentTreeUI(JDKGroupGUI);//通过SingUtilities来frame容器UI外观。
}
catch (Exception e){System.out.println("JDKGroup提醒您,没有此外观设置!!!");}
/**
* 这个部分可以去掉↑↑↑↑↑↑↑
* */
JDKGroupGUI.setVisible(true);
}
}
class Computer extends JFrame{
private JPanel pane;
Computer(){
pane=new JPanel();
pane.setLayout(null);
pane.setSize(255,240);
Dimension paneSize=pane.getSize(); //窗体大小及窗体基本属性的定义
setTitle("界面--JDKGroup->尔林");
Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize();
setSize(paneSize.width,paneSize.height);
Dimension frameSize=getSize();
setLocation((screenSize.width-frameSize.width)/2,(screenSize.height-frameSize.height)/2);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
JmenuItem menuItem=new JmenuItem(); //菜单类实例
//窗体布局方法
getContentPane().add(pane);//窗体使用null布局
JButtonClass jButtonClass=new JButtonClass();//计算器中的按钮布局
jButtonClass.showStringNumberSize();//计算器中的显示屏。
}
//命令按键的类定义
private class JButtonClass implements ActionListener,KeyListener {
private JTextField JFTextNumber;// 计算器中所有按钮的定义
private JButton jiyiBut;
private JButton[] JButtonClear=new JButton[3];
private JButton[] JButtonM=new JButton[4];
private JButton[] JButtonGroup=new JButton[20];
JButtonClass(){
//按钮定位量:Pointx 表示x轴,Pointy 表示y轴,
//JButtonWidth 表示按钮的宽度,JButtonHeight 表示按钮的高度。
int Pointx=54,Pointy=35,JButtonWidth=75,JButtonHeight=25;
// 计算器在所有按钮的名称定义
String[] ButtonClearName=new String[]{"Backspace
nHeight=25;
for(int i=0;i<20;i++){
if(i==5 || i==10 || i==15 ) {
Pointy+= JButtonHeight+5;
Pointx=53;
}
JButtonGroup=new JButton(ButtonGroupName);
JButtonFC(JButtonGroup,Pointx,Pointy,JButtonWidth,JButtonHeight);
if(i==3||i==8||i==13||i==18||i==19)
JButtonGroup.setForeground(Color.RED);
else
JButtonGroup.setForeground(Color.BLUE);
Pointx+=JButtonWidth+3;
}
pane.add(jiyiBut);
}
//所有的0~9,-+*/=的键盘事件代码
public void keyPressed(KeyEvent eTwo){}
public void keyTyped(KeyEvent e){}
public void keyReleased(KeyEvent e){}
//所有命令按钮的事件代码
public void actionPerformed(ActionEvent e){}
//显示数字的文本框方法。
public void showStringNumberSize(){
JFTextNumber=new JTextField("0.
;
itemStandard.setMnemonic(KeyEvent.VK_T);
itemStandard.addActionListener(this);
LookOver.add(itemStandard);
itemOlogy=new JRadioButtonMenuItem("科学型(S)");
itemOlogy.setMnemonic(KeyEvent.VK_S);
itemOlogy.addActionListener(this);
LookOver.add(itemOlogy);
LookOver.addSeparator();//分组线
itemNumberGroup=new JCheckBoxMenuItem("数字分组(I)");
itemNumberGroup.setMnemonic(KeyEvent.VK_I);
itemNumberGroup.addActionListener(this);
LookOver.add(itemNumberGroup);
menuBar.add(LookOver);
//帮助菜单项。
Help=new JMenu("帮助(H)");
Help.setMnemonic(KeyEvent.VK_H);
itemHelpText=new JMenuItem("帮助主题(H)");
itemHelpText.setMnemonic(KeyEvent.VK_H);
itemHelpText.addActionListener(this);
Help.add(itemHelpText);
Help.addSeparator();
itemComputer=new JMenuItem("关于计算器(A)");
itemComputer.setMnemonic(KeyEvent.VK_A);
itemComputer.addActionListener(this);
Help.add(itemComputer);
menuBar.add(Help);
setJMenuBar(menuBar);
}
public void actionPerformed(ActionEvent e){ }
}
}
第10章 创建Swing界面
这章继续介绍一些常用的Swing组件,这些组件的用法都比较简单,新手可以自己去看书或者相关资料。下面我还是贴两段代码。
提一句:如果是用命令行执行,按Ctrl+C来结束程序(当然关闭命令行也行)
------------------------------------------------------------------------------
import java.awt.GridLayout;
import java.awt.event.*;
import javax.swing.*;
public class FeedInfo extends JFrame{
private JLabel nameLabel=new JLabel("Name:
题栏中显示的文本
0, //对话框按钮类型,为0表示自定义
JOptionPane.QUESTION_MESSAGE, //要显示的图标,为0表示自定义,这段代码用的是系统的选项
null, //上个参数的图标对象,null表示不指定,因为上面用的系统选项
choice, //按钮的文本数组
choice[0]); //默认被选中的按钮
//由上面点击按钮后得到按钮编号作为下标(对话框的按钮也是按顺序排的)
type=new JTextField(choice[response3],20);
setLayout(new GridLayout(3,2)); //网格布局,3行2列
//添加组件
add(nameLabel);
add(name);
add(urlLabel);
add(url);
add(typeLabel);
add(type);
setLookAndFeel();//由于要在对话框填写完后添加组件,所以再次设置外观
setVisible(true);
}
//设置外观的方法
private void setLookAndFeel(){
try{
UIManager.setLookAndFeel(
UIManager.getSystemLookAndFeelClassName()); //参数的方法时返回一个系统外观
SwingUtilities.updateComponentTreeUI(this); //调用这个方法更新上面设置的外观
}catch(Exception e){
System.err.println("Couldn't use the system look and feel: "+e);
}
}
public static void main(String[] arguments){
FeedInfo frame=new FeedInfo();
}
}
------------------------------------------------------------------------------
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ProgressMonitor extends JFrame{
JProgressBar current; //进度条组件的引用
int num=0; //进度条跟踪的变化值
public ProgressMonitor(){
super("Progress Monitor");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(205,68);
setLayout(new FlowLayout()); //顺序布局
current=new JProgressBar(0,2000); //参数为进度条最小值和最大值
current.setStringPainted(true); //显示百分比
add(current);
}
public void iterate(){
//循环增加这个被跟踪的值,每次加95
//实际上当num==1995时是最后一次循环,但是进度条百分比依然显示的是100%
while(num<2000){
current.setValue(num);
try{
Thread.sleep(1000); // 让线程停滞1000毫秒(=1秒)
}catch(InterruptedException e){
}
num+=95;
}
}
public static void main(String[] arguments){
ProgressMonitor frame=new ProgressMonitor();
frame.setVisible(true);
frame.iterate();
}
}
第11章 在界面上排列组件
布局啊布局,伤透脑筋了,前段时间用记事本来写程序,学习布局把我折腾掺了!新手学习GUI方面
就当了解了解吧,暂时也不用学太深,根据自己选择的方向来更深入的学习好了!
6大布局(一般别人都说是5大):FlowLayout GridLayout BorderLayout CardLayout GridBagLayout
不用想了,这些都是“类”,知道是类了,该怎么做就很简单了——查帮助文档!上面才5个,第6个呢,它就是null。对:setLayout(null); 在第9天的3楼跟贴的就是null布局,做的一个计算器界面,跟XP的计算器非常像的。至于用null怎么布局,我也不会,是跟帖的那个朋友告诉我的。那个计算器界面的例子我以后会在2楼进行详细分析。
下面我贴一个网格袋布局的例子好了,因为传说这是最难学的。不过不能光靠网格袋布局,复杂的界面需要结合多种布局模式——也就是,各个面板中的组件该采用哪种布局模式最好,而所有面板加入框架时又采用哪种布局模式最好。
这里另外提一句,在你继承的框架A中创建了其它框架B(作为属性new)之后,这个B框架的全屏坐标位置好像是以A框架为准,具体我也没研究过,如果谁知道的话跟帖讲解讲解吧。
复制内容到剪贴板
代码:
class MessagePanel extends JPanel{
GridBagLayout gridbag=new GridBagLayout();
public MessagePanel(){
super();
setLayout(gridbag);
//标签和文本框
JLabel toLabel=new JLabel("To: ");
JTextField to=new JTextField(15);
JLabel subjectLabel=new JLabel("Subject: ");
JTextField subject=new JTextField(15);
JLabel ccLabel=new JLabel("CC: ");
JTextField cc=new JTextField(15);
JLabel bccLabel=new JLabel("BCC: ");
JTextField bcc=new JTextField(15);
//调用自定义方法为组件设置布局并添加组件到面板
addComponent(toLabel,0,0,1,1,10,100,GridBagConstraints.NONE,GridBagConstraints.EAST);
addComponent(to,1,0,9,1,90,100,GridBagConstraints.HORIZONTAL,GridBagConstraints.WEST);
addComponent(subjectLabel,0,1,1,1,10,100,GridBagConstraints.NONE,GridBagConstraints.EAST);
addComponent(subject,1,1,9,1,90,100,GridBagConstraints.HORIZONTAL,GridBagConstraints.WEST);
addComponent(ccLabel,0,2,1,1,10,100,GridBagConstraints.NONE,GridBagConstraints.EAST);
addComponent(cc,1,2,4,1,40,100,GridBagConstraints.HORIZONTAL,GridBagConstraints.WEST);
addComponent(bccLabel,5,2,1,1,10,100,GridBagConstraints.NONE,GridBagConstraints.EAST);
addComponent(bcc,6,2,4,1,10,100,GridBagConstraints.HORIZONTAL,GridBagConstraints.WEST);
}
//GridBagConstraints是配合GridBagLayout来使用的,用它定义组件的位置
private void addComponent(Component component,int gridx,int gridy,int gridwidth,int gridheight,int weightx,int weighty,int fill,int anchor){
GridBagConstraints constraints=new GridBa
gConstraints();
constraints.gridx=gridx;
constraints.gridy=gridy;
constraints.gridwidth=gridwidth;
constraints.gridheight=gridheight;
constraints.weightx=weightx;
constraints.weighty=weighty;
constraints.fill=fill;
constraints.anchor=anchor;
gridbag.setConstraints(component,constraints);
add(component);
}
}
你自己写个框架,把这个这个面板添加进去就可以看了。addComponent()这个自定义方法是用来重用的,可以复制下来备用。不过这里的参数还不完全,你需要根据情况添加参数。请参照帮助文档。下面我说一下几个重要的参数的使用和网格袋布局的特点。
网格袋布局是按照你的界面组件的所占空间比例进行格局的。把它想象成一张网格,而整个网格的行和列的数量是你自己决定的。你先确定你的组件之间的比例和位置关系(画一张草图),然后你再确定行和列的网格数量。行和列的网格被你定义得越多,说明你需要的界面排列得越精细(也就是你的所有组件间的比例更加精细,比如你两个组件的长度比例为19:15)。当然,你可以先把网格画出来,这样也更直观一点。组件之间的比例需要分别在横向和纵向上一起考虑,也就是一个连比(19:15:5)。你把图画好了,界面自然就出来了。组件所在的坐标是以左上角为(0,0)号网格(编程中的坐标都是以左上角为原点)
[ 本帖最后由 冰封之灵 于 2009-7-11 22:42 编辑 ]
第12章 响应用户输入
实现组件的事件监听就3步骤:实现接口、为组件设置监听、实现接口的方法。当然也可以使用事件适配器。(在2楼中我将会讨论事件处理机制)
这些监听接口和事件适配器都在java.awt.event包中。
例子:
public class Demo extends JFrame implements ActionListener{
/*代码其它部分省略*/
JButton b=new JButton(“OK”);
public Demo(){
b.addActionListener(this); //将本类作为事件
}
//行为事件接口的方法
public void actionPerformed(){
//添加事件处理的实现
}
}
选择监听接口的时候需要注意的问题是合理选择你需要的接口,有些监听接口并不支持所有的组件。比如这个ActionListener就不支持JLabel的监听。你可以实现更多的接口来让一个组件响应各种事件(如,鼠标点击和敲Enter)
如果你觉得监听接口的方法太多并且不是都能用得上,并且也使你的程序结构更清晰。你可以选择事件适配器作为。它们一些实现了相应监听接口的类。
例子:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public cl
ass KeyChecker extends JFrame{
JLabel keyLabel=new JLabel("Hit any key");
public KeyChecker(){
super("Hit a Key");
setSize(300,200);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLayout(new FlowLayout(FlowLayout.CENTER));
KeyMonitor monitor=new KeyMonitor(this);//创建
setFocusable(true);//设置本类可以获得焦点(我试了,这个方法这里可以不要)(焦点就是默认可以直接键盘操作)
addKeyListener(monitor);//将本类关联到
add(keyLabel);
setVisible(true);
}
public static void main(String[] arguments){
new KeyChecker();
}
}
class KeyMonitor extends KeyAdapter{
KeyChecker display; //上面框架的引用,有了它就可以在事件里面修改其中的组件了
KeyMonitor(KeyChecker display){
this.display=display;
}
public void keyTyped(KeyEvent event){
display.keyLabel.setText(""+event.getKeyChar());//设置那个标签的文本(getKeyChar()方法返回char类型,加上空字符串就可以变成字符串啦)
display.repaint();//重绘此框架(我也试了,这个方法这里可以不要)
}
}
关于事件处理机制原理的推测:
先说说JDK相关源代码的结构(已在源代码中证实)
1、每个事件对象的构造方法都接受一个Component(组件的超类)作为参数(这里可以看出在生成事件对象之前系统就获得了发生事件的那个组件)
2、组件注册的方法(如addActionListener(this))实际上是将对象的引用传递给Component中相关的变量
有了上述两点,那么我们就可以开始解释原理了:
当事件发生,虚拟机通过线程捕捉到事件信息,并获得事件发生位置的组件对象。然后系统将事件信息封装成事件对象。接下来,调用组件中的的引用,通过这个引用就可以将事件对象传递到相应的处理方法中。
由此看来,实际上不是真正意义上的监听者,而更像是一个处理方法的载体
有些源代码中的细节性的东西暂时就不在这里讲了,如果有需要讨论,可以跟帖!
另外说下,在1.0时代,事件处理是在系统获取了组件后,直接在组件的方法中进行处理事件。如果当前组件处理不了,就交给下一层的容器(这种处理模式被称为层次模型)。而现在1.1之后的事件处理被称为委托代理模型(交给处理)
当有多个组件共享一个的时候:就需要用if判断出是哪个组件,代码如下
public actionPerformed(ActionEvent e){
Object source=e.getSource();
if(source==a){
//为a组件时的处理代码
}else if(source==b){
//为b
组件时的处理代码
}
}
从这个侧面也反映出之前原理推测的正确性
[ 本帖最后由 冰封之灵 于 2009-7-15 02:14 编辑 ]
第13章 使用颜色、字体和图形
这章介绍JDK的图形处理库。
使用Java2D进行图形绘制需要继承面板等组件,然后重写相应的方法。如JPanel的paintComponent( )方法。这个绘制方法是在你定义之后是由系统自动调用(导致这个方法被调用的事件有很多,比如,首次显示、组件上窗口被关闭、界面大小调整等情况)
public void paintComponent(Graphics g){
Graphics2D comp2D=(Graphics2D)g;
//用comp2D执行绘制方法
}
对这里的强制转换的理解需要注意:g一定在内存上已经是Graphics2D才能转换,否则不行,当然这是系统自己传进的参数,我们不需要关心。参数定义为父类Graphics的目的是为了让这个参数适应更多类型。(强制转换的问题在较早的天数中已经说过了)
坐标系的问题
界面设计最麻烦的就是坐标系的问题了。Java里面有两种坐标系,设备坐标系和用户坐标系。设备坐标系就是显示器和打印机的输出坐标系,而用户坐标系在这里是一种相对坐标系,以界面左上角为原点(0,0)。在非Java范畴里有一种被称为逻辑坐标系的抽象坐标系。它是操作系统级别上的一种抽象坐标系。
我们按自己的抽象坐标系标准定义了界面组件的坐标位置关系后,转化到设备输出(如屏幕)上时,坐标值是发生了变化的,因为各种设备的尺寸是有差别的。这就需要把我们的抽象坐标系的位置关系转化成设备上的坐标位置关系(坐标值会产生偏移,甚至位置关系也会有所不同),这也被称为坐标映射,对不同的设备,映射模式是不同的。当然这些不需要我们操心了,做个了解就行了,我也没有深入的研究这方面的知识。
Java中的用户坐标系的坐标传进Graphics2D中进行绘制的时候,系统会将用户坐标系向设备坐标系进行转换。而定义这种转换关系的类是java.awt.geom.AffineTransformk 有需要和兴趣的可以自己下来研究一下哈。
[ 本帖最后由 冰封之灵 于 2009-7-18 11:39 编辑 ]
第14章 开发Swing应用程序
这一章介绍了Java Web Start技术和SwingWorker(工作线程)。Web Start使应用程序能运行在浏览器中。而SwingWorker是一个新增的线程类,通过使用线程,简化了GUI的负担,使界面可以更加快速的响应用户的操作。
由于Web Start的介绍比较麻烦,所以暂时放在一边,以后有机会再2楼进行介绍。下面说说java.swing.SwingWorker这个类。(当然你也可以自己定义线程,不过这个类是专门为优化swing而设计的)
S
wingWorker是个抽象类。继承它,并覆盖doInBackground()方法。
例子:
import javax.swing.*;
public class DiceWorker extends SwingWorker{
//自己定义需要执行的代码,覆盖doInBackground()
protected int[] doInBackground(){ //访问控制符定义为protected,返回的对象自己定义
//线程执行的代码
}
}
这个类的对象一般是在发生界面事件的时候,在事件处理方法中创建。具体执行方法如下:
DiceWorker worker; //这个引用应该放在事件处理方法的外面,作为实例变量
worker=new DiceWorker(); //有需要时可以定义构造方法参数
worker.addPropertyChangeListener(this); //注册。(不一定要用this作为)
worker.execute();//开始执行线程,这个方法会调用doInBackground()方法
以上4个步骤就OK了,另外有个(int[])worker.get();这个方法来获取doInBackground()的回值
这里的必须实现PropertyChangLister接口,它在java.beans包中,所以应该import。这里用this是表示:用当前类实现这个接口作为。此也遵循我在第12天的活动中介绍的事件处理原理中的一点,也就是把引用传到这个对象中(这里用的this)。当线程的工作执行结束之后,就会调用接口PropertyChangLister的propertyChange()方法,并传入一个事件对象。你也可以在接口的事件处理方法中执行一些后期处理(比如处理工作线程的返回值)
例子:
public void propertyChange(PropertyChangeEvent event){
//该干嘛干嘛
//书中的例子程序把当前类作为,由于实例变量定义了worker这个引用,所以可以直接在这个方法中使用
}
附加说明:
1、SwingWorker实现了RunnableFuture接口,而这个接口extends了Runnable和Future这两个接口(接口可以extends多个接口,用逗号隔开)。而实现Runnable就使它具备了能传入线程的性质。
2、SwingWorker是个泛型类——SwingWorker最好把它按泛型的的使用方式来使用。
3、建议使用protected作为doInBackground()方法的访问控制符,防止外包中的类调用这个方法。
[ 本帖最后由 冰封之灵 于 2009-7-19 22:43 编辑 ]
第15章 输入和输出
这一章讲了输入输出流的使用,初学者需要理清java.io包中各种流的类的关系就OK了。I/O流分为字节流和字符流(也叫byte流或char流)。这两种流分别对应各自的高层超类。Byte流继承自InputStream和OutputStream。Char流继承自Reader和Writer。
字节流
FileOutputStream、BufferedOutputStream、DataOutputStream属于字节流。其中后两个又被称为过滤流。它们的存在是属于设计模式的范畴了。这3个流是可
嵌套的,初学者可以把这些类想象成一种管道。而FileOutputStream是比较底层的流管道,底层就是说它离数据存储地更近。而后两种流可以想象成接在FileOutputStream管道上的器件,具有对数据进行特殊处理的方法。
BufferedOutputStream用于缓冲数据(划分一定的内存空间,把数据先存在内存中,等空间满了后一次写入磁盘,这样就减少了对磁盘的反复操作,因为磁盘的速率比内存慢很多)
DataOutputStream使我们可以直接读写boolean、byte、double、float、int、long、short这些数据,方便了我们的使用。
上面列举的都是用来输出的,输入的情况类似:FileInputStream、BufferedInputStream、DataInputStream。只是流的方向变了
/*下面是管道的拼接,实际上就是传递引用*/
FileOutputStream file=new FileOutputStream(“name1\\\
ame2.dat”);
BufferedOutputStream buff=new BufferedOutputStream(file);
DataOutputStream data=new DataOutputStream(buff);
附加说明:上述流是可以嵌套使用的,这是JDK类库的设计模式,使对流的控制更加灵活。其实缓冲就是定义了byte类型的数组作为缓冲区,而清空缓冲区实际上也是改变下标变量为0然后重新写入数据,覆盖原来的数据,具体的实现你可以看看JDK的源代码。可以参见这篇帖子:http://bbs.chinaitlab.com/thread-263116-1-1.html
字符流
字符流是以字符为单位来读取或写入数据,也就是根据字符的字节数来进行处理。这样就维护了文本所表示的数据的正确性。一般使用的是FileReader和FileWriter。
FileReader是继承自InputStreamReader(这个类已经可以使用了,只是FileReader作了进一步的扩展),FileWriter的情况是类似的。
FileReader file=new FileReader(“SourceReader.txt”);
BufferedReader buff=new Buffered Reader(file);
流的使用规则是大致相同的,创建流之后就可以使用read(),readLine(),write等输出输出方法来读写数据了。另外,当流使用完毕之后就应该将其关闭,调用方法如buff.close( );以释放其占用的系统资源。
[ 本帖最后由 冰封之灵 于 2009-7-24 22:41 编辑 ]
第16章 序列化和查看对象
这章介绍了对象序列化和反射。
序列化是用流来实现的,ObjectInputStream和ObjectOutputStream直接继承自InputStream和OutputStream。因此它们自己也具有流的方法,可以当做I/O流来使用。
反序列化过程中的判断
如果被反序列化的对象定义了一个readObject()方法,那么在反序列化的过程中这个方法会被调用。
现在推测:在内存中先复原出这个方法并调用,如果这个方法里面调用了对象流的defaultReadObject()方法,那么就会再复原出这个
个对象的属性(会认为是在对象完全复原之前就先调用了这个方法的原因就是,因为如果是在对象复原完毕后才调用这个方法,那么就不需要用defaultReadObject()来复原属性)。
通过对属性值的判断,我们就可以知道这个对象是不是我们所需要的,这时,你就可以抛出一个异常,并在catch块中进行处理。
注意,这个需要说明的是:抛出这个异常是一种正确的处理行为。在流那一章,使用while(true)这个死循环来读取整数,最后抛出EOFException。这些都是是程序员的一种正确的处理行为,并不是程序所出现的问题。
复制内容到剪贴板
代码:
import java.io.*;
/*实现这个接口使这个类能被序列化,这个接口是空的,这是接口的一种用法是:赋予这个类一个新的特性,使它可以传入参数被声明为接口的方法中。*/
public class Test implements Serializable {
int n = 1; //我只是编写了一个简单的类来说明这种机制
private void readObject(ObjectInputStream ois) throws IOException,ClassNotFoundException {
ois.defaultReadObject();//复原属性
if (n == 1) { //如果n==1就抛出异常,使反序列化过程结束
System.out.println("将出现异常");
throw new IOException("异常出现");
}
}
public static void main(String[] args) {
try {
//序列化对象,使其成为串行的字节序列。序列化后得到的文件为每个被引用的对象都分配了一个内存序列号,使某个对象被多个对象引用后,确保只有一个这个对象的拷贝被序列化
FileOutputStreasm fos = new FileOutputStream("out.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Test t1 = new Test();
oos.writeObject(t1);
oos.close();
//反序列化过程
FileInputStream fis = new FileInputStream("out.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
Test t2 = (Test) ois.readObject();
ois.close();
System.out.println("完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
附加说明:为readObject这个方法抛出了IOException和ClassNotFoundException这两个异常,是将defaultReadObject这个方法可能抛出的这两个异常传递给了readObject方法,这样就不用再方法内部用try-catch去捕捉。另外,关闭流的时候,只需要关闭最后一个附加的流就可以了,这种装饰者设计模式在方法中调用了关闭其它流的方法。
[ 本帖最后由 冰封之灵 于 2009-7-28 11:56 编辑 ]
第17章
通过Internet进行通信
使用Socket和ServerSocket实现C/S结构的程序不是Java的强项,不过可以作一个了解。下面我贴出我写的简易QQ服务器和客户端通信的部分代码,可能稍微粗糙了点,主要是也没想过要太仔细的去学C/S结构。
客户端链接服务器部分代码
复制内容到剪贴板
代码:
//连接服务器
try{
Socket digit=new Socket("localhost
//设置输入流
InputStreamReader isr=new InputStreamReader(conn.getInputStream());
BufferedReader is=new BufferedReader(isr);
//获取客户端IP
InetAddress ip=conn.getLocalAddress();
String ipAddress=ip.getHostAddress()+"&"+ip.getHostName();
System.out.println("成功获取地址");
//获取输入流字符串
String inLine="";
boolean bool=true;
while(bool){
String inTemp=is.readLine();
if(inTemp==null){
bool=false;
break;
}
inLine=inLine+inTemp;
}
//分隔输入流字符串并判断输入流字符串类型
String[] inString=inLine.split("#
登陆!");
ifLogin+=friendsList;
System.out.println("回复字符串:"+ifLogin);
//回复客户端
pw.println(ifLogin);
pw.flush();
//关闭流
pw.close();
is.close();
System.out.println("回复发送成功!");
}
}else if(inString[0].equals("register")){
//处理其它请求
}
[ 本帖最后由 冰封之灵 于 2009-7-29 16:25 编辑 ]