第一章 对象导论
1.抽象过程
- 所有编程语言都提供抽象机制
- 解决问题的复杂性直接取决于抽象的类型和质量
- 抽象的类型指 “所抽象的是什么?”
- 汇编语言是对底层机器的轻微抽象
- “命令式” 语言(如 C、Java 等等)是对汇编语言的抽象
- OOP 是根据问题描述问题,而不是根据运行解决方案的计算机描述问题
- 面向对象的特点
- 万物皆对象,如 狗、房子、服务等等都可以作为对象
- 程序是对象的集合
- 对象里面可以包含对象
- 每个对象都对应有一个类型,类型是指 类,即 Class
- 某一特定类型的所有对象都可以接收同样的消息,我理解为多态
2.每个对象都有一个接口
OOP 的封装性:封装就是将数据和操作数据的方法封装起来,然后对数据的访问只能通过一个已定义好的接口(所以我觉得接口可以理解为类中成员变量和成员方法的统称)
类名位于类图的顶部,成员变量位于中间(上面的类图没有表示出来),方法位于底部
调用接口:
Light lt = new Light();
lt.on();
3.每个对象都提供服务
- 对象时服务的提供者
- 当一个业务的功能特别多的时候,仅用一个对象的话就太过繁杂,所以可以用多个对象来分担这些功能,然后通过对象调用其他对象提供的服务(即功能)来完成这个业务,这也提高了对象的内聚性(高内聚是软件设计的基本质量要求之一)
4.被隐藏的具体实现
- 类创建者:负责构建类
- 客户端程序员:使用创建好的类(使用的是类提供的接口,具体实现被隐藏起来了)
- 隐藏的内容:对象中脆弱的部分,只能被类创建者修改,防止被粗心或不知内情的客户端程序员毁坏,减少程序 bug(隐藏内容其实就是一种访问控制)
- 访问控制的两个作用
- 让客户端程序员无法触及他们不该触及的部分,这部分数据是类中内部操作必须的,但不是客户端解决问题所必需的(客户端程序员只需要负责调用接口即可)
- 可以保证类创建者在修改这部分数据的时候不会影响到客户端程序员,比如说你认为实现某个功能的代码可以用更好的代码实现,那么你尽管去修改它,客户端程序员仍然是调用这个接口,不会受到任何影响
5.复用的具体实现
- 面向对象的巨大优点之一:类可以复用,可以简单地直接使用这个类的一个对象,也可以将该类的一个对象内置到另一个新的类中使用,即创建一个成员对象,新的类中可以有任意数量、任意类型的对象
6.继承
- 继承是指从已有的类中继承信息并创建新的类的过程,被继承的类叫父类、基类、超类、源类,得到继承信息的类叫子类、派生类、导出类、继承类。
- 子类中不仅包含父类中所有方法与属性(包含私有方法与私有属性,但是不能访问他们),而且可以根据需要去创建自己的方法与属性
- Java 中只支持单继承
- 类图
- “是一个”与“像是一个”
- 当子类仅仅继承父类,没有做任何扩展时,子类与父类的关系是 is-a 关系
- 当子类继承父类,并有添加额外的接口元素的时候,他们的关系是 is-like-a 关系
7.多态
-
多态分编译时多态(重载)和运行时多态(重写),实现多态需要做两件事:
- 子类继承父类并重写父类方法
- 父类类型引用子类类型对象
这样子,同样的引用调用同样的方法会根据不同的子类对象表现出不同的行为
父类中的方法:
main函数:
8.单根继承
参考:单根继承的好处
- Object 类为终极基类,即最顶层父类
- 所有的类最终都来自 Object 类
- 单根继承的好处:
- 传递参数,当函数不知道该传什么样的参数时,就可以写成
public void function(Object obj)
但是传入的是父类,所以只能调用父类中的方法,如果要调用子类中的方法,需要用到向下类型转换(强制类型转换) - 每个类都具有了基础的功能,也就是 Object 类中的那些功能
- 方便垃圾回收机制,其实就是方便找到垃圾所在位置,会从根类 Object 开始一层层往下找
- 方便异常处理,如果写明抛出那个子类异常,那么当遇到其他子类异常时就用不了这个异常函数了,所以可以直接抛出一个父类异常
- 传递参数,当函数不知道该传什么样的参数时,就可以写成
9. 容器
- 使用容器来存储多个对象
- 参数化类型,即泛型
10. 对象的创建和生命周期
参考:对象创建和生命周期
- 出于运行速度的考虑,如果把对象放在栈上或者静态内存区域,在程序编写时存储位置及其生命周期都能够确定
- 另一种方式是在堆上动态创建对象,这种方式只有在程序运行时(即相关代码被执行时)才知道需要多少对象、对象的生命周期如何、以及对象的具体类型是什么
- Java 完全采用动态内存分配的方式(基本数据类型除外),当我们创建一个对象时,我们使用 new 操作符来构建一个动态实例
- 对于 non-Java 语言:允许对象在栈上创建,编译器决定对象存在的时间并能自动地销毁对象。若在堆上创建对象,编译器并不知道对象的生命周期,此时由程序员决定销毁对象的时间,所有如果操作不当可能造成内存溢出的问题
- 对于 Java 语言:对象在堆上创建,并使用 Java 的垃圾回收器来解决内存释放的问题
11. 异常处理
- Java 中的异常常常被表示为一个对象,但是异常不是面向对象的特征,异常在面向对象语言出现之前就存在了
12.并发编程
- 并发是指两个及以上的事件发生在一段时间间隔内,且这些事件发生在同一个实体上
- 并行是指两个及以上的事件发生在同一时刻,且这些事件发生在不同实体上
第二章 一切皆是对象
1.用引用操纵对象
- 引用就像遥控器,对象就像电视机,通过操作引用来修改对象的内容
- 引用可以单独存在(即可以不关联任何对象实例),但是此时操作它会返回运行时异常(空指针异常),如要要操作它,需要给它初始化
2.必须有你创建所有对象
- 对象使用 new 操作符来创建,String 类型还可以使用 双引号
2.1 存储到什么地方
RAM:随机访问存储器,也叫主存,是与CPU直接交换数据的内部存储器
有五个地方可以存储数据:
- 寄存器:最快的存储区,位于处理器内部,但是寄存器数量非常有限,而且你不能直接控制
- 堆栈:速度仅次于寄存器,位于RAM中,堆栈指针向下移动,则分配新的内存,指针向上移动,则释放那些内存,Java系统需要知道堆栈中所有项的确切生命周期,这限制了程序的灵活性
- 堆:一块通用的内存池,也位于RAM区,用于存放对象实例,编译器不需要知道存储的数据在堆中可以存活多长时间,当需要一个对象时,先 new 一行代码,然后在执行这行代码的时候,会在堆上自动为该对象分配内存,提高了程序的灵活性,但是堆上进行存储分配和清理的时间可能比在堆栈进行存储需要更多的时间(这种比较的前提是允许在堆栈上创建对象)
- 常量存储:常量永远不会被改变,可以存放在程序代码内部,在嵌入式系统中,存放在ROM中(即只读存储器)
- 非 RAM 存储:
- 流对象:对象被转化为字节流,然后被发往另一台机器
- 持久化对象:对象被存放在磁盘上
2.2 基本类型
- 基本类型的变量直接存储值,而不用 new 为对象,然后用对象存储数据,他们直接置于堆栈,更加灵活高效
- Java 中的基本类型所占存储空间大小固定,具有更好的可移植性
- 但是 boolean 类型所占存储空间大小没有明确指定
- 高精度数字
- BigInteger:支持任意精度整数,包装类,没有对应基本类型,可以作用于 int 操作
- BigDecimal:支持任意精度浮点数,包装类,没有对应基本类型,可以作用于 float 操作
3.永远不需要销毁对象
- Java 有垃圾回收机制,会不定期自动清理不用的垃圾
- 作用域
参考:Java 中变量与对象的作用域- 变量:局部变量和成员变量
- 对象:和变量一样,但是当 s 超过作用域后,s 所指向的对象仍然存在,为了避免不需要使用的对象沾满内存,Java 的垃圾回收器会不定期清理这些不用的对象
- 变量:局部变量和成员变量
4.创建新的数据类型:类
- 类中包含字段和方法:
- 字段:可以是基本类型,也可以是引用类型,若是引用类型则必须初始化,以便让该引用关联一个对象,基本类型字段若没有指定值,就使用默认值
- 字段:可以是基本类型,也可以是引用类型,若是引用类型则必须初始化,以便让该引用关联一个对象,基本类型字段若没有指定值,就使用默认值
5.方法、参数和返回值
- 方法和参数列表合起来称为方法签名,可以唯一地标识出某个方法
- 方法是类的一部分,只能通过对象实例来调用
- 一个字符串中每个字符占两个字节
- return 关键字有两个作用
- 表示“已经做完,离开此方法”
- 返回该方法产生的值(如果是 void 方法,则 return 只用于退出当前方法)
6.构建一个程序
import 关键字:用于导包
static 关键字:同一个类共享static修饰的成员,他们指向同一个存储空间
7.注释和嵌入式文档
- 注释:
- 单行注释://
- 多行注释: /* */
- javadoc :用来提取注释的工具,它输出的是一个html文件,这个过程叫做文档注释,但它只能对 public 或者 protected 修饰的成员进行文档注释
8.编码风格
- 类名首字母大写,若由多个单词组成,则每个单词首字母大写
- 变量名,方法名小写,若由多个单词组成,则除了第一个单词首字母小写,其他单词首字母大写
- 变量命名规则:
- 首字母:英文字母、$和下划线
- 变量名:可以由$、字母、数字和下划线组成
参考:Java 命名规范
第三章 操作符
- 在最底层,Java 的数据是通过操作符来操作的
- 更简单的打印语句
- 使用操作符:
- 操作符作用于操作数,有 + ,- ,* ,/ ,负号(-) ,正号(+) ,= ,== ,!= 等等
- 如果操作符改变了操作数自身的值,就称为“副作用”
- 所有操作符几乎都只能操作基本类型,当然,= ,== ,!= 可以操作所有对象,String 支持 + ,+=
- 优先级:
- 当一个表达式中存在多个操作符的时候,最好用小括号()括起来
- 对于 System.out.println 中的 + 表示连接符(拼接字符串),当然要表示加法可以用小括号括起来
- 赋值:
- 使用 = ,把右边的值赋给左边
- 对一个对象进行操作,我们真正操作的是对象的引用
- 比如把对象引用 d 赋给 c ,即 c = d,那么此时 c 、d 指向同一个对象,操作 c 的同时会同步改变 d,因为他们是同一个引用(即指向同一个对象),这种现象叫做别名现象,举例如下
- 方法中的别名问题
- 算术操作符:
- 加号(+,也叫二元加号)、减号(-,也叫二元减号)、乘号(*)、除号(/)、取模(%)
- 整数除法只取整数部分,会丢弃小数部分,而不是四舍五入
- 取模去余数做结果
- 一元加、减操作符:一元减号(负号)用于转变数据的符号,一元加号只是为了和一元减号对应,它可以将较小类型(short、char、byte等等)的操作数提升为 int 型
- 自动递增和递减
- 自动递增:++,前缀递增(++i)和后缀递增(i++)
- 前缀递增先加 1,再参与表达式计算
- 自动递减同理
- 关系操作符
- 关系操作符生成一个 boolean 类型的结果
- < 、> 、<= 、>= 、== 、!=
- == 、!= 适用于所有基本类型,其他比较符不能用于 boolean 类型,boolean 只有 true 和 false ,没有大于,小于什么的
- == 、!= 使用于对象,用于测试对象的等价性
- 比较对象还可以用 equals() 方法
- 使用 equals() 时,若比较对象所属的类是自定义的,则需要自己去重写 equals() 方法,不重写默认使用 Object 中的 equals() ,即进行引用值的比较
- 逻辑操作符
- 逻辑操作符的结果为 boolean 类型
- 与(&&)、或(||)、 非(!)
- 逻辑与有短路功能,即若左边表达式为 false ,则不会执行右边表达式
- 直接常量(Java默认整型为 int ,浮点型为 double)
- long 类型,常量后面的后缀字符用 大写(或小写)L 表示
- float 类型,常量后面的后缀字符用 大写(或小写)F 表示
- double 类型,常量后面的后缀字符用 大写(或小写)D 表示,可以不写D或d,因为Java 小数默认就是 double
- float f = 1e-43 会报类型转换异常,Java 默认小数使用 double 类型
float f = 1e-43f 这样就不会报错 - 指数计数法,e 表示 10,即 1e-43 表示 1 乘以 10的负 43 次方
- 按位操作符
- 按位与(&)、按位或(|)、异或(^)、非(~,也叫取反)
- 除了非是一元操作符,其他按位操作符都是二元操作符
- 按位运算中 boolean 被看做 1 或 0(大小是 1/8 byte),可以与 &、|、^搭配使用,但不能用 ~,boolean 中的取反用 !
- 按位运算符参与 boolean 的运算就相当于逻辑运算符的作用,且 & 没有短路功能
- 除了~(因为它是一元操作符),其他的按位操作符可以与 = 搭配使用,如 &=
- 移位操作符
- 移位操作符只处理整数类型
- 左移(<<):先转化为对应的二进制,按右边指定的位数 n,将操作数向左移动 n 位,低位补 0
- 有符号 右移(>>):先转化为对应的二进制,按右边指定的位数 n,将操作数向右移动 n 位,若操作数为整数,高位补 0,若为负数,高位补 1
- 无符号 右移(>>>,Java特有,C/C++中没有),使用零扩展,即无论操作数是正是负,高位都补 0
- 如果是对char、short、byte的数值进行移位,需要将他们转换为 int 型再计算,最后结果是 int 型,对于 long 类型最后结果是 long 类型
- 位移运算符可以搭配 = 使用,如 <<=
- 三元操作符
- 相当于 if-else
- 格式:boolean-exp ? exp1 : exp2
- boolean-exp 为 true 时执行 exp1
- 字符串操作符
- = 和 +=
- 作用就是连接不同的字符串
- 如果要实现加法运算可以适当使用小括号
- 类型转换操作符
- 类型转换的原意叫做“模型铸造”
- 将能容纳更多信息的类型转换为无法容纳那么多信息的类型,叫窄(zhai,第三声)化转换,这时需要强制类型转换,如 long lon = 455l; int i = (int) lon;
- 另一种相反的转换叫做扩展转换,不需要强制转换,如 byte b = 12;int i = b;
- float 和 double 转换为 int 时会对该数字进行截尾(即只保留整数部分),如果想要得到舍入的结果需要使用 java.lang.Math 中的 round() 方法
- 表达式中最大类型决定了结果的类型,如 float 与 double 相乘,结果为 double 类型,int 与 long 相加结果为 long 类型,这种现象叫做类型提升
- char short byte 在参与 int 的运算前,底层会自动转换为 int 型 (小的类型参与大的类型的运算时,运算前会先转换为大的类型)
- 除了 boolean ,任何其他基本类型都可以相互转换,但是窄化转换可能会导致数据信息丢失
第四章 控制执行流程
- 条件
- if - else
- if
- if - else if ····
- 循环(迭代)
- for
- while:先判断再循环
- do {}while:先循环再判断,与 while 的区别在于至少会循环一次
- 逗号操作符
- 增强 for 循环(Foreach)
- 适合遍历 Iterable 对象
- 也可以遍历基本类型,但首先得先获得对应的数组,这种方式比使用普通 for 效率低
- return
- 有返回值,返回结果并退出方法
- 返回值类型为 void ,则作用是退出方法
- 流程控制语句
- break :强制退出循环,并不执行后面的语句
- continue :退出当前迭代,重新回到循环的起点,继续下一次迭代
- Java 不支持 goto ,但 goto 是 Java 的一个保留字
- goto 的作用就是若条件成立,则跳转到指定语句(这里),否则跳转到指定语句(那里)
- break 和 continue 的是一种中断迭代语句的方法,而不是实现跳转
- Java 使用 label:来实现跳转特性,标签可以用于循环,条件
- 选择语句 switch
- integral-selector 只能是结果为整型(int)或者枚举型(enum)的表达式
- switch 也支持 short、char、byte 等可以隐式转换为 int 的类型
- jdk 1.8 开始,swtich 也支持 string 类型
- case 后如果没有 break 则会继续执行后面的 case,直到遇到 break 或者执行到最后一行
- 如果 integral-selector 的结果没有相应的 case 与之对应,则执行 default 里的语句,default 也可以加 break(当 default 在最后一行时,加 break 无意义)
- default 可以在任意位置,如果执行 default 且没有 break,则会继续执行后面的 case,直到遇到 break 或者执行到最后一行
第五章 初始化和清理
1.使用构造器确保初始化
- 创建对象时,即 new Rock() 时,将会为对象分配内存空间,并自动调用相应的构造器方法
- 构造器分:无参构造器,有参构造器,这两种函数体现了一种重载的思想
- 无参构造器是默认构造器,当类中没有声明构造器时,类中默认存在一个无参构造器
- 若在类中声明了带参构造器,则该类中的无参构造器不会默认存在,需要你自己显式声明出来
2.方法重载
- 方法名相同,参数列表不同,与返回值类型无关,发生在同一个类中,编译时多态
3.this 关键字
- this 只能在方法内部使用
- 表示调用方法的那个对象的引用,有时可以理解为当前对象的引用
- 如果方法体内调用的是同一个类内的方法,直接调用即可,因为编译器会自动帮你添加 this
- 当构造器调用另一个构造器的时候可以使用 this ,但是一个构造器里只能调用一个构造器,不能调用多个,而且必须将调用的构造器置于最开始的位置,否则会报错
4.static 关键字
- static 方法中不能有非静态成员,但非静态方法中可以有静态成员
- 主要用途:在没有创建任何对象的前提下,使用类名来调用静态方法
- 静态方法中不存在 this 关键字,因为静态方法先执行于对象的创建
5.垃圾回收
- 使用本地方法:是一种在 Java 中调用了非 Java 代码的方式
- 对象可能不被垃圾回收
- 垃圾回收不等同于析构(finalize() 方法在 C++ 中叫做析构函数,用于销毁对象的)
- 垃圾回收只与内存有关,垃圾回收本身也需要开销
- 垃圾回收和终结都并不保证一定会发生,如果 JVM 并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的
- finalize() 方法:finalize() – 玩垃圾的人
- 标记 - 清除算法:速度不快,但当你知道只会产生少量垃圾甚至不产生垃圾时,速度会很快,像老年代,使用标记 - 整理算法,老年代每次垃圾回收只有少量垃圾被回收,而且老年代存储大对象,如果使用复制算法会导致大量内存复制