第一讲:Android开发环境的搭建

说明,本讲和后续内容讨论的是Android2.2版本.
本讲内容

Android简介
Android开发环境的搭建
测试开发环境,新建Android程序
测试开发环境,运行Android程序

本讲源代码下载链接Lesson1_HelloAndroid

 

一、Android简介
Android 是基于Linux内核的软件平台和操作系统。
Android构架主要由3部分组成,linux内核层,类库、虚拟机和核心组件库层,应用程序框架层
Android应用程序使用JAVA语言进行开发。
二、开发环境的搭建

软件的准备:
JAVA  JDK 1.6
Eclipse 3.6           (eclipse-java-helios-win32.zip)
ADT 0.9.7             (Android Development Tools)
SDK Tools R6       (android-sdk_r06-windows.zip)

  1. JDK的安装
  2. Eclipse的安装
  3. ADT(Android Development Tools)的安装
    ADT是Eclipse的插件,是用Eclipse进行Android开发的开发工具,它本身不是Android SDK.
    安装方法和同其他Eclipse插件的方法一样(Help-> Install New Software…).
    ADT安装成功后eclipse工具栏中会出现小机器人图标
    image
  4. SDK Tools的安装
    SDK Tools 本身也不是Android SDK,而是SDK的下载工具和配置工具,通过SDK Tools去下载各种版本的SDK。ADT里配置SDK路径的时候实际上他是在寻找SDK Tools,所以我们不单独下载和讨论SDK。
    安装过程需要联网
    第一步:Windows下运行SDK Setup.exe, Mac下运行tools\android
    第二步:在国内安装SDK时,需要把Settings里的Force https://… 选项勾选中才可以正常下载.image第三步:选择可用安装包Available Packages,选择安装选择的内容,然后联网下载整个过程需要大约1小时。
    image
    安装完成后可以在Installed Packages里看到所有的安装包。image
  5. 在ADT里配置SDK
    Eclipse->Windows->Android ->SDK Location,把SDK TOOLS的根目录指定给它,至此环境搭建完毕
    image

 

 

三、测试环境:建新项目

我们通过新建一个Android项目并运行的方式来测试环境是否安装正确。创建步骤如下:
1、Eclipse ->File ->new Android Project
2、Project name: Lesson1_HelloAndroid
3、Build Target 勾选 Android 2.2
4、Application name: HelloAndroid
5、Package name: android.basic.lesson1.helloandroid
6、Create Activity: MainHelloAndroid
7、Min SDK Version:8

创建该项目的教学视频在这里:第一讲视频:创建Lesson1_HelloAndroid项目

image

四、测试环境:运行新项目

 

新建虚拟设备AVD,运行一个项目之前需要先建一个手机模拟器。
在Eclipse中点小机器人 -> Virtua Devices -> New…
Name: 2.2_400
Target: Android 2.2 – API level 8
SD card : Size     50MiB
Skin:  WQVGA400
Create AVD
image

运行Android项目:
在项目名称上点右键,Run AS…
选择 Android Application,此时会启动模拟器并运行程序,启动时间需要几分钟,启动并运行正常时会出现类似下图的内容。这就说明你的开发环境搭建完毕。

模拟器启动后效果如下页所示:

image

向右拖动锁头,可以看到程序运行情况,Hello,World. MainHelloAndroid ! 至此说明环境搭建并测试通过。

image

注:我在Windows 7,Eclipse 3.6 和 Mac 10.6.4,Eclipse3.6下测试通过。

分享至上:分享源头

第二十一讲:网络编程(一)

本讲内容: socket 只会装傻的”智能“机器人

套接字(socket)是代表计算机之间网络连接的对象。要进行计算机间的网络通讯,建立TCP连接,那么就需要Socket编程,Java提供了ServerSocket类和Socket类,可以利用他们很方便的实现,计算机间的网络通讯。

因为Java把数据的传输已经抽象成InputStream和OutputStream的输入输出流,那么对网络通讯来说,其实就是利用ServerSocket类和Socket类建立起来网络连接,进行字节流或者字符流的输入输出操作。

下面我们还是通过一个例子来学习Socket编程:只会装傻的”智能“机器人

直接上代码,服务器端 TalkServer.java :

 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TalkServer {

	public static void main(String[] args) throws IOException {

		// 创建一个ServerSocket
		ServerSocket server = new ServerSocket(8080);

		// 准备接受客户连接,如果有客户连接到,则创建一个socket对象,如果没有客户连接到,那么该程序会停在本行一直等待,像是在睡眠或者假死,这种行为叫做“阻塞”,阻塞是网络通信中的术语,就是你等别人响应,如果必须等到,等不到就一直等,就是阻塞,等不到也继续向前走的当然就是非阻塞。
		Socket socket = server.accept();

		// 从socket中获取一个输入对象,以便接受从网络上来的数据
		BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));

		// 从标准输入中获取一个输入对象,以便接受从键盘上传来的数据,如果想让服务器端和客户端自由对话的话可以用此对象
		//BufferedReader keyboardInput = new BufferedReader(new InputStreamReader(System.in));

		// 从socket中获取一个
		PrintWriter socketOutput = new PrintWriter(socket.getOutputStream(),true);

		//定义一些变量
		String input;		

		// 一直执行,直到客户端表示要断开
		while(true){
			//从客户端获取用户输入的字符串
			input = socketInput.readLine();
			//发送到屏幕
			System.out.print("客户:");
			System.out.println(input);			

			//准备好回复语句
			String reply= reply(input);
			//发送到客户端
			socketOutput.println(reply);
			//发送到屏幕
			System.out.print("小瑶瑶:");
			System.out.println(reply);

			//客户如果输入的是再见则退出循环
			if(input.equals("再见")){
				break;
			}
		}	

		//关闭释放资源
		socketInput.close();
		socketOutput.close();
		socket.close();

	}

	//回复方法
	private static String reply(String input) {

		String output=replys[number%7];

		if(number%7==5){
			output = output+" "+input;
		}

		number++;
		return output;
	}

	//回复方法用到的一些变量
	static int number =0;

	static String[] replys = new String[7];

	static{
		replys[0]= "你说的啥,我听不见?";
		replys[1]= "声音再大点行不?";
		replys[2]= "声音再大点一点点行不?";
		replys[3]= "还是听不清……";
		replys[4]= "看来是网络的问题,你要不说慢点?";
		replys[5]= "听到了!听到了!你说的是:";
		replys[6]= "完了,又听不清了……";
	}

}

客户端代码 TalkClient.java :

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class TalkClient {

	public static void main(String[] args) throws UnknownHostException, IOException {

		Socket socket = new Socket("127.0.0.1",8080);

		// 从socket中获取一个输入对象,以便接受从网络上来的数据
		BufferedReader socketInput = new BufferedReader(new InputStreamReader(socket.getInputStream()));

		// 从标准输入中获取一个输入对象,以便接受从键盘上传来的数据
		BufferedReader keyboardInput = new BufferedReader(new InputStreamReader(System.in));

		// 从socket中获取一个
		PrintWriter socketOutput = new PrintWriter(socket.getOutputStream(),true);

		// 定义一些变量
		String input;		

		// 一直执行
		while(true){

			//从键盘获取数据,通过socket对象发送出去
			input = keyboardInput.readLine();
			socketOutput.println(input);

			//接收从socket中获取的服务器端发回的回应信息,并发送到屏幕上
			input = socketInput.readLine();
			System.out.println(input);
		}

	}

}

编译两个文件,先运行TalkServer,开始接收客户端的连接,然后运行客户端,在客户端上打字,服务器端会有回应,运行截图如下,这里上一个服务器端的截图:

image

好了,本将就到这里。

分享至上:分享源头

Java基础第二十讲:输入输出(二)

本讲内容:示例《圣诞节的表白》

有一个害羞的男孩决定在圣诞节的这天向她表白……,因为奇怪的条件限制只能把表白的内容写在一台公共的计算机上。为了只让她看到,而不会被其他人发 现,男孩用他刚学习的输入输出知识写了一些代码,他建立了一个文件,在文件里打印了一些内容,建立了1亿个文件夹,把自己的文件藏在混淆目录中,待女孩子 看完之后,又把这些代码和文件夹都删除了:

 

001 import java.io.BufferedReader;
002 import java.io.File;
003 import java.io.FileNotFoundException;
004 import java.io.FileReader;
005 import java.io.FileWriter;
006 import java.io.IOException;
007 import java.io.InputStreamReader;
008
009 public class HappyChristmas {
010
011     // 创建多层文件夹
012     public static boolean createDirectory(File parent, int level) {
013
014         File[] fa = new File[10];
015         for (int i = 0; i < level; i++) {
016             for (int j = 0; j < 10; j++) {
017                 fa[j] = new File(parent, "" + j);
018                 fa[j].mkdir();
019                 createDirectory(fa[j], level - (i + 1));
020             }
021         }
022
023         return true;
024     }
025
026     // 删除文件夹
027     public static boolean deleteDirectory(File file) {
028         if (file.delete()) {
029             return true;
030         } else {
031             File[] files = file.listFiles();
032             for (int i = 0; i < files.length; i++) {
033                 if (files[i].isFile()) {
034                     files[i].delete();
035                 } else {
036                     deleteDirectory(files[i]);
037                 }
038             }
039             if (file.delete()) {
040                 return true;
041             }
042         }
043         return true;
044
045     }
046
047     public static void main(String[] args) {
048
049         // 创建一个目录 c:\happy
050
051         File file1 = new File("c:\\happy");
052
053         if (file1.mkdir()) {
054             System.out.println("c:\\happy" + "目录创建成功");
055         } else {
056             System.out.println("c:\\happy" + "目录创建失败");
057         }
058
059         // 创建一个文件 c:\java\happyChristmas.txt
060         File file2 = new File("c:\\happy\\happyChristmas.txt");
061         try {
062             if (file2.createNewFile()) {
063                 System.out.println("c:\\happy\\happyChristmas.txt" + "文件创建成功");
064             } else {
065                 System.out.println("c:\\happy\\happyChristmas.txt" + "文件创建失败");
066             }
067         } catch (IOException e) {
068             e.printStackTrace();
069         }
070
071         // 创建一个多级目录 c:\java\2\0\1\0\1\2\2\4
072
073         File file3 = new File("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4");
074
075         if (file3.mkdirs()) {
076             System.out
077                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4" + "多级目录创建成功");
078         } else {
079             System.out
080                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4" + "多级目录创建失败");
081         }
082
083         // 移动happyChristmas.txt到 c:\java\2\0\1\0\1\2\2\4
084         File file4 = new File(
085                 "c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt");
086
087         if (file2.renameTo(file4)) {
088             System.out
089                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt"
090                             + "文件拷贝成功");
091         } else {
092             System.out
093                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt"
094                             + "文件拷贝失败");
095         }
096
097         //从控制台输入内容存储到happyChristmas.txt中
098         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
099         System.out.println("请输入内容:\n");
100         try {
101             FileWriter writer = new FileWriter(file4.toString());
102
103             while(!(in.readLine().equalsIgnoreCase("exit"))){
104                 String happyString = in.readLine();
105                 System.out.print("您输入的内容是: \""+happyString+"\",正在存储中\n");
106                 writer.write(happyString+System.getProperty("line.separator"));
107             }
108             writer.flush();
109             writer.close();                
110
111         } catch (IOException e) {
112             e.printStackTrace();
113         }
114
115         //从文件中读取
116         try {
117             FileReader fr = new FileReader(file4.toString());
118             BufferedReader br = new BufferedReader(fr);
119             String ls = br.readLine();
120
121             System.out.println("读取中,文件的内容如下:");
122
123             while(ls!=null){
124                 System.out.println(ls);
125                 ls = br.readLine();
126             }
127
128         } catch (FileNotFoundException e) {
129             e.printStackTrace();
130         } catch (IOException e) {
131             e.printStackTrace();
132         }
133
134         // 创建8层混淆目录
135         if (createDirectory(file1, 8)) {
136             System.out.println("创建混淆目录成功");
137         } else {
138             System.out.println("创建混淆目录失败");
139         }
140
141         // 删除文件 c:\happy\2\0\1\0\1\2\2\4\happyChristmas.txt
142         if (file4.delete()) {
143             System.out
144                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt"
145                             + "删除文件成功");
146         } else {
147             System.out
148                     .println("c:\\happy\\2\\0\\1\\0\\1\\2\\2\\4\\happyChristmas.txt"
149                             + "删除文件失败");
150         }
151
152         // 删除 c:\happy 目录
153         if (deleteDirectory(file1)) {
154             System.out.println(file1.toString() + "文件夹删除成功");
155         } else {
156             System.out.println(file1.toString() + "文件夹删除失败");
157         }
158
159     }
160
161 }

这里爆个小料,男孩子在文件里的内容我悄悄的看了一下,大致是这样的:“我想了很久,终于鼓起勇气向你写这封信,你…… 你欠我的钱该还了吧!” …… 随后是欠债清单:“ 5岁时,借了一块钱买了2只棒棒糖,自己吃了一只,另外一只竟然给了另一个小男孩,6岁时……,12岁时…… 等等 ”

按照好莱坞式的结局……

女孩子看完信之后,心里一片感动,神情的拉着男孩子的手说:我愿意拿一生来还债^_^,随后他们一起过着幸福而快乐的生活。

分享至上:分享源头

Java基础第十七讲:异常处理(一)

本讲内容:异常

软件开发中有80%的工作是用来检查和处理错误,而检查并处理错误很多时候是一件枯燥无趣的事情,如果在语言级别提供一些帮助的话,会减轻一些程序员的负担。

而Java提供了一套比较优秀的异常处理机制:
1、使开发人员不必编写特殊代码来测试返回值就能发现问题,
2、在语法结构就把正常的代码和异常处理的代码清晰的分开来,
3、允许我们使用相同的异常处理代码来处理一定范围内的所有异常。
以期产生一种高效的、有组织的异常处理方式。

1、异常及异常的分类

异常是指在程序中出现的异常状况,在Java中异常被抽象成一个叫做Throwable的类。

其中如果程序出错并不是由程序本身引起的,而是硬件等其他原因引起的,我们称之为Error,一般情况下Error一旦产生,对程序来说都是致命的错误,程序本身无能为力,所以我们可以不对Error作出任何处理和响应。

异常如果是由程序引起的我们称之为Exception,Exception又分两种,我们把运行时才会出现的异常叫做 RuntimeException,RuntimeException我们不好在程序编写阶段加以事先处理,而其他异常则可以在程序编写和编译阶段加以事 先检查和处理,我们把这种异常叫做检验异常。

程序只需要捕捉和处理检验异常。

相应的我们把除检验异常(可检查异常)之外的异常称之为非检验异常,包括Error和RuntimeException ,非检验异常可以捕捉也可以不捕捉,更多的时候我们不捕捉,因为捕捉了我们也没办法处理,譬如程序运行时发生了一个 VirtualMachineError异常,虚拟机都出错了,作为运行在虚拟机内的程序又有什么办法处理呢?

下面我们用图来表示一下上面讲述的内容:

image
2、异常的处理 try ^ catch ^ finally

异常相关的处理语法可以用下图概括一下:

image

异常处理的几条规则:

  1. try用于定义可能发生异常的代码段,这个代码块被称为监视区域,所有可能出现检验异常的代码写在这里。
  2. catch代码段紧跟在try代码段后面,中间不能有任何其他代码。
  3. try后面可以没catch代码段,这实际上是放弃了捕捉异常,把异常捕捉的任务交给调用栈的上一层代码。
  4. try后面可以有一个或者多个catch代码段,如果有多个catch代码段那么程序只会进入其中某一个catch。
  5. catch捕捉的多个异常之间有继承关系的话,要先捕捉子类后捕捉父类。
  6. finally代码段可以要也可以不要。
  7. 如果try代码段没有产生异常,那么finally代码段会被立即执行,如果产生了异常,那么finally代码段会在catch代码段执行完成后立即执行。
  8. 可以只有try和finally没有catch。

下面提供一个使用异常的例子,请注意看相关注释:

001 import java.io.BufferedReader;
002 import java.io.BufferedWriter;
003 import java.io.FileNotFoundException;
004 import java.io.FileReader;
005 import java.io.FileWriter;
006 import java.io.IOException;
007 import java.util.ArrayList;
008 import java.util.List;
009
010 public class Bank02 {
011
012     //用ArrayList存储储户信息
013     List<String[]> list = new ArrayList<String[]>();
014
015     //用初始化块准备一些测试数据
016     {
017         String[] account = { "10001", "aya", "111111", "60000" };
018         list.add(account);
019         account = new String[] { "10002", "bean", "222222", "40000" };
020         list.add(account);
021     }
022
023     //用BufferedWriter和BufferedReader读写文件
024     BufferedWriter bw;
025     BufferedReader br;
026
027     // 字符串数组转字符串,试图写一个通用方法
028     public static String arrayToString(String[] input, String space) {
029         String output = "";
030         for (int i = 0; i < input.length; i++) {
031             output += input[i] + space;
032         }
033         return output.substring(0, output.length() - 1);
034     }
035
036     // 写文件
037     public void writeFile() {
038         try {
039             //try代码块又称之为"监视区域",表示有可能出问题闹异常的代码写在这里
040             bw = new BufferedWriter(new FileWriter("bank.txt"));
041             for (String[] account : list) {
042                 bw.write(arrayToString(account, " "));
043                 bw.newLine();
044             }
045             bw.flush();
046         } catch (IOException e) {
047             //可以使用一个catch来捕捉异常
048             //doSomething
049
050             //打印出异常的堆栈踪迹
051             e.printStackTrace();
052         } finally {
053             //finally提供了一个在正常时和异常时都需要执行的代码段,在这里可以进行一些资源的释放工作,数据的清理工作
054             try {
055                 bw.close();
056             } catch (IOException e) {
057                 e.printStackTrace();
058             }
059         }
060
061     }
062
063     // 读文件
064     public void readFile() {
065         try {
066             //try代码块又称之为"监视区域",表示有可能出问题闹异常的代码写在这里
067             br = new BufferedReader(new FileReader("bank.txt"));
068             String temp = "";
069             list.clear();
070             while ((temp = br.readLine()) != null) {
071                 list.add(temp.split(""));
072             }
073         } catch (FileNotFoundException e) {
074             //可以使用一个catch来捕捉异常
075             //doSomething
076
077             //打印出异常的堆栈踪迹
078             e.printStackTrace();
079         } catch (IOException e) {
080             //也可以用一组catch来捕捉多个异常,如果多个异常之间有继承关系,那么父类异常写下面
081             e.printStackTrace();
082         }finally{
083             //finally提供了一个在正常时和异常时都需要执行的代码段,在这里可以进行一些资源的释放工作,数据的清理工作
084             try {
085                 br.close();
086             } catch (IOException e) {
087                 e.printStackTrace();
088             }
089         }
090     }
091
092     public static void main(String[] args) {
093         //创建一个bank对象
094         Bank02 bank = new Bank02();
095         //写入数据
096         bank.writeFile();
097         //读取数据
098         bank.readFile();
099     }
100
101 }

3、常见异常

ArrayIndexOfBoundsException 数组下标越界异常
ClassCastException 强制转换类失败异常
IllegalArgumentException 方法参数类型传入异常
IllegalStateException 非法的设备状态异常
NullPointException 传说中的空指针异常,如果一个对象不存在,你有对这个对象使用点操作,那么就会出现该异常
NumberFormatException 把字符串转成数字失败时出现的数字格式异常
AssertionError 断言错误
ExceptionInInitializerError 试图初始化静态变量或者静态初始化块时抛出
StackOverflowError 栈溢出错误
NoClassDefFoundError 找不到指定的类错误

 

好了本讲就到这里,下节课我们讲自定义异常和调用栈。

分享至上:分享源头

Java基础第十六讲:集合(二)

本讲内容:Map HashMap

前面课程中我们知道Map是个接口,它关心的是映射关系,它里面的元素是成对出现的,键和值都是对象且键必须保持唯一。这一点上看它和Collection是很不相同的。

一、Map接口

Map接口的常用方法如下表所示:

put(K key, V value) 向集合中添加指定的键值对
putAll(Map <? extends K,? extends V> t) 把一个Map中的所有键值对添加到该集合
containsKey(Object key) 如果包含该键,则返回true
containsValue(Object value) 如果包含该值,则返回true
get(Object key) 根据键,返回相应的值对象
keySet() 将该集合中的所有键以Set集合形式返回
values() 将该集合中所有的值以Collection形式返回
remove(Object key) 如果存在指定的键,则移除该键值对,返回键所对应的值,如果不存在则返回null
clear() 移除Map中的所有键值对,或者说就是清空集合
isEmpty() 查看Map中是否存在键值对
size() 查看集合中包含键值对的个数,返回int类型

 

因为Map中的键必须是唯一的,所以虽然键可以是null,只能由一个键是null,而Map中的值可没有这种限制,值为null的情况经 常出现,因此get(Object key)方法返回null,有两种情况一种是确实不存在该键值对,二是该键对应的值对象为null。为了确保某Map中确实有某个键,应该使用的方法是 containsKey(Object key) 。

二、HashMap

HashMap是最常用的Map集合,它的键值对在存储时要根据键的哈希码来确定值放在哪里。

1、HashMap的基本使用:

 

01 import java.util.Collection;
02 import java.util.HashMap;
03 import java.util.Map;
04 import java.util.Set;
05
06 public class Test {
07
08     public static void main(String[] args) {
09
10         Map<Integer,String> map = new HashMap<Integer,String>();
11
12         map.put(1, "白菜");
13         map.put(2, "萝卜");
14         map.put(3, "茄子");
15         map.put(4, null);
16         map.put(null, null);
17         map.put(null, null);
18
19         System.out.println("map.size()="+map.size());
20         System.out.println("map.containsKey(1)="+map.containsKey(2));
21         System.out.println("map.containsKey(null)="+map.containsKey(null));
22         System.out.println("map.get(null)="+map.get(null));
23
24         System.out.println("map.get(2)="+map.get(2));
25         map.put(null, "黄瓜");
26         System.out.println("map.get(null)="+map.get(null));
27
28         Set set = map.keySet();
29         System.out.println("set="+set);
30
31         Collection<String> c = map.values();
32
33         System.out.println("Collection="+c);
34
35     }
36
37 }

编译并运行程序,查看结果:

1 map.size()=5
2 map.containsKey(1)=true
3 map.containsKey(null)=true
4 map.get(null)=null
5 map.get(2)=萝卜
6 map.get(null)=黄瓜
7 set=[null, 1, 2, 3, 4]
8 Collection=[黄瓜, 白菜, 萝卜, 茄子, null]

2、HashMap 中作为键的对象必须重写Object的hashCode()方法和equals()方法

下面看一个我花了1个小时构思的例子,熟悉龙枪的朋友看起来会比较亲切,设定了龙和龙的巢穴,然后把它们用Map集合对应起来,我们可以根据龙查看它巢穴中的宝藏数量,例子只是为了说明hashCode这个知识点,所以未必有太强的故事性和合理性,凑合看吧:

01 import java.util.HashMap;
02 import java.util.Map;
03
04 public class Test {
05
06     public static void main(String[] args) {
07
08         // 龙和它的巢穴映射表
09         Map<dragon , Nest> map = new HashMap<dragon , Nest>();
10
11         // 在Map中放入四只克莱恩大陆上的龙
12         map.put(new Dragon("锐刃", 98), new Nest(98));
13         map.put(new Dragon("明镜", 95), new Nest(95));
14         map.put(new Dragon("碧雷", 176), new Nest(176));
15         map.put(new Dragon("玛烈", 255), new Nest(255));
16
17         // 查看宝藏
18         System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure());
19     }
20
21 }
22
23 // 龙
24 class Dragon {
25
26     Dragon(String name, int level) {
27         this.level = level;
28         this.name = name;
29     }
30
31     // 龙的名字
32     private String name;
33
34     // 龙的级别
35     private int level;
36
37     public int getLevel() {
38         return level;
39     }
40
41     public void setLevel(int level) {
42         this.level = level;
43     }
44
45     public String getName() {
46         return name;
47     }
48
49     public void setName(String name) {
50         this.name = name;
51     }
52
53 }
54
55 // 巢穴
56 class Nest {
57
58     //我研究的龙之常数
59     final int DRAGON_M = 4162;
60
61     // 宝藏
62     private int treasure;
63
64     // 居住的龙的级别
65     private int level;
66
67     Nest(int level) {
68         this.level = level;
69         this.treasure = level * level * DRAGON_M;
70     }
71
72     int getTreasure() {
73         return treasure;
74     }
75
76     public int getLevel() {
77         return level;
78     }
79
80     public void setLevel(int level) {
81         this.level = level;
82         this.treasure = level * level * DRAGON_M;
83     }
84
85 }

编译并运行查看结果:

1 Exception in thread "main" java.lang.NullPointerException
2     at Test.main(Test.java:18)

我们发现竟然报了错误,第18行出了空指针错误,也就是说get方法竟然没有拿到预期的巢穴对象。

在这里我们就要研究一下为什么取不到了。我们这里先解释一下HashMap的工作方式。

image

假设现在有个6张中奖彩票的存根,放在5个桶里(彩票首位只有1-5,首位是1的就放在一号桶,是2的就放在2号桶,依次类推),现在你拿了3张彩 票来兑奖,一个号码是113,一个号码是213,一个号码是313。那么现在先兑第一张,取出一号桶里的存根发现存根号码和你的号码不符,所以你第一张没 中奖。继续兑第二张,二号桶里就没存根所以就直接放弃了,把三号桶里的所有彩票存根都拿出来对应一番,最后发现有一个存根恰好是313,那么恭喜你中奖 了。

HashMap在确定一个键对象和另一个键对象是否是相同时用了同样的方法,每个桶就是一个键对象的散列码值,桶里放的就是散列码相同的彩票存根, 如果散列码不同,那么肯定没有相关元素存在,如果散列码相同,那么还要用键的equals()方法去比较是否相同,如果相同才认为是相同的键。简单的说就 是 hashCode()相同 && equals()==true 时才算两者相同。

到了这里我们应该明白了,在没有重写一个对象的hashcode()和equals()方法之前,它们执行的是Object中对应的方法。而 Object的hashcode()是用对象在内存中存放的位置计算出来的,每个对象实例都不相同。Object的equals()的实现更简单就是看两 个对象是否==,也就是两个对象除非是同一个对象,否则根本不会相同。因此上面的例子虽然都是名字叫碧雷的龙,但是HashMap中却无法认可它们是相同 的。

因此我们只有重写Key对象的hashCode()和equals()方法,才能避免这种情形出现,好在Eclipse可以帮我们自动生成一个类的hashCode()和equals(),我们把上面的例子加上这两个方法再试试看:

001 import java.util.HashMap;
002 import java.util.Map;
003
004 public class Test {
005
006     public static void main(String[] args) {
007
008         // 龙和它的巢穴映射表
009         Map<dragon , Nest> map = new HashMap<dragon , Nest>();
010
011         // 在Map中放入四只克莱恩大陆上的龙
012         map.put(new Dragon("锐刃", 98), new Nest(98));
013         map.put(new Dragon("明镜", 95), new Nest(95));
014         map.put(new Dragon("碧雷", 176), new Nest(176));
015         map.put(new Dragon("玛烈", 255), new Nest(255));
016
017         // 查看宝藏
018         System.out.println("碧雷巢穴中有多少宝藏:" + map.get(new Dragon("碧雷", 176)).getTreasure());
019     }
020
021 }
022
023 // 龙
024 class Dragon {
025
026     Dragon(String name, int level) {
027         this.level = level;
028         this.name = name;
029     }
030
031     // 龙的名字
032     private String name;
033
034     // 龙的级别
035     private int level;
036
037     public int getLevel() {
038         return level;
039     }
040
041     public void setLevel(int level) {
042         this.level = level;
043     }
044
045     public String getName() {
046         return name;
047     }
048
049     public void setName(String name) {
050         this.name = name;
051     }
052
053     @Override
054     public int hashCode() {
055         final int PRIME = 31;
056         int result = 1;
057         result = PRIME * result + level;
058         result = PRIME * result + ((name == null) ? 0 : name.hashCode());
059         return result;
060     }
061
062     @Override
063     public boolean equals(Object obj) {
064         if (this == obj)
065             return true;
066         if (obj == null)
067             return false;
068         if (getClass() != obj.getClass())
069             return false;
070         final Dragon other = (Dragon) obj;
071         if (level != other.level)
072             return false;
073         if (name == null) {
074             if (other.name != null)
075                 return false;
076         } else if (!name.equals(other.name))
077             return false;
078         return true;
079     }
080
081 }
082
083 // 巢穴
084 class Nest {
085
086     //我研究的龙之常数
087     final int DRAGON_M = 4162;
088
089     // 宝藏
090     private int treasure;
091
092     // 居住的龙的级别
093     private int level;
094
095     Nest(int level) {
096         this.level = level;
097         this.treasure = level * level * DRAGON_M;
098     }
099
100     int getTreasure() {
101         return treasure;
102     }
103
104     public int getLevel() {
105         return level;
106     }
107
108     public void setLevel(int level) {
109         this.level = level;
110         this.treasure = level * level * DRAGON_M;
111     }
112
113 }

编译并运行查看结果:

1 碧雷巢穴中有多少宝藏:128922112

这一次正常输出了,真不容易^_^

好了本讲就到这里。

分享至上:分享源头

Java基础第十五讲:集合(一)

本讲内容:集合 collection

讲集合collection之前,我们先分清三个概念:

  1. collection 集合,用来表示任何一种数据结构
  2. Collection 集合接口,指的是 java.util.Collection接口,是 Set、List 和 Queue 接口的超类接口
  3. Collections 集合工具类,指的是 java.util.Collections 类。

 

SCJP考试要求了解的接口有:Collection , Set , SortedSet , List , Map , SortedMap , Queue , NavigableSet , NavigableMap, 还有一个 Iterator 接口也是必须了解的。

SCJP考试要求了解的类有: HashMap , Hashtable ,TreeMap , LinkedHashMap , HashSet , LinkedHashSet ,TreeSet , ArrayList , Vector , LinkedList , PriorityQueuee , Collections , Arrays

下面给出一个集合之间的关系图:

collection

上图中加粗线的ArrayList 和 HashMap 是我们重点讲解的对象。下面这张图看起来层级结构更清晰些。

collection

我们这里说的集合指的是小写的collection,集合有4种基本形式,其中前三种的父接口是Collection。

  1. List 关注事物的索引列表
  2. Set 关注事物的唯一性
  3. Queue 关注事物被处理时的顺序
  4. Map 关注事物的映射和键值的唯一性

 

一、Collection 接口

Collection接口是 Set 、List 和 Queue 接口的父接口,提供了多数集合常用的方法声明,包括 add()、remove()、contains() 、size() 、iterator() 等。

add(E e) 将指定对象添加到集合中
remove(Object o) 将指定的对象从集合中移除,移除成功返回true,不成功返回false
contains(Object o) 查看该集合中是否包含指定的对象,包含返回true,不包含返回flase
size() 返回集合中存放的对象的个数。返回值为int
clear() 移除该集合中的所有对象,清空该集合。
iterator() 返回一个包含所有对象的iterator对象,用来循环遍历
toArray() 返回一个包含所有对象的数组,类型是Object
toArray(T[] t) 返回一个包含所有对象的指定类型的数组

 

 

我们在这里只举一个把集合转成数组的例子,因为Collection本身是个接口所以,我们用它的实现类ArrayList做这个例子:

01 import java.util.ArrayList;
02 import java.util.Collection;
03
04 public class CollectionTest {
05
06     public static void main(String[] args) {
07
08         String a = "a",b="b",c="c";
09         Collection list = new ArrayList();
10         list.add(a);
11         list.add(b);
12         list.add(c);
13
14         String[] array =  list.toArray(new String[1]);
15
16         for(String s : array){
17             System.out.println(s);
18         }
19     }
20 }

编译并运行程序,检查结果:

image

二、几个比较重要的接口和类简介

1、List接口

List 关心的是索引,与其他集合相比,List特有的就是和索引相关的一些方法:get(int index) 、 add(int index,Object o) 、 indexOf(Object o) 。

ArrayList 可以将它理解成一个可增长的数组,它提供快速迭代和快速随机访问的能力。

LinkedList 中的元素之间是双链接的,当需要快速插入和删除时LinkedList成为List中的不二选择。

Vector 是ArrayList的线程安全版本,性能比ArrayList要低,现在已经很少使用

2、Set接口

Set关心唯一性,它不允许重复。

HashSet 当不希望集合中有重复值,并且不关心元素之间的顺序时可以使用此类。

LinkedHashset 当不希望集合中有重复值,并且希望按照元素的插入顺序进行迭代遍历时可采用此类。

TreeSet 当不希望集合中有重复值,并且希望按照元素的自然顺序进行排序时可以采用此类。(自然顺序意思是某种和插入顺序无关,而是和元素本身的内容和特质有关的排序方式,譬如“abc”排在“abd”前面。)

3、Queue接口

Queue用于保存将要执行的任务列表。

LinkedList 同样实现了Queue接口,可以实现先进先出的队列。

PriorityQueue 用来创建自然排序的优先级队列。番外篇中有个例子,你可以看一下。

4、Map接口

Map关心的是唯一的标识符。他将唯一的键映射到某个元素。当然键和值都是对象。

HashMap 当需要键值对表示,又不关心顺序时可采用HashMap。

Hashtable 注意Hashtable中的t是小写的,它是HashMap的线程安全版本,现在已经很少使用。

LinkedHashMap 当需要键值对,并且关心插入顺序时可采用它。

TreeMap 当需要键值对,并关心元素的自然排序时可采用它。

三、ArrayList的使用

ArrayList是一个可变长的数组实现,读取效率很高,是最常用的集合类型。

1、ArrayList的创建

在Java5版本之前我们使用:

1 List list = new ArrayList();

在Java5版本之后,我们使用带泛型的写法:

1 List<String> list = new ArrayList<String>();

上面的代码定义了一个只允许保存字符串的列表,尖括号括住的类型就是参数类型,也成泛型。带泛型的写法给了我们一个类型安全的集合。关于泛型的知识可以参见这里。

2、ArrayList的使用:

01 List<String> list = new ArrayList<String>();
02 list.add("nihao!");
03 list.add("hi!");
04 list.add("konikiwa!");
05 list.add("hola");
06 list.add("Bonjour");
07 System.out.println(list.size());
08 System.out.println(list.contains(21));
09 System.out.println(list.remove("hi!"));
10 System.out.println(list.size());

关于List接口中的方法和ArrayList中的方法,大家可以看看JDK中的帮助。

3、基本数据类型的的自动装箱:

我们知道集合中存放的是对象,而不能是基本数据类型,在Java5之后可以使用自动装箱功能,更方便的导入基本数据类型。

1 List<Integer> list = new ArrayList<Integer>();
2 list.add(new Integer(42));
3 list.add(43);

4、ArrayList的排序:

ArrayList本身不具备排序能力,但是我们可以使用Collections类的sort方法使其排序。我们看一个例子:

01 import java.util.ArrayList;
02 import java.util.Collections;
03 import java.util.List;
04
05 public class Test {
06
07     public static void main(String[] args) {
08         List<String> list = new ArrayList<String>();
09         list.add("nihao!");
10         list.add("hi!");
11         list.add("konikiwa!");
12         list.add("hola");
13         list.add("Bonjour");
14
15         System.out.println("排序前:"+ list);
16
17         Collections.sort(list);
18
19         System.out.println("排序后:"+ list);
20     }
21
22 }

编译并运行程序查看结果:

排序前:[nihao!, hi!, konikiwa!, hola, Bonjour]

排序后:[Bonjour, hi!, hola, konikiwa!, nihao!]

5、数组和List之间的转换

从数组转换成list,可以使用Arrays类的asList()方法:

01 import java.util.ArrayList;
02 import java.util.Collections;
03 import java.util.List;
04
05 public class Test {
06
07     public static void main(String[] args) {
08
09             String[] sa = {"one","two","three","four"};
10             List list = Arrays.asList(sa);
11             System.out.println("list:"+list);
12             System.out.println("list.size()="+list.size());
13     }
14
15 }

6、Iterator和for-each

在for-each出现之前,我们想遍历ArrayList中的每个元素我们会使用Iterator接口:

01 import java.util.Arrays;
02 import java.util.Iterator;
03 import java.util.List;
04
05 public class Test {
06
07     public static void main(String[] args) {
08
09         // Arrays类为我们提供了一种list的便捷创建方式
10         List<String> list = Arrays.asList("one", "two", "three", "four");
11
12         // 转换成Iterator实例
13         Iterator<String> it = list.iterator();
14
15         //遍历
16         while (it.hasNext()) {
17             System.out.println(it.next());
18         }
19
20     }
21
22 }

在for-each出现之后,遍历变得简单一些:

01 import java.util.Arrays;
02 import java.util.Iterator;
03 import java.util.List;
04
05 public class Test {
06
07     public static void main(String[] args) {
08
09         // Arrays类为我们提供了一种list的便捷创建方式
10         List<String> list = Arrays.asList("one", "two", "three", "four");
11
12         for (String s : list) {
13             System.out.println(s);
14         }
15
16     }
17
18 }

好了,本讲就到这里,下次我们讲HashMap。

分享至上:分享源头

Java基础第十四讲:字符串

本讲内容:字符串

程序开发的工作中80%的操作都和字符串有关,这这句话请起来还是蛮有道理。

字符串成了串,就形成了一个类,这类就叫String。

让我们留意一下String的源代码,第一,String永远不可能有子类,它的实例也是无法改变的。第二,String实现了 CharSequence 接口,而这个接口我们在Android开发中还是经常可以看到的。

FTLMA[LQ{DLBG1GRCH8{`HA

一、创建字符串对象

 

1 String s1 = new String("Milestone");
2 String s2 = "String";

以上就是创建字符串的两种方法,第一种是常规写法,创建一个对象当然就可以用new跟上个构造函数完成。第二种是字符串对象的特殊写法,主要是字符 串太常用了,所以Java在语言级别对其做了特殊照顾(作弊?)。第二种写法,最常用,效率也高。(为什么说效率高,可以参见Java番外篇的相关文章)

二、字符串操作中的加号

我们经常要把两个或者更多的字符串拼接成一个字符串,除了普通的连接字符串的方法以外,Java语言专门为String提供了一个字符串连接符号“+” ,下面看一个例子:

01 public class StringTest {
02     public static void main(String[] args) {
03
04         String s1 = "abc";
05         String s2= "xyz";
06         String s3=s1.concat(s2);    //第一种,用方法连接两个字符串
07         String s4=s1+s2;        //第二种,用+号连接
08         System.out.println(s1);
09         System.out.println(s3);
10         System.out.println(s4);
11
12         int i = 1;
13         int j = 2;
14         String s5="3";
15         System.out.println(i+j+s5); //第一个加号是数字和数字相加,是算数运算,第二个加号是数字和字符串相加,就是连接操作了
16         System.out.println(""+i+j+s5); //为了保证都是字符串连接,我们再前面加一个空串。
17     }
18 }

编译并运行程序,查看结果:

image

三、字符串中的常用方法

charAt() 返回位于指定索引处的字符串
concat() 将一个字符串追加到另一个字符串的末尾
equalseIgnoseCase() 判断两个字符串的相等性,忽略大小写
length() 返回字符串中的字符个数
replace() 用新字符代替指定的字符
substring() 返回字符串的一部分
toLowerCase() 将字符串中的大写字符转换成小写字符返回
toString() 返回字符串的值
toUpperCase() 将字符串中的小写字符转换成大写字符返回。
trim() 删除字符串前后的空格
splite() 将字符串按照指定的规则拆分成字符串数组

(此处差一个例子)

好了,本讲就到这里。

分享至上:分享源头

Java基础第十三讲:数组

本讲内容:数组

数组是Java中的对象,它用来存储多个相同类型的基本数据类型或者对象引用。

一、声明数组

数组是通过说明它将要保存的元素类型来声明的,元素类型可以是对象或者基本类型。类型后面的方括号可以在写在标识符的前面,也可以写在后面。当然我们推荐还是写在前面。

 

1 int[] number1;
2 int number2[];

int[] number1 ; 把方括号紧贴着类型写,会明确的告诉我们声明的是一个对象他的名字是 number1,他的类型是数组类型,而且是只能存储int类型的数组。 而后一种写法是C程序员更喜欢的写法。

可以声明多维数组,可以声明对象类型的数组:

1 String[][] s1; //二维数组
2 String[][][][] s2; //四维数组
3 String[] s3[]; //怪异写法的二维数组,这样写也是正确的,但是不建议这么干

Java中的二维数组就是一维数组中的每一个元素都还是个数组,那么合起来就是二维数组了,以此类推。

最后记住一句话在声明数组的时候不能在方括号中写数组的长度,因为声明数组的过程并没有创建数组本身,只是定义了一个变量,但是变量并没被赋值。

二、构建数组 | 创建数组 | 实例化数组

构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存储就设计空间分配问题,因此此时需要指定数组的大小)。而此时虽然有了数组对象,但数组对象里还没有值。

1 int[] scores; //声明数组
2 scores = new int[34]; //创建数组
3
4 int[] i = new int[22]; //声明并创建数组

构建数组意味着在堆上创建数组对象(所有的对象都存储在堆上,堆是一种内存存储结构,既然要存储就设计空间分配问题,因此此时需要指定数组的大小)。

1 int[][] xy= new int[2][3]; //声明并创建二维数组
2 int[][] mn= new int[2][];  //声明并创建二维数组,只创建第一级数组也是可以的。
3 mn[0]=int[4]; //分别定义第二级数组
4 mn[1]=int[5]; //他们的长度可以不同

三、初始化数组 | 给数组赋值

初始化数组就是把内容放在数组中。数组中的内容就是数组的元素。他们可以是基本数据类型也可以是引用数据类型。如同引用类型的变量中保存的是指向对象的引用而不是对象本身一样。数组中保存的也是对象的引用而不是对象本身。

1 Pig[] pigs = new Pig[3]; //声明并创建猪数组
2 pigs[0] = new Pig();      //给每一个元素赋值,创建了三个猪对象,此时数组里才真正有了对象
3 pigs[1] = new Pig();      //数组用下标来赋值和访问,下标写在[]中,数组下标最大是声明数量减1
4 pigs[2] = new Pig();   

下面我们再看一个例子:

1 int[] numbers={0,1,2,3,4,5,6,7,8,9};
2 Pig[] pigs = {new Pig(),new Pig(),new Pig};
3 int[][] xy={{2,3},{4,5},{6,7}};

这种写法就是把声明、创建和初始化同时完成的快捷写法。注意这种写法不能被分拆:

1 int[] numbers;
2 numbers={0,1,2,3,4,5,6,7,8,9}; //这样的写法在java中是不允许的,这……很不幸

然而,下面的写法则是合法的:

1 int[] numbers;
2 numbers=new int[]{0,1,2,3,4,5,6,7,8,9};  //创建匿名数组并赋值
3
4 int[][] xy= new int[][]{{2,3},{4,5},{5,6}}; //创建二维匿名数组并赋值
5
6 int[] x=new int[3]{1,2,3};  //这样的写法是错误的

我们看到这样的写法多了个创建匿名数组的过程,记住创建匿名数组的时候不要在中括号中填写数组的大小,否则会报错。

好了,本讲就到这里。

分享至上:分享源头