1. Java编程基础
1.1. 基本概念
Ø 什么是计算机语言
计算机语言指用于人与计算机之间通讯的语言。计算机语言是人与计算机之间传递信息的媒介。为了使电子计算机进行各种工作,就需要有一套用于编写计算机程序的数字、字符和语法规划,由这些组成计算机指令就是计算机语言。
软件就是由若干条计算机语言所组成的。
Ø 计算机语言分类
机器语言:
机器语言是直接用二进制代码指令表达的计算机语言,指令是用0和1组成的一串代码,它们有一定的位数,并分成若干段,各段的编码表示不同的含义。
汇编语言:
汇编语言是使用一些特殊的符号来代替机器语言的二进制码,计算机不能直接识别,需要用一种软件将汇编语言翻译成机器语言。
高级语言:
使用普通英语进行编写源代码,通过编译器将源代码翻译成计算机直接识别的机器语言,之后再由计算机执行。
Ø 高级语言工作原理
1.2. Java开发环境搭建
Ø JDK与JRE
JDK(Java Development Kit) Java开发工具,包含开发Java程序的所有组件,包含JRE
JRE(Java Runtime Environment) Java运行环境,如果要运行Java程序,就需要JRE的支持
常用组件:
src.zip Java是一门开源的语言,其源代码都在这个压缩包中
rt.jar Java的基础核心类库,我们编写Java程序时使用的class都在这个jar包中
javac.exe 编译器,将.java源代码编译为.class文件
java.exe 虚拟机,运行Java程序的工具
jar.exe 将class文件打成jar包的工具
javadoc.exe 生成帮助文档的工具
Ø 环境变量:环境变量是指在操作系统中用来指定操作系统运行环境的一些参数
path:
如果想在任意目录下运行一个程序,我们就需要将程序所在的路径配置在path环境变量中。
通常我们会将javac.exe所在目录配置到path中,因为我们需要在任意目录下都能编译Java源文件。
配置完成之后可以在命令行输入javac测试,如果显式帮助信息则是配置成功。
classpath:
Java虚拟机运行时加载类的路径。JDK5之后不配置默认为当前目录“.”。如使用JDK1.4或以下版本时需要人工配置。
暂时不需要配置,默认加载当前目录下的所有class文件。
配置方式:
a. 命令行
点击屏幕左下角开始– 运行– 输入cmd – 在命令行中直接输入命令进行修改
查看变量值:set 变量名
设置变量值:set 变量名=变量值,多个值之间使用分号“;”分割,引用变量时使用“%变量名%”形式
注意:此种方式仅适用于当前窗口
b. 我的电脑
鼠标右键点击我的电脑 – 属性 – 高级 – 环境变量
找到要修改的变量将其值修改,此种方式永久有效
注意:
配置环境变量之后可以查看编译器(javac.exe)和虚拟机(java.exe)版本,虚拟机版本不能低于编译器。
使用哪个版本的编译器和虚拟机取决于path环境变量,如果虚拟机版本过低,可以通过环境变量来修改。
编译器版本查看方式:javac –version
虚拟机版本查看方式:java –version
1.3. 第一个Java程序
Ø 编写源代码
新建文本文档,扩展名改为.java,在文件中写入代码。
注意:
windows操作系统默认是隐藏已知文件扩展名的。
请测试新建一个文本文档,如果看到的文件名是“新建文本文档”而不是“新建文本文档.txt”,那么说明你的扩展名被隐藏了。
请选择菜单栏中的工具 – 文件夹选项– 查看 – 下拉滚动条找到“隐藏已知文件扩展名” – 取消掉这一项。
Ø 编译字节码文件
a. 左键单机屏幕左下角开始– 运行– 输入cmd启动命令行窗口
b. 使用DOS命令进入源代码所在目录
c. 使用编译器(javac.exe)编译源代码,javac 文件名.java,编译后在该目录中会出现扩展名为class的字节码文件
常用DOS命令:
跳转到指定盘符: 盘符: 例:C: D: E:
跳转到指定目录: cd 目录 例:cd Itcast\day01 cd Tencent\QQ\Bin
显示当前目录下文件: dir
跳转到上级目录: cd..
跳转到根目录: cd\
清屏: cls
Ø 运行程序
使用虚拟机(java.exe)运行class文件,java 文件名,注意不要加扩展名,因为虚拟机只能运行class文件,扩展名省略不写,如写则报错。
1.4. 进制
Ø 十进制
由0到9的数字组成,逢十进一
我们最常用的一种进制
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Ø 二进制
由0和1组成,逢二进一
计算机中存储任何数据都是以二进制的形式进行存储的
0 1 10 11 100 101 110 111 1000
Ø 八进制
由0到7的数字组成,逢八进一
八进制在程序中以0开头
0 1 2 3 4 5 6 7 10 11 12 13 14 15 16 17 20
Ø 十六进制
由0到9的数字和A-F的字母组成,逢十六进一
十六进制在程序中以0x开头
0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20
Ø 进制转换
a. 十进制转二、八、十六进制
除法取余,将要转换的数除以进制数,记住余数,再除以进制数,记住余数,直到这个数等于0为止,将所有余数反转就是对应的二进制表现形式。
b.二、八、十六进制转十进制
乘法,将要转换的数编号,编号从低位开始,从0开始,将每一位上的数乘以进制数的编号次方,最后将所有乘得的结果相加就是十进制表现形式。
c.二进制和八进制互转
八进制的每一位对应二进制的三位。
d. 二进制和十六进制互转
十六进制的每一位对应二进制的四位。
Ø 二进制负数
一个负数的二进制表现形式就是这个负数忽略符号的正数对应的二进制取反再加一。
计算机中存储的二进制数最高位是0则是正数,是1则是负数。
1.5. 码表
ASCII:英文码表,每个字符占1个字节。A是65,a是97
GB2312:兼容ASCII,包含中文,每个英文占1个字节(正数),中文占2个字节(2个负数)。
GBK:兼容GB2312,包含更多中文,每个英文占1个字节(正数),中文占2个字节(第一个负数、第二个可正可负)。
Unicode:国际码表,每个字符占2个字节。Java中存储字符类型就是使用的Unicode编码。
UTF-8:国际码表,英文占1个字节,中文占3个字节。
2. Java语法
2.1. 基本格式
所有Java代码都应该在一个class中。
Java是严格区分大小写的。
Java是一种自由格式的语言。Java代码分为结构定义语句和功能执行语句,功能执行语句最后必须以分号结束。
2.2. 注释
单行注释和多行注释是在程序用来标记一些特殊的文本,这些文本不参与编译运行。
文档注释是Java中特有的一种注释,它可以通过JDK中的工具(javadoc.exe)解析,生成帮助文档。
文档注释: /** 注释内容 */
2.3. 标识符
Ø 什么是标识符
标识符可以理解为程序中我们自定义的一些名字,包括:包名、类名、函数名、变量名、常量名。
Ø 标识符的命名规则
由大小写字母、数字、下划线(_)和美元符号($)组成,开头不能是数字。不能使用关键字。推荐使用全英文。
Ø 标识符通用规范
类名、接口名:
所有单词首字母大写,驼峰式命名,例如:XxxYyyZzz
变量名、函数名:
第一个单词首字母小写,其他单词首字母大写,驼峰式命名,例如:xxxYyyZzz
常量名:
所有字母都大写,单词之间用下划线分割,例如:XXX_YYY_ZZZ
包名:
全部小写,例如:xxx.yyy.zzz
2.4. 关键字
catch |
||||
short |
||||
throws |
||||
注:java 无sizeof ,goto, const 关键字,但不能用goto const作为变量名
2.5. 常量
Ø 整型
整数,4个字节。
Ø 长整型
整数,8个字节。以L结尾。
Ø 单精度浮点数
小数,4个字节。以F结尾。
Ø 双精度浮点数
小数,8个字节。
Ø 布尔
只有两个值,真(true)或假(false),1个字节。
Ø 字符
单个字符,2个字节。例如:'a', '中', '5', '\u0026' , '\u0027'
在字符常量中,斜杠(\)是一个特殊的字符,它的作用是用来转义后面一个字符,这些字符通常是不可见的或者有特殊意义的。
'\r' 回车,回到一行的开始
'\n' 换行,换到下一行
'\t' 制表符,键盘上的Tab
'\b' 类似退格,键盘上的Backspace
以上字符都不可见,无法直接表示,所以用斜杠加上另外一个字符来表示。
'\'' 单引号,Java代码中单引号表示字符的开始和结束,如果直接写程序会认为前两个是一对,报错。
'\"' 双引号,Java代码中双引号表示字符串的开始和结尾,如果要写一个包含双引号的字符串那么这个双引号也需要转义。
'\\' 斜杠,Java代码中的斜杠是转义字符,用来和后面一个字符配合使用,在真正需要用斜杠的时候那么就要用另一个斜杠来转义。
以上字符都有特殊意义,无法直接表示,所以用斜杠加上另外一个字符来表示。
Ø 字符串
由若干个字符组成的一串。可以是一个字符、多个字符、或者一个都没有。字符串没有固定大小。
Ø 空
null,只有这一个值,用来表示一个引用为空。
2.6. 变量
int x = 5;
System.out.println(x);
x = 1 + 1;
System.out.println(x);
x = x + 1;
System.out.println(x);
上面的x就是一个变量,变量没有固定的值,是在内存中开辟的一片空间。
Java中的变量中只能存储同一种类型的值。
变量在被取值之前必须初始化(第一次给变量赋值)。
Ø 变量分类
a.基本数据类型: 8种
整数:
byte 1个字节,最小值:-128,最大值:127
short 2个字节,最小值:-32768,最大值:32767
int 4个字节,最小值:-2147483648,最大值:2147483647
long 8个字节,最小值:- 9223372036854775808,最大值:9223372036854775807
浮点数:
float 4个字节,最小值:1.4E-45,最大值:3.4028235E38
double 8个字节,最小值:4.9E-324,最大值:1.7976931348623157E308
字符:
char 2个字节,最小值:0,最大值:65535
布尔:
boolean 1个字节,true或false
b.引用数据类型:
类、接口、数组都是引用数据类型,除了8种基本数据类型,其他所有类型都是引用数据类型。
Ø 类型转化
a. 自动类型转换
在byte、short、char参与运算的时候会自动提升为int,相当于将一个占空间较小的值放入了一个较大的空间。
b. 强制类型转换
可以将一个占空间较大的值使用(类型)的形式强制放入一个较小的空间,有可能损失精度。
c. 字符串转换
任何值和字符串相加都会得到字符串。
Ø 变量的作用域与生命周期
作用域:变量定义在哪一级大括号中,哪个大括号的范围就是这个变量的作用域。相同的作用域中不能定义两个同名变量。
生命周期:变量的生命周期从定义时开始,超出作用域后结束。变量生命周期以外不能使用。
2.7. 函数
Ø 函数的定义
函数就是一段有名字的代码,可以完成某一特定功能。
如果有一段代码要使用多次,我们可以给它起个名字,每次使用时通过名字调用,这样就不用每次都写一大段代码了。
如果某个函数在执行的时候需要调用者传入数据,那么可以定义参数列表,用于接收数据。
如果函数运行之后需要返回给调用者数据,那么需要指定返回值类型,并且用关键字return返回。
定义函数的3个必要条件:函数名、参数列表、返回值类型。如果不需要参数也要写小括号,如果没有返回值类型要写void。
Ø 名词解释
形参:在定义函数时小括号中的参数,用来接收数据的参数。
实参:在调用函数时真正传入的参数,传递给函数的数据。
参数类型:函数的参数的类型,一旦定义传入时必须匹配。
返回值:函数运行结束后返回的值,使用return关键字返回。
返回值类型:函数运行结束后返回的值的类型,在类型非void情况下必须返回,而且必须类型匹配。
Ø 函数的重载
多个函数的函数名相同,参数列表不同(个数、顺序、类型),这就是函数的重载。在调用函数的时候通过传入的实参找到匹配的函数调用。
函数的重载和返回值类型无关。
2.8. 运算符
Ø 算数运算符
加号:在操作数字、字符、字符串时是不同的,两个字符相加得到的是码表值,两个字符串相加是将字符串连接在一起。
除号:整数在使用除号操作时,得到的结果仍为整数(小数部分忽略)。
取模:模数的符号忽略不计,结果的正负取决于被模数。
自增:符号在前就是先运算后取值,符号在后则是先取值后运算。
习题:
a.System.out.println(3500 / 1000 * 1000);
b.某个培训中心要为新到的学员安排房间,假设共有x个学员,每个房间可以住6人,让你用一个公式来计算他们要住的房间数?
Ø 赋值运算符
等于:可以多个连用,例如:x = y = z = 5;
加等于:x += 5; 相当于 x = x + 5;
面试题:
以下代码正确的是? (多选)
a. byte b = 1 + 1;
b. byte b = 1; b = b + 1;
c. byte b = 1; b += 1;
d. byte b = 1; b = ++b;
Ø 比较运算符
比较运算符运行结束之后返回的都是boolean值。
注意运算符==不要写成=
Ø 逻辑运算符
逻辑运算符运行结束之后返回的也是boolean值
& 两边都为true结果才为true,只要有一边是false,结果就是false
| 两边都为false结果才为false,只要有一边是true,结果就是true
^ 判断两边是否不同,不同则为true,相同则为false
! 取反,!true结果是false,!fasle结果是true
&& 和&结果相同,具有短路效果,如果前半是false,表达式结果一定为false,不运行后一半
|| 和||结果相同,具有短路效果,如果前半是true,表达式结果一定为true,不运行后一半
分析以下程序运行结果:
int x = 1;
int y = 2;
System.out.println(x++ == y & ++x > y++);
System.out.println(x);
System.out.println(y);
int x = 1;
int y = 2;
System.out.println(x++ == y && ++x > y++);
System.out.println(x);
System.out.println(y);
int x = 1;
int y = 2;
System.out.println(x++ == y | ++x > y++);
System.out.println(x);
System.out.println(y);
int x = 1;
int y = 2;
System.out.println(x++ == y || ++x > y++);
System.out.println(x);
System.out.println(y);
&& 在前半是false的时候短路
|| 在前半是true的时候短路
Ø 位运算符
任何信息在计算机中都是以二进制的形式保存的,&、|、^除了可以作为逻辑运算符,也可以做为位算符。
它们对两个操作数中的每一个二进制位都进行运算,0当做false,1当做true。
& 将两个二进制数每一位进行&运算,两边都为1结果才为1,只要有一边是0,结果就为0。
| 将两个二进制数每一位进行|运算,两边都为0结果才为0,只要有一边是1,结果就为1。
^ 将两个二进制数每一位进行^运算,只要两边不同结果就为1,相同则为0。
我们可以对数据按二进制位进行移位操作,java的移位运算符有三种:
<< 左移 将二进制的每一位向左移,低位补0。左移几位就相当于乘以2的几次方。
>> 右移 将二进制的每一位向右移,原来高位是0就补0,原来高位是1就补1。右移几位就相当于除以2的几次方。
>>> 无无符号右移 将二进制的每一位向右移,高位补0。正数移动没区别,负数移动后变为正数。
练习:
a. 用&和>>来做十进制转十六进制
b. 有两个int型变量a和b,在不使用第三个变量的情况下交换两个变量中的值
Ø 运算符优先级
思考一下代码运行结果:
System.out.println(1 + 2 * 3);
System.out.println(false && true || true);
System.out.println(true || true && false);
int a = 2;
int b = a + 3 * a++;
System.out.println(b);
int a = 2;
int b = a++ + 3 * a;
System.out.println(b);
int a = 1;
int b = 2;
System.out.println(a+++b);
尽量写简单的表达式,遇到运算符优先级的问题使用括号解决。
2.9. 语句
Ø 顺序结构
顾名思义,就是程序从上到下一行一行执行的结构,中间没有判断和跳转,直到程序结束。
Ø 选择结构
程序具备多个分支,通过条件判断决定程序选择那一条分支执行
a. if语句:
通过if...else if...else决定程序流程。
如果if中的条件满足则执行其中语句,if未满足则继续判断else if,如果满足则执行,不满足继续判断下一个else if,如果所有都不满足,则执行else。
练习:
用if else语句判断一个数是奇数还是偶数。
用户输入一个字符,用程序判断是否为小写字母,如果是,请输出“您输入的字符是小写字母”。
b. switch语句:
通过switch...case...default语句控制程序流程。
根据switch后括号中的值判断运行哪一个case,这个值可以是byte、short、ch;ar、int。
default语句是可选的,如果所有case都不满足,则会执行default。
一旦匹配到一个case,程序就会从这个case向下执行,执行完一个case之后不会跳过其他的case,如需跳过请使用break。
c. 三元运算符
语法:表达式 ? 结果1 : 结果2
如果表达式结尾为true取结果1,为false则取结果2。
注意三元运算符也是有短路的效果,根据表达式的结果,只运行冒号一边的,另外一边的不参与运行。
练习:
定义一个函数,接收两个int参数,返回较大的一个。
Ø 循环结构
通过循环语句让同一段代码反复执行多次,执行完毕程序才会继续往后运行
a. while
先判断while中的表达式结果是否为true,true则执行循环体,执行结束之后再次判断,如果表达式结果为false则跳出循环。
练习:
打印出0-9
打印出a-z
b. do...while
先执行一次循环体,然后判断while中的表达式,如果是true继续执行,如果是false则跳出循环。
练习:
编写一个程序,这个程序不断地读取键盘上输入的字符,直到读到字符’q’时,程序结束。
c. for
for循环的括号中有三条语句,都是可选项。
语句1:这条语句会在整个循环开始之前执行,且仅运行一次,不参与循环。
语句2:必须是一个返回boolean值的表达式,如果写了这个语句那么每次循环开始之前会判断,true则执行循环,false则不执行。没写则直接执行。
语句3:这条语句在每次循环体运行结束之后执行。
练习:
使用星号打印如下图案
*****
*****
*****
*****
*****
i *
* 0 1
** 1 2
*** 2 3
**** 3 4
***** 4 5
i 空格 *
* 0 4 1
*** 1 3 3
***** 2 2 5
******* 3 1 7
********* 4 0 9
d. continue、break、return
continue:跳过一次循环,继续执行下一次
break:结束循环
return:结束方法
2.10. 数组
Ø 什么是数组
数组是一个类型一致,长度不可变的容器。可以通过索引操作容器中的每一个元素。
如果有多个类型相同的数据需要存储,我们就可以将其定义为一个数组,这样做省去了创建多个变量的麻烦。
Ø 如何定义数组
int[] arr = {1,2,3};
定义int数组arr,长度为3,其中3个元素分别为1、2、3。这种方式只能在定义数组的时候使用。
int[] arr = new int[]{1,2,3};
定义int数组arr,长度为3,其中3个元素分别为1、2、3。可以再任何情况使用。
int[] arr = new int[3];
定义int数组arr,长度为3。其中所有元素都为默认值0。
Ø 访问数组元素、遍历数组
存在数组中的数据是有索引的,从0开始递增,我们通过数组名和索引就可以操作其中每一个元素。例如:
System.out.println(arr[0]); // 打印数组中索引为0的元素
arr[1] = 100; // 给数组中索引为1的元素赋值为100
数组的长度
数组可以使用length属性获取其长度。
遍历数组
由于数组可以通过索引获取每一个元素,又可以通过length获取长度,那么我们就可以定义循环来遍历数组中的每一个元素了。
Ø 使用数组时的异常
如果访问数组时索引越界(小于0或者大于length-1),会抛出异常:ArrayIndexOutOfBoundsExcepion
如果访问数组的引用为空(null),会抛出空指针异常:NullPointerException
Ø 数组练习
a. 定义一个函数,将数组中所有元素打印。要求打印成一行,每个元素之间以逗号分隔。
b. 定义一个函数,交换数组中的两个元素。
c. 定义一个函数,找出数组中的最大数。
d. 定义一个函数,将数组中所有元素反转。例如:{1, 2, 3} 反转后为 {3, 2, 1}。
e. 定义一个函数,对数组进行排序。
Ø 与数组操作相关函数
Arrays.toString() 查找帮助文档Arrays类,学习使用此方法将字符串转为字符串形式。
将一个数组转为字符串表示形式
System.arraycopy() 查找帮助文档System类,学习使用此方法拷贝数组中元素。
将一个数组中的某些元素拷贝到另一个数组的指定位置
Ø 多维数组
数组中的每一个元素都是数组,这样的数组就是多维数组。
int[][] arr = { { 1, 2, 3 }, { 4, 5 }, { 6, 7, 8, 9 } };
定义二维数组arr, 其中有三个元素都是数组, 第一个数组3个元素, 第二个2个元素, 第三个4个元素.
int[][] arr = newint[][] { { 1, 2, 3 }, { 4, 5 }, { 6, 7, 8, 9 } };
定义二维数组arr, 其中有三个元素都是数组, 第一个数组3个元素, 第二个2个元素, 第三个4个元素.
int[][] arr = newint[3][3];
定义二维数组arr, 其中有三个元素都是数组, 每个小数组都是3个元素.
int[][] arr = newint[3][];
定义二维数组arr, 其中有三个元素都是数组, 每个小数组元素个数不确定.
2.11. 综合练习
a. 编写一个程序,程序接收键盘上输入的三个数,并输出这三个数的最大数。
b. 编写一个程序,它先将键盘上输入的一个字符串转换成十进制整数,然后打印出这个十进制整数对应的二进制形式。
c. 使用移位方式将一个十进制数转换为十六进制。三种方式:
0-9之间的数值直接加上字符'0',9以上的数值减去10以后再加上字符'A'
定义一个数组,其中包含0-F这些字符,然后用要计算的数值作为数组的索引号,即可获得其对应的十六进制数据。
Character.forDigit静态方法可以将一个十六进制的数字转变成其对应的字符表示形式,例如,根据数值15返回字符'F'。
3. 面向对象
3.1. 面向对象概念
Ø 什么是面向对象面向对象(Object Oriented)是一种思想,90年代以后软件开发的主流思想。
由于现实社会是由各种各样的事物所组成的,而我们编程又是在模拟现实社会,那么在程序也要用一些东西来表示现实社会中的事物,这些东西就是程序中的对象。我们在程序中使用这些对象,对其特征和行为进行操作进行编程,这就是面向对象编程。
在使用面向对象编程思想之前,我们通常用面向过程的思想编程,先分析出解决问题的步骤,然后按照步骤一步一步实现。
Ø 面向对象编程的优点
提高代码复用性。
使用者无需关心具体细节。
转变程序员角色,更加符合人的思维习惯。
3.2. 类与对象
Ø 什么是类
类是用来描述对象的。由于对象是虚拟出来的东西,是看不见摸不着的,我们需要在程序中使用对象,就需要用一种方式来描述对象,然后根据这个描述来创建对象。
Ø 类和对象的关系
对象是类的实例,类是对象的抽象。
Ø 怎么定义类
将一系列特征相似的对象的共同特征及行为抽取出来进行描述,写在一个class中,用成员变量描述对象的特征,用成员方法来描述对象的行为。
class Person {
String name;
int age;
void speak(){
System.out.println("My name is " + name);
System.out.println("I am " + age + " years of age");
}
}
Ø 怎么使用类创建对象
使用new关键字和指定类名来创建一个对象。
Ø 对象的产生
Person p = new Person();
这句话先在堆内存中创建了一个对象,然后栈内存中创建一个变量引用了对象的地址。
Ø 成员变量初始化
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。基本数据类型初始化值为0,引用数据类型初始化值为null。
Ø 对象的使用
当我们创建对象之后可以使用点语法来访问对象的属性和方法。例如:
Person p = new Person();
p.name = "张三"; // 访问属性(成员变量)
p.age = 20;
p.speak(); // 访问方法
Ø 对象的生命周期
对象的生命周期从new关键字创建时开始,到没有任何引用到达对象时结束(成为垃圾)。
Ø 匿名对象
我们可以不定义变量引用对象,使用new关键字创建对象后直接使用,这样的对象没有名字,所以叫匿名对象。
匿名对象因为没有任何引用到达,在使用一次之后即成为垃圾。
通常我们需要使用一个对象且只使用一次的时候,就可以使用匿名对象。比如将对象作为一个参数传递给另外一个函数。
3.3. 封装(Encapsulation)
Ø 什么是封装
封装是指隐藏对象的属性和一些实现细节,仅对外提供必须的访问方式。
Ø 怎么封装
将所有属性隐藏,提供公有方法对其访问。
将不需要对外提供的方法隐藏。
Ø 封装的优点
提高安全性:在访问对象的属性时候通过方法实现,在方法中可以进行校验。隐藏不必要提供的方法避免错误的调用。
简化编程:使用者无需关心对象内部具体实现细节,只要根据对象功能调用指定方法。
3.4. 构造函数(Constructor)
Ø 什么是构造函数
构造函数(Constructor)是一个特殊的函数。
函数名和类名相同。
没有返回值类型。注意:没有返回值类型不等同于void,void也是一种返回值类型。不能使用return关键字返回任何值。
在使用new关键字创建对象之后自动调用。
Ø 构造函数的重载
构造函数的重载和普通函数相同,函数名相同,参数列表不同即可。
Ø 构造函数的调用
构造函数在new关键字创建对象时调用。
构造函数可以在该类其他构造函数的第一个语句使用this关键字调用。
Ø 所有类都有构造函数
每一个类都有构造函数,即使我们没有显式定义构造函数,也会生成一个默认无参的构造函数,其中没有任何内容。
注意:这个自动生成的构造函数只在未定义任何构造函数时生成,如果我们定义了一个有参的构造函数,那么就不会生成无参的了。
Ø 构造函数的访问权限
在定义构造函数时,如无特殊需要,应使用public关键字修饰构造函数。
在一些特定情况下,我们不想让别人创建该类对象,那么可以使用private修饰构造函数,例如单态设计模式。
3.5. this关键字
this关键字除了在构造函数中调用其他构造函数以外,还可以当做一个引用使用。其用于方法中,哪个对象调用该方法,this就引用哪个对象。例如:
方法中局部变量和成员变量重名,我们想调用成员变量时就可以使用this.变量名形式访问成员变量。
在方法中要将调用该方法的对象作为参数传递给另一个方法时,可以将this作为实参传给该方法。
在内部类中访问外部类的成员时,需要使用外部类名.this.成员名形式访问。
3.6. 函数的参数传递
基本数据类型的变量作为实参传入函数之后,在函数中将形参改变,调用处的实参不变。
因为基本数据类型的值是直接存在变量中,传入函数之后函数中的形参也同样存了一个值,这两个值是没有联系的,所以函数中将形参改变时修改的只是函数中的变量的值,和调用处的实参无关。
引用数据类型的变量作为实参传入函数之后,在函数中将形参改变,调用处的实参改变。
因为引用数据类型变量中存储的是地址,传入函数之后函数中的形参存储的也是同样一个地址,函数中将这个形参改变时改变的都是同一个地址上的对象,所以一边改变两边都变。
3.7. static关键字
static关键字用来修饰类的成员,被这个关键字修饰的成员都和类加载有关。
JVM运行时不会将所有类加载到内存,因为无法确定程序中要使用哪些。类在第一次使用时加载,只加载一次。
Ø 静态变量
用static修饰的变量就是静态变量。
静态变量在类加载后就初始化。
静态变量被类的所有实例所共享。
静态变量可以使用类名.变量名形式访问。
如果在定义一个类的时候,发现一个成员变量需要被所有实例所共享,那么这个成员变量就需要定义为static的。
Ø 静态方法
用static修饰的方法就是静态方法。
静态方法在类加载后就可以使用。
静态方法可以使用类名.方法名形式访问。
静态方法不能直接访问外部非静态成员。
因为外部非静态成员必须在类创建对象之后才能使用,而静态方法可以在没创建对象时就使用。
如果要在静态方法内部访问外部非静态成员,需要先创建该类对象,通过对象访问。
静态方法中不能使用this关键字。
因为this是个引用,哪个对象调用方法就引用哪个对象。而静态方法有可能不是被对象调用的,this无从引用。
如果一个方法不用访问对象的非静态成员,那么就可以定义为静态的,这样使用者就不需要创建对象,直接用类名调用。
静态方法通常是作为工具方法或者一个可以产生对象的方法被声明,目的是为了让调用者更方便的使用,不必创建对象。
Ø 静态代码块
用static修饰的代码块就是静态代码块。
静态代码块在类加载后执行。
静态代码块和静态方法相同,不能使用外部非静态成员。
静态代码块执行和静态变量的初始化顺序由代码从上到下顺序决定。
Ø 静态内部类
用static修饰的内部类就是静态内部类。
静态内部类在类加载后就可以创建对象,无需创建外部类对象。
具体内容详见3.18内部类
3.8. 垃圾回收
对象在没有任何引用可以到达时,生命周期结束,成为垃圾。
所有对象在被回收之前都会自动调用finalize()方法。
一个对象在成为垃圾之后不会被马上回收,JVM会检测内存中的垃圾堆积到一定程度时才会回收,如果我们不想等到这个时候才回收,可以使用System.gc()方法来通知虚拟机回收垃圾。调用该方法之后JVM会开启新线程做处理垃圾的工作,这需要一定时间。
3.9. 单态设计模式(SingletonPattern)
Ø 什么是设计模式
在编程过程中我们经常会遇到一些典型的问题或需要完成某种特定需求,而这些问题和需求前人也曾经遇到过,他们经过大量理论总结和实践验证之后优选出的代码结构、编程风格、以及解决问题的思考方式,这就是设计模式(Design pattern)。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免得我们自己再去思考和摸索。
Ø 单态(单例)设计模式
单态设计模式(Singleton pattern)就是要保证在整个程序中某个类只能存在一个对象,这个类不能再创建第二个对象。
Ø 单态设计模式的写法
私有化构造函数,阻止创建新对象。
由于需要返回一个对象,那么我们就需要在类内部自己创建一个对象,并使用成员变量记住它。
由于该类不能创建对象,所以这个成员变量不能是普通的成员变量,需要静态,这样在类加载之后就可以创建一个唯一的对象了。
我们不希望其他类修改这个成员变量,所以将其私有。
提供一个公有的方法用来获取唯一的一个对象。
这个方法由于需要在不创建对象的情况下使用,所以需要静态。
3.10. 继承(Inherit)
Ø 什么是继承
在程序中,可以使用extends关键字可以让一个类继承另外一个类。
继承的类为子类(派生类),被继承的类为父类(超类, 基类)。
子类会自动继承父类所有的方法和属性。
Ø 为什么要使用继承
当我们发现一个类的功能不行,方法不够用时,就可以派生子类,增加方法。
当我们需要定义一个能实现某项特殊功能的类时,就可以使用继承。
最终还是为了一个目的,实现代码的复用性。
当我们定义一个类时,发现另一个类的功能这个类都需要,而这个类又要增加一些新功能时,就可以使用extends关键字继承那个类,这样那个被继承类的功能就都有了,不必重写编写代码。这时只要在新的类中编写新的功能即可,原有代码就被复用了。
Ø 继承的特点
Java只支持单继承,不支持多继承,但是可以多重继承
因为如果一个类继承多个类,多个类中有相同的方法,子类调用该方法时就不知道该调用哪一个类中的方法了。
子类中可以使用super调用父类成员
super用法和this类似,this是谁调用该方法就引用谁,super是调用该方法的对象的父类对象。
Ø 向上转型
把一个子类当做父类来用是可以的,因为父类有的子类都有
把一个父类当做子类来用就不可以了,因为子类有的父类不一定有
可以定义一个父类类型的变量来记住子类对象,这在程序中称之为向上转型
Ø 强制类型转换
把一个子类当做父类来用的时候,不能调用子类特有方法。
因为编译时编译器会做语法检查,看到变量是父类类型那么就会到父类中查找是否有该方法,没有则报错。
这种情况下,就需要强制类型转换,将父类类型强转成子类类型。
以(子类名)变量名形式进行强制类型转换
强制类型转换时,无论类型是否匹配编译都不会报错,但如果类型不匹配运行会报错,我们可以使用instanceof进行判断,编译时预知错误。
在子类当做父类来用时,不能调用特有方法,如果一定要调用,就需要强制类型转换回子类。在做转换时最好instanceof判断一下类型是否匹配。
Ø 子类覆盖(Override)父类方法
覆盖方法必须和被覆盖方法具有相同的方法名称、参数列表和返回值类型。
子类的方法返回值类型可以是父类方法返回值类型的子类。
如果在子类中想调用父类中的那个被覆盖的方法,我们可以用super.方法的格式。
如果直接调用方法,是在当前子类中先查找,如果子类有会调用子类的。使用super形式只在父类中查找,子类有没有都不调用。
覆盖方法时,不能使用比父类中被覆盖的方法更严格的访问权限。
因为有可能将子类对象当做父类对象来使用,那么能获取到的父类对象中的方法在子类中必须都能获取到。
覆盖方法时,不能比父类抛出更多的异常。
子类只能比父类强,不能比父类弱。
重载(Overload)和重写(Override)的区别:
重载是方法名相同,参数列表不同,和返回值类型无关。
重写是方法名、参数列表、返回值类型全相同。
@Override 注解,可以检查覆盖是否成功
Ø 子类当做父类使用时需要注意
当我们在调用某个类的一个方法时,此方法声明需要一个父类对象,这时我们可以将一个子类对象作为实参传递过去,注意此时方法定义的形参为父类,在方法中使用父类变量调用方法时,其实是调用子类的方法。
思考:上述情形下,在方法中用父类变量访问属性,访问的是子类还是父类的属性 ?
在把子类当做父类来用时,使用父类变量访问方法,访问的是子类的方法,因为虚拟机会找到变量引用的地址,根据这个地址来访问方法,这叫动态分配。
这种机制没有被使用到类的成员变量上,如果用父类变量访问属性,那么会直接找到父类的属性,不会看地址是哪个对象。
Ø 继承的应用细节
子类不继承父类私有成员
父类中私有成员对外不可见,子类对象中无法访问这些成员。
构造函数不被继承
构造函数通常用来初始化类的成员变量,父类和子类的成员变量不同,初始化方式也不同,构造函数的名字也不同。
为什么只支持单继承
如果一个类继承多个类,那么多个类中有相同的方法,调用时会引起歧义。
Ø 子类对象实例化过程
子类构造函数中可以使用super关键字调用父类构造函数。
在子类创建对象时一定会调用父类构造函数。即使没有显式调用,也会默认调用父类无参构造函数。
在子类中第一行用this关键字去调其他的构造方法,这时系统将不再自动调父类的。但其他构造函数中会调用父类构造函数。
在构造方法中this和super关键字只能出现一次,而且必须是第一个语句。
以后在设计类的时候,最好定义一个无参的构造方法,不然子类实例化的时候就容易出错。
3.11. 对象的比较
在我们使用运算符“==”来比较两个对象时,其实比较的是两个对象的地址。如果运算符两边是同一个对象,地址相同则会等到true,只要是不同对象地址就会不同,返回false。
我们在编程过程中经常会比较两个对象的属性,这时我们就无法用“==”来比较了,因为即使两个对象所有属性都相同但不是同一个对象“==”号比较后也会得到false。这种情况下我们一般会定义一个equals()方法来进行比较。
3.12. 文档注释
文档注释以“/**”开始,以“*/”标志结束,相应的信息和批注所对应的位置很重要!类的说明应在类定义之前,方法的说明应在方法的定义之前。
使用文档注释修饰一个类的源代码之后可以通过javadoc.exe来生成帮助文档。
生成文档的命令:
javadoc -d (目录) -version –author (源文件)
批注参数来标记一些特殊的属性及其相应的说明。
@author<作者姓名>
@version<版本信息>
@param<参数名称><参数说明>
@return<返回值说明>
3.13. 组合设计模式(CompositePattern)
Ø 什么时候用组合
组合是一种实现代码复用的方式,当我们在定义一个类的时候需要用到另外一个类的方法时,就可以用组合。
Ø 怎么用组合
定义一个所需要的类类型的成员变量
通过构造函数进行装配,接收一个该类类型的对象,用成员变量引用
在需要使用另一个类的方法时通过成员变量访问
Ø 组合的优点
如果两个类没有父子关系,不合适用继承。
Java只支持单继承,组合不占用继承位置。
3.14. 多态(Polymorphism)
Ø 什么是多态
多态字面上的意思就是多种形态。在面向对象语言中,我们可以将函数的形参定义为一个父类类型,而在真正调用该函数时这个父类类型的所有子类对象都可以传入,根据传入的子类对象不同函数可以运行处多种形态。
Ø 多态的特点
应用程序不必为每一个派生类(子类)编写功能调用,只需要对抽象基类进行处理即可。这一招叫“以不变应万变”,可以大大提高程序的可复用性。
派生类的功能可以被基类的引用变量引用,这叫向后兼容,可以提高程序的可扩充性和可维护性。现在写的程序可以调用将来写的程序不足为奇。
3.15. 抽象类
Ø 什么是抽象类
使用abstract关键字修饰的类就是抽象类,抽象类不能new对象,原因在于抽象类含有抽象方法,不能被调用。
没有方法体的方法为抽象方法,使用abstract关键字修饰。
有抽象方法的类必须声明为抽象类,抽象类不一定含有抽象方法。
Ø 为什么要定义抽象类
如果有多个类具有相同的方法声明,而方法的实现不一样,这时就可以抽象出父类,将方法在父类中声明
别人在学习我们的软件时,只需要学习父类就知道子类有什么方法
在设计软件时,要尽力抽象父类,继承关系以3~4层为宜
3.16. final关键字
final标记的类不能被继承。
final标记的方法不能被子类重写。
final标记的变量即为常量,只能赋值一次。注意引用数据类型和基本数据类型的区别。
使用public static final共同修饰的常量就是全局常量。通常全部字母大写。
3.17. 模板设计模式(TemplatePattern)
Ø 为什么要使用模板方法设计模式
在解决一些问题或者设计一个软件的时候,需要先定义一个模板,就相当于一种事先定义好的协议。
以后要做这系列的事情都按照这个模板来做。这样就实现统一化管理。
Ø 如何实现模板方法设计模式
定义一个抽象的父类做为模板,定义所有需要的方法
在父类中实现供外界调用的主方法,将方法声明为final
根据不同业务需求定义子类实现父类的抽象方法
3.18. 内部类(InnerClass)
Ø 类中的内部类
在类里面定义的类称之为内部类(Inner Class),内部类是外部类的一个成员。
内部类必须创建外部类对象才能使用。
创建内部类对象时必须先创建一个外部类对象,通过一个外部类对象才能创建内部类对象。
外部类名.内部类名变量名 = new 外部类名().new 内部类名();
内部类可以直接访问外部类的成员,而外部类不能直接访问内部类的成员。访问方式:外部类名.this.成员名
内部类可以访问外部类成员,因为在使用内部类时一定会有外部类对象,且只对应一个。
外部类不能访问内部类成员,因为在使用外部类时有可能还没有创建内部类对象。
如果一定要在外部类中使用内部类成员,那么需要创建内部类对象,通过对象来访问。
内部类中不能定义静态成员。
因为内部类需要创建外部类对象才能使用,static的本意是不创建对象就能使用,这是矛盾的。
内部类的class文件名为:外部类名.内部类名.class
Ø 方法中的内部类
一个类如果只在某个方法中使用,那么可以在方法中定义。
定义在方法中的类只能在方法中使用,而且使用的代码只能在声明的代码下面。
方法中的内部类只有在运行到类定义之后才能使用。
方法中定义的内部类不能访问方法中定义的局部变量,除非这个局部变量被声明为final的。
在方法中定义的局部变量在方法运行结束之后生命周期结束,不能再被访问。
方法中的内部类创建的对象有可能生命周期比这个局部变量长,例如这个对象被作为返回值返回,那么方法运行结束之后还可以访问这个对象。
这时变量被销毁了,对象还在,如果在对象的某个方法内访问这个变量就访问不到了。
我们需要使用final修饰这个变量,被final修饰的变量会一直存储在内存中,方法运行结束之后不被销毁。
方法中的内部类class文件名为:外部类名$.编号内部类名.class
Ø 匿名内部类
如果一个类只使用一次,那么可以定义为匿名内部类。
使用 new 父类名(){类定义} 形式声明,先创建一个指定类的子类,然后根据这个类创建一个对象。
匿名内部类的class文件名为:外部类名$编号.class
Ø 静态内部类
可以使用static修饰一个类中的内部类。
静态内部类不用创建外部类对象就可以直接创建对象。
外部类名.内部类名变量名 = new 外部类名.内部类名();
静态内部类可以定义静态成员。
因为静态内部类可以直接使用,无需创建外部类对象。
静态内部类中不能访问外部非静态成员。
因为创建静态内部类不需要外部类对象,也就是有可能没有创建外部类对象,使用外部类成员必须有外部类对象。
3.19. 接口
Ø 什么是接口
接口是一种特殊的抽象类,接口中声明的所有方法都是抽象的
使用interface关键字修饰一个接口
Ø 接口的用法
我们可以定义一个类来实现接口,使用implements关键字
实现一个接口需要实现接口中所有的方法,抽象类除外
通常使用匿名内部类来实现一个接口
接口可以继承接口,使用extends关键字。接口不能继承抽象类,因为抽象类中可能有不抽象的方法。
一个类可以实现多个接口,为了实现多态
Ø 接口中的方法和变量
接口中定义的方法默认是公有的抽象的,被public abstract修饰
接口中定义的变量默认为全局常量,使用public static final修饰
Ø abstract class和interface的区别
抽象类中可以有不抽象的方法,接口中全是抽象的方法
抽象类用extends继承,接口用implements实现
抽象类中的变量和方法没有默认修饰符,接口中的变量默认为public static final的,接口中的方法默认为public abstract
一个类只能继承一个抽象类,一个类可以实现多个接口
Ø 什么时候用抽象类,什么时候用接口
如果能用接口,就不用抽象类,因为别人实现接口可以不占用继承的位置。
如果定义一个抽象的父类,其中所有方法都是抽象的,那么就定义为接口。
如果定义一个抽象的父类的时候,需要有不抽象的方法,那么只能定义为抽象类。
3.20. 异常
Ø 什么是异常
异常就是Java程序在运行过程中出现的错误。如程序要打开一个不存的文件、网络连接中断、操作数组越界、装载一个不存在的类等。
Ø Throwable
Throwable表示Java中可被抛出的对象,它是所有错误和异常的父类
Throwable有两个子类:Error、Exception
Error表示错误
Exception表示异常
RuntimeException表示运行时异常,是Exception的子类
Throwable |
Error
|
Exception |
|
|
子类 |
|
|
子类 |
RuntimeException |
|
|
子类 |
Ø 异常的分类
Error(错误)
由Java虚拟机生成并抛出,包括动态链接失败、虚拟机错误等,程序对其不进行处理
Exception(异常)
所有异常类的父类,子类定义了各种各样可能出现的异常事件,一般需要用户显式地声明向外抛出或捕获。
Runtime Exception(运行时异常)
一类特殊的异常,如被0除、数组角标越界等。产生比较频繁,处理麻烦,如果每次都处理,会对程序可读性和运行效率影响比较大,因此由系统检测并将它们交给缺省的异常处理程序,用户不必对其进行处理。这类异常不处理,编译时不会报错,只是在运行时出现错误时才报告异常,所以我们称之为运行时异常,所有RuntimeException的子类都是运行时异常。我们也可以对运行时异常进行处理。
编译时异常
Exception中除了RuntimeException的子类,其他异常都是必须要处理的,如果不处理,编译时会报错,这些异常我们称之为编译时异常。
Ø 异常的用法
处理异常
在程序中可以在方法后面使用throws关键字声明向外抛出异常
对于编译时异常,通常我们需要使用try……catch语句进行捕获
finally可以结合try……catch使用,出现异常,finally里面的代码也会执行
异常的一些细节
如果父类方法中声明抛出多个异常,那么重写(覆盖)该方法只能抛出那些异常的一个子集,也就是说子类不能比父类抛出更多的异常。
如何处理多个异常
try语句与finally的嵌套使用
自定义异常
可以通过继承Exception类来自定义一个异常
如果要定义一个运行时异常则需要继承RuntimeException类
3.21. 包
Ø Java中常用的包
java.lang
包含一些Java语言的核心类,如String、Math、Integer、System和Thread,提供常用功能。
java.awt
包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
java.net
包含执行与网络相关的操作的类。
java.io
包含能提供多种输入/输出功能的类。
java.util
包含一些实用工具类,如定义系统特性、使用与日期日历相关的函数。
Ø 定义带包类
使用package语句加上包名来定义类所属于的包,包名全部小写
package语句为Java源文件的第一条语句
如果一个类中没有使用package语句,这个类为缺省无包名
一个类如果想被其他包中的类引用,必须使用public关键字修饰。构造函数也需要public
如果一个类被声明为public,那么必须和文件名同名
Ø 使用带包的类
在使用带包的类时需要使用全限定名(包名.类名)
在每次写类名时都使用全限定名很麻烦,我们可以使用import导入包,之后再使用就无需写包名了
星号*:导入一个包中所有类。优先匹配当前包中的类,如果当前包没有再匹配导入包中的类。
具体类名:导入指定一个类。无论当前包中是否有同名类,都直接匹配导入的类。
无包的类可以使用有包的类,有包的类不能使用无包的类。
Ø 编译运行带包的类
编译一个带包的源文件,在生成class文件的同时需要生成包文件
编译命令:javac –d <目录> 源文件名.java
运行有包的类时需要加上包名
运行命令:java 包名.类名
3.22. jar文件
Ø 什么是jar文件
jar文件是Java文件的一种压缩格式
一般来讲,我们会将一个软件系统的所有class文件打成一个jar文件以供别人使用
当我们用到jar包中的类时,需要将jar文件的绝对路径加到classpath当中
Ø 如何压缩jar文件
将编译好的带包的class文件压缩成一个jar文件称为打jar
打jar命令:jar cvf jar包名.jar 要打包的文件/文件夹
运行jar文件命令: java -jar jar文件名.jar
3.23. 访问控制符
类的访问控制符有两种:
public关键字修饰:可以被所有的类访问
缺省为default:只能被同一包中的类访问
3.24. 代码编写规范
标识符命名规则(驼峰式)
类名首字母大写:XxxYyyZzz
变量名、方法名首字母小写:xxxYyyZzz
包名全小写:xxx.yyy.zzz
常量名全大写:XXX_YYY_ZZZ
大括号的位置
大括号应成对出现,第一个大括号应在第一行语句后面
方法后面紧跟大括号,没有空格
关键字(while、for、if)后面应留一个空格
赋值语句之间用分号分隔,分号后面应空一格
代码折行应按照代码等级对齐,运算符写在下一行