前言
这是学习java编程思想的读书笔记
简介
相比c++,java加入了虚拟机和垃圾回收机制
要熟悉语言为什么这么设计
一.对象的概念
抽象
将实际问题抽象为计算机运行的程序
oop可以让我们根据问题来写程序,而不是根据计算机.把一个个问题参与者抽象成对象
oop特征:
1.万物皆对象
2.程序是一组对象,通过消息传递来知道自己要做什么
3.每个对象可以接受相同的消息
4.每个对象有自己的存储空间
接口
(每个对象可以接受相同的消息),接受什么样的消息由类给出的'接口'来定义
服务提供
把问题一一分解,抽象成一组服务
良好的面向对象设计中,每个对象功能单一且高效
封装
程序员分为两类,造轮子的和用轮子的.
造轮子的要隐藏轮子的具体实现细节,这就需要用到访问控制(作用.1,防止用轮子程序员乱用轮子,2,更方便后续的升级)
protected 类似 private. 但是子类可以访问protected成员,并且protected提供包访问权限
default 包访问
复用
一个类应该是可复用的
一个对象使用其他对象,两种关系.组合,聚合
组合表示拥有(生命周期相同),聚合表示可以灵活使用(生命周期不一定相同)
设计一个新类时,应该优先考虑组合
继承
继承的子类,拥有父类的全部成员和方法(尽管private成员无法访问),更重要的是,能够接受相同的消息
当对象接受到特定消息时,必须有可执行方法
区别父类和子类.1.添加新方法 2.重载
判断是否有继承 看有没有 is a 的关系. 圆是形状
多态
通常把一个对象看成是它所属的基类,而不是具体类.这种把子类当成基类来处理的过程叫做"向上转型".
程序不知道接受的具体类型是什么,最后结果依然是正确的的,这个性质叫做对象的多态性.oop是通过动态绑定来实现多态性的.
早期绑定
后期绑定(动态绑定):被调用的代码知道运行时才被确定
单继承
单继承结构使得垃圾收集器更容易设计
集合
集合(又称容器),可以根据需要自动扩容
集合中的参数化类型称为"泛型"
对象的生命周期
c++数据放在栈区,堆区,用户数据区.栈区和用户数据区的变量生命周期由编译器决定,其中堆区的数据由动态创立,用完后要手动清理.
java的对象仅仅在堆区创建对象.垃圾收集器能够自动发现对象不再使用并释放内存
异常处理
对于错误处理,之前的语言采用了如返回值,标志位来表示错误产生了.(容易被忽略)
而java使用了异常,异常不可被忽略,平时也不影响程序的运行,并提供了错误恢复机制
万物皆对象
对象操纵,创建
java通过引用来操纵对象
数据存储:
引用放在栈里面,对象放在堆里面. 序列化对象和持久化对象放在外存里面
基本类型和c++一样,放在栈里面.并且基本变量的内存都是确定好的,这使得java移植性好. 在堆中表示基本数据可以用包装类
BigInteger和BigDecimal可以进行高精度运算,如货币计算
数组: 使用之前要初始化,并且会检查范围
对象数组是引用数组,全部初始化为null.基本类型则初始化为0
堆栈区别
堆与栈实际上是操作系统对进程占用的内存空间的两种管理方式,主要有如下几种区别: (1)管理方式不同。栈由操作系统自动分配释放,无需我们手动控制;堆的申请和释放工作由程序员控制,容易产生内存泄漏;
(2)空间大小不同。每个进程拥有的栈的大小要远远小于堆的大小。理论上,程序员可申请的堆大小为虚拟内存的大小,进程栈的大小 64bits 的 Windows 默认 1MB,64bits 的 Linux 默认 10MB;
(3)生长方向不同。堆的生长方向向上,内存地址由低到高;栈的生长方向向下,内存地址由高到低。
(4)分配方式不同。堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是由操作系统完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由操作系统进行释放,无需我们手工实现。
(5)分配效率不同。栈由操作系统自动分配,会在硬件层级对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是由C/C++提供的库函数或运算符来完成申请与管理,实现机制较为复杂,频繁的内存申请容易产生内存碎片。显然,堆的效率比栈要低得多。
(6)存放内容不同。栈存放的内容,函数返回地址、相关参数、局部变量和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存,需要使用栈来实现,首先入栈的是主函数下一条语句的地址,即扩展指针寄存器的内容(EIP),然后是当前栈帧的底部地址,即扩展基址指针寄存器内容(EBP),再然后是被调函数的实参等,一般情况下是按照从右向左的顺序入栈,之后是被调函数的局部变量,注意静态变量是存放在数据段或者BSS段,是不入栈的。出栈的顺序正好相反,最终栈顶指向主函数下一条语句的地址,主程序又从该地址开始执行。堆,一般情况堆顶使用一个字节的空间来存放堆的大小,而堆中具体存放内容是由程序员来填充的
生命周期
作用域:
java作用域由{}决定,并且不允许将一个大的作用域"隐藏"起来的做法
基本类型的生命周期不超过作用域,而对象的生命周期可以超过作用域,只要有需要,new出来的对象会一直存活下去
GC:java垃圾回收期会检查所有new出来的对象并判断哪些不可达,并释放内存
类的创建
方法,字段 method field
字段基本类型有默认值,但是方法中的基本类型没有默认值,程序员必须给变量初始化,否则程序会报错
方法名和参数列表统称为方法签名
程序编写
命名可见性:
c程序很难管理名称海洋; c++除了用类嵌套了函数之外,还保留了全局函数,用命名空间解决了全局函数冲突的问题 ;java用反转域名来命名;另外,java没有c里面的前向引用问题
static
以下情况要使用static.
1,创立一个共享存储空间
2,创建一个和对象无关的方法
对象和类都可以引用static修饰的东西.静态方法不依赖任何对象,因此不能使用非静态的成员和方法
初始化与清理
没有初始化是很多c程序bug的原因,c++没有清理会出现内存泄露
构造器保证初始化
方法重载
利用重载,可以用一个命名来定义一个概念.
构造器的重载,可以让人以不同的方式创建对象
区分重载方法:参数的个数或者顺序
无参构造器
当没有写构造器时,编译器会自动创建一个无参构造器
当写了构造器后,编译器不会自动创建一个无参构造器
this关键字
当return this的时候,可以在一个对象上进行多次操作
可以用this把自身传递出去
用this防止参数和成员名混淆,做区分
this可以在构造器中调用其他构造器
static方法中不能有this
垃圾回收器
finalize()函数:在对象被回收之前,可以在这个函数做点事情
不同于析构函数,析构函数是程序员主动回收对象,时间由程序员决定 而finalize是表示在回收之前可以做的一些工作,回收时间由gc决定
垃圾回收器工作机制:
1,引用计数器(循环引用问题)
2,实际运用方法:
遍历栈中所有的引用,找到对象后再遍历对象中的引用,反复进行,直到找到所有"活着"的对象
找到所有活着的对象后,"停止-复制"(此时程序会暂停,gc优先级比较低,只有内存较低的时候才会启动).但是没有新垃圾产生的话,就会转换到另一个模式,"标记-清理" "标记清理"后是不连续的空间,想要连续的空间还需要再整理
jit技术把字节码翻译成机器码
Hotspot,编译运行过的程序,执行次数越多,就越快
成员初始化
成员会在构造器之前被初始化
先顺序初始化static,再顺序初始化其他成员
静态代码块,非静态代码块
构造器初始化
数组初始化
数组初始化,实际上是初始化了一个数组对象,并有边界访问控制.不同于c,数组的大小是在运行时确定的.除了通过对象初始化数组,也可以用花括号初始化数组,且最后一个逗号是可选的
可变参数列表:编译器把参数实际上转换成了一个数组.如果传入的就是一个数组 那么编译器就会把这个数组里的元素看成可变参数列表.可变参数可能让重载变得困难 比如f()这种没有参数的函数应该调用谁呢?可以在可变参数列表中加一个不可变参数来解决这个问题
枚举类型
枚举类型事实上是一个类,并且有自己的方法.如toString,ordinal,values .枚举常用于Switch结构
封装
包
包提供了一种命名空间的管理机制
一个java文件就是一个编译单元,一个java文件里面只能有一个public类,包外无法访问除了public的其他类,编译一个java文件,每个类得到一个同名的.class文件 java的可运行程序是一组.class文件.它们可以打包生成一个jar文件.java通过classpath和通过包名形成的文件层次来查找需要的类
利用import可以实现类似c中的条件编译来进行调试
访问权限修饰符
四个修饰符.可以修饰类,方法,成员.效果是类似的(但是private,protected不能修饰类)
对于private,常用的default一般就够了(包访问.private在多线程中非常重要
接口与实现
实现就是接口的实现方式,把数据和接口的实现方式隐藏起来叫做封装
如果不想让外界访问到这个类,可以把这个类的构造器设为private,这样这有在本地的方法中能够访问到这个类(如static main)
复用
组合语法
继承语法
对于继承,子类的对象里面包含了基类的对象.因此创建子类对象时,基类对象必须进行正确的初始化.对于基类的无参构造器,不需要使用super进行显式初始化,而有参构造器则需要
委托
java并不直接支持委托.委托就是转发方法.建立一个委托对象,把本对象的方法转发到委托对象的方法.
结合组合与继承
区分重载和重写.另外@Override注解可以有助于区分这两个
当要开发一个新版本的类时(为了特殊需求使其特殊化),使用继承
protect
向上转型
final
final 基本类型,就是常量
final对象引用,是指这个对象的引用不能指向别的对象,但是对象本身是可以修改的,java没有能让对象变为常量的方法
常用的常量定义方法.static final.static代表只有一个, final代表是常量.常量全部大写
某个数据被final修饰,不代表编译时候就能知道它的值,事实上,都是运行时被初始化后才知道的值的.
空白final:对于在成员变量处未被初始化的空白final,必须要在构造器处进行初始化,否则会报错
参数final:表示方法中不能改变参数指向的对象或者基本变量(主要用于传递数据给匿名内部类)
final方法:1,防止子类继承改变方法的行为 2,jvm早期为了性能原因(现在不用了)
final类: 意味着这个类不能被继承
类初始化与加载
多态
理解
可以提高扩展性
向下转型与运行时类型信息RTTI
每次转型都会被检查,如果转型失败,编译器会抛出异常
接口
接口和抽象类提供了一种将接口和实现分离的更加结构化的方法
抽象类和方法
abstract关键词
创建一个抽象类,是为了表示接口,而不是实现,所以它的对象是毫无意义的,抽象类也创建不了对象.创建这个通用接口唯一理由是 不同的子类可以用不同的方式实现此接口.
包含一个或者多个抽象方法的类叫做抽象类.当一个类继承抽象类时有两种选择.1,不实现所有的抽象方法,这是这个类就会成为一个新的抽象类 2,实现所有方法,这个成为一个正常类
abstract方法不能用private修饰.可以用另外三种修饰符修饰
抽象类的作用:目的明确,可以用来作为重构的工具
接口创建
接口不需要abstract关键词修饰.interface关键字相当于产生了一个完全抽象的类.接口中所有方法都默认为public
java 8中,接口允许包含默认方法(default方法,接口方法的默认实现)和静态方法(可以实现一些工具方法)
使用默认方法的理由是,可以允许在不破坏已有接口的代码的情况下,在接口中增加新的方法
多继承问题:由于default方法的存在,java看起来有了C++的多继承问题了.但是interface里面没有成员,所以不存在"状态"的多继承
使用多个接口时可能会出现多个接口的方法签名重复的问题,这个时候可以使用super关键词来解决
抽象类和接口
尽量多使用接口而不是抽象类
接口 | 抽象类 |
---|---|
可以实现多个接口 | 只能实现继承一个抽象类 |
没有对象状态 | 有属性,非抽象方法可能会引用到成员属性 |
无构造器 | 可以有构造器 |
全是public | 有修饰词来修饰 |
完全解耦
多接口结合
使用继承扩展接口
接口之间可以通过extend关键字来扩展接口.一个接口可以extend多个接口
多个接口可能导致方法签名相同的问题
接口适配
一个接受接口类型的方法提供了一种让任意类型都可以与该方法进行适配的方式
接口字段
接口里面可以放字段(field).这些字段是自动static final的,因此,java 5没有枚举之前,用interface来代替枚举
接口嵌套
在类的内部和接口的内部也可以创建接口,类似内部类.不过接口自带public属性,除非此接口使用private修饰
接口和工厂方法模式
内部类
一种隐藏机制.内部类了解外围类并能和它通信.lambda表达式减少了内部类的编写需求
创建内部类
在外围类中,使用内部类和使用其他正常的类没有太大的区别.
常在外部类中创建一个方法,返回内部类的对象
在其他类中,使用内部类需要用:外围类.内部类方式来使用
链接外部类
内部类有外围类所有元素的访问权,就像自己拥有它们似的
在编译器内部,外围类创建内部类时,内部类会自动捕获外围类的引用,利用外围类的引用来访问外围类的成员(这一切都是自动的,从程序中看不到外围类的引用,因为编译器已经帮程序员处理好了)
内部类的this和new
在内部类中返回外部类的引用只能用 "外部类名字.this"
在静态方法或者是其它类的方法中创建内部类的对象,只能使用 "外部类对象.new" 来创建内部类,因为内部类时依附在外围类上的.没有外围类对象就没有内部类对象,这种方式可以保证内部类对象连接到外围类对象. 静态的内部类不需要这样
内部类向上转型
没看懂
内部类的方法和作用域
在方法里面也可以定义内部类,在任意作用域里都可以嵌入一个内部类(在这些地方定义的类,作用域和变量一样(这里的作用域是指能用这个类创建对象的地方))
匿名内部类
有些复杂,没看太懂
其实是继承的简要写法.但是相比起继承有一些不一样.没有构造器,只能用成员初始化来初始化,并且智能实现一个接口
嵌套类
static内部类.不依靠外围对象而存在,也不能访问外部对象.
普通内部类不能含有static成员,因为普通内部类是依赖外围类对象而存在的
为什么要内部类
解决多继承问题.有内部类可以更加灵活.实现闭包(java 8中可以用lambda表达式来实现)
继承内部类
内部类局部变量
内部类标识符
集合
集合无法装载基本类型,但自动装箱机制会执行转换
队列和栈的行为都是通过Linkedlist提供的
不要在代码中使用遗留类:Vector,Hashtable,Stack
泛型和类型安全的集合
基本概念
两个概念: 集合(collection) 映射(map)
collection: linkedList ArrayList HashSet TreeSet linkedHashSet(List 和 Set都是接口,具体实现由前面五个实现,collection也是接口)
List存储按顺序来,HashSet存储不按照顺序,TreeSet按照升序存储,LinkedHashSet按照顺序存储
map: HashMap TreeMap LinkedHashMap(Map也是接口)
,HashMap存储不按照顺序,TreeMap按照升序存储(红黑树维护顺序),LinkedHashMap按照顺序存储(使用了散列函数,但是用链表维护了顺序)
添加元素组
集合的打印
列表list
list中各种方法的应用.equals是判断两个对象引用是否相同的方法,equals是objects类里面的一个方法.
迭代器iterators
对于集合的使用(访问集合内的元素),只有知道确切的集合类型才能对集合进行操作.这样造成了一个问题,代码的通用性变差.迭代器可以解决这个问题,因为每个集合都实现了iterable接口,所以都可以用迭代器来访问其中的元素.迭代器统一了对集合的访问方式
迭代器是个轻量的对象,创建它的代价很小,java众多Iterator只能单向移动.有next hasNext remove方法
ListIterator更加强大,只能由各种list生成,功能略
链表linkedlist
Linkedlist的方法使用
stack
jdk 1中的stack设计很差,所以用Deque来代替.可以用泛型和委托把Deque换成Stack.类型参数会在使用类时被实际类型替换
set
set常用HashSet来实现,用来查找.用contain测试成员的归属性
map
queue
Queue可以用Linkedlist实现,util中的Queue本身是一个接口,所以不能用Queue来创建对象
PriorityQueue优先级队列.插入一个元素后,对队列里面的元素进行排序.对于Integer,String,Character来说,优先队列自带排序方法.对于其他类,要提供自己的comparator方法才行
集合和迭代器
集合都实现了迭代器接口.
for in和迭代器
for-in语法可以遍历集合.实际上,任何实现了迭代器接口的类都可以用for in遍历(有个特例是数组,数组没有实现迭代器接口,但可以用for in遍历)