《疯狂Java讲义》阅读笔记1
2.2 UML统一建模语言
从粗粒度到细粒度,最常用的UML图:
-
部署图:从物理处理器和设备的角度画图,其中一个设备中可能包括零个或若干个组件
-
用例图:表示的是一系列功能,一个用例表示系统的一个功能模块,如登录模块
-
组件图:多个类共同组成的jar包、war包或可复用的函数库,或DDL文件
-
类图:表示的是类的结构和它们相互之间的关联(聚合和组合关系)、泛化(继承)、依赖关系(一个类的改动会导致另一个类的改动)
-
顺序图:表示具体用例的一部分的详细流程,分为垂直维度(时间)和水平维度(消息在对象实例间的发送与接收)
-
活动图:用于描述用例内部的活动或方法的流程,最大优点是可以并行操作,单支类似于流程图
-
状态机图:描述某个对象所处的不同状态和状态间的转换信息,其符号集包含5个基本元素:
1-初始状态-实心圆
2-终止状态-内部包含实心圆的圆
3-状态-圆角矩形
4-状态之间的转换-带箭头的线段
5-判断点-空心圆
状态机图擅长表现单个对象的跨用例行为,对于多个对象的交互行为应该采用顺序图
2.3 Java的面向对象特征:
对象的状态-用数据来描述-Java用成员变量来描述
对象的操作(对象的行为)-用来改变对象的状态-Java用方法来描述
对象实现了数据和操作的结合
Java语言不允许直接访问对象,而是通过对对象的引用来操作对象。
- 上面这句话我认为可以从GC的角度来理解-Java里的对象具有唯一性,每个对象都有一个标识来引用它,如果某个对象失去了标识,这个对象将变成垃圾,只能等着垃圾回收机制来回收它。
3.1 注释和API文档
可读性第一,效率第二
-
生成API文档的方法:
可以用如下语句生成一个API文档:
javadoc -d apidoc -windowtitle windowtitlename -doctitle doctitlename -header 我的类 *.java
其中,
-windowtitle
表示文档的窗口标题
-doctitle
表示概述页面的标题(只有处于多个包下的源文件生成API文档时,才有概述页面)
header
指定一个HTML格式的文本,包含每个页面的页眉效果:
如果想生成包描述文件,可以将上述命令的最后指定的*.java
文件改为包名
,这样生成的文档中会有一个package.html文件,在其中的<body>元素中添加描述。
3.2 标识符和关键字
标识符必须以字母、下划线(_)、美元符($)开头,后面除了可以跟上面三种外,还可以跟数字,且这里的字符还可跟中文日文等字符。
Java中区分大小写
Java的所有关键字都是小写的
Java关键字:
其中,enum
和const
是保留字,true
、false
和null
称为直接量(literal)
Java语言是强类型语言,即可以在编译时进行更严格的语法检查,从而减少编程错误。
3.4 基本数据类型
希望系统将整数当long型处理时:在整数后加l或L(推荐),否则如果写以下语句:
long bigV=9999999999999;
系统会报“the literal of type int is out of range”的错误,表明这时还是将数以int型处理了;
如果这里是个没有超出int类型的值,就不报错,因为int类型的值会自动类型转换到long类型。
字符型:
Java中的单引号、双引号和反斜线都有特殊的用途,需要在前面再加一个\
浮点型:
使用float和double都不能很精确地表示一个浮点数,如果要精确保存一个浮点数,用BigDecimal类
浮点类型默认是double类型,在浮点数后加f或F表示float型
三个特殊的浮点数值:
-
正无穷大:正数除以0,通过
Float.POSITIVE_INFINITY
或Double.POSITIVE_INFINITY
表示 -
负无穷大:负数除以0,通过
Float.NEGATIVE_INFINITY
或Double.NEGATIVE_INFINITY
表示 -
非数:0.0除以0.0,通过
Float.NaN
或Double.NaN
表示
所有正无穷大都是相等的
所有负无穷大都是相等的
两个非数之间是不相等的
整数除以0会报错
数值之间可以使用下划线分割,如:
int binV=10_10_10;
3.5 基本类型的类型转换
自动类型转换:
把一个表数范围小的数值或变量直接赋值给另一个表数范围大的变量,否则需要强制类型转换
自动类型转换图如下:
强制类型转换:
- 随机输出"a"~"z":
int x=(int) (Math.random()*26+97);
String res=""+(char)x;
System.out.println(res);
- 字符串不能直接转换为基本类型,因此使用基本类型的包装类型可以实现把字符串转换成基本类型,例如:
String a="45";
int va=Integer.parseInt(a);
System.out.println(va+2);//输出47
表达式的类型提升:
也按照上图自动类型转换的图来进行转换,右边等级最高,除了byte、char、short将被提升到int类型特殊外,其它会转换成表达式中等级最高的类型
两个整数相除,即使除不尽,也会得到一个整型的值,也是表达式类型严格保持和表达式中最高等级操作数相同的类型的体现。
3.6 直接量
指在程序中通过源代码直接给出的值
能指定直接量的类型:基本类型、字符串类型和null类型
Java支持8种直接量,基本类型除去short型和byte型,再加上String类型和null类型
当程序第一次使用某个字符串直接量时,Java会使用常量池来缓存该字符串直接量,后面用到时会直接使用常量池中的字符串直接量
常量池(constant pool)指的是在编译器被确定,并被保存在已编译的.class文件中的一些数据,它包括关于类、方法、接口中的常量,也包括字符串直接量
(字符串是一个典型的不可变类,所以共享String对象时不会导致混乱,Java会保证每个字符串常量只有一个)
3.7 运算符
除法运算符是截断取整,即直接舍弃小数
求余运算%也需要进行除法运算,所以除数也不能是0,否则也将引发除以0异常,求余运算也会出现非数:0%0.0或0.0%0.0或0.0%0
移位运算:
向右移位a>>33和a>>1的结果一样,如果a是整型,且右边的数大于整型的最大位数32,会先对32求余,之后得到的数才是真正的移位位数。如果a是long类型,则会对64求余
移位运算>>x可以用除以2x计算,<<x可以用乘以2x计算,移位运算只是得到了一个新的运算结果,原来的操作数本身不会变
扩展后的赋值运算符,例如>>=
不仅具有更好的性能,而且程序会更健壮
比较运算符:
>、<、>=、<=只支持数值类型,==不管两边的数据类型是否想同,只看值是否相等;
但是基本类型、引用类型和boolean类型的值和变量不能互相比较;
引用类型的两个引用类型之间需要有父子继承关系才可以比较
逻辑运算符:
用于操作两个布尔类型的变量或常量
&&:短路与
&:不短路与
运算符优先级见下表(上一行的优先级大于下一行的):
4.2 分支结构
if语句总是优先把包含范围小的条件放在前面处理
switch语句后面的控制表达式的数据类型只能是byte、short、char、int四种整数类型、枚举类型和String类型(JDK 7开始允许)
使用break;
和continue;
来控制循环的例子:
break的两个对应例子:
如果有标签,结果会是:
outer://这里是一个标签
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(i==1&&j==1) {
break outer;//break后加标签名,表明结束标签指定的循环
}
System.out.println("i为:"+i+",j为:"+j);
}
}
//输出结果:
//i为:0,j为:0
//i为:0,j为:1
//i为:0,j为:2
//i为:1,j为:0
如果没有标签,结果会是:
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(i==1&&j==1) {
break;
}
System.out.println("i为:"+i+",j为:"+j);
}
}
//结果:
//i为:0,j为:0
//i为:0,j为:1
//i为:0,j为:2
//i为:1,j为:0
//i为:2,j为:0
//i为:2,j为:1
//i为:2,j为:2
continue的两个对应例子:
如果有标签,结果会是:
outer:
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(i==1&&j==1) {
continue outer;
}
System.out.println("i为:"+i+",j为:"+j);
}
}
//结果:
//i为:0,j为:0
//i为:0,j为:1
//i为:0,j为:2
//i为:1,j为:0
//i为:2,j为:0
//i为:2,j为:1
//i为:2,j为:2
如果没有标签,结果会是:
for(int i=0;i<3;i++) {
for(int j=0;j<3;j++) {
if(i==1&&j==1) {
continue;
}
System.out.println("i为:"+i+",j为:"+j);
}
}
//i为:0,j为:0
//i为:0,j为:1
//i为:0,j为:2
//i为:1,j为:0
//i为:1,j为:2
//i为:2,j为:0
//i为:2,j为:1
//i为:2,j为:2
4.6 深入数组
数组的初始化过程和在内存中的存放:
在初始化数组后,由于数组是引用类型,所以代码中显式表示出的数组引用的变量存放在栈中(如果该引用变量是局部变量),实际的数值存放在堆中,数组引用的变量指向堆中的数组对象
引用类型数组:数组中的每个变量也是引用,该引用变量存储在堆中,指向堆中的有效的数组内存,实际的变量值
5.1 类和对象
static是一个特殊的关键字,它可用于修饰方法、成员变量等成员。static修饰的成员表明它属于这个类本身,而不属于该类的单个实例。因为通常把static修饰的成员变量和方法也称为类变量、类方法。不使用static修饰的普通方法、成员变量则属于该类的单个实例,而不属于该类。
有时也把static修饰的成员变量和方法称为静态变量和静态方法,把不使用static修饰的成员变量和方法称为非静态变量和非静态方法,静态成员不能直接访问非静态成员。
对象的this引用:
this作为对象的默认引用有两种情形:
在构造器中表示引用该构造器正在初始化的对象
在方法中表示引用调用该方法的对象
注意:是对象的默认引用,而不是类的
对于static修饰的方法而言,可以使用类直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象,所以static修饰的方法中不能使用this引用。
由于static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。
5.2 方法的参数传递机制
Java的参数传递机制:Java里方法的参数传递机制只有一种:值传递,即将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。这是因为每个方法都有一个栈区,在一个方法中调用另一个方法,并传递实参,另一个方法中会将这些实参放到自己的栈区并进行操作,原先方法栈区中的值并未发生改变。
引用变量的传递也是值传递,系统会将引用变量(可以理解为指针)在另一个方法的栈区中复制一份,两个栈区中对应引用变量的内容都相同,都是堆中的同一个地址,所以另一个方法中对堆中的实际值作修改后,原方法中的实际值也会发生修改,但这也是值传递,因为引用变量和引用变量的副本对应的是堆中的同一个地址,而与基本类型的变量一样,也是复制了引用变量的副本。
形参个数可变的方法:
- 在最后一个形参的类型后增加三点(...),如:
public void test(int a,String ... books)
{
for(String tmp:books)
{
System.out.println(tmp);
}
}
可用如下两种形式调用:
test(5,"aaa","bbb");
test(6,new String[] {"aa","bb"});
- 用数组形参:
public static void test(int a,String[] books) {
for(String tmp:books) {
System.out.println(tmp);
}
}
只能用如下方法来调用:
test(6,new String[] {"aa","bb"});
注意:长度可变的形参只能处于形参列表的最后,且只能有一个长度可变形参
递归方法:一个方法的方法体实现中再次调用了方法本身
递归要向已知方向递归
方法重载:
同一个类中两个或两个以上方法的方法名相同,形参列表不同,则被称为方法重载。(两同一不同)
5.4 隐藏和封装
对类和对象实现良好封装,可以实现以下目的:
- 隐藏类的实现细节
- 在方法中加入控制逻辑,限制对成员变量的不合理访问
- 可进行数据检查,从而有利于保证对象信息的完整性
- 便于修改,提高代码的可维护性
封装就是把对象的成员变量和实现细节隐藏起来,把控制这些成员变量和实现细节的方法暴露出来进行安全的访问和操作
访问控制符:
访问控制符从图中也可以看出:
对于类中的成员变量、方法或构造器来说:
private修饰的只能在当前类的内部使用,适合修饰类内成员变量;
不使用任何访问控制符修饰的可以在当前类和包中被访问,拥有default包访问权限;
使用protected修饰的表示其可以被同一包中的其它类访问,也可以被不同包中的子类访问
使用public修饰的表示其可以被所有类访问。
对于外部类来说:
只能有public和默认(同一个包中的其它类),因为没有所在类的内部和所在类的子类两个范围
在使用javac
命令来编译Java文件时,要使用-d
选项,这样编译器会根据源文件中所写的包名和类名共同生成相应的文件结构,例如,如果在Main.java文件中写了package test;
,用javac -d . Main.java
,会先生成对应的包文件夹test,再在test文件夹中生成对应的Main.class文件;反之,如果不使用-d .
选项,会忽略源文件中的包名,也不会按照包名所在的路径创建class文件,而是会直接在当前文件夹下创建Main.class文件;如果将Main.java文件直接放在test文件夹中,写成javac test/Main.java
也可以在test文件夹中生成Main.class文件。
在使用 java
命令运行Main.class文件时,如果运行的是java test.Main
,则会正确地找到对应的Main.class文件,如果进入test文件夹后运行java Main
,则会报错,找不到Main文件,这是因为虚拟机在装载命令行中所写的test.Main
类时,会依次搜索CLASSPATH所指定的系列路径,查找这些路径下是否包含test路径,并在test路径下查找是否包含Main.class文件,即按命令所写的与包层次对应的目录结构去查找class文件。
并且,位于同一个包中的类不必位于相同的目录下,只要让CLASSPATH环境变量里包含这两个路径即可,虚拟机会自动搜索CLASSPATH下的路径,把它们当成同一个包下的类来处理。
项目中建议的.java和.class文件存放结构:
Java的包机制需要两方面的保证:
① 源文件里使用package语句指定包名
② class文件必须放在对应的路径下
import语句中的(*)只能代表类,表示导入包下的所有类
Java默认为所有源文件导入java.lang包下的所有类,包括String类,System类
import语句还可以导入类中的单个或所有静态成员变量或方法:import static packagename..classname.staticfieldname
或import static packagename..classname.*
import语句和import static语句都是用于减少程序中代码编写量的。
5.5 深入构造器
在一个构造器中调用另一个构造器,用this(形参1,形参2...);
来调用,且该语句必须作为构造器执行器的第一条语句
5.6 类的继承
Java的子类不能获得父类的构造器
Java类只能有一个直接父类
重写父类的方法要遵循“两同两小一大”规则:
- 方法名、形参列表相同
- 子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等
- 子类比父类的访问权限更大或相等
方法重载和方法重写的区别?
前者发生在同一个类的多个同名方法之间,后者发生在子类和父类的同名方法之间
使用super调用父类构造器和使用this调用同一个方法中的构造器都需要放在当前构造器的第一行
执行子类构造器时,总会先执行父类构造器一次
5.7 多态
Java引用变量的两个类型:编译时类型和运行时类型,前者是声明变量时使用的类型,后者是实际赋给该变量的对象的类型
多态的定义:相同类型的变量,调用同一个方法时呈现出多种不同的行为特征
对象的实例变量不具备多态性,只有实例方法具有多态性。所以引用变量在转换成子类变量时,需要强制类型转换
在强制类型转换之前用instanceof
判断某个引用变量(实例)是否可以转换到对应的类型(是否是后一个类的实例),如下:
if (引用类型A的变量 instanceof 引用类型A,或其父类型或一个接口) {
//相应的操作
}
即前面的变量类型对应的父类和接口都可以作为后面被转换的类型,用instanceof判断后可以让程序更健壮
使用时要注意在编译时,前面的变量的编译类型与后面的类型具有相同类型或父子继承关系时不会引起编译错误
向上转型:把子类对象赋给父类引用变量
5.8 继承与组合
继承:‘
派生子类的条件:子类需要额外增加属性和独有的行为方式
继承表达的是一种“is a”的关系,组合表达的是一种“has a”的关系
5.9 初始化块
初始化块也可以对Java对象进行初始化操作,初始化块只在创建对象时隐式执行,且在执行构造器之前执行,执行时实例变量语句也会执行,如int a=9;
也会在创建对象时执行,且也在执行构造器之前执行。创建对象时父类中的初始化块也会和构造器一样被执行。
适用于初始化处理代码对所有对象都相同,且无须接收任何参数的情况
静态初始化块:(类初始化块)
- 初始化块的修饰符只能是static
- 系统在类初始化阶段执行静态初始化块
- 所有的静态初始化块,包括父类的和子类的,先于所有的普通初始化块和构造器执行,且只执行一次。