«

»

第一讲: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下测试通过。

分享至上:分享源头

2011.07.31
H
阅读全文...

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

快抢沙发

本讲内容: 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

好了,本将就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

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岁时…… 等等 ”

按照好莱坞式的结局……

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

分享至上:分享源头

2011.07.31
H
阅读全文...

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

本讲内容: File、InputStream、OutputStream、Reader、Writer

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第十八讲:异常处理(二)

快抢沙发

本讲内容预报:使用电影《盗梦空间》的情节,来讲述方法的调用栈、自定义异常、异常的抛出和传播,看本讲的朋友可以多看两遍盗梦空间再来看本讲内容。

具体内容有空的时候再写哈^_^

下面是一个备用图:

image

 

00663_ghostfog_1280x800

分享至上:分享源头

2011.07.31
H
阅读全文...

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 找不到指定的类错误

 

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

分享至上:分享源头

2011.07.31
H
阅读全文...

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

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

好了本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

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。

分享至上:分享源头

2011.07.31
H
阅读全文...

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() 将字符串按照指定的规则拆分成字符串数组

(此处差一个例子)

好了,本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

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};  //这样的写法是错误的

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

好了,本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第十二讲:面向对象基础(六)

快抢沙发

本讲内容:内部类

Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类。内部类又分为:常规内部类、局部内部类、匿名内部类和静态嵌套类四种。我们内部类的知识在Android手机开发中经常用到。

一、常规内部类

所谓常规内部类,或者说内部类,指的就是除去后面三种之外的内部类(这算什么解释。。。)

先写一个最简单的内部类的例子,大家感觉一下:

public class Outer {
	public class Inner{
	}
}

编译一下,我们看到目录中出现了两个class文件,其中有一个文件名叫做Outer$Inner.class,带了一个$符号,这个特点让我们很容易的认出来这是内部类编译后的class文件。

K6DP_~XUH}@$_Y$ZEYMFE@T

再写一个稍微复杂一点的内部类:

public class Outer {	

	private int x=1;

	public Outer(){
		System.out.println("Outer initial");
	}

	public class Inner{

		public Inner(){
			System.out.println("Inner initial");
		}

		private int x=2;

		public  void add(){
			int x=3;
			System.out.println(x);
			System.out.println(this.x);
			System.out.println(Outer.this.x);
		}

	}

	public static void main(String[] args){
		Inner inner = new Outer().new Inner();
		inner.add();
	}
}

我们编译以后,运行一下看看:

image

在上面的例子里我们可以清晰的看到:

  1. 内部类就像一个实例成员一样存在于外部类中。
  2. 内部类可以访问外部类的所有成员就想访问自己的成员一样没有限制。
  3. 内部类中的this指的是内部类的实例对象本身,如果要用外部类的实例对象就可以用类名.this的方式获得。
  4. 内部类对象中不能有静态成员,原因很简单,内部类的实例对象是外部类实例对象的一个成员。

下面我们再小结一下内部类的创建方法:

  1. 在外部类的内部,可以用 Inner inner = new Inner(); 方法直接创建
  2. 在外部类外部,必须先创建外部类实例,然后再创建内部类实例,除了上面 Inner inner = new Outer().new Inner()的写法以外,还有 Outer outer = new Outer(); Inner inner = outer.new Inner();的写法

 

二、局部内部类

我们也可以把类定义在方法内部,这时候我们称这个类叫局部内部类。

我们再看一个例子:

public class Outer {

	int x =1;
	public void doSomething(){
		final int y=2;
		class Inner{
			int x =3;
			void print(){
				int x=4;
				System.out.println(x);
				System.out.println(this.x);
				System.out.println(Outer.this.x);
				System.out.println(y);
			}
		}
		Inner inner = new Inner();
		inner.print();
	}

	public static void main(String[] args){
		Outer outer = new Outer();
		outer.doSomething();
	}
}

运行程序,查看结果:

image

我们通过上面这里例子也可以看到下面几点:

  1. 局部内部类的地位和方法内的局部变量的位置类似,因此不能修饰局部变量的修饰符也不能修饰局部内部类,譬如public、private、protected、static、transient等
  2. 局部内部类只能在声明的方法内是可见的,因此定义局部内部类之后,想用的话就要在方法内直接实例化,记住这里顺序不能反了,一定是要先声明后使用,否则编译器会说找不到。
  3. 局部内部类不能访问定义它的方法内的局部变量,除非这个变量被定义为final 。

是不是有点不好理解?关于为什么用final修饰以后就可以用了,我打算专门在番外篇里专门写一篇博客给你讲清楚,先记住吧。

三、匿名内部类

当我们把内部类的定义和声明写到一起时,就不用给这个类起个类名而是直接使用了,这种形式的内部类根本就没有类名,因此我们叫它匿名内部类。

我们再看一个有趣的例子:

public class Dog {

	public interface Pet {

		public void beFriendly();
		public void play();

	}

	public static void main(String[] args){

		Pet dog = new Pet(){
			@Override
			public void beFriendly() {
				System.out.println("蹭蹭你^_^");
			}
			@Override
			public void play() {
				System.out.println("把飞盘叼给你,逼你把飞盘丢出去,然后它再捡回来让你继续扔,连续500次^_^");
			}
		};

		dog.beFriendly();
		dog.play();

	}
}

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

image

竟然编译和运行都很正常,我们知道抽象类和接口肯定无法实例化的,因此刚才的例子肯定有点意思:

  1. 第一匿名内部类可以是个接口,这个没什么好奇怪的哈。
  2. 第12行到第21行是一个语句,就是定义了一个对象,因此21行大括号后面有个分号。
  3. 匿名内部类用 new Pet(){ … } 的方式把声明类的过程和创建类的实例的过程合二为一。
  4. 匿名内部类可以是某个类的继承子类也可以是某个接口的实现类。

好吧我们再看一个例子,方法参数内的匿名内部类

public class Dog {

	static abstract class Ball {
		abstract String getName();
	}

	void play(Ball b){
		System.out.println(b.getName());
	}

	public static void main(String[] args){
		Dog dog = new Dog();

		dog.play(new Ball(){
			@Override
			String getName() {
				return "qiu qiu";
			}});
	}
}

编译和运行以后的截图我就不给你了,返回值就是“qiu qiu”。

从第14行到第18行是一句话,就是执行一个play方法,而这个方法的参数就由一个匿名内部类的实例来提供。

四、静态嵌套类

为了让你感觉舒服一些,我们也把最简单的内部类放在最后讲。

当一个内部类前面用static修饰时,我们称之为静态嵌套类或者说静态内部类。

上面的例子里其实我们已经看到过静态嵌套类了,下面我们再举一个例子:

public class Outer {

	static int x =1;

	static class Nest {

		void print(){
			System.out.println("Nest "+x);
		}
	}

	public static void main(String[] args){
		Outer.Nest nest = new Outer.Nest();
		nest.print();
	}
}

因为静态嵌套类和其他静态方法一样只能访问其它静态的成员,而不能访问实例成员。因此静态嵌套类和外部类(封装类)之间的联系就很少了,他们之间可 能也就是命名空间上的一些关联。上面例子中你需要注意的就是静态嵌套类的声明方法 new Outer.Nest() 连续写了两个类名,以至于我们都怀疑前面的Outer是个包名了,好在包名一般都小写的,要不还真分不清……

再强调一遍,内部类在Android中应用的非常多,理解和使用好显得蛮重要。好了,本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第十一讲:面向对象基础(五)

快抢沙发

本讲内容:接口

一、为什么要有接口

我们已经知道Java中只支持单继承,或者说不允许多重继承的出现,又可以说一个类只能有一个父类。为了提供类似多重继承的功能,Java提供了接口的功能,下面我们共同学习一下接口。

我们还是拿一个例子来引入接口的概念吧。

image

上面是一个高度浓缩过的例子,假设下面的子类数量远远不止4种,也假设方法数来那个也不止2个。

首先我们定义了一个动物的类,它有吃和叫的方法,接下来我们想增加一个玩耍的方法和亲近主人的方法,如果把这两个方法定义在动物类里,看起来确实不 合理,因为老虎对象也会继承到亲近主人的方法,如果该方法默认实现是用舌头舔主人的脖子的话,就会产生老虎舔你脖子讨好你的场景,似乎有点太销魂了,这种 设计方式副作用太大。

如果把这两个方法定义在猫狗等需要的类里,这时候又会产生同样的内容重复写多次的情形,更不可接受。

哎这时候你可能在想如果Java里可以多重继承多好啊,我再定义一个宠物类,在里面定义玩耍和亲近主人的方法,然后让猫狗这些类也去继承宠物类问题 不就解决了?可惜Java不允许这么干…… (关于为什么Java中不允许多重继承,可以参见Java番外篇致命方块的诞生)。

好在Java里提供了接口的功能,你可以把宠物和动物都定义成接口,让猫狗去实现这两个接口,也可以把动物定义成一个普通类或者抽象类,让猫狗去继承动物,再让猫狗去实现宠物接口。

下面我们用代码表达出来。在这里我用中文标识符是为了提高教学效果,请在实际编程中彻底断绝用中文标识符的想法,别因为猎奇的原因今后开始用中文,还说是我教你的^_^

 

01 class Animal {
02
03     public void 吃() {
04         System.out.println("吃");
05     }
06
07     public void 叫() {
08         System.out.println("叫");
09     }
10
11 }
12
13 interface Pet {
14     public void 玩耍();
15
16     public void 讨好主人();
17 }
18
19 class Lion extends Animal {
20 }
21
22 class Tiger extends Animal {
23 }
24
25 class Cat extends Animal implements Pet{
26
27     @Override
28     public void 玩耍() {
29         System.out.println("玩耍");
30     }
31
32     @Override
33     public void 讨好主人() {
34         System.out.println("舔你脖子(什么嗜好……)");
35     }
36 }
37
38 class Dog extends Animal implements Pet{
39
40     @Override
41     public void 玩耍() {
42         System.out.println("玩耍");
43     }
44
45     @Override
46     public void 讨好主人() {
47         System.out.println("舔你脖子(狗也这样……)");
48     }
49 }
50
51 public class XiaoBai{
52     public static void main(String[] args){
53         Dog xiaobai = new Dog();
54         xiaobai.叫();
55         xiaobai.吃();
56         xiaobai.玩耍();
57         xiaobai.讨好主人();
58     }
59 }

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

image

我们看到使用接口完美的解决了上面的问题。

最后记住接口不仅仅是为了解决多重继承问题才出现的,要不然会被人笑话的^_^。

二、接口的几个规则

image

  1. 接口名用 interface 修饰, 相对应的 类名用 class修饰
  2. 接口里定义的方法都是抽象方法,可以用abstract修饰,当然也可以不用它修饰
  3. 接口只能被实现 ( implements )
  4. 可以用抽象类实现接口,也就是说虽然实现了,但是没有真正写接口的任何方法,它把责任推给了抽象类的子类
  5. 普通类实现接口,则必须按照接口的契约,实现接口所定义的所有方法。
  6. 接口可以继承接口,或者说一个接口可以是另一个接口的父类
  7. 一个接口可以继承多个父类,也就是说一个接口之间可以多重继承。

 

以上规则,有点超浓缩了,请同学们慢慢体会。

最后总结一下,当你实现接口时就表明你同意遵守定义在接口中的契约,也意味着你肯定实现了接口定义的所有方法。那么任何了解该接口方法形式的人,都 确信他们能够调用你所实现的类,去执行接口中的方法。 这时候我们说接口是一个契约,是一个like a 的关系(继承是 is a 关系)。

很多时候我们说不要滥用继承,要用接口,不要用继承,也是基于这样的思考。

好了本讲就到这里,Take your time and enjoy it 。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第十讲:面向对象基础(四)

快抢沙发

本讲内容:抽象类、初始化块

一、抽象类

用 abstract 修饰的类定义,我们称之为抽象类,抽象类不能被实例化。

用 abstract 修饰的方法,我们称之为抽象方法,抽象方法不能有方法体。

面向对象中,所有的对象都是某一个类的实例,但是并不是每个类都可以实例化成一个对象。如果一个类中没有足够的信息来描绘一个具体的对象,那么这个 类就不能被实例化,我们称之为抽象类。抽象类用来描述一系列看起来不同,但究其本质是相同的对象。譬如把苹果、橘子、梨等抽象出来一个概念叫水果,我们把 狗、老鼠、猫、狮子、大象、猪等抽象出来个概念叫动物。这时候我们把动物抽象成一个Animal类时,就最好不要让它直接初始化,创建出一个 Animal()实例对象的结果似乎难以想象。

抽象类被继承之外,没有用途,没有目的。

下面我们用一个Test.java的例子看一下什么叫抽象类:

 

01 abstract class Animal {
02     abstract void makenoise();
03 }
04
05 class Lion extends Animal {
06
07     @Override
08     void makenoise() {
09         System.out.println("狮子吼!");
10     }
11 }
12
13 class Dog extends Animal {
14
15     @Override
16     void makenoise() {
17         System.out.println("狗叫!");
18     }
19 }
20
21 public class Test {
22
23     public static void main(String[] args){
24
25         Animal a1 = new Dog();
26         Animal a2 = new Lion();
27
28         a1.makenoise();
29         a2.makenoise();
30     }
31 }

编译和运行程序,我们看看结果:

image

这个例子里,我们有这么几点需要留意:

  1. 一个编译单元里是可以写多个顶级类的,只要public修饰的顶级类只有一个就行了。
  2. 用 abstract 修饰的类是抽象类
  3. 用 abstract 修饰的方法是抽象方法,抽象方法没有方法体,也就是说不能写大括号。
  4. 抽象类实际上是定义了一个标准和规范,等着他的子类们去实现,譬如动物这个抽象类里定义了一个发出声音的抽象方法,它就定义了一个规则,那就是谁要是动物类的子类,谁就要去实现这个抽象方法。
  5. 狗和狮子的类继承了动物这个抽象类,实现了发出声音的方法。
  6. 一个对象除了被看成自身的类的实例,也可以被看成它的超类的实例。我们把一个对象看做超类对象的做法叫做向上转型。譬如 Animal a1 = new Dog();
  7. 虽然都是动物类型,但是方法在运行时是按照它本身的实际类型来执行操作的。因此 a1.makenoise()执行的是狗叫,a2.makenoise()执行的是狮子吼,我们称之为运行时多态。

 

我们再看一下把一个类看做一个超类有什么样的损失或者不便,我们把上面的例子稍微改一下:

01 abstract class Animal {
02     abstract void makenoise();
03 }
04
05 class Lion extends Animal {
06
07     @Override
08     void makenoise() {
09         System.out.println("狮子吼!");
10     }
11 }
12
13 class Dog extends Animal {
14
15     @Override
16     void makenoise() {
17         System.out.println("狗叫!");
18     }
19
20     void bark(){
21         System.out.println("汪,汪!");
22     }
23 }
24
25 public class Test {
26
27     public static void main(String[] args){
28
29         Animal a1 = new Dog();
30         Animal a2 = new Lion();
31
32         a1.makenoise();
33         a2.makenoise();
34
35         ((Dog)a1).bark();
36     }
37 }

运行程序,查看结果:

image

我们把焦点放在第35行,我们再a1前面加了一个(Dog),这个做法的意思是把a1强制转换为Dog对象,只有转换为Dog对象后,才能使用bark方法,否则即使你知道他是一个Dog对象也不能调用bark方法。这就是子类对象付给超类引用所带来的不便或者说是损失。

二、初始化块

我们已经知道在类中有两个位置可以放置执行操作的代码,这两个位置是方法和构造函数。初始化块是第三个可以放置执行操作的位置。当首次加载类(静态初始化块)或者创建一个实例(实例初始化块)时,就会运行初始化块。

我们看一个例子:

01 class SuperClass{
02     SuperClass(){
03         System.out.println("父类SuperClass的构造函数");
04     }
05 }
06
07 public class Initialize extends SuperClass {
08
09     Initialize(int x){
10         System.out.println("带参数的构造函数");
11     }
12
13     Initialize(){
14         System.out.println("不带参数的构造函数");
15     }
16
17     static {
18         System.out.println("第一个静态初始化块");
19     }
20
21     {   System.out.println("第一个实例初始化块");}
22
23     {   System.out.println("第二个实例初始化块");}
24
25     static {
26         System.out.println("第二个静态初始化块");
27     }
28
29     public static void main(String[] args){
30         new Initialize(1);
31         new Initialize();
32     }
33 }

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

image

从上面的例子中我们需要留意如下几点:

  1. 初始化块的语法相当简单,它没有名称,没有参数,也没有返回值,只有一个大括号。用 static 修饰的初始化块就要静态初始化块,相对应的,没有static修饰的初始化块就叫实例初始化块。
  2. 静态初始化块在首次加载类时会运行一次。
  3. 实例初始化块在每次创建对象时会运行一次。
  4. 实例初始化块在构造函数的super()调用之后运行。
  5. 初始化块之间的运行顺序取决于他们在类文件中出现的顺序,出现在前面的先执行。
  6. 初始化块从书写惯例上应该写在靠近类文件的顶部,构造函数附近的某个位置。

 

好吧,本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第九讲:面向对象基础(三)

快抢沙发

本讲内容:继承、变量隐藏、方法重写、包、修饰符、this、super

一、继承

1、继承的概念

继承是面向对象的三大特性之一。在语义上继承的意思是照法律或遵照遗嘱接受死者的财产、头衔、地位等,在Java程序中的继承也有这个意思,不过子类继承的是父类的属性和方法。

2、继承的语法结构(子类的定义方式)

image

3、继承的例子:

关于继承我们第七讲举了一个白马和马的例子,我们再举一个动物父类和鸟类子类、鱼类子类的例子,这次我们用类图表示。

image

当我们写好了一个动物类,我们写鸟类的时候就可以继承动物类,自动获得动物类所拥有的属性和方法,提高了代码重用性。

4、Object类

Java中的所有对象(类)都是Object类的子类。我们可以用javap查看一下一个最简单的类的字节码:

1 public class Lesson09 {
2
3 }

image

5、继承的原则:

  1. 子类能够继承父类中被声明为public和protected的成员变量和成员方法。
  2. 子类能够继承在同一个包中的默认修饰符修饰的成员变量和成员方法。
  3. 如果子类声明了一个与父类变量同名的成员变量,则子类不能继承父类的成员变量,这种做法叫做变量的隐藏。
  4. 如果子类声明了一个与父类方法同名的成员方法,则子类不能继承父类的成员方法,这种做法方法的重写。

 

二、包 package

1、编译单元(compilation unit)

在Java里,一个编译单元就是一个用来书写Java源代码的文本文件。我们前面讲类的定义的时候只关注了类内部的东西,类外面是不是也有东西?答案是肯定的。编译单元有三个组成部分:

image

这三个部分都是可选的,包声明如果要有必须写在最前面,并且只能写一份。导入声明可以写多个,类声明也可以写多个。

2、包的概念(package)

类名是类之间彼此区分的标示,一个程序中类数量增多是,必然会遇到类名冲突的情形。包提供了类的组织和管理方式。包的用途有以下三种:

  1. 将功能相近的类放在同一个包里,方便使用和查找
  2. 类名相同的文件可以放在不同的包里而不会产生冲突
  3. 可以依据包设定访问权限

3、包的声明

image

4、 包的例子:

01 //包的声明
02 package android.java.basic;
03
04 //导入声明
05 import java.util.Date;
06
07 //类声明
08 class Animal{
09     long birthTime = new Date().getTime();
10
11     void eat(){
12         System.out.println("eating");
13     }
14 }
15
16 //类声明
17 class Fish extends Animal {
18     void swim(){
19         System.out.println("swimming");
20     }
21 }
22
23 //类声明
24 public class Lesson09 {
25     public static void main(String[] args){
26
27         //动物类
28         Animal a = new Animal();
29         a.eat();
30         System.out.println(a.birthTime);
31
32         //鱼类
33         Fish f = new Fish();
34         f.eat();
35         f.swim();
36         System.out.println(f.birthTime);
37     }
38 }

运行程序,查看结果:

image

四、访问修饰符 public protected 默认的 private

在Java中可以用访问修饰符来控制类或者类成员对程序的其它部分的可见性,从而在语言级别实现访问控制。当一个类无权访问另一个类或者类的成员时,编译器会提示你试图访问一些可能不存在的内容。

看见性 public protected 默认 private
从同一个类
从同一个包中的任何类
从同一个包中的子类
从包外的子类 是,通过继承
从包外的任何非子类的类
  1. 对于类的修饰符,只能有2个选择,用public修饰或者不用(不用就是默认修饰符)。
  2. 如果一个类本身对另一个类不可见,则即使将其成员声明为public,也没有一个成员是可见的,只有当你确类本身对你是可见的时,查看其各个成员的访问级别才有意义。
  3. 对于类的成员(member, 包括属性和方法),可以用 public protected 默认的和private 4种修饰符。
  4. 永远不要用访问修饰符修饰局部变量,编译器会毫不留情的报错。(记住:局部变量只有一个修饰符可以用,那就是final)

除了访问修饰符外,还有非访问修饰符 static、final、abstract、transient、synchronization、native、strictfy ,我们在今后的学习中逐步掌握。

五、变量隐藏(shadowing)、方法重写(Overiding)

当子类继承父类时,子类中一不小心就会定义出父类名字相同的成员变量,对于这种现象,规则里是怎么说的,又是怎么应用的?用一句话说,就是子类成员会覆盖父类成员;对于变量就是变量隐藏,对于方法就是方法重写(方法覆盖)。

1、变量隐藏

shadow在做名词时意思是阴影,在做动词时意思是遮蔽,那么这里的意思shadowing更多的是遮蔽的意思,不过我们翻译的时候大家已经习惯说这个叫变量的隐藏。

先看一个局部变量遮蔽成员变量的例子:

01 public class Lesson09_1 {
02
03     int i=1;
04     int j=1;
05     int k=1;
06
07     void test(int i){
08         int j=2;
09         System.out.println("i="+i);
10         System.out.println("j="+j);
11         System.out.println("k="+k);
12     }
13
14     public static void main(String[] args){
15         Lesson09_1 lesson = new Lesson09_1();
16         lesson.test(2);
17     }
18 }

image

我们可以看到,当方法内的局部和成员变量名字相同时,在方法内,局部变量遮蔽住了成员变量,因此打印出来的是2,而不是1。

再看一个子类成员变量遮蔽父类成员变量的例子。

01 public class WhiteHorse extends Horse {
02
03     private static String color ="白色";
04
05     public static int leg =4;
06
07     public static void main(String[] args){
08
09         WhiteHorse xiaobai = new WhiteHorse();
10         System.out.println(xiaobai.color);
11         System.out.println(xiaobai.leg);
12
13         //类变量是遮蔽不住的
14         System.out.println(Horse.color);
15
16         //强制转换后我们看到父类的实体leg变量还在,只是被隐藏了
17         Horse xiaobai1 = (Horse)xiaobai;
18         System.out.println(xiaobai1.leg);
19
20     }
21
22 }

运行程序,查看结果:

image

2、方法重写 Override

当子类继承父类时,如果子类方法的签名和父类方法的签名相同时,子类就无法继承父类的方法,此时子类的方法就覆盖了父类的方法,我们称之为重写。重写可以定义子类某个行为的特殊性。

譬如动物会喝水,但是猫喝水和人喝水的具体行为就不同。

重写方法的规则如下:

  1. 参数列表必须与重写的方法的参数列表完全匹配(方法签名相同)。如果不匹配,你得到的将是方法重载。
  2. 返回类型必须与超类中被重写方法中原先声明的返回类型或其子类型相同。
  3. 访问级别的限制性可以比被重写方法弱,但是访问级别的限制性一定不能比被重写方法的更严格。
  4. 仅当实例方法被子类继承时,它们才能被重写。子类和超类在同一个包内时,子类可以重写未标示为private和final的任何超类方法。不同包的子类只能重写标示为public或protected的非final方法。
  5. 无论父类的方法是否抛出某种运行时异常,子类的重写方法都可以抛出任意类型的运行时异常。
  6. 重写方法一定不能抛出比被重写方法声明的检验异常更新或更广的检验异常,可以抛出更少或更有限的异常。
  7. 不能重写标示为final的方法。
  8. 不能重写标示为static的方法。
  9. 如果方法不能被继承,那么方法不能被重写。

 

我们举一个重写的例子:

Horse.java

1 public class Horse {
2     //给马写个摆Pose的方法
3     public void pose(){
4         //样子很酷
5         System.out.println("Cool!");
6     }
7 }

WhiteHorse.java

01 public class WhiteHorse extends Horse {
02
03     //白马重写了摆pose的方法
04     public void pose(){
05         //白马更酷一点
06         System.out.println("Cool!!!!");
07     }
08
09     public static void main(String[] args){
10         WhiteHorse xiaobai = new WhiteHorse();
11         xiaobai.pose();
12     }
13 }

运行程序,查看结果:

image

我们再把白马类中pose方法的访问修饰符改成private试试看:

image

六、this  和 super

1、this.成员变量

当成员变量被局部变量隐藏时想使用成员变量,可以用this关键字来访问成员变量。

01 public class Lesson09_1 {
02
03     int i=1;
04     int j=1;
05     int k=1;
06     static int l = 1;
07
08     void test(int i){
09         int j=2;
10         int l=2;
11         System.out.println("i="+i);
12         System.out.println("j="+j);
13         System.out.println("k="+k);
14         System.out.println("l="+l);
15
16         System.out.println("this.i="+this.i);
17         System.out.println("this.j="+this.j);
18         System.out.println("this.k="+this.k);
19         System.out.println("this.l="+this.l);  
20
21     }
22
23     public static void main(String[] args){
24         Lesson09_1 lesson = new Lesson09_1();
25         lesson.test(2);
26
27     }
28 }

运行程序,我们可以看到使用this关键字时可以看到被隐藏的成员变量可以正常访问了。

image

2、this()  构造函数

在构造方法中可以使用 this() 来引用另一个构造方法。

01 public class Lesson {
02
03     private int minute=0;
04
05     Lesson(){
06         this(45);
07     }
08
09     Lesson(int minute){
10         this.minute = minute;
11     }
12
13     public static void main(String[] args){
14         Lesson lesson = new Lesson();
15         System.out.println(lesson.minute);
16
17         Lesson lesson2 = new Lesson(30);
18         System.out.println(lesson2.minute);
19     }
20 }

运行程序查看结果:

image

我们看到this(45),的确调用了另外一个带参数的构造方法。需要注意的是this()必须写在构造方法的第一行。

3、super.成员

当父类的成员变量被隐藏、成员方法被重写(覆盖),此时想使用父类的这些成员时就要用super关键字。我们改一下上面马和白马的例子:

Horse.java

01 public class Horse {
02
03     public int height =120;
04
05     //给马写个摆Pose的方法
06     public void pose(){
07         //样子很酷
08         System.out.println("Cool!");
09     }
10 }

WhiteHorse.java

01 public class WhiteHorse extends Horse {
02
03     public int height =150;
04
05     //白马重写了摆pose的方法
06     public void pose(){
07
08         //先摆一个马的pose
09         super.pose();
10
11         //白马更酷一点
12         System.out.println("Cool!!!!");
13     }  
14
15     public void printHeight(){
16
17         //打印父类被隐藏的变量
18         System.out.println(super.height);
19
20         //打印实例变量
21         System.out.println(height);
22     }  
23
24     public static void main(String[] args){
25         WhiteHorse xiaobai = new WhiteHorse();
26         xiaobai.pose();
27         xiaobai.printHeight();
28
29     }
30 }

运行程序查看结果:

image

我们看到在子类的方法里可以使用super来引用被隐藏的父类变量,被覆盖(重写)的父类方法。

4、super() 父类构造函数

讲super()之前,我们先看一下这个例子:

Horse.java

1 public class Horse {
2
3     public Horse(){
4         System.out.println("马类的构造函数");
5     }
6
7 }

WhiteHorse.java

01 public class WhiteHorse extends Horse {
02
03     public WhiteHorse(){
04         System.out.println("白马类的构造函数");
05
06     }
07
08     public static void main(String[] args){
09         new WhiteHorse();
10     }
11 }

运行程序查看结果:

image

我们看到,构造白马类之前,虚拟机先构造了它的父类马类,由此我们看到了白马类能继承马类的属性和方法的根本原因,原来每一个白马类同时也是一个马类,还是一个Object类。在创建对象时,一个对象的逐级父类自顶向下依次都创建了。

image

上图是一个白马对象在内存中的示意图,我们看到最外面的是WhiteHorse,内层还有一个Horse对象,更内层还有一个Object对象。

用下面的栈上的示意图可以更清晰的看到对象的创建过程。

image

首先调用的是main方法,main方法调用 new WhiteHorse() ,WhiteHorse()构造函数调用了一个默认的super(),super()方法就是父类的构造方法,以此类推最后调用了Object()构造方法。

5、带参数的的super()方法

在上面的例子里,我们看到编译器在你没有调用super()方法的时候,插入了一个默认的super()方法。可惜的是编译器并不会自动插入带参数的super(), 因此我们遇到这种情况就只能自己手工插入对super()的调用。

下面我们把上面的例子更改一下:

Horse.java的构造函数添加一个参数:

01 public class Horse {
02
03     protected int leg = 0;
04
05     public Horse(int leg){
06
07         this.leg=4;
08
09         System.out.println("马类的构造函数");
10     }
11
12 }

再次编译WhiteHorse.java,出错提示如下:

image

标准Java编译器的提示有点故作神秘,这个提示几乎什么都没说;我们换个工具,在中文Eclipse上的提示就明显多了:

`4Q677YJB_D8L`I8[D`%4]Y

我们按照它的提示更改一下WhiteHorse类:

01 public class WhiteHorse extends Horse {
02
03     public WhiteHorse(){
04         super(4);
05         System.out.println("白马类的构造函数");
06     }
07
08     public static void main(String[] args){
09         new WhiteHorse();
10     }
11 }

再次编译和运行程序,我们发现这次安然通过。

image

到这里,我们是不是可以小小总结一下,构造函数只能用new、this() 和 super() 的方式来访问,是不能像方法一样写方法名访问的。

本讲就到这里,下次再见。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第八讲:面向对象基础(二)

快抢沙发

本讲内容:成员变量、方法、方法的重载、构造函数

一、用程序讲解小白的故事

小白是一条狗,它心情好的时候会恭喜人发财,它心情差的时候会对路人撒野,吓得路人落荒而逃。下面我们用面向对象的方式用程序讲述一下小白的故事。

01 public class Dog {
02
03     //构造函数
04     public Dog(){
05         size =3;
06     }
07
08     //定义叫声常量
09     final String BARK_NORMAL = "汪!汪汪!";
10     final String BARK_HAPPY = "旺!旺旺!";
11     final String BARK_SAD = "呜……嗷!";
12
13     //定义心情常量
14     static final int NORMAL =0;
15     static final int HAPPY =1;
16     static final int SAD = 2;  
17
18     // 定义了狗的个头大小的属性
19     private int size;
20
21     // 定义获取个头的方法
22     public int getSize() {
23         return size;
24     }
25
26     // 定义狗叫的方法
27     public void bark(){
28         if(size<5){
29             System.out.println("汪汪汪!");
30         }else{
31             System.out.println("嗷!嗷!");
32         }
33     }
34
35     //定义狗叫的方法,带心情参数
36     public void bark(int mood){
37         switch(mood){
38         case NORMAL:
39             System.out.println(BARK_NORMAL);
40             break;
41         case HAPPY:
42             System.out.println(BARK_HAPPY);
43             break;
44         case SAD:
45             System.out.println(BARK_SAD);
46             break;
47         }
48     }
49
50     //定义main方法
51     public static void main(String[] args) {
52         //创建了名字叫小白的狗对象
53         Dog xiaoBai = new Dog();
54         //调用它叫的方法
55         xiaoBai.bark();
56
57         //调用带参数的方法
58         xiaoBai.bark(HAPPY);
59     }
60
61 }

运行程序,看一下输出结果:

image

二、类定义中的五个顶级成员(top-level member)

实体 static修饰 没用static修饰
成员变量 类变量 实例变量
初始化块 静态初始化块 实例初始化块
构造方法 / 构造方法
方法 类方法 实例方法
接口 嵌套的接口 member interface /
嵌套顶层类 nested top-level class 内部成员类 inner member class

 

三、成员变量(类或对象的状态)

1、认识成员变量(类或对象的状态)、类变量、实例变量、局部变量、方法参数之间的区别

成员变量(field)是没有定义在代码块(包括初始化块、成员方法)中的变量。成员变量是类变量还是实例变量取决于在其声明中是否使用了static关键字。

类变量在声明是用了static关键字,它的另一个名字叫静态变量、静态成员变量(static field) 。

实例变量是在声明时没有使用static关键字的成员变量,它的另一个名字叫非静态成员变量(non-static field)。

定义在代码块里的变量被称为局部变量(local variable)。

定义在方法声明中的变量叫方法参数

01 public class Lesson08 {
02
03     // 类变量
04     static String s1 = "类变量";
05
06     // 实例变量
07     String s2 = "实例变量";
08
09     // 初始化代码块里的局部变量
10     {
11         String s3 = "初始化代码块里的局部变量";
12         System.out.println(s3);
13     }
14
15     // 静态初始化代码块里的局部变量
16     static {
17         String s4 = "静态初始化代码块里的局部变量";
18         System.out.println(s4);
19     }
20
21     // 方法的参数和方法里的局部变量
22     public void printString(String s5) {
23         String s6 = "方法里的局部变量";
24         System.out.println("方法的参数:"+s5);
25         System.out.println(s6);
26     }
27
28     // 类方法
29     public static void printString() {
30         String s7="类方法里的局部变量";
31         System.out.println(s7);
32     }
33
34     // main方法
35     public static void main(String[] args) {
36
37         //调用类方法
38         Lesson08.printString();
39
40         //打印类变量
41         System.out.println(s1);
42
43         //创建对象
44         Lesson08 lesson = new Lesson08();
45
46         //打印实例变量
47         System.out.println(lesson.s2);
48
49         //调用实例方法
50         lesson.printString("参数的值");
51
52     }
53
54 }

对于他们之间的区别,我们在以后的学习中你会越来越清晰的。

2、变量的初始化

实例变量一经定义就会有初始值,局部变量定义时不赋初值而直接使用,编译器会报错

01 public class Lesson08_1 {
02
03     int i;
04     static int j;
05     {
06         int k;
07         System.out.println(k);
08     }
09
10     static {
11         int l;
12         System.out.println(l);
13     }
14
15     public void print(String m){
16
17         System.out.println(m);
18     }
19
20     // main方法
21     public static void main(String[] args) {
22         int n;
23         System.out.println(n);
24
25         Lesson08_1 lesson =new Lesson08_1();
26         lesson.print("m");
27     }
28
29 }

运行程序,查看结果:

image

然后我们再给局部变量都附上初值,再把实例变量和类变量都打印出来看看,代码如下:

01 public class Lesson08_1 {
02
03     int i;
04     static int j;
05
06     {
07         int k=2;
08         System.out.println(k);
09     }
10
11     static {
12         int l=2;
13         System.out.println(l);
14     }
15
16     public void print(String m){
17
18         System.out.println(m);
19     }
20
21     // main方法
22     public static void main(String[] args) {   
23
24         System.out.println(j);
25
26         int n=2;
27         System.out.println(n);
28
29         Lesson08_1 lesson =new Lesson08_1();
30         lesson.print("m");
31         System.out.println(lesson.i);
32     }
33
34 }

运行程序,查看结果:

image

我们看到类变量和实例变量没赋值照样有值打印出来,我们也看到int的初始值是0 。

实例变量和类变量的类型 初始值
整数 0
浮点类型 0.0
字符类型 ‘/u0000′
布尔类型 boolean false
引用数据类型(譬如数组、接口、类) null

 

四、方法(类或对象的行为)

 

1、方法

Java中类的行为由类的成员方法来实现。类的成员方法由方法的声明和方法体两部分组成。

image

修饰符,可选,用于指定谁有权限访问此方法。

返回值类型,必选,用于指定该方法的返回值数据类型;如果该方法没有返回值,则要用关键字 void 进行标示。方法的返回值只能有一个。

参数列表,可以有0到多个,多个参数之间要用逗号隔开,参数的写法形如:String[] args, int age 这样。

方法名,必选,这个……,好吧命名规则是方法名和变量名的首字母要小写,别丢我人,弄个大写方法名出来。

方法体,可选,这个……,

大括号,大括号不写的方法叫抽象方法。

2、属性和方法之间的关系

有句绕口令是这么说的:“状态影响行为,行为影响状态”。你有没有想过这问题,如果每个对象都是从同一个类中生 成出来,每个对象如果都一摸一样,那么这个世界是不是太无趣了。好在,我们看到前面的例子中,小狗的大小属性影响了他叫的方式。通过设置狗大小的方法又改 变了它的状态。这些属性和方法的细节上的不同导致了,多姿多彩的对象,我们后面还会讲到更多的技术,也会导致更多的多样性。

五、方法重载 overload

Java里可以提供同一个方法的多个不同参数的版本供我们调用,譬如上面的小白,它叫 bark() 的方法有两种,一种是很随意的叫,无拘无束的叫,还有一种是根据它心情的不同来叫,当然我还可以再定义一个方法可以让他根据主人的脸色来叫,我们也可以再 定义一个方法,穿的参数是食物,那么它的叫声可能就是边吃边幸福的吼叫了…… 这样一个bark方法就带来了丰富多彩的变化。

在Java 中允许类定义中多个方法的方法名相同,只要它们的参数声明不同即可。这种情况下,该方法就被称为重载(overloaded ),这种方式就叫做方法重载(method overloading )。方法重载是实现程序多样性的一个重要手段。也可以称作多态的一种表现方式。

image

重载规则:

  1. 重载方法必须改变方法参数列表
  2. 重载方法可以改变返回类型
  3. 重载方法可以改变访问修饰符
  4. 重载方法可以声明新的或更广的检验异常
  5. 方法能够在同一个类或者一个子类中被重载
01     public class lesson08_2 {
02
03     static long max(long a,long b){
04         System.out.println("max(long a,long b)");
05         return a>b?a:b;
06     }  
07
08     static long max(long a,int b){
09         System.out.println("max(long a,int b)");
10         return a>b?a:b;
11     }
12
13     static int max(int a,int b){
14         System.out.println("max(int a,int b)");
15         return a>b?a:b;
16     }
17
18     static byte max(byte a,byte b){
19         System.out.println("max(byte a,byte b)");
20         return a>b?a:b;
21     }
22
23     public static void main(String[] args) {
24         byte byte1 = 125;
25         byte byte2 = 126;
26         int int1 = 1;
27         int int2 = 2;
28         long long1 = 1000;
29         long long2 = 2000;
30
31         System.out.println(max(byte1,byte2));
32         System.out.println(max(int1,int2));
33         System.out.println(max(byte1,int2));
34         System.out.println(max(int1,long2));
35         System.out.println(max(long1,int2));
36         System.out.println(max(long1,long2));
37     }
38 }

上面的例子说明了参数声明不同的含义,那就是只要参数的个数,类型和顺序任意一项不同就算不同的参数声明,即使它们看起来很相似,甚至看起来可能会让虚拟机搞混。不过没关系,虚拟机很聪明,只要你按照规则走他就能分清。

六、构造函数

在Java中,对象是构造出来的,特意用了一个new关键字来标示这个创建的过程。

image

我们把上一讲的例子修改一下,看看创建对象的过程发生了什么。

01 public class Dog {
02
03     // 定义了狗的个头大小的属性
04     private int size=3;
05
06     public Dog(int size){
07         System.out.println("带参数的构造函数");
08         this.size = size;
09     }
10
11     public Dog(){
12         System.out.println("不带参数的构造函数");
13         this.size=2;
14     }
15
16     // 定义狗叫的方法
17     public void bark(){
18         if(size<5){
19             System.out.println("汪汪汪!");
20         }else{
21             System.out.println("嗷!嗷!");
22         }
23     }
24
25     //定义main方法
26     public static void main(String[] args) {
27
28         //创建了名字叫小黄的狗对象
29         Dog xiaoHang = new Dog(4);
30
31         //调用它的叫方法
32         xiaoHang.bark();
33
34         //创建了名字叫大黄的狗对象
35         Dog daHang = new Dog(6);
36
37         //调用它的叫方法
38         daHang.bark();
39
40         //创建了名字叫小黑的狗对象
41         Dog xiaoHei = new Dog();
42
43         //调用它的叫方法
44         xiaoHei.bark();
45     }
46 }

我们看到创建对象的过程就是执行构造函数的过程,而且也看到构造方法也可以重载。

我们在这里明确的是说明构造函数或者说构造方法,它不是方法。它们之间的三大区别,为了让你记清楚,我做了个图:

image

关于构造方法,还有一部分非常有趣的内容我们放在继承章节和大家一起分享。

本讲就到这里,各位再见,Take some time and enjoy it 。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第七讲:面向对象基础(一)

快抢沙发

本讲内容:面向对象的概念和发展、面向对象的特征

一、面向对象(Object Oriented)编程语言的历史

1950年有个叫做荷兰德的学生作为程序员进入IBM的时候,这个世界上的程序员只有几个而已。当时计算机很少,计算机性能也差,程序员也少,加上程序员都是天才中的天才,智商超高,所以他们用十六进制的机器编码来操纵计算机,似乎没有什么问题。

1960年,计算机性能不断提升,应用领域也不断增多,程序员的人数也在增多,程序的复杂程度也不断提高,很多程序需要好多人一起才能完成。而在此 时在大型项目中由于软件的原因导致的大量问题也不断暴露出来。由此催生了结构化程序设计方法。结构化程序设计思想采取了模块分解和功能抽象的方法,把一个 个复杂的问题,分解成一个个易于控制的子程序,便于开发和维护,因此结构化程序设计迅速走红,并从70年代起逐渐占据统治地位。

70年代末,随着计算机科学的发展,结构化程序设计方法也渐渐显得力不从心。于是面向对象设计思路和语言慢慢浮出水面。

1967年挪威两个科学家发布了simula语言(simulation模拟、仿真),它引入了后来所有面向对象程序设计语言都会遵循的几个基础概 念:类、对象、继承。虽然因为simula比较难懂、难学,功能不完善而没有流行开来,但是它的思想却指导着计算机这数十年的编程实践。

1972年诞生的smalltalk,被公认为是历史上第二个面向对象的程序设计语言,和第一个真正的集成开发环境(IDE),smalltalk 对 Java 、Objective-C 、Ruby 的诞生都起到了极大的推动作用。90年代的许多软件开发思想,如设计模式、敏捷编程和重构等也都源自于smalltalk。在smalltalk里所有的 东西都是对象,15*19 会被理解成向15这个对象发送一个乘法的消息,参数是19。

1985年c++商业版本的正式发布,标志着一个面向对象领域里的王者诞生了。C++在c语言的基础上,借鉴了simula中类的概念、从 algol语言中继承了运算符重载、引用以及在任何地方都可以声明变量的能力,从BCPL获得了//注释,从Ada语言中得到了模板,命名空间,从 Ada、Clu和ML去取得了异常……C++是第一个广泛流行起来的面向对象的编程语言,至今魅力不减。

1995年Java诞生的故事大家都耳熟能详了,我们也知道Java是C++的语法与Smalltalk语义的结合。由此面向对象领域里又一个王者诞生了。

Java里面向对象的概念六次讲座也就可以讲完。不要怕,这些概念很好理解;不要轻视,很多深层的思想需要在实践中不断思考和分析才可以领悟。

二、类和对象的概念

1、类和对象的概念

人类自古就喜欢听故事,也喜欢写故事,我们从小也被要求写作文,为了帮助你写作文。老师还总结了一些规律,譬如记叙文六要素:时间、地点、人物、起因、经过、结果。 有了这样指导性的东西,我们写作文的时候就简单了许多。

面向对象程序语言的核心思想就是把一个事物的状态和行为封装起来作为一个整体看待。类描述的就是对象知道知道什么和执行什么。

譬如我们用面向对象的思想来看待一架飞机:

如果我们站在顾客角度看飞机,那么它的状态是名字波音777,座位数380人,飞行速度940公里每小时,它的行为就是飞行,能把你从A地送到B地。

如果站在航空公司角度看飞机,那么它的状态是名字波音777,资产编号HNHK20100321,购买价格18.7亿人民币。它的行为就是能赚钱。

我们从不同角度去看待和抽象同一架飞机它的状态和行为不相同。

image

再从面向对象的角度看待一个家乐福超市的员工王丽:

她在上班的时候是个收银员,那么她的状态是编号067,她的行为就是收银。她下班以后去家门口的小店买菜,那么他的身份就是顾客,她的状态是有个购物商品清单,她的行为就是付款。

image

我们从不同的角度和时间去看待同一个人,她的状态和行为也是不相同的,甚至看起来是相反的。

好了,我们自己尝试分析一下,电脑的状态和行为,手机的状态和行为,桌子的状态和行为,QQ的状态和行为,小狗、小猫、老虎、大象、蚊子、苍蝇…… 有一个简单的方法区别什么是状态什么是行为:就是状态是个名词,行为是个动词。

2、类和对象的关系

类是对象的蓝图,它告诉虚拟机如何创建某个类型的对象。对象是根据蓝图建造出来的实例。

譬如我们设计一个模拟WOW的格斗游戏,需要人或者怪兽来战斗吧,战斗需要武器吧。那么圣骑士就是个类,人类圣骑士“锦马超”就是一个对象。如果双手剑件是个类,那么拿在“锦马超”手里的“霜之哀伤”就是一个对象。

譬如我们要建立一个全班同学的通讯录,设计一个通讯录的格式,包括姓名、性别、手机号、QQ号、宿舍号。然后我们按照一定的格式印出来,交由每个同学填写,那么每个同学填写的那一份就叫对象,我们填写的通讯录格式本身就是类。

譬如由一个寂寞的老人需要找个伴,要求:随时都可以陪着他,还不唠叨。有人带了一条狗。那么老人提的需求就是蓝图,就是类。狗就是对类的实现,就是对象。

3、定义类,创建对象

下面我们学习如何用Java的程序代码来定义类、创建对象。

定义一个类的步骤是:定义类名,编写类的属性(状态),编写类的方法(行为)

01 public class Dog {
02
03     // 定义了狗的个头大小的属性
04     private int size;
05
06     // 定义设置个头的方法
07     public void setSize(int size) {
08         if (size > 0 && size < 10) {
09             this.size = size;
10         } else {
11             size = 1;
12         }
13     }
14
15     // 定义获取个头的方法
16     public int getSize() {
17         return size;
18     }
19
20     // 定义狗叫的方法
21     public void bark(){
22         if(size<5){
23             System.out.println("汪汪汪!");
24         }else{
25             System.out.println("嗷!嗷!");
26         }
27     }
28
29     //定义main方法
30     public static void main(String[] args) {
31
32         //创建了名字叫小黄的狗对象
33         Dog xiaoHang = new Dog();
34         //设置它的大小属性
35         xiaoHang.setSize(3);
36         //调用它的叫方法
37         xiaoHang.bark();
38
39         //创建了名字叫大黄的狗对象
40         Dog daHang = new Dog();
41         //设置它的大小属性
42         daHang.setSize(7);
43         //调用它的叫方法
44         daHang.bark();
45     }
46 }

运行程序查看运行结果:

image

 

三、面向对象的三大特性

封装、继承、多态是面向对象的三大特性。这里先让大家有个概念,通过今后漫长的学习过程不断加深对它们的理解。

封装(encapsulation):就是把属性私有化,提供公共方法访问私有对象。 这里面有两层意思,第一隐藏数据,第二把数据和对数据操作的方法绑定。

实现封装的步骤:

1 修改属性的可见性来限制对属性的访问。

2 为每个属性创建一对赋值方法和取值方法,用于对这些属性的访问。

3 在赋值和取值方法中,加入对属性的存取限制。

封装的优点:

1 隐藏类的实现细节;

2 可加入控制逻辑,限制对属性的不合理操作;

3 便于修改,增强代码的可维护性;

这里举一个智能冰箱的例子(可乐灌装工厂也可)。

01 public class Lesson07 {
02
03     private String 牛奶="一瓶牛奶";
04
05     public String 得到牛奶(){
06         System.out.println("给出了"+牛奶);
07         return 牛奶;
08     }
09
10     public void 设置牛奶(String s){
11         this.牛奶=s;
12     }
13
14     public static void main(String[] args) {
15
16         Lesson07 lesson = new Lesson07();
17         lesson.得到牛奶();
18
19         lesson.设置牛奶("一罐牛奶");
20         lesson.得到牛奶();
21     }
22
23 }

继承(inheritance):同类事物之间有它的共同性也有各自的独特性,我们把共同的部分抽离出 来,就可以得到使用这些事物的一般性的类,我们在把那些具有特殊性的共同点再次抽象就可到了一些具有特殊性的类。而特殊类拥有一般类所具有的一般性属性和 方法,也拥有自己特有的某些属性和方法。我们把特殊类和一般类之间的关系叫做继承。

举个例子:马儿都有四条腿,马儿都有会跑,我们把这些共同性抽象出来就成了马类;而其中有一些马是白色的马,还有一些是黑色的马,我们把这些特殊性 也分别抽象出来,就成了白马类和黑马类。那么白马类和马类之间的关系就是继承关系。它们是父子关系,马类是夫类、白马类是子类。

image

继承简化了人们对事物的认识和描述,清晰的体现了相关类间的层次关系。

继承达到了功能抽象、继承促进了代码复用、继承也带来了多态性。

这里先对继承有个概念,下面还会有详细的讲解。

多态(polymorphism):多态就是“一种定义、多种实现” 。Java中可以把一个子类的对象赋给一个父类的引用,这就出现了多态。这里先对多态有个概念,下面还会有详细的讲解。

image

这一讲里我们迈入了对象的世界。学习了类和对象的概念,并对面向对象的特点有了初步的了解,从下一讲开始将近距离接触它们。

好,本讲就到这里。

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第六讲:流程控制(二)

快抢沙发

本讲内容:循环、跳出循环、标签跳转

Java中循环有三种形式 while循环、do-while循环 和 for循环。其中从Java 6 开始for循环又分 普通for循环 和 for-each循环两种,我们接下来分别讲解。

一、while 循环

当条件为真时执行while循环,一直到条件为假时再退出循环体,如果第一次条件表达式就是假,那么while循环将被忽略,如果条件表达式一直为 真,那么while循环将一直执行。关于while 括号后的表达式,要求和if语句一样需要返回一个布尔值,用作判断是否进入循环的条件。

1 public class Lesson06_6 {
2     public static void main(String[] args) {
3         int x = 8;
4         while (x > 0) {
5             System.out.println(x);
6             x--;
7         }
8     }
9 }

执行结果:

image

如果你把 x>0 改成大于8 ,while循环将一次都不执行

image

二、do-while 循环

好,如果你无论如何都想执行一次循环体内的代码,可以选择do-while循环,它的特点是做了再说。

1 public class Lesson06_6 {
2     public static void main(String[] args) {
3         int x = 8;
4         do{
5             System.out.println(x);
6             x--;
7         }while(x>8);
8     }
9 }

x=8,条件是大于8,查看运行结果,我们发现他总是会执行一次。

image

三、for 循环

当知道可以循环多少次时,是使用for循环的最佳时机。

1、基本for循环:

image

先举一个例子:

1 public class Lesson06_6 {
2     public static void main(String[] args) {
3         for (int i = 2, j = 1; j < 10; j++) {
4             if (j >= i) {
5                 System.out.println(i + "x" + j + "=" + i * j);
6             }
7         }
8     }
9 }

这个例子打印了从九九乘法表的一部分:

image

原谅我没有从最常用的for循环开始写,你把int i=2 写在for循环前面就变成最常用的for循环了。

下面说一下for循环的规则:

  1. for循环的三个部分任意部分都可以省略,最简单的for循环就是这样的 for(;;){ }
  2. 中间的条件表达式必须返回一个布尔值,用来作为是否进行循环的判断依据
  3. 初始化语句可以由初始化多个变量,多个变量之间可以用逗号隔开,这些在for循环中声明的变量作用范围就只在for循环内部
  4. 最后的迭代语句可以是 i++,j++ 这样的表达式,也可以是毫无干系的 System.out.println(“哈哈”) 之类的语句,它照样在循环体执行完毕之后被执行。

 

2、for-each循环:

for-each循环又叫增强型for循环,它用来遍历数组和集合中的元素,因此我们会在数组一章和集合一章里分别讲到,放心,你会掌握的很好。

这里举个例子给你看看先:

1 public class Lesson06_6 {
2     public static void main(String[] args) {
3         int[] a = { 6, 2, 3, 8 };
4         for (int n : a) {
5             System.out.println(n);
6         }
7     }
8 }

运行结果如下:

image

四、跳出循环 break 、continue

break关键字用来终止循环或switch语句,continue关键字用来终止循环的当前迭代。当存在多层循环时,不带标签的break和 continue只能终止离它所在的最内层循环,如果需要终止它所在的较外层的循环则必须用,标签标注外层的循环,并使用break和continue带 标签的形式予以明确标示。

先看一个不带标签的例子BreakAndContinue.java:

01 public class BreakAndContinue {
02
03     public static void main(String[] args) {
04
05         int i =0;
06         while(true){
07             System.out.println("i="+i);
08             if(i==12){
09                 i++;
10                 i++;
11                 continue;
12             }
13             i++;
14             if(i==20){
15                 break;
16             }
17         }
18
19     }
20
21 }
22    

好例子自己会说话,这个例子打印了从1到20中除去13的数字。我们只需要看明白这个例子的输出结果就能明白break和continue的区别了。

编译并运行代码,查看结果:

image

我们再看一个break带标签的例子:

01 public class BreakAndContinue {
02
03     public static void main(String[] args) {
04
05         boolean isTrue = true;
06         outer:
07             for(int i=0;i<5;i++){
08                 while(isTrue){
09                     System.out.println("Hello");
10                     break outer;
11                 }
12                 System.out.println("Outer loop.");
13             }
14         System.out.println("Good Bye!");
15
16     }
17
18 }
19    

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

image

把上面的例子中break替换成continue,再次编译和运行,查看结果:

image

ok,本讲就到这里,take your time and enjoy it.

分享至上:分享源头

2011.07.31
H
阅读全文...

Java基础第五讲:流程控制(一)

快抢沙发

本讲内容: 分支语句

流程控制 Flow Control

流程控制语句是编程语言中的核心之一。可以分为 分支语句、循环语句和跳转语句。

本讲内容包括分支语句的 if-else 和 switch , 它们又被称为判决语句(decision statements),意思是根据某种条件做出朝哪个方向前进的判断。

一、if-else分支控制语句 ( if-else Branching )

1、最简单的if语句

假设我到办公室里问黄文强在不在?如果他在的话会说在,不在的话一般情况是没人说话了。我们用程序模拟一下:

image

public class Lesson06_1 {
	public static void main(String[] args) {
		//设置黄文强在
		boolean flag = true;
		System.out.println("开始");
		if (flag){
			System.out.println("在");
		}
		System.out.println("结束");
	}
}

为了把分支语句的前后界定清楚,我加了开始和结束标识。上面的运行结果是:

image

2、最简单的if-else语句

假设我到办公室里问黄文强在不在?如果他在的话会说在,不在的时候有热心同事回答了一句“他不在”,那我就不立刻明白了。我们用程序模拟一下:

image

public class Lesson06_1 {
	public static void main(String[] args) {
		//设置黄文强不在
		boolean flag = false;
		System.out.println("开始");
		if (flag){
			System.out.println("在");
		}else{
			System.out.println("他不在");
		}
		System.out.println("结束");
	}
}

上面的运行结果是:

image

3、简单的 if – else if 语句

好吧,如果黄文强不在的话,我想问问刘克强在不在?恰好,刘克强在,那么用程序模拟是这样的:

image

public class Lesson06_1 {
	public static void main(String[] args) {
		// 设置黄文强不在
		boolean flag1 = false;
		// 设置刘克强在
		boolean flag2 = true;
		System.out.println("开始->");
		if (flag1) {
			System.out.println("黄文强在");
		} else if (flag2) {
			System.out.println("刘克强在");
		}
		System.out.println("->结束");
	}
}

上面的运行结果是:

image

4、复合 if- else if – else 语句

如果刘克强也不在,那么用程序模拟是这样的:

public class Lesson06_1 {
	public static void main(String[] args) {
		// 设置黄文强不在
		boolean flag1 = false;
		// 设置刘克强在
		boolean flag2 = true;
		System.out.println("开始->");
		if (flag1) {
			System.out.println("黄文强在");
		} else if (flag2) {
			System.out.println("刘克强在");
		} else {
			System.out.println("他们不在");
		}
		System.out.println("->结束");
	}
}

上面的运行结果是:

image

5、if-else语句规则

  1. if后的括号不能省略,括号里表达式的值最终必须返回的是布尔值
  2. 如果条件体内只有一条语句需要执行,那么if后面的大括号可以省略,但这是一种极为不好的编程习惯。
  3. 对于给定的if,else语句是可选的,else if 语句也是可选的
  4. else和else if同时出现时,else必须出现在else if 之后
  5. 如果有多条else if语句同时出现,那么如果有一条else if语句的表达式测试成功,那么会忽略掉其他所有else if和else分支。
  6. 如果出现多个if,只有一个else的情形,else子句归属于最内层的if语句

6、实例练习:

下面我们看一个例子,(请注意这个例子的写法是不被推荐的,这里这么写是为了讲解if-else的规则)

public class Lesson06 {
	public static void main(String[] args) {
		boolean examIsDone = true;
		int score = 65;
		if (examIsDone)
		if (score >= 90)System.out.println("A ,Excellent");
		else if (score >= 80)
		System.out.println("B ,Good");
		else if (score >= 70)
		System.out.println("C ,Middle");
		else if (score >= 60)
		System.out.println("D ,Pass");
		else
		System.out.println("E ,Fail");
		System.out.println("Done is Done");
	}
}

你认为else是属于哪个if语句的?System.out.println(“Done is Done”);是在哪一行代码之后执行的?

二、分支控制语句 switch Statement

Java中有一个和if语句比较相似的分支控制语句叫switch ,它在有一系列固定值做分支时使用效率要比if-else方式效率高(别急,等一下再告诉你为什么效率高)。

先看一个例子:假设我们不考虑闰年的话,我们如何知道一个月有多少天?先用if-else的方式来实现:

public class Lesson06_2 {
	public static void main(String[] args) {
		int month=9;
		if(month==1){
			System.out.println(month+"月有31天");
		}else if(month==2){
			System.out.println(month+"月有28天");
		}else if(month==3){
			System.out.println(month+"月有31天");
		}else if(month==4){
			System.out.println(month+"月有30天");
		}else if(month==5){
			System.out.println(month+"月有31天");
		}else if(month==6){
			System.out.println(month+"月有30天");
		}else if(month==7){
			System.out.println(month+"月有31天");
		}else if(month==8){
			System.out.println(month+"月有31天");
		}else if(month==9){
			System.out.println(month+"月有30天");
		}else if(month==10){
			System.out.println(month+"月有31天");
		}else if(month==11){
			System.out.println(month+"月有30天");
		}else if(month==12){
			System.out.println(month+"月有31天");
		}else{
			System.out.println("没有这个月份吧");
		}
	}
}

接下来我们使用switch语句重新实现一次:

public class Lesson06_4 {
	public static void main(String[] args) {
		int month = 9;
		switch (month) {
		case 1:
			System.out.println(month + "月有31天");
			break;
		case 2:
			System.out.println(month + "月有28天");
			break;
		case 3:
			System.out.println(month + "月有31天");
			break;
		case 4:
			System.out.println(month + "月有30天");
			break;
		case 5:
			System.out.println(month + "月有31天");
			break;
		case 6:
			System.out.println(month + "月有30天");
			break;
		case 7:
			System.out.println(month + "月有31天");
			break;
		case 8:
			System.out.println(month + "月有31天");
			break;
		case 9:
			System.out.println(month + "月有30天");
			break;
		case 10:
			System.out.println(month + "月有31天");
			break;
		case 11:
			System.out.println(month + "月有30天");
			break;
		case 12:
			System.out.println(month + "月有31天");
			break;
		default:
			System.out.println("没有这个月份吧");
			break;
		}
	}
}

运行2个程序,结果都是 9月有30天。

image

从简洁程度和效率上,switch都略胜一筹(上面的2个程序,把输出语句屏蔽掉,各自循环运行10亿次,switch快了6636毫秒,也就是6秒钟)。

为什么会快一些呢,因为switch是在编译时优化的。运行时进行的不是变量的比较运算,而是直接跳转。举个例子7×9你是反射式的出来的结果,9×7你需要稍稍楞一小下,99×7你就需要计算一下了。这就是他们之间的区别。如果你觉得举例子不科学,改天我们再找虚拟机一起聊这个话题^_^

好了,我们还是先学习一下switch都有什么使用规则吧:

1、留意switch格式的写法

标准且合法的格式:

image

非法格式说明:

switch(x){

case 0 {}

}

switch(x){

0:{}

1:{}

}

能看到哪里错了吧,第一个没冒号,第二个没case关键字

2、switch表达式必须能被求值成char byte short int 或 enum

记忆的话可以记忆成非lang整形加枚举。请切记在java 6 及以前版本中string是不允许用在switch表达式中的(Java 7可以用string,现在正式版还没出来)

3、case常量必须是编译时常量,因为case的参数必须在编译时解析,也就是说必须是字面量或者是在声明时就赋值的final变量。

这个不太好理解,举个例子看看:

public class Lesson06_5 {
	public static void main(String[] args) {
		final int a = 2;
		final int b;
		b = 3;
		int x =2;
		switch(x){
			case 1: //编译OK
			case a: //编译OK
			case b: //无法通过编译
		}
	}
}

编译一下看看:

image

4、case常量被会转换成switch表达式的类型,如果类型不匹配也会出错:

public class Lesson06_5 {
	public static void main(String[] args) {
		byte x =2;
		switch(x){
			case 1: //编译OK
			case 128: //无法自动转换成byte,编译器会报错		}
	}
}

image

5、多个case常量重复也会出错:

public class Lesson06_5 {
	public static void main(String[] args) {
		byte x =2;
		switch(x){
			case 1: //编译OK
			case 1:
		}
	}
}

编译一下看看:

image

6、匹配的case的语句是入口而不是独立分支:

public class Lesson06_5 {
	public static void main(String[] args) {
		int x = 1;
		switch (x) {
			case 1:System.out.println("周一");
			case 2:System.out.println("周二");
			case 3:System.out.println("周三");
			case 4:System.out.println("周四");
			case 5:System.out.println("周五");
			case 6:System.out.println("周六");
			case 7:System.out.println("周日");
			default:System.out.println("这个是星期几啊");
		}
	}
}

image

switch代码是找到一个匹配的case常量入口,然后自顶向下执行的。也就是英文里的Fall-Through,你可以形象的理解他的执行方式是自由落体式的一股脑碰到啥就执行啥。

7、break:在switch语句块中,执行到break关键词时,立刻退出switch语句块,转到switch的下一条语句。

我们结合6、7两点规则来重写一次,判断月份天数的例子:

public class Lesson06_3 {
	public static void main(String[] args) {
		int month = 9;
		switch (month) {
		case 1:
		case 3:
		case 5:
		case 7:
		case 8:
		case 10:
		case 12:
			System.out.println(month + "月有31天");
			break;
		case 2:
			System.out.println(month + "月有28天");
			break;
		case 4:
		case 6:
		case 9:
		case 11:
			System.out.println(month + "月有30天");
			break;
		default:
			System.out.println("没有这个月份吧");
			break;
		}
	}
}

运行程序:

image

8、default :当switch中所有的case常量都不匹配时,会执行default分支:

再把上面判断星期几的代码改一下:

public class Lesson06_5 {
	public static void main(String[] args) {
		int x = 8;
		switch (x) {
			case 1:System.out.println("周一");
			default:System.out.println("这个是星期几啊");
			case 2:System.out.println("周二");
			case 3:System.out.println("周三");
			case 4:System.out.println("周四");
			case 5:System.out.println("周五");
			break;
			case 6:System.out.println("周六");
			case 7:System.out.println("周日");
		}
	}
}

运行程序:

image

我们故意写了星期8,所以触发了default分支,可是因为没有使用break语句,所以又直落执行了。于是出现了这个看起来有点奇怪,其实很正常的输出。

好了,到这里我们就把switch讲完了。大家要记住的是当程序中有很多需要判断的成点状数据需要分别处理时优先采用switch,如果遇到区间判断时,不用思考,还是用if-else吧。

对编程基础知识的理解会随着你的编程经验不断加深,不要奢望一次理解有多到位,淡定,淡定。

本讲就到这里,Take your time and enjoy it .

分享至上:分享源头

2011.07.30
H
阅读全文...
  • sitemap_baidu