第一章 java语言概述
1.1 java语言的发展简史
1990末: sun公司“Green计划”(James Gosling领导) ----目的是智能家电编写一个通用嵌入式控制系统,为此创建oak
1992夏天: "Green计划"完成新平台的部分功能
1992年11月: "Green计划"被转为"FirstPerson有限公司"-一个sun的全资子公司,致力于创建一个高度互动的设备
1994夏天: 互联网浏览器给Oak带来信的生机,Gosling开始对Oak进行小规模的改造
1994秋: WebRunner完成,Oak更名为java
1995初: Sun发布java语言,公开源代码 ,applet小程序风
1996初: Sun发布jdk1.0,包括jre(API,JVM,发布技术等)和jdk(javac等)
1997.2.18: 发布jdk1.1,增加JIT编译器
1998.12: Sun发布jdk1.2/jsp/servlet/EJB等规范,将java分成j2se,j2ee,j2me三个版本 ----最重要的jdk版本
2002.2: jdk1.4 ----最成熟的一个版本,出现各种开源框架
2004.10: jdk1.5 ----增加了泛型/增强for循环/可变数量型参/注释/自动拆箱和装箱
目录 2006.12: jdk1.6
2007.11: Android问世
2009.4.20: Oracle收购sun(java和solaris)
2011.7.28:Oracle发布jdk1.7
1.2 java的竞争对手即各自优势
1.2.1 c#: 和java由90%的重叠度,依赖windows平台,具有许多windows特性
1.2.2 ruby: 拥有Roby on Rails这个MVC框架,简洁,是与开发中小型应用
1.2.3 python: 高可扩展性/清晰的语法
1.3 java程序运行机制
1.3.1 高级语言的运行机制
1.3.2 java程序的运行机制和jvm
1.4 开发java的准备
1.4.1 下载和安装java 7的jdk
1. jre和jvm的关系
jre包含:类加载器,字节码校验器以及大量的基础类库
2. 为什么不安装公共jre
公共jre是一个独立的jre系统,系统上任何一个程序都可以使用公共jre,网页上的applet很少了,没必要。jdk中包含的jre就够用了。
1.4.2 设置path环境变量
1. 用户变量与系统变量的区别: 用户变量只对当前用户有效,系统变量对所有用户有效。系统变量比用户变量优先级高。
1.5 第一个java程序
1.5.1 编辑java程序源代码
1.5.2 编辑java程序
1. 为和不需要指定.class文件的名称
1.5.3 运行java程序
$ mkdir -p ~/git/github/practice/read/classfile; cd ~/git/github/practice/read/classfile
$ vim HeloWorld.java
$ javac -d classfile/ HelloWorld.java ---- -d 指定编译文件放置的目录
$ java -classpath classfile/ HelloWorld ---- -classpath 指定搜索的目录
1.5.4 根据classpath环境变量定位类
1. jdk1.4之前需要设置CLASSPATH环境变量: .;%JAVA_HOME%\lib\dt.jar;%JAVA_HOME%\lib\tools.jar ----指定jvm运行时搜索当前路径和那两个jar包,因为那两个jar中有必需的编译/运行工具
2. 后来的jre会自动搜索当前目录下的类文件,使用java的编译和运行工具时系统也会自动加载那两个jar包中的java类。
3. 运行时指定搜索路径
$ java -classpath dir1;dir2;...;dirN java类名 ----win下在多个目录中搜索的方式
$ java -classpath dir1:dir2:...:dirN java类名 ----linux下
1.6 java程序的基本规则
1.6.1 java程序的组织形式
1. java程序都必需以类的形式存在;
2. main方法作为程序的入口的形式是固定的;
3. 没有main方法的class HelloWorld{}这个最小的类是正确的,编译可以成功,但这个类不能作为程序的入口来解释执行;
4. 源文件的名字必需与public的类名相同,因此一个java源文件有且只有一个Public类;
1.6.2 java程序的组织形式
1.6.3 初学者容易犯的错误
1.7 垃圾回收机制
1.8 何时开始使用ide工具
第二章 理解面向对象
2.1 面向对象
2.1.1 结构化程序设计简介
2.1.2 程序的三种基本结构
1. 顺序结构
2. 选择结构
3. 循环结构
2.1.3 面向对象程序设计简介
1. 是一种更加优秀的程序设计方法,它的基本思想是使用类/对象/继承/封装/消息等基本概念进行程序设计。
2.1.4 面向对象的基本特征
1. 封装: 将对象的实现细节隐藏起来,然后通过一些共有方法来暴露该对象的功能
2. 继承: 是面向对象实现软件复用的重要手段,当子类继承父类后,子类作为一种特殊的父类直接继承父类的属性和方法
3. 多态: 指的是子类对象可以直接赋给父类对象,但运行时依然表现出子类的行为特征,这意味着同一个类型的对象在执行同一个方法时可以有多种行为特征
2.2 UML(统一建模语言)介绍
2.2.1 用例图
1. 开发阶段: 需求分析阶段
2. 用途: 帮助开发团队以一种可视化的方式理解系统的需求功能
3. 元素: 用例(椭圆) + 角色(人形)
4. 注意: 描述系统的功能,方便和客户交流;不要指望用例图和系统的各个类之间由任何联系;不要将用例做的过多难以理解;尽可能多地使用文字说明。
2.2.2 类图
1. 表示方法
2. 三种基本关系:关联(聚合/组合),泛化(继承),依赖
2.2.3 组件图
2.2.4 部署图
2.2.5 顺序图
2.2.6 活动图
2.2.7 状态机图
2.3 java面向对象特征
2.3.1 一切都是对象
2.3.2 类和对象
第三章 数据类型和运算符
3.1 注释
3.1.1 单行注释和多行注释
3.1.2 文档注释
1. api文档是什么
1. 生成java api的工具使用案例
$ javadoc -d apidoc -docencoding UTF-8 -charset UTF-8 -windowtitle 测试 -doctitle 学习javadoc工具测试 -header 我的类 -author -version TestDoc.java
-------------------------------------------------------------------------------------------------------------------------------------------------------
-d <dir>: 将生成的api文档放在apidoc这个目录下
-docencoding <name>: 指定输出编码的名称
-charset <name>: 指定夸平台查看文档的字符集
-windowtitle <text>: 指定API文档的浏览器窗口标题
-doctitle <html-code>: 指定html格式的文本,用于指定概述页面的标题(针对源文件有多个包的情况),这里是单文件所以其实不应该加这个参数
-header <html-code>: 每个页面的页眉
-version -author: 如果有的话,提取源代码中的版本和作者信息(默认不提取)
----------------------------------------------------------------------------------------------------------------------------------------------------------
$ javadoc ----查看详细帮助信息
2. 为什么要掌握查看api文档的方法
3. 文档注释标记
1. 类/接口文档注释:
@see: "参见",用于指定交叉参考的内容
@deprecated: 不推荐使用的方法
@author: 作者信息
@version: 指定源文件的版本
2. 方法/构造器文档注释
@see: "参见",用于指定交叉参考的内容
@deprecated: 不推荐使用的方法
@param: 方法的参数说明信息
@return: 方法的返回值说明信息
@throws: 抛出的异常,和exception同义
@exception: 抛出异常的类型
3. Field文档注释
@see: "参见",用于指定交叉参考的内容
@deprecated: 不推荐使用的方法
3.2 标识符和关键字
3.2.1 分隔符
3.2.2 标识符规则
3.2.3 java关键字
3.3 数据类型分类
3.4 基本数据类型
3.4.1 整数: byte,short,int,long
3.4.2 java 7新增的二进制整数
1. 0b或0B打头,后面只接二进制数字为32位,后面还有L或l为64位
2. 二进制相关
原码: 直接将数字换算为二进制
反码: 反码是原码最高位不变,其余取反
补码: 正数的补码就是原码,复数是其反码+1
3.4.3 字符型(16位)
1. 表示方式
‘A’: 单个字符
‘\n’: 转义字符
‘\uxxxx’: 十六进制编码(‘\0000‘ - ‘\uFFFF‘)
2. 使用16位unicode字符集编码
3.4.4 浮点型(IEEE754)
1. 使用二进制的科学计数法表示浮点数,无法精确表示浮点数
2. 分类
float(32位): 符号位(1)+指数(8)+尾数(23)
double(64位): 符号位(1)+指数(11)+尾数(52)
3. 表示方法
float: 5.5f 或 5.5F
double: 5.5 或 5.5d 或 5.5D
4. 3个特殊的浮点数值
Double.POSITIVE_INFINITY或Floate.POSITIVE_INFINITY: 正无穷大(1/0)
Double.POSITIVE_INFINITY或Floate.NEGATIVE_INFINITY: 负无穷大(-1/0)
Double.NaN或Float.NaN: 非数(0.0/0.0 or (-2)^2)
3.4.5 java 7新增的数值中使用下划线分隔
1. 举例
int binVal = OB1000_0000_0000_0000_0000_0000_0000_0001;
float pi = 3.14_15_92_65_36;
2. 便于阅读
3.4.6 布尔型
1. boolean: true or false
2. 用途
流程控制: if/while/for/do
三目运算符: ?:
3. 注意
1. boolean类型不能转化为其他类型
2. 其它类型不能转化为boolean型
3 true和false不会直接转换为boolean类型
4. String str = true+"";
3.5 基本类型的类型转换
3.5.1 自动类型转换
1. 基本数据类型自动转换: 范围小的转换成范围大的
char
|
v
byte ->short->int->long->float->double
2. 基本数据类型自动转换为字符串类型: 从左到右,能计算就计算,和字符串连接即转换为字符串
3+4 + "Hello" -> "34Hello"
"hello" + 3 + 4 -> "hello34"
3.5f + "" -> "3.5"
3.5.2 强制类型转换
1. 缩小转换(溢出)
int - > byte: 从低位截取8位
3.5.3 表达式类型的自动提升
1. 不管有没有int,运算中byte,short,char被提升为int
2. 自动提升为最高类型
3.6 直接量
3.6.1 直接量的类型
1. int(byte/short):
二进制: 0b10000 or 0B10000
八进制: 020
十进制: 16
十六进制: 0xf or 0Xf
2. long:
[int]l or [int]L
3. float:
534f or 5.34E2f or 534F or 5.34E2F
4. double:
534 or 5.34E2
5. boolean:
true or false
6. char:
‘a‘ or ‘\n‘ or ‘\u0061‘
7. String:
"hello"
8. null(特殊):
null: 可以赋给任何引用类型
3.6.2 直接量的赋值
1. 常量池: 指的是编译期间已确定,并被保存到已编译的.class文件中的一些数据。包括关于类/方法/接口中的常量,也包括字符串直接量。
2. java会确保每个字符串常量只有一个,没有副本
("hello"和"hel" + "lo"一样,因为编译时就进行了优化。)
3.7 运算符
3.7.1 算术运算符
1. 分类: "+", "-", "*", "/", "%", "++", "--"
2. 重点:
1. ”+“除了作为数学加法运算符外还可以作为字符串连接符
2. “++”或“--”只能用于变量,不能用于直接量,比如5++是错误的
3. 没有平方,乘方运算符,可以用库中的方法实现(java.lang.Math)
3.7.2 赋值运算符
1. 变量 = 直接量;
2. 变量 = 变量;
3. 变量 = 变量 = ... =变量 = 直接量; ----java支持连续赋值,但会降低可读性,因此不推荐
4. 增强的赋值表达式: += -= /= *= %= &= |= ^= >>= <<= >>>= ----拥有更好的性能
3.7.3 位运算符(注意计算机中的运算都是基于补码的)
1. &: 按位与: 只有都为1是才为1
2. |: 按位或: 只要有一个为1就为1
3. ~: 按位非: 单目运算,1->0,0->1
4. ^: 按位亦或: 两者不同才为1
5. <<: 左移运算符: 二进制补码整体左移指定位数,空出的位补0
6. >>: 右移运算符: 二进制补码整体右移,正数补0,复数补1
7. >>>: 无符号右移运算符: 二进制补码整体右移,无论正负都补0
注意事项:
1. 只有整数类型可以进行移位操作
2. byte/short/char类型进行移位操作时自动提升为int
3. 32的类型移位超过32位时将对位数求余,a>>33等价于a>>1
4. 64位类型(long)移位超过64位时将对64求余
3.7.4 扩展后的赋值运算符
1. 分类:+= -= /= *= %= &= |= ^= >>= <<= >>>=
2. 扩展后的赋值运算符拥有更好的性能,底层机制更加健壮(a+=b和a=a+b底层是不一样的)
3.7.5 比较运算符: > < == >= <= !=
1. 基本类型变量或直接量不能与引用类型变量或值进行==比较
2. boolean类型的值或变量不能与其它任何类型进行==比较
3. 不同引用类型变量之间没有父子继承关系也不能进行==比较
3.7.6 逻辑运算符
1. &&: 逻辑与
2 ||: 逻辑或
3. !: 逻辑非
4. &: 不短路与
5. |: 不短路非
6. ^: 亦或
(位运算符的操作数是整数类型,逻辑运算符操作的是boolean)
3.7.7 三目运算符
1. 结构: (expression)?if-true-statement:if-false-statement;
2. 举例: String str = 3 > 5?"3大于5":"3小于5";
3.7.8 运算符的结合性和优先级
第四章 流程控制与数组
4.1 顺序结构
4.2 分支结构
4.2.1 if条件语句
1. if(boolean)
{
expression;
}
2. if(boolean)
{
expression;
}
else
{
expresion
}
3. if(boolean)
{
expression;
}
else if(boolean)
{
expresion
}
...
else
{
expression;
}
4.2.2 java 7的switch分支语句
1. 结构
switch(expression)
{
case condition1:
{ ----可省略
statement;
break;
} ----可省略
...
case conditionN:
{
statement;
break;
}
default:
{
statement;
}
}
2. 控制表达式的数据类型
1. 传统: byte, short, char, int
2. jdk 7: byte, short,char, int, java.lang.String
4.3 循环结构
4.3.1 while循环语句
while(boolean)
{
循环体;
}
4.3.2 do while循环语句
do
{
循环体;
}while(boolean);
4.3.3 for循环
1. 结构
for([init_statement]; [test_statement]; [iteration_statement])
{
statement;
}
2. [init_statement]初始化语句只在循环开始前执行一次
3. for循环圆括号中只有两个";"是必需的,其它都可以省略,在语法上没有问题: for(;;){}
4. 习惯选择i,j,k作为循环变量
4.3.4 嵌套循环
4.4 控制循环结构
4.4.1 使用break结束循环
1. 单纯使用break: 结束所在的整个循环
2. 配合标签使用: 结束外层循环
outer: ----通常紧跟在break后面的标签必需在break所在的循环之前定义才有意义
for(int i = 0; i < 5; i++)
{
for(int j = 0; j < 3; j++ )
{
System.out.println(i);
if(j == 1)
{
break outer;
}
}
}
4.4.2 使用continue结束本次循环
1. 单纯使用continue: 跳出本次循环
2. 配合标签使用: 直接跳过标签所标识循环的剩下语句,重新开始下一次循环
4.4.3 使用return 结束方法
1. 不管潜逃多深,直接结束整个方法(不止循环体)
4.5 数组类型
4.5.1 理解数组: 数组也是一种类型
1. int[]是一种类型吗?怎么使用这种类型?
是。需要使用创建数组的语法。
4.5.2 定义数组
1. 定义方式
type[] arrayName; ----推荐
type arrayName[]; ----不推荐(最好永远别这样定义)
2. 注意: 定义数组不需要指定数组的长度,只是定义了一个引用变量,并未指向任何内存空间
4.5.3 数组的初始化
1. 能不能只分配内存空间,不赋初始值呢?
答:不行!不管以那种方式初始化数组,只要分配了内存空间,数组元素就有了初始值。哪怕是一个null值。
2. 初始化方式
1. 静态初始化: 不知定数组长度,显示指定每个数组元素的初始值,由系统决定数组的长度。
int[] a; ----先定义
a = new int[]{1, 2, 3, 4} ---再初始化
int[] b = new int{5, 6, 7, 8} or int[] b = {5, 6, 7, 8} ----定义的同时初始化
2. 动态初始化: 初始化时程序员只指定数组长度,由系统为数组元素分配初始值
Object[] obj = new String[9];
系统确定初始值的方式:整数类型(0),浮点数(0.0),字符类型(‘\u0000‘),boolean(false),类/接口/数组(null)
3. 不要进行数组初始化时既指定数组的长度,又指定每个数组元素的值
4.5.4 使用数组
1. 为什么要我记住这些异常信息?
4.5.5 for each循环
1. 结构
for(type valName : arrayName/collectionName)
{
//valName自动迭代数组或集合中的每个元素
}
2. 如果希望循环中改变数组元素的值,不要使用foreach循环
4.6 深入数组
4.6.1 内存中的数组
1. 为什么有栈内存和堆内存之分?
4.6.2 基本类型数组的初始化
4.6.3 引用类型数组的初始化
4.6.4 没有多维数组
1. 我是否可以将数组元素指向另外一个数组来扩展成多为数组?
4.6.5 操作数组的工具类
4.6.6 数组的应用举例
4.7 本章小结
第五章 面向对象(上)
5.1 类和对象
5.1.1 定义类
构造器不是没有返回值吗?为什么不能用void修饰?
答:简答的说,这是java的语法规定。实际上类的构造器是有返回值的,当我们我能用new关键字来调用构造器时,构造器返回该类的实例,可以把这个类的实例当成这个类的返回值。需要注意的是,不能在构造器显式使用return 返回当前类的对象,因为构造器的返回值是隐式的。
5.1.2 对象的产生与使用
1. 产生: 创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例
Persion p; //先定义引用
p = new Persion(); //后创建
or
Persion p = new Persion();
5.1.3 对象/引用和指针
Persion p = new Persion(); ///产生两个东西,指向Persion实例的p变量,和一个Persion实例
5.1.4 对象的this引用
1. 总是指向调用该方法的对象
2. 两种形式
在构造器中: 引用该构造器正在初始化的对象
在方法中: 引用调用该方法的对象
3. 在方法中调用该类另一个方法是常常生省略this
调用static修饰的成员时省略主调: 默认使用该类作为主调
调用没有static修饰的成员时省略主调: 默认使用this作为主调
4. 使用场景
1. 在构造器中访问成员变量,恰巧该成员变量与构造器中定义的一个变量重名,可以用this指定
2. 在方法中return this: 表示返回调用该方法的对象本身。
5.2 方法详解
5.2.1 方法的所属性
1. 为什么说java中的方法是二等公民?c中的方法是一等公民?
1. 方法不能独立定义,方法只能在类体里定义
2. 从逻辑上来说,方法要么属于这个类本身,要么属于某个对象
3. 永远不能独立执行方法,执行方法必须使用类或者对象作为调用者
(明白了没,java中只有类或者对象才是一等公民)
5.2.2 方法的参数传递机制
值传递: 将实际参数的副本(复制品)传入方法内,而参数本身不会受到影响
5.2.3 形参个数可变的方法
1. 举例
public static void test(int a, String... books);//以可变个数形参定义方法
public static void test(int a, String[] books);//和上面比,调用方式不如上面灵活,但对books都是数组这一点是一致的
2. 注意
1. 长度可变的形参只能处于列表的最后
2. 一个方法只能包括一个长度可变的形参
3. 这个长度可变的形参既可以传入多个参数,也可以传入一个数组
5.2.4 递归方法
5.2.5 方法重载
1. 为什么方法的返回值类型不能用于区分重载的方法?
因为我们调用方法的形式无法反应被调用方法的返回值类型,系统无法判断到底调用那个方法。
2. 重载参数可变的方法
大部分时候,不推荐重载长度可变的方法,因为没有太大的实际意义,而且容易减低可读性。
5.3 成员变量和局部变量
5.3.1 成员变量和局部变量
0. 定义
成员变量: 成员变量就是方法外部,类的内部定义的变量。
局部变量: 局部变量就是方法或语句块内部定义的变量。
(局部变量的数据存在于栈内存中。栈内存中的局部变量随着方法的消失而消失。成员变量存储在堆中的对象里面,
由垃圾回收器负责回收。)
1. 变量分类图
| 实例Field(不以static修饰)
| 成员变量——|
| |类Filed(以static修饰)
所有变量——|
| |形参(方法签名中定义的变量)
|局部变量 ——|方法局部变量(在方法内定义)
|代码块局部变量(在代码中定义)
2. 初始化情况
| 实例Field:-------------
| 成员变量——| |----无须显示初始化(系统会在这个类的准备阶段或创建实例的阶段进行默认初始化,见4.5.3.2.2)
| | 类Filed:---------------- 但对引用类型来说调用前必需初始化,因为默认为null
所有变量——|
| | 形参: 无须显式初始化,在调用该方法时由方法的调用者完成初始化
|局部变量 ——| 方法局部变量: 必需显式初始化
| 代码块局部变量: 必需显式初始化
3. 生命周期
| 实例Field: 随实例的存在而存在
| 成员变量——|
| | 类Filed: 随类的存在而存在
所有变量——|
| | 形参: 在整个方法内有效
|局部变量 ——| 方法局部变量: 从定义的地方生效,到该方法结束时失效
| 代码块局部变量: 从定义的地方生效,到该代码块结束时失效
4. 变量命名问题
1. 一个类内不能定义两个同名的成员变量,实例成员变量或类成员变量都不可以;
2. 一个方法内不能定义同名的局部变量,即使一个是代码块局部变量一个是方法局部变量也不行;
3. 允许局部变量和类变量重名。这是局部变量会覆盖成员变量;
4. 如果想在方法或块中调用类成员变量,可以使用类名.变量名,如果调用实例成员变量,可以使用this.变量名或实例名.成员变量名的方式。
5.3.2 成员变量的初始化和内存中的运行机制
1. 初始化的时机和位置(这里的初始化指的是:类在用new创建其第一个实例时初始化)
(这里不包括final修饰的情况)
|基本数据类型: 在堆中那个对象中分配内存(如果没有显示初始化,将给默认值)
|实例Field——|
| |引用类型: 如果是第一次使用这个引用类型,会首先在堆中初始化这个类,创建类对象;如果没有通过
| new显示初始化该引用类型,这个引用变量对应null;显式初始化后将在堆中分配该引用变量
| 引用另外一个实例的内存空间。但这个空间不再引用变量所在的对象的空间中,而是另外一
| 个新开辟的空间。(String类是个特例,可以通过new以外的方式初始化,这种情况后面单独
| 讨论)
成员变量—|
| |基本数据类型: 只在必要时进行初始化,存储在静态域(方法区)中
|类Filed——|
|引用类型: 只在必要时初始化,存储在静态域(方法区)中
2. java虚拟机的懒加载(lazy-load)时机
1. 创建类的实例
2. 调用类的静态方法
3. 操作类的非常量静态字段(非final static)
4. 调用特定的反射方法
5. 初始化一个类的子类
6. 指定一个类为虚拟机启动时的初始化类
3. final修饰成员变量(final也可以修饰局部变量)
|基本数据类型: 在堆中那个对象中分配内存(如果没有显示初始化,将给默认值)
实例Field——|
|引用类型: 如果是第一次使用这个引用类型,会首先在堆中初始化这个类,创建类对象;如果没有通过 **************************************************new显示初始化该引用类型,这个引用变量对应null;显式初始化后将在堆中分配该引用变量
引用另外一个实例的内存空间。但这个空间不再引用变量所在的对象的空间中,而是另外一
个新开辟的空间。(String类是个特例,可以通过new以外的方式初始化,这种情况后面单独
讨论)
|基本数据类型: 只在必要时进行初始化,存储在静态域(方法区)中
类Filed——|
|引用类型: 只在必要时初始化,存储在静态域(方法区)中
4. String特例讲解
1. 两种创建实例的方法
String str1 = new String("abc"); ----和一般的引用类型没有区别,在堆中创建对象,栈中创建引用变量
String str2 = "abc"; ----想在栈中创建str2引用变量,然后如果常量池中有"abc",则将地址给str2,如果没有
将"abc"加入常量池后将地址赋给str2
2.
5. 常量池
常量池指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。
5.3.3 局部变量的初始化和内存中的运行机制
1. 初始化的时机和位置
| 形参: 方法被调用过程中将实参的值赋给形参时初始化内存空间(所在方法的栈内存)
局部变量 ——| 方法局部变量: 赋值时初始化内存空间(所在方法的栈内存)
| 代码块局部变量: 赋值是初始化内存空间
2. 特点
1. 局部变量定义后,必需经过显式初始化后才能使用,系统不会为局部变量进行自动初始化。这意味着直到程序
为这个局部变量赋初始值时,系统才会为这个变量分配内存空间。
2. 与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈中。引用类型保存堆中实例的地址。
3. 局部变量质保存基本类型的值或对象的引用,因此局部变量所占的内存区通常比较小。
5.3.4 变量的使用规则
5.4 隐藏和封装
5.4.1 理解封装
1. 隐藏类的实现细节,限制不合理访问
2. 进行类型检查,保证对象信息的完整性
3. 便于修改代码,可维护性
4. 暴露方法,控制访问和操作
5.4.2 使用访问控制符
1. 三个访问控制符 ,四个访问控制级别
private(当前类访问控制权限): 限制只能在类内访问其field
默认(包访问权限): 被相同包下的其它类访问
protected(子类访问权限): 同包的其它类或不同包的子类访问
public (公共访问权限): 可以被所有类以恰当的方式访问
2. 使用规则
private: 除被static修饰的和必需暴露的Field之外的绝大部分Field
protected: 仅希望被子重写,不希望被外界直接调用的Field
public: 大部分外部类/构造函数等希望其它类自由调用的Field
5.4.3 package import和import static
1. package
1. 为什么使用package
java引入包机制,提供类的多层命名空间,用以解决类的命名冲突,类文件管理等问题。
2. 包管理机制是什么
java允许将一组功能相关的类放在同一个package下,从而组成逻辑上的类库单元。
1. 定义方式: 将一个类放在指定包下的方式
package packageName; ----java源程序的第一个非注释行写下,该文件中定义的所有的类都属于这个包。
如果没有指定package语句,则会处于默认包下
2. 编译方式
javac -d . ClassName.java ----会自动建立文件层次结构来存放class文件
(java规定,位于包中的类,在文件系统中也必须有与包名层次相同的目录结构)
3. 执行方式
java packageName.ClassName ----虚拟机装载packageName.ClassName类时,会依次搜索CLASSPATH
环境变量指定的恶系列路径中packageName文件夹,并在packageName
下查找是否包含ClassName.class文件。
注: 1. 同一个包中的类不必位于相同的目录下,只要将路径加入到CLASSPATH中就可以;
2. 把生成的class文件放在某个目录下并不会属于同一个包,目录名更不会成为包名。
3. 如何正确使用包机制
1. 源文件里使用package语句指定包名,一个源文件只能指定一个包,即只能有一个package语句
2. class文件必需放在对应的路径下
项目文件夹
__________|____________
src classes
____|______ _____|_____
| ... ... | ... ...
com com
... ...
Hello.java Hello.class
2. import关键字
1. 使用方法
1. import com.github.eli.Test; ----导入com.github.eli这个包下的Test类
2. import com.github.eli.*; ----导入这个包下的所有类
3. import static com.github.eli.Test; --------导入com.github.eli这个包下的Test类下的所有static修饰的Field(类成员)
4. import static com.github.eli.*; ---- 导入这个包下的所有类的类成员
2. 作用
1. import: 可以省略写包名
2. import static: 省略些报名和类名
3. 冲突(必需使用全名的情况)
1. import相同名称的类时必需使用该类的全名
2. import static相同名称的类Field时需要使用全名
4. 默认导入
java默认所有源文件导入java.lang包下的所有类
5.4.4 java的常用包
1. java.lang: 包含了java语言的核心类,如String,Math,System,和Thread类等,系统会自动导入这个包下的所有类;
2. java.util: 包含了java的大量工具类/接口和集合框架/接口,例如Arrays/List/Set等
3. java.net: 包含了java 网络编程相关的类/接口
4. java.io: 包含了java输入/输出相关的类/接口
5. java.text: 包含了java格式化相关的类
6. java.sql: 包含了java进行JDBC数据库编程相关的类/接口
7. java.awt: 抽象窗口工具集的相关类/接口,主要用于GUI
8. java.swing: Swing图形用户界面变成的相关类/接口,用于构建平台无关的GUI程序
5.5 深入构造器
5.5.1 使用构造器执行初始化
1. 构造器是java对象的重要途径,是不是说构造器完全负责java对象?
答: 不是!当程序员调用构造器而构造器还没有返回初始化的对象前,系统已经产生了一个对象并进行了默认初始化
,只是这个对象不能被外部程序访问,只能在构造器中通过this来引用,当构造器执行完毕后,将对象作为返回值
返回,通常还会赋给一个引用类型变量,从而让外部程序访问该对象。
2. 构造器的访问权限
public: 因为构造器主要用于被其它方法调用,用以返回该类的实例,因此通常设置为public访问权限
protected: 只要用于被子类调用
private: 阻止其它类创建该类的实例
注: 通常建议为java类保留无参数的构造器
5.5.2 构造器重载
1. 为什么要用this来调用另一个重载的构造器?
答: 为了尽量避免代码重复出现。
2. 使用this调用重载的构造器
this(name, color); ----1. 只能在构造器中使用
2. 必需作为构造器执行体的第一个语句
3. 系统会根据this后括号内的实参来调用形参列表与之对应的构造器
5.6 类的继承
5.6.1 继承的特点
1. 通过extends关键字来实现,实现继承的类被称为子类,被继承的类成为父类(基类/超类);
2. java子类不能获得父类的构造器;
3. 子类只能有一个直接父类,如果没有显式指定直接父类,默认扩展(继承)java.lang.Object,可见
java.lang.Object是所有类的直接或间接父类。
5.6.2 重写(覆盖)父类的方法
(重写的规则)
1. “两同”: 方法名相同,参数列表相同;
2. “两小”: 子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常
类型应比父类方法声明抛出的异常类型更小或相等。
3. “一大”: 子类方法的访问权限应比父类方法的访问权限更大或相等
注:
1. 覆盖方法和被覆盖的方法要么都是实例方法要么都是类方法,一个是类方法,一个是实例方法会发生编译错误;
2. 如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就无法重写该
方法;如果子类“重写”了该方法,只不过是在子类中定义了一个新方法;
3. 子类覆盖了父类方法后,子类对象无法访问父类的方法;
4. 在子类的方法中调用父类被覆盖的方法的方式:
1. 调用被覆盖的实例方法: super.方法名()
2. 调用被覆盖的类方法: 父类名.方法名() ----和在与这个父类没有继承关系的类中调用方式一样
5.6.3 super限定
1. super用于限定子类的对象调用从父类那里继承的是实例Field(注意不能是类Field);
2. 重载(overload)和重写(override)
1. 重写和重载放在一起比较没有太大的意义,因为重写发生在同一个类中的同名方法(包括继承来的方法)之间,
重载发生在父类和子类的同名方法之间;
3. 在某个方法中访问某个field而没有显式指定调用者的查找顺序
1. 方法中的局部变量;
2. 当前类的Field;
3. 直接父类的Field -> 上溯所有父类的Field - > ... -> java.lang.Object
注: 1. 当程序创建一个子类对象时,系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承的到的所有
实例变量分配内存,即使是同名的实例变量也会分别分配内存;
2. 子类中定义的和父类中同名的实例变量会隐藏父类的实例变量,但不会覆盖,系统依然会为被阴藏的实例对象
分配内存,这就是为什么用super可以访问到父类被隐藏的实例变量的原因。
5.6.4 调用父类构造器
1. 父类构造器被调用的三种情况(无论如何都会被调用)
1. 子类构造器执行体的第一行使用super显式调用父类构造器,系统将根据super调用的实参列表调用父类对应
的构造器;
2. 在子类构造器的执行体的第一行使用this显式调用本类中重载的构造器,系统根据this后的参数列表调用对应的构造
器。调用本类中另一个构造器时会调用父类的构造器;
3. 子类构造器中既没有this也没有super,系统会在执行子类构造器之前,隐式调用父类无参数的构造器。
问: 为什么我创建java对象时从未感觉到java.lang Object类的构造器被调用过?
答: 因为自定义的类从未显式调用过java.lang.Object类的构造器,即使显式调用java.lang.Object中的构造器,也只能调用
那个默认的构造器,不会输出任何内容。
5.7 多态
(多态的本质:java引用变量的编译时类型和运行时类型不一致。)
5.7.1 多态性
1. 表现多态: 父类引用变量调用子类重写父类的方法: 编译时类型是父类,运行时类型是子类,当把子类对象赋给父类引用变
量(系统自动向上转型)后,表现子类的特征;
2. 不表现多态的情况: 父类引用变量调用子类中隐藏(不是覆盖)父类Field的Field
3. 编译出错的情况: 父类引用变量调用子类中定义的方法或Field(未重载或覆盖)
5.7.2 引用变量的强制类型转换
1. 基本类型之间的转化只能在数值类型之间进行,数值类型与布尔类型之间不能转换;
2. 引用类型之间的转化只能是在具有继承关系的两个类型之间。特别地,如果想把父类对象转换成子类对象,则父类变量必需引用的是子类实例才可以。
5.7.3 instanceof运算符
1. 用法
Object obj = "abc";
if(obj instanceof String) { ----前一个操作数是一个引用类型变量,后一个操作数通常是一个类或接口
String str = (String)obj;
System.out.println("obj是String类型"):
}
2. 作用: 用于在强制类型转换前,判断前面的对象是否是后面的类或其子类/接口的实现,保证代码更加健壮。
5.8 继承与组合
5.8.1 使用继承的注意点
(继承带来高度复用的同时严重破坏了父类的封装性。)
1. 减轻继承的副作用
1. 尽量隐藏父类的内部数据(private修饰Field);
2. 不要让子类可以随意访问/修改父类的方法;
3. 尽量不要在父类的构造器中调用被子类重写的方法: 因为调用者默认是this,最终将调用子类的重写后的方法;
4. 如果想设计成最终类,不想被继承,使用final修饰类;
2. 合适需要派生新类设为场景
1. 子类需要额外增加属性;
2. 子类需要增加自己独有的行为方式。
5.8.2 利用组合实现复用
1. 什么组合
为了实现复用又要避免破坏服用对象的封装性,可以在将服用对象作为一个Field组合到派生类中,可以重载构造函数来实现初始化。
2. 继承和组合
总之,继承表达的是一种"是(is a)"的关系,组合表达的是“有(has a)”的关系。
问: 使用组合关系来实现复用时需要创建两个animal对象,是不是一位着使用组合关系对系统开销更大?
答: 不会,没有本质的差异。因为当创建一个子类的实例时,不仅要为子类Field分配内存空间,还要为其父类Field分配内存空间。
5.9 初始化块
5.9.1 使用初始化块
1. 语法格式
[修饰符]{ ----只能是static或没有
//初始化块的可执行代码
...
}
2. 执行时机: 当java创建一个对象时,系统先为所有实例Field分配内存(前提是该类已经加载过了),接着对实例变量初始化:
先执行初始化块或声明Field是指定的初始值,再执行构造器里指定的初始值。
5.9.2 初始化块和构造器
1. 初始化块是构造器的补充,总是在构造器执行前执行;
2. 初始化块是一段固定的代码,不接受参数,也无法被显式调用;
3. 使用static修饰可以对整个类进行初始化。
5.9.3 静态初始化块
1. 执行时机
系统将在类初始化阶段执行静态初始化块,而不是在创建对象时才执行。因此总是必普通初始化块先执行。
2. 使用陷阱: 静态初始化块(类初始化块)属于类的静态成员,不能访问非静态成员。
注: 类的初始化
1. java加载并初始化某个类时,总是保证类的所有父类全部加载并初始化。
2. 当JVM第一次使用(懒加载)某个类时,系统会在类的准备阶段为该类的所有静态Field分配内存;在
初始化阶段则初始化静态成员,执行顺序与在代码中的排列顺序相同。
第六章 面向对象(下)
6.1 java 7增强的包装类
1. jdk的8包装类(关于类的详细内容见API)
Byte(byte) Short(short) Integer(int) Long(long) Character(char) Float(float) Double(double) Boolean(boolean)
2. 包装类与基本类型之间的转换(以float)
1. jdk1.5之前
Float ff = new Float(4.56);//基本类型想包装类型转换
float f = ff.floatValue();//包装类型向基本类型转换
2. jdk1.5增加的自动拆箱/装箱功能
Float ff = 4.56;//自动装箱
float f = ff;//自动拆箱
Object boolObj = true; //自动封装为Boolean类型然后赋给boolObj
注意事项:
1. 对基本类型来说,自动拆箱/装箱只能发生在同种数据类型和对应的包装类(或对应包装类的父类)之间;
2. 基本类型/包装类型与字符串之间的转换(部分借助了对应包装类的静态方法)
(因为自动拆箱装箱功能的存在)
1. 基本类型(包装类型)->字符串类型:
方式一: String类提供了多个重载的valueOf()方法,用于将基本数据类型转换成字符串;
方式二: String s = 3+"";
2. 字符串类型->基本类型(包装类型)
方式一: 利用包装类提供的Xxx(String s)构造器,如果字符串无法转化成对应的基本类型会报异常,特别的对
boolean来说,"true"不分大小些会转化成true,其它字符串一律转化成false;
方式二: 除了Character,其它7中包装类都提供了parseXxx(String s)静态方法;
3. 基本类型与包装类型的比较: 直接取出包装类包装的数值来进行比较
4. 包装类型之间的比较
1. 情况一: 包装类型实际上是引用类型,指向同一个对象是返回true
2. 情况二: Interger包装类的实现方式是这样的,缓冲一个Interger[]类型的数组,包装-128-127,其它的才会单独创
建Interger对象,因此,-128-127这个范围内的包装类型无论创建多少次都引用相同的地址
3. jdk7增强了包装类的功能:
1. 为所有包装类提供了一个静态的compare(xxx vall, xxx val2)来比较两个基本类型的大小;
2. 为Character包装类增加了大量工具方法对一个字符基尼吸纳给判断(见API)
问: java为什么要对这些数据进行缓存呢?
答: 将一些创建成本大,需要频繁使用的对象缓存起来,从而提高程序的运行性能。
6.2 处理对象
6.2.1 打印对象和tostring()方法
1. 干什么用的
返回该对象“自我描述信息”:“类名[email protected]+hashcode”值,用以告诉外界该对象的状态信息
2, 何时调用该方法
1. 和字符串做连接运算时自动调用;
2. 需要返回对象的描述信息时(必要的话可以想重写它)。
6.2.2 ==和equals方法
1. 区别
==: 判读引用是否相等(要求两个对象的类型相同或有父子继承关系)
equals(): Object提供的一个实例方法(和==功能一样),所有引用变量都可以调用这个方法判断与其它对象是否相等。
重写该方法可以可以自定义两个对象相等的规则。(片面说equals()是判断值是否相等是错误的)(通常要求两个对象是同一个类的实例)
2. 特例:String
String s = new String("hello"); //会创建两个对象,“hello”直接量在常量池中,同时有个对象在堆内存中
String ss = "hello"; //要么直接引用常量池中的"hello",要么在常量池中加入"hello"然后引用
String重写了equals()方法,判断标准是字符串序列是否相同
常量池: 专门用于管理在编译期间确定并被保存在已编译的.class文件中的一些数据。它包括类,方法,接口中的常量,还包括字符常量
3. 重写equals()方法的规则
1. 自反性
2. 传递性
3. 一致性
4. x.equals(null)一定返回空(前提是x != null)
问: 判断obj是否是person类的实例时为什么不用obj instanceof person来判断呢?
答: 使用obj instanceof person时,obj引用对象的类型是person或persion的的子类就可以。判断对象是否是某个类型使用
obj.getClass() == Persion.class更合适。
6.3 类成员
6.3.1 理解类成员
1. 是什么
static关键字修饰的成员就是类成员。类成员在类初始化是初始化,生命周期和类本身相同,属于整个类,不属于单个实例。
2. 有哪些/怎么用
(java类有5中成员(Field,方法,构造器,初始化块,内部类(接口,枚举)),可以作为类成员的由4种(构造器除外))
1. 类Field: 可以通过类或对象来访问,但通过对象访问实际还未委托给类访问,null对象引用一样可以访问类Field;
2. 类方法: 和类Field类;
3. 类初始化块: 在类的初始化阶段,系统会调用该类的初始化块对类进行初始化,。一旦初始化结束,静态初始化块
永远不会获得执行的机会。
4. 内部类(后面章节讨论)
3. static关键字重要规则和原因
类成员不能访问实例成员。因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化
完成而实例成员还没有初始化的情况,如果允许类成员访问类成员将会引起大量错误。
6.3.2 单例(singleton)类
1. 为什么
有些时候允许其它类自由创建该类的兑现更没有任何意义,还可能造成系统性能的下降(频繁创建/回收对象造成的系统开销);
2. 是什么
如果一个类只能为其创建一个实例,则这个类被成为单例类。
3. 怎么作
1. 把该类的构造器使用private修饰。从而把该类的所有构造器隐藏起来;
2. 提供一个public static的方法,用于创建该类的对象并返回这个对象;
3. 创建一个类Field缓存已经创建的对象,否则无法知道是否创建过对象。
实作:【1】
6.4 final修饰符
(final可修饰类/变量/方法,表示类/变量/方法不可改变。)
6.4.1 final成员变量
1. 使用方法: final修饰的成员变量必需显式地指定初始值,且只能在一处进行,系统不会对final变量进行隐式初始化。
final 类Field: 必需在静态初始化块或声明该Field时指定初始值
final 实例FIeld: 必需在非静态初始化块,声明该Field或构造器中指定初始值
2. 提示
final 类Field不能在普通初始化块中指定初始值,因为类Field在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值;
final 实例FIeld不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不能访问实例变量。
6.4.2 final局部变量
1. 初始化方式: 和普通局部变量的差别只在于只能赋值一次
(final修饰的形参不能进行显式赋值操作)
6.4.3 final修饰基本类型变量和引用类型变量的区别
final修饰基本类型变量: 被修饰的变量不可改变
final修饰引用类型变量: 被修饰的变量对应的对象可以改变,但被修饰的变量的值(地址)不能改变
6.4.4 可执行宏替换的final变量
1. 什么是宏替换: 在编译阶段编译器会把程序中用到“宏变量”的地方直接替换成该变量的值
2. 如何实现“宏变量”(同时满足三个条件)
1. 使用final修饰符修饰;
2. 在定义该final变量时指定了初始值;
3. 该初始值在编译时就可以确定下来(也就是说初始值是算数表达式或字符串链接运算也没关系)
6.4.5 final方法
1. 特点/为什么
final修饰的方法不可被重写。出于某些原因,不希望子类重写父类的某些方法,即可以使用final修饰这些方法。
2. private final方法
在子类中试图重写父类的final方法会发生编译错误,但因为子类无法重写父类的private方法,相应的“重写”父类
的private final方法并不会出错,因为这种情况只是在子类定义了一个方法,不是真的重写。
6.4.6 final类
1. 特点/为什么
final修饰的类不可以被继承。子类方法可以改变父类方法的一些实现细节会导致一些不安全的因素,final类可以避免这种情况。
6.4.7 不可变类
1. 特点/为什么
不可边类的意思是创建该类的实例后,实例的Field不可改变。不可变类在某种意义上比一般的类更加安全。
2. 如何自定义不可变类
1. 使用private和final修饰符来修饰该类的FIeld
2. 提供带参数构造器,用于根据传入参数来初始化类里的Field
3. 仅为该类的Field提供getter方法,不要为该类的FIeld提供setter方法,因为普通方法无法修改final修饰的Field.
4. 如果有必要,重写Object类的hashCode和equals方法,要保证equals方法判断为相等的对象的hshcode也相等。
(如果要设计一个不可变类,尤其要注意其引用类型Field,如果引用类型Field的类是可变的,就必需采取必要的措施来保护该Field
所引用的对象不会被修改,这样才能才能创建真正的不可边类。)
6.4.8 缓存实例的不可变类
6.5 抽象类
6.5.1 抽象方法和抽象类
1. 是什么: 用abstrace关键字修饰的方法和类
(满足的规则)
1. 抽象类必需使用abstract修饰符来修饰,抽象方法也必需用abstract来修饰,抽象发发不能由方法体;
2. 抽象类不能通过new关键字实例化,即使抽象类不包含抽象方法也不;
3. 抽象类可以包含Field/方法(普通方法和抽象方法都可以)/构造器/初始化块/内部类/枚举类6中成分。抽象类的构造器不
能用于创建实例,主要用于被其子类调用;
4. 含有抽象方法的类(包括直接定义了一个抽象方法;继承了一个抽象方法,但没有完全实现父类包含的抽象方法,以及
实现了一个接口,但没有完全实现父类包含的抽象方法3种情况)只能被定义成抽象类。
2. 注意
1. 有得有失: 抽象类可以包含抽象方法,抽象类不能用于创建实例;
2. 抽象方法和空方法体不同: public abstract void test(); ----抽象方法 public void test(){} ----空方法体
3. abstract不能用于修饰:
Field: 没有抽象成员变量,抽象类里的Field只能是普通Field
局部变量: 没有抽象变量,只能是普通变量
构造器: :抽象类里定义的构造器只能是普通构造器,提供给子类调用
任何static修饰的方法: static修饰的方法属于类本身,如果被abstract修饰将没有方法体,被类调用必然出错,因此
static和abstract不同时出现
final修饰的方法: abstrac修饰的类只能被继承,即必须由子类重写方法。而final修饰的类不能被继承,final修饰的方法
不能够被重写。因此final和abstract永远不能同时使用。
private修饰的方法: abstract修饰的方法必需被子类重写才有方法体,而private修饰的方法不能被继承和重写,因此
private和abstract不能在一起
6.5.2 抽象类的作用: 模板模式简介
(模板模式的规则)
1. 抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象为抽象方法,留给子类去实现。
2. 父类中可以包含其它系列方法: 这些方法既可以由父类实现,也可以由子类实现。父类提供的方法只是一个通用算法,可能需
要依赖子类的辅助。
6.6 更彻底的抽象: 接口
6.6.1 接口的概念: 接口是从多个相似类中抽象出来的规范,接口不提供任何实现,通常是定义一组共用方法。接口体现的是规范和实
现分离的设计哲学。
6.6.2 接口的定义
【修饰符】 interface 接口名 extends 父接口1,父接口2...
{
零到多个常量定义...
零到多个抽象方法定义...
}
规则:
1. 修饰符: public 或者省略(包访问权限)
2. 接口名: 和类名采用相同的命名规则
3. 接口的继承: 一个接口可以由多个父接口,不能继承类
4. 接口的成员:
Field(只能是常量): public static final
方法(只能是抽象实例方法): public abstract
内部类(包括内部接口/枚举)定义: public
对比接口和类: 比类的成员少两种,Field只能是常量,方法只能是抽象方法。
5. 接口成员的修饰符: 接口里定义的内部类/接口/枚举类默认都采用public static 两个修饰符,不管定义时是否
指定这两个修饰符,系统都会自动用public static对他们进行修饰
6. 接口是一个特殊的类: 因此一个java源文件里最多只能有一个public接口,如果一个java源文件里定义了一个
public接口,则该源文件名必须与该接口名相同。
6.6.3 接口的继承
1. 多继承概念: 接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口。
2. 多继承格式: 多父接口排在extends关键字之后,多个富姐口直接以英文逗号(,)隔开。
6.6.4 使用接口
1. 语法格式
【修饰符】 class 类名 extends 父类 implements 接口1, 接口2...
{
实体部分...
}
2. 接口的使用方式
1. 声明引用类型变量: 接口不能用于创建实例,但可以用于声明引用类型变量,改变量引用的必需是其实现类的对象;所有的
接口类型的引用变量都可以直接赋给Object类型的引用变量,利用上转型来实现的。
2. 被实现类实现(可以当成继承一个更加彻底的抽象类)
(实现的意义)
1. 获得的成员: 获得所实现接口里定义的常量Field/抽象方法/内部类和枚举类定义;
2. 抽象方法: 必须实现继承的接口里的全部抽象方法,除非定义为抽象类;
3. 实现接口方法时,必须使用public访问控制修饰符,因为接口里的方法都是public的,实现和继承一样,子类重写或实现
类实现的方法只能更大或相等;
3. 错误使用方式: 接口不能显式继承任何类
6.6.5 接口和抽象类
1. 相同点
1. 被其他类实现或继承: 接口和抽象类都不能被实例化,他们都位于继承树的顶端,用于被其他类实现和继承;
2. 实现抽象方法: 接口和抽象类都可以包含抽象方法,实现接口或抽象类的子类都必须实现这些方法。
2. 不同点
1. 设计思想
接口: 接口体现的是一种规范。当在一个程序中使用接口是,接口是多个模块间的耦合标准;当在多个应用程序间使用接口时
,接口是多个程序间的 通信标准;
抽象类: 作为系统中多个子类的共同父类,体现的是一种模板式设计,抽象类作为多个子类的共同父类,可以当作中间产品,
需要进一步完善。
2. 用法的差异
1. 方法
接口: 只能包含抽象方法,不能定义静态方法,不包含以及提供实现的方法
抽象类: 可以是普通方法(提供实现),可以定义静态方法
2. Field
接口: 只能是静态常量Field
抽象类: 任何Field
3. 构造器
接口: 不包含构造器
抽象类: 包含构造器器(不用于创建对象,用于被子类调用完成抽象类的初始化)
4. 初始化块
接口: 不包含
抽象类: 包含
5. 继承/实现机制
接口: 可以实现多个接口,你补java类单继承的不足
抽象类: 类(包括抽象类)是单一继承的
6.6.6 面向接口编程
1. 简单工厂模式
2. 命令模式
6.7 内部类
(内部类简分析)
1. 是什么: 在某些情况下,我们吧一个类放在另一个类的内部定义,这个定义在其他类内部的类叫做内部类(嵌套类)。
2. 为什么
1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内;
2. 内部类可以访问外部类的私有数据,但外部类不能访问内部类的实现细节;
3. 匿名内部类适合创建那些仅需创建一次的类。
3. 内部类的分类想
| 静态内部类
|成员内部类|
| 非静态内部类
内部类-|
|局部内部类
|匿名内部类
注: 1. 大部分时候,内部类作为成员内部类定义;
2. 成员内部类是一种与Field,方法,构造器,初始化快相似的类成员(可以用任意访问控制符修饰)
3. 外部类和内部类的访问控制权限比较
外部类(2个作用域): 同一个包(不加修饰符)和任意位置(public)
内部类( 4个作用域): 同一个类(private),同一个包(默认),父子类(protected),任何位置(public)
6.7.1 非静态内部类
1. 外部类不允许直接访问内部类的实例Field,因为外部类直接访问内部类的实例Field时内部类对象没有创建;
2. 外部类的静态方法、静态块不能访问非静态内部类(甚至不能定义内部类对象或创建内部类对象);
3. 非静态内部类不能包含静态成员(包括静态Field、静态方法、静态块)
问: 非静态内部类对象和外部类对象的关系是怎样的?
答: 非静态内部类必须寄存在外部类对象里,而外部类对象中不一定有内部类的实例寄存其中。
6.7.2 静态内部类 (优先使用静态内部类)
1. 是什么: 如果用static修饰一个内部类,则这个类属于外部类本身,而不属于外部类的某个对象,成为类内部类或静态内部类。
2. static可以修饰外部类吗?
static关键字不可修饰外部类,但可修饰内部类。
解析: static关键字的作用是把类的成员变成类相关的,否则就是实例相关的。外部类的上一级是包,我们可以说外部类
是属于包的,通过static修饰没有意义,内部类是外部类的成员,这是用static修饰才有意义。
3. 行为特征
1. 静态内部类可以包括静态成员,也可以包括非静态成员;
2. 根据静态成员不能访问非静态成员的规则,静态内部类(包括内部类的实例方法)不能访问外部类的实例成员,只能访问外部类的类成员;
3. 静态内部类也可以用于创建对象,静态内部类是外部类的静态成员,因此外部类的静态方法、静态初始化块中可以使用静态内部类来定义变量、创建对象;
4. 外部类可以通过静态内部类的类名访问静态内部类的类成员或通过静态内部类的对象访问其实例成员;
5. 接口中定义的内部类默认使用public static修饰,如果指定修饰符也只能和默认相同,因此接口中的内部类只能是静态内部类。
问题1: 为什么静态内部类的实例方法也不能访问外部类的实例属性?
答: 因为静态内部类对象不寄存在外部类对象中,而是寄存在外部类本身中。静态内部类持有的是外部类的类引用而不是外部类的对象的引用,因
此无法访问实例相关的外部类实例属性。
问题2: 接口里是否能定义内部接口?
答: 可以。但接口中定义的接口只能是public static修饰的,当被类实现时,实现类获得这个内部接口。通常不这样使用接口,接口通常被作为一
种公共规范。
6.7.3 使用内部类
(使用场景)
1. 在外部类中使用内部类(和使用一般类比较)
相同点:
1. 可以直接通过内部类类名来定义变量,通过new调用内部类构造器创建实例;
2. 在外部类内部定义内部类的子类也没有太大区别。
区别: 不能在外部类的静态成员中使用非静态内部类,因为静态成员不能使用非静态内部成员
2. 在外部类以外使用非静态内部类
1. 访问权限
(private修饰的内部类只能在外部类中访问)
1. 省略访问控制符: 只能被与外部类处于同一个包中的其它类访问;
2. protected: 与外部类同包的类或外部类的子类;
3. public: 可以在任何地方被访问
2. 在外部类以外的类中定义内部类变量(静态或非静态)
OuterClass.InnerClass varNme; ----如果外部类有包名还应该增减包名前缀
3. 在外部类以外的类中创建内部类对象
调用构造器创建对象: OuterInstance.new InnerConstructor(); ----非静态内部类对象,需要通过外部类对象调用非静态内部类的构造器
创建非静态内部类的子类:
class SubClass extends Out.In { ----子类继承Out类的非静态内部类In
public SubClass(Out out) {
out.super("hello"); ----创建非静态内部类的子类时需要给构造函数传递外部类对象,然后在构造器中通过OuterInstance.super()调用父类的构造器
}
}
注: 非静态类的子类不一定是内部类,它可以是一个外部类。但非静态内部类的子类的实例必须具有一个指向外部类实例
的引用。也就是说,如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。
4. 在外部类以外使用静态内部类
调用构造器创建对象: new OuterClass.InnerConstructor(); ----静态内部类是类相关的,创建内部类对象时无需外部类对象
创建静态内部类的子类: class SubClass extends Out.In {} ----子类继承Out类的非静态内部类In,只要包外部类当成包空间即可
问: 既然内部类是外部类的成员,那么是否可以为外部类定义子类,在子类中在定义一个内部类来重写其父类中的内部类?
答: 不可以!因为所处的外部类不同,子类中的内部类和父类中的内部类不会同名,也就无法重写。
6.7.4 局部内部类
1. 定义: 在方法内定义的内部类,仅在该方法内有效,不能使用static 和访问控制符修饰。
2. class文件命名格式: OuterClass$NInnerClass ----比成员内部类多一个数字,因为同一个类中可以有多个同名局部内部类,增加一个数字用以区分。
(局部成员的上一级程序单元都是方法而不是类,因此使用static没有意义;其它程序单元永远无法访问另一个方法中的局部成员,所以不能用访问控制符修饰。)
6.7.5 匿名内部类
1. 定义: 创建时立即创建一个对象,只使用一次,不能重复使用。
2. 格式: new 父类构造器(实参列表) | 实现接口(){
//匿名内部类的类体部分;
}
3. 规则:
1. 必须继承一个父类或实现一个接口,但最多只能继承一个父类或或实现一个接口;
2. 不许将匿名内部类定义成抽象类,因为匿名内部类会立即创建对象;
3. 不能定义构造器,可以定义实例初始化块完成初始化;
4. 必须实现它的抽象父类或接口中的所有抽象方法(否则就变成抽象类);
5. 匿名内部类只能访问用final修饰的外部类的局部变量
4. 创建匿名内部类的机制的区别(继承父类和实现接口)
1. 构造器(不能显式创建构造器)
实现接口: 只有一个隐式无参构造器,因此“new 接口名()"后名的()中不能有参数
继承父类: 和父类的构造器相似,拥有相同的形参列表
6.7.6 闭包和回调
1. 闭包: 闭包是一种能被调用的对象,保存了创建它的作用域信息,可以将非静态内部类当作面向对象领域的闭包。
2. 回调: 内部对象可以很方便地回调其外部类的Field,方法,这样可以让编程更加灵活。
6.8 枚举类: 实例有限而且固定的类
6.8.1 手动实现枚举类
1. 设计方式
1. 通过private将构造器隐藏起来
2. 把这个类所有可能的实例都使用public static final修饰的变量来保存
3. 如果有必要,提供一个静态方法,允许其它程序根据特定参数来获取与之匹配的实例
6.8.2 枚举类入门: java1.5之后增加了对枚举类的支持
(和普通类比较)
1. 相同点: 可以有自己的Field,方法,可以实现一个或多个接口,可以定义自己的构造器。一个java源文件最多只能定义一个
public 访问权限的枚举类,且该java源文件也必需和该public枚举类同名。
2. 区别
1. 继承
普通类: 默认继承java.lang.Object
枚举类: 默认继承java.lang.Enum(父类还是Object),该类实现了java.lang.Serializable和java.lang.Comparable两个接口
2. 被继承
普通类: 可以被继承(除非用final修饰)
枚举类: 不可被继承,因为enum定义的类默认用final修饰(除非是抽象类)
3. 构造器
普通类: 构造器可以使用三种访问控制符修饰,默认使用public修饰
枚举类: 只能使用你该private修饰,默认使用private修饰,强制只能使用private修饰
4. 实例
普通类: 一般可以在任何地方用new创建
枚举类: 所有实例必需在第一行显式列出,否则这个枚举类永远无法产生实例(系统会自动添加public static final修饰,无须程序员显式添加)
5. 遍历
普通类: 一般需要其它迭代器的协助
枚举类: 所有枚举类都提供了一个values方法,可以方便地遍历所有的枚举值
3. 使用方法
1. 枚举类语法格式示例
public enum SeasonEnum {
SPRING, SUMMER, FALL, WINTER; ----创建四个枚举类实例,中间用","隔开,以";"结束
}
2. 调用方法: EnumClass.varible
SeasonEnum.SPRING ----以上面定义的枚举类为例
3. 迭代方法
for(Season s:SeasonEnum.values) {
//处理代码
}
4. java.lang.Enum提供的方法
1. int compareTo(E o): 该方法用于与指定枚举对象比较顺序
2. String name(): 返回此实例的名称
3. int ordinal: 返回枚举值在枚举类中的索引值(从0开始)
4. String toString(): 返回枚举常量的名称,和name()相似,但更常用
5. public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name): 返回指定枚举类中的某个枚举值
6.8.3 枚举类的field/方法和构造器
1. Field
1. 原则: 枚举类通常应该设置成不可边类,Field最好用private final修饰,在初始化块或构造器中完成初始化。
2. 创建带初始化参数的枚举值:MALE("男"), FEMALE("女");
2. 方法
1. 原则
普通方法: 此时所有枚举值拥有相同的方法
抽象方法: 每个枚举值可以通过匿名内部类的方式分别实现该方法
2. 通过匿名内部类创建枚举值
MALE("男") {
public void info(){
System.out.prnitln("这个枚举值代表男性!");
}
},
FEMALE("女") {
public void infp() {
System.out.println("这个枚举值代表女性!");
}
};
问: 枚举类不是用final修饰了吗?怎么还能派生子类呢?
答: 并不是所有的枚举类都使用final修饰。对于一个抽象枚举类而言——只要包含抽象方法,就是抽象枚举类,系统默认使用abstract
修饰,而不使用final修饰。
6.8.4 实现接口的枚举类: 与普通类实现接口完全一样: 使用implements实现接口,实现接口里包含的抽象方法。
6.8.5 包含抽象方法的枚举类: 由不同的枚举值提供不同的实现
注: 枚举类中定义抽象方法时不使用abstract关键字将枚举类定义成抽象类(因为系统会自动添加)。
6.9 对象与垃圾回收
(垃圾回收机制的特征)
1. 只回收堆内存中的对象,不回收任何物理资源(数据库连接/网络IO等)
2. 程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候发生
3. 垃圾回收任何对象之前会调用finalize()方法,该方法可能使对象复活而取消垃圾回收
6.9.1 对象在内存中的状态
1. 可达状态: 对象被创建并被一个以上的引用变量引用,则对象处于可达状态;
2. 可恢复状态: 如果某个对象不再由任何引用变量引用,就进入可恢复状态。finalize()方法进行资源清理后,
如果重新让一个引用变量引用了这个对象,则进入可达状态;否则进入不可达状态。
3. 不可达状态: 当对象与所有引用变量的关联被切断,且系统调用了所有对象的finalize()方法后对象没有变成
可达状态,则永久失去引用,进入不可达状态。此时系统才会回首对象占用的资源。
创建之后 失去引用
起点 --------------->可达状态------------------------>可回复状态 垃圾回收
<------------------------- -------------------------->不可达状态------------>终点
重新获得引用 彻底失去引用
6.9.2 强制垃圾回收: 程序只能控制一个对象何时不再被引用,不能控制其合适被回收
1. 两种方式(只是通知系统进行垃圾回收,系统是否执行是不确定的,大部分时候总会有一定效果)
1. 调用System类的gc()静态方法: System.gc()
2. 调用Runtime对象的gc()实例方法: Runtime.getRuntime().gc()
2. 运行时打印每次垃圾回收是的提示信息
java -verbose:gc GcTest ---- -verbose:gc选项指定打印垃圾回收的提示信息
6.9.3 finalize方法: 方法返回时对象消失,垃圾回收机制开始执行,任何java类都可以重写这个Object的方法
1. 特点
1. 永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用
2. finalize()方法合适被调用,是否被调用具有不确定性,不一定会被执行
3. 当JVM执行可恢复对象的finalize()方法时,该对象或系统中其他对象可能变为可达状态(how)
4. finalize()出现异常时,垃圾回收机制不会报告异常,程序继续执行
注意: 由于finalize()并不一定会被执行,因此如果想清理某个类打开的资源,不要放在finalize()中清理
2. 强制调用可恢复对象的finalize()方法
Runtime.getRuntime().runFinalization();
6.9.4 对象的软/弱和虚引用
(java.lang.ref提供了三个类)
1. 强引用(StrongReference)
回收时机: 当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被垃圾回收机制回收。
2. 软引用(SoftReference)
回收时机: 当系统空间不足时,系统可能回首它。通常用于对内存敏感的程序中。
3. 弱引用(WeakReference)
回收时机: 不管内存是否充足,只要垃圾回收机制运行就会回收。
4. 虚引用(PhantomReference)
回收时机: 主要用于跟踪对象被垃圾回收的状态,除此之外和没有引用效果相当。必需和引用队列联合使用。
注: 如果要使用这些特殊的引用类,就不能保留对对象的强引用,否则将不会发挥任何价值
6.10 修饰符的适用范围
外部类/接口 | 成员属性 | 方法 | 构造器 | 初始化块 | 成员内部类 | 局部成员 | |
public | y | y | y | y | y | ||
protected | y | y | y | y | |||
包访问控制符 | y | y | y | y | 看起来使用 | y | 看起来使用 |
private | y | y | y | y | |||
abstract | y | y | y | ||||
final | y | y | y | y | y | ||
static | y | y | y | y | |||
strictfp | y | y | y | ||||
synchronized | y | ||||||
native | y | ||||||
transient | y | ||||||
volatile | y |
注:
1. strictfp关键字的含义是FP-strict,精确浮点数。所修饰范围内java编译器的运行时环境会依照浮点规范IEEE-754来执行。
2. native关键字修饰的方法类似与一个抽象方法,将交由c来实现,程序中包含native方法意味着失去跨平台能力。
3. 四个访问权限控制符是互斥的。
4. abstract和(final,static,private)中任何一个都不能同时使用
6.11 使用jar文件
(jar)
1. 是什么(Java Archive File)
Java的档案文件,一种压缩文件,和zip文件的区别是文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件。
2. 为什么
1. 安全: 可以进行数字签名
2. 加快下载速度: 压缩后的单个文件只需一个HTTP链接
3. 压缩
4. 包装性
5. 可移植性: 作为Java平台内部处理的标准,在各种平台直接使用
6.11.1 jar命令详解
1. 创建jar文件: jar cf test.jar test ----将test路径下的全部内容生成一个test.jar文件,如果当前路径中已有test.jar,覆盖之,不显示压缩过程
2. 创建jar文件并显式压缩过程: jar cvf test.jar test
3. 不使用清单文件: jar cvfM test.jar test
4. 自定义清单文件: jar cvfm test.jar a.txt test ----在原清单文件基础上增加a.txt中的内容到META-INF/MANIFEST.MF文件中
注:清单文件的格式(清单文件只是普通的文本文件)
1. 每行只能定义一个key-value对,不能由任何空格
2. 每组key-value对之间以": "(英文冒号后面跟一个英文空格)
3. 文件开头不能由空行
4. 文件必需以一个空行结束
5. 查看jar包内容: jar tf test.jar
6. 查看jar包详细内容: jar tvf test.jar
7. 解压缩: jar xf test.jar
8. 带提示信息解压缩: jar xvf test.jar
9. 更新jar文件: jar uf test.jar Hello.class ----如果test.jar中由Hello.class则更新之,没有则添加进去
10. 更新时显式详细信息: jar uvf test.jar Hello.class
6.11.2 创建可执行的jar包
1. 三种发布方式
1. 使用平台相关的编译器将整个应用编译成平台相关的可执行性文件。通常需要第三方编译器的支持。会丧失跨平台性,甚至性能下降。
2. 为应用编辑一个批处理文件
java package.MainClass ----运行程序的主类
start javaw package.MainClass ----不使用java的命令行窗口
3. 将一个应用程序制作成一个可执行的jar包
制作:
jar cvfe test.jar Test *.class ----将所有.class文件都压缩到test.jar中,并指定使用Test类作为程序的入口
运行:
java -jar test.jar ----使用java命令
javaw test.jar ----使用javaw命令
6.11.3 关于jar包的技巧
第七章 与运行环境交互
7.1 与用户交互
7.1.1 运行java程序的参数
1. 解析main方法签名(public static void main(String[] args){})
1. public修饰符: java类由jvm调用,为了让jvm可以自由调用main方法,所以使用public修饰符将这个方法暴露出来。
2. static修饰符: jvm调用该方法时不会向创建该主类的对象,然后通过对象来调用该主方法。JVM直接通过该类来调用
主方法,因此使用static修饰该主方法。
3. void返回值: 因为该方法被jvm调用,返回值会返回给jvm,这没有任何意义,因此main方法没有返回值
4. “String[] args”形参: main方法由jvm调用,因此由jvm赋值
2. 给mian方法传递参数: java 主类 第一个参数 第二个参数 第三个参数 ...
1. 举例
java javaCrazyBook.ArgsTest aa bb cc "xiao li" ----传递字符串数组参数["aa", "bb", "cc" ,"xiao li"]给jvm(jvm是main的调用者)
2. 原则
1. java程序后紧跟一个或多个字符串,以空格隔开,jvm会依次将这些字符串赋给args数组元素
2. 如果参数本身包含空格,则应将参数用双引号("")括起来
7.1.2 使用scanner获取键盘输入(也可以讲文件作为输入源)
1. 简述: Scanner是一个基于正则表达式的文本扫描器,它可以从文件、输入流、字符串中解析出基本类型值和字符串值。Scanner提供了多
个构造器,不同的构造器可以接收文件、输入流、字符串作为数据源,用于从文件、输入流、字符串中解析数据。
2. 提供了两个方法进行数据的扫描
1. hasNextXxx(): 是否还有下一个输入项,其中Xxx可以是Int、Long等代表基本数据类型的字串或Line。如果需要判断是否还有下一个
字符串,则可以省略Xxx。默认空格为扫描的分隔符,遇到分隔符就认为当前扫描的这一项结束,返回true。
常常配合while使用,如果下一项没有输入将会阻塞,等待输入,直到获得下一项输入为止。
2. nextXxx(): 获取下一个字符串或基本数据类型,如果类型不符会中断程序。默认空格为分隔符。
注: Scanner类提供useDelimiter(String pattern)实例方法设置自定义的分隔符,其中pattern是一个正则表达式。例如如果想程序
不管有没有空格,每次读取一行,可以讲"\n"作为 分隔符。当然也可以用hasNextLine()和nextLine()配合实现每次读取一行。
7.1.3 使用BufferedReader获取键盘输入
(在jdk5中增加Scanner类之前的获得键盘输入的方式)
1. 什么是
BufferedRreader是java IO流中的一个字符、包装流,必须建立在另一个字符流的基础之上。
2. 怎么用
/*System.in是字节流,new InputStreamReader(System.in)将其包装成为一个字符流*/
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = null;
/*逐行读取键盘输入,BufferedReader只能一行一行读取用户的键入,每行被当做一个String对象
无法读取基本输入类型项*/
while((line = br.readLine()) != null){
System.out.println("用户键盘输入是:" + line);
}
7.2 系统相关
7.2.1 system类
1. 是什么
System类代表当前Java程序的运行平台,程序不能创建System类的对象,System类提供了一些类Field和类方法。
2. 具体用法(具体请查看api)
1. System类提供标准输入、标准输出、和错误输出的类Field
System.in ---标准输入(键盘),对应修改方法 System.setIn()
System.out ----标准输出(屏幕),对应修改方法 System.setOut()
System.err ----错误输出,对应修改方法 System.setErr()
2. 访问环境变量、系统属性的静态方法
System.getenv() ----获取所有的环境变量(返回Map对象)
System.getenv("JAVA_HOME") ----获得指定环境变量
System.getProperties() ----获得所有系统属性(返回Properties对象)
System.getProperties(”os.name“) ----获得指定系统属性
3. GC相关
System.gc() ----通知系统进行垃圾回收
Systeem.runFinalization() ----通知系统进行资源清理
4. 获得系统时间的方法
System.currentTimeMillis() ----返回与UTC1970.1.1午夜的世间差(毫秒),在某些操作系统不精确,因为,某些操作系统以几十毫秒为单位
System.nanoTime() ----返回与UTC1970.1.1午夜的世间差(纳秒),大部分操作系统不支持纳秒
System.identityHashCode(Object x) ----返回指定对象的精确hashcode值(根据对象的地址进行计算),唯一地标识该对象
5. 提供了加载文件动态链接库的方法(主要对native方法有用)
System.loadLibrary(String libname) ----加载动态链接库
System.load(String filename) ----加载文件
注:java程序完成诸如访问操作系统底层硬件等特殊功能,需要借助c为java提供实现,然后用java调用之。步骤如下
1. java程序中声明native方法,类似于abstract方法,只有方法签名,没有实现,生成一个class文件;
2. 用javah将上面的class文件编译产生一个.h文件;
3. 写一个包含上面的.h文件的.cpp文件实现native方法;
4. 将上面的.cpp 编译成动态链接库文件;
5. 在java中加载上面的动态链接库文件,可用的加载方式:
System.loadLibrary(String libname)
Runtime.loadLibrary(String libname)
6. 在java程序中调用这个native方法。
7.2.2 runtime类
1. 是什么
Runtime代表java程序的运行时环境,每个java程序都有一个与之对应的Runtime实例,应用程序通过该对象与运行时环境相连。
应用程序不能创建自己的Runtime实例,但可以通过Runtime.getRuntime()方法获取与之关联的Runtime对象
2. 具体用法
1. GC相关
Runtime.gc() ----通知系统进行垃圾回收
Runtime.runFinalization() ----通知系统进行资源清理
2. 提供了加载文件动态链接库的方法(主要对native方法有用)
Runtime.loadLibrary(String libname) ----加载动态链接库
Runtime.load(String filename) ----加载文件
3. 访问jvm相关信息
(Runtime rt = Runtime.getRuntime();)
rt.availableProcessors() ----处理器数量
rt.freeMemory() ----空闲内存数
rt.totalMemory() ----总内存数
rt.maxMemory() ----可用最大内存
4. 单独启动一个进程运行操作系统的命令
(Runtime rt = Runtime.getRuntime();)
rt.exec("notepad.exe"); //运行记事本程序
注: Runtime提供了一系列exec()方法来运行操作系统的命令,详见API.
7.3 常用类
7.3.1 object类
1. 简介
Object类是java所有类、数组、枚举类的父类,任何java对象都可以调用Object的方法。
2. Object的常用方法
1. boolean equals(Object obj): 判断两个对象是否是同一个对象,和==一样,因此没有太大价值。
2. protected void finalize(): 当失去所有引用时,垃圾回回收器调用该方法清理对象的资源。
3. Class<?> getClass: 返回该对象的运行时类。
4. toString(): 返回对象的字符串表示。显示调用或系统自动调用(System.out.println()方法输出一个对象或者与字符串进行连接运算时)。
5. wait(),notify(),notifyAll(): 控制线程。
6. protected clone(): 通过”自我克隆得到当前对象的一个”浅拷贝“,即只会克隆该对象所有Field的值,不会对引用类型Field引用的对象也
进行拷贝。“该方法只能被子类重写和调用。开发者可以通过递归克隆实现”深拷贝“。
7.3.2 java 7新增的objects 类
1. 简介: 一个工具类,提供一些工具方法来操作对象,这些工具方法大多是“空指针”安全的。
7.3.3 String, StringBuffer和StringBuilder类
1. String: 不可变类(字符序列不可变,知道这个对象被销毁)
1. 种类众多的构造器(API)
2. 大量操作字符串对象的方法(API)
2. StringBuffer: 字符序列可变的字符串类(线程安全)
append()、insert()、reverse()、setChar()、setLength()等方法修改字符串序列;
toString() ----调用它的这个方法将其转换成String
3. StringBuilder: JDK1.5后出现的字符串对象(线程不安全,性能略高)
7.3.4 math类
1. 简介
Java提供Math类来完成一些复杂的运算,比如三角函数、对数运算、指数运算。Math类是一个工具类,构造器是private的,
所有的方法都是类方法。
7.3.5 java 7的threadLocalRandom与Random
1. Random: 该类专门用来生成一个伪随机数。
1. new Random() ----默认使用当前时间作为种子
2. new Random(Long long) ----指定一个Long型整数作为种子,如果两个实例使用相同的种子,而且按相同的顺序调
用相同的方法,这些方法就会产生相同的伪随机值。为了避免这种情况推荐使用当前
时间作为种子:”Random rd = new Random(System.currentTimeMilis()); “ 。
2. ThreadLocalRandom: JDK7新增的一个类,与Random用法基本相似,在并发环境鲜可以减少多线程资源竞争,提升性能。
ThreadLocalRandom rand = ThreadLocalRandom.current() ----提供了一个current()静态方法获得对象,这一点与Random不同
7.3.6 BigDecimal类: 为了能精确表示、计算浮点数,Java提供了java.math.BigDecimal类
1. 创建BigDecimal对象
1. 提供了大量构造器用于创建BigDecimal对象,包括:
1. 基本数值型变量;
注: 不推荐使用new BigDecimal(double dou),有一定不可预知性,推荐替代方案:
BigDecimal.valueOf(double dou) 或 new BigDecimal(String dou)
2. 数字字符串;
3. 数字字符数组。
2. 提供了构造BigDecimal的静态方法
BigDecimal.valueOf(double val)
BigDecimal.valueOf(long val)
BigDecimal.valueOf(long unscaledVal, int scale)
2. 对精确浮点数进行常规算数运算
add()、substract()、multiply()、divide()、pow()等实例方法
注: 程序中可以定义一个工具类对浮点数进行精确计算,计算时就不用每次都将浮点数先包装成BigDecimal。
7.4 处理日期的类
7.4.1 java.util.Date类: kdk1.0就开始存在的处理日期、时间的类,大部分构造器、方法已经过时,不推荐使用
1. 构造器(6个构造器-4个Deprecated = 2个可用的)
Date(): 生成一个代表当前日期的Date对象。该构造器调用底层的System.current TimeMillis()
获得long整数作为日期参数。
Date(long date): 指定long整数(和GMT1970.1.1 00:00:00之间的时间差)
2. 方法(多数已经Deprecated,只剩下少数几个方法)
boolean after(Date when): 测试该日期是否是在when之后
boolean before(Date when): 测试该日期是否是在when之前
int compareTo(Date anotherDate): 比较两个日期的大小
boolean equals(Object obj): 当两个日期表示同一时刻时返回true
long getTime(): 返回改时间对应的long型整数(和GMT1970.1.1 00:00:00之间的时间差,单位是毫秒)
void setTime(long time): 设置该Date对象的时间
注: Date中的很多方法已经不被推荐使用了,如果想对日期进行加减运算或者获取指定对象中的年月日,
应该用Calender工具类。
7.4.2 java.util.Canlendar类
1. 说明: Calendar是一个抽象类,用来更好的处理日期和时间(替代java.util.Date)。
2. 使用方式
1. 创建Calendar对象(因为是抽象类,不能使用构造器创建实例对象)
static Calendar getInstance() ----使用默认的Locale和TimeZone
static Calendar getInstance(Locale aLocale) ----指定Localestatic Calendar getInstance(TimeZone zon) ----指定TimeZonestatic Calendar getInstance(TimeZone zone, Locale aLocale) ----指定Locale和TimeZone
2. 访问、修改时间日期的常用方法
void add(int field, int amount): 为指定的日历的字段增加或减去指定的时间量
int get(int field): 返回指定日历字段的值
int getActualMaximum(int field): 返回指定日历字段的最大值(比如月份是11)
int getActualMinimum(int field): 返回指定日历字段的最小值(比如月份是0)
void roll(int field, int amount): 和add()类似,不过超过field字段的最大值也不会自动按照日历规则使上级字段进位
void set(int field, int value): 将给定的日历字段设置为给定值
void set(int year, int mounth, int day): 设置Calendar对象的年、月、日三个字段的值
void set(int year, int mounth, int day, int hourOfDay, int minute, int second): 设置Calendar对象的年、月、日、时、分、秒
3. Calendar类注意点
1. add和roll的区别
(add(int field, int amount)和roll都是用来改变特定字段的值,amount为正时增减某个字段的值,amount为负时减少某个字段的值)
1. add的规则
1. 当修改的字段超出允许范围时,会发生进位,上一级字段也会增大;
2. 如果需要下一级字段的值改变,会修正到变化最小的值
2. roll的规则
1. 当修改的字段超出允许范围时,会发生进位,但上一级字段不会增大;
2. 同add
2. 设置Calendar的容错性
1. 说明: Canlendar有两种解释日历字段的模式(lenient和no-lenient模式)
lenient: 该模式下每个字段可以接受超出它允许范围的值
no-lenient: 该模式下如果字段超出允许的值将跑出异常
2. 设置方法
ssetLenient(false) ----Calendar提供了了这个方法用于设置容错性,参数为false时将变为no-lenient模式
3. set方法延迟修改: set方法修某个字段后该字段会立即改变,但Calendar对象所表示的时间不会立即改变,直到下次调用get()、
getTime()、getTimeMillis()、add()或roll()时才会重新计算日历的时间。
7.4.3 java.util.TimeZone类