《thinking in java》引发的思考
关于java面向对象的思考——抽象、并发
thinking in java中1.1抽象过程的描述
问题空间——>解空间的映射(所有问题最终都是列表,所有问题最终都是算法,面向对象)
对象是现实世界的抽象,到解空间中很好表述
难点:如何在问题空间的元素 到 解空间的对象之间创造一一对应的映射
java中的不足——interface、abst?、 (1.必须先定义最高层[或者使用适配器方式] 2.类似的类如何高效的相互复用[比如你发现了一个物种,已经定义出了类,突然发现和它很类似的另一物种,需要添加属性,那是重新定义一个类吗,或者再已知类中添加属性,那后面又发现会重复添加下去,最后变得很复杂 还影响以前的实现?])
go中是如何做的——interface(从类中发现共性再抽象出更高层,现实中我们其实也是这样子的,我们发现了羊、牛等,然后抽象出了动物,而不是先定义动物后发现羊牛。而且最高层可以随意的抽象出任意多个来) struct(包含其它struct)
从面向对象的继承层次关系来看,这种组织结构确实有些奇怪。但是,当你了解了java.util 中更多的有关容器的内容后(特別是第17章中的内容),你就会看到除了继承结构有些奇怪外, 还有更多的问题。容器类库一直以来都是设计难题——解决这些难题涉及到要去满足经常彼此 之间互为牵制的各方面需求。因此你应该学会中庸之道。
一、对象导论
抽象过程:问题空间 -> 解空间 (对象)
每个对象都有一个接口:圆点符号链接一个消息请求
每个对象都是一个服务提供者
二、一切都是对象
所有对象都是通过引用去操作;String s只是创建一个引用,需要你去new创建对象赋值,否则使用它会产生运行时错误。
基本类型作为类成员会自动初始化默认值,但是在函数中定义时会像c++一样任意值。
对象自动释放。
static静态成员,和c++一样所有对象共享一个。
文件中必须有一个和文件名相同的类。
三 四、 操作法与控制流程
equals默认是引用比较,自己写的类需要覆盖该函数
基础类型自动向上转换,向下需要手动,否则编译器报错
五、初始化与清理
构造函数(没有创建,则编译器会为你创建一个默认无参的构造函数,如果有创建构造函数,则编译器不会再创建默认构造函数。构造函数其实是隐式的static)
方法重载(同名不同参,重载构造函数)
this作为非静态函数的第一个参数传入(构造函数中调用其他构造函数: this(arg1,arg2) 非构造函数中不能这么用)
自适应垃圾回收:标记清扫(碎片) + 停止复制(耗时)。自己需要特殊清理用finalize。
- 静态块初始化/实例初始化 :构造器之前执行(1.直接初始化 2.块初始化 3.构造函数)。
- 数组和对象一样,int[] a只是定义一个引用,需要a=new int[10]来初始化,此时数组元素是没有初始化的,对象数组也一样。
六、访问权限控制
1.public 2.protected(同一包内都可以访问) 3.private 4.不写:默认包访问权限(friendly)
1.让使用者更轻松 不用去关心那些private,2.封装变化,重新实现后,使用者不用更改
七、复用类
组合、继承(只有单继承extends)、代理
继承:is-a,组合:has-a
final成员:可以在构造函数及之前初始化(定义处、块、构造函数)
final参数:确保方法中不会去改该参数。
final方法:确保导出类不去改变该方法的实现,不会被覆盖。
final类:处于安全或设计考虑,不允许有子类
Vector->ArrayList, HashTable->HashMap使用后者,设计得更好
八、多态
多态:分离做什么和怎么做。将改变的事物和未变的事物分离开(例如:插件架构)
封装:通过合并特性和行为来创建新的数据类型
实现隐藏:通过将细节私有化,把接口和实现分离
【除了static和final方法(private也是final)都是默认动态绑定】
- final(private)无法覆盖,即无法多态,static是与类关联而非对象,也无法多态。
- 不要在构造器中调用方法,如果此方法被子类重载,创建一个子类对象时,会先调用基类的构造器,基类构造器中会调用子类重载后的方法,此时子类的构造器还没有执行,数据未初始化,如果再改方法中访问了数据成员,会造成bug。唯一能安全调用的是final(private)方法。
- 子类重载函数可返回协变类型(即子类重载函数的返回类型可以是基类返回类型的子类)。
- 继承与组合的选择:用继承表达行为间的差异,用字段表达状态的变化。
九、接口
- interface中的字段都是static & final & public,方法默认且必须是public。interface可以继承其他interface,接口继承可以是多继承。
- implements后可以跟多个interface,使用接口的核心原因:能够向上转型为多个基类。implements必须在extends之后,否则编译会报错。
- “确定接口是理想选择,因而应该总是选择接口而不是具体的类。”这其实是一种诱惑:
十、内部类
成员内部类(类似类成员)、局部内部类(类似局部变量)、匿名内部类(常用于事件回调函数)、静态内部类(类似类静态成员)
TODO
十一、持有对象 TODO11.7-11.11(具体每个容器用的时候看)
1. Collection
2. Map
3.Iterator:统一了不同容器元素的访问方式
- foreach与iterable
十二、异常
- RuntimeException可以不用捕获,而且运行过程中,java虚拟机也会抛出。
- 继承中,子类覆盖方法只能抛出基类方法中声明的那些异常(少于等于),以确保使用基类的地方能正常使用派生类。
- 构造器子类可以抛出任何异常,不受基类限制。
异常处理的重要原则:只有在你知道如何处理的时候才捕获异常。
异常处理的一个目的是:错误处理的代码同错误发生的地点相分离。
- 可用用RuntimeException来包装异常,来避免异常声明。
十三、字符串
- String对象是不可变的,所有修改操作都会生成一个全新的String对象,所以多个字符串相加会生成多个临时对象浪费性能(简单语法的编译器会用StringBuilder优化,但像for循环中是不行的)。
- StringBuilder是后引入的操作字符串,StringBuffer是线程安全的但是更费性能。
- javap -c 类名 在编译好的classes目录执行,生成JVM字节码来分析你的代码。
TODO13.6 正则表达式
十四、类型信息
Class.forName("Name") 加载类,
基本类型包装类有TYPE字段等价与基本类型的class:int.class == Integer.TYPE
泛化的class引用(主要是为了添加编译器类型检测):Class<Number> Class<?> Class<? extends Number>
list装多种类型:List< Class<? extends Number> >
instanceof, Class.isInstance(), Class.isAssignableFrom()
反射与RTTI的区别在于:反射.class文件在运行时才知道。
【14.6.1制作工具,22.GUI版本】使用反射获取某类的所有方法,而无需在jdk文档中翻阅繁杂的继承树。
可以利用反射调用私有类/内部类/私有方法等等(14.9)。
动态代理:写代理类时,不要要把每个代理函数都写一遍;通过反射实现不同类型的通用代理。
空对象:避免到处都是null判断。
总结:凡是可以使用多态的地方都应该使用多态,只有在必要时才使用RTTI或switch。
十五、泛型
编译器为你转型操作,并负责类型的正确性。
泛型接口/类,泛型方法,在能使用泛型的地方尽量使用泛型(分离类型和逻辑)
static方法不能访问泛型类的类型参数,所以要使它有泛型能力只能用泛型方法。
类型擦除:(new HashMap<String,String>()).getClass() 与 (new HashMap<Integer,String>()).getClass()相等;擦除会移除类型参数的信息,那么使用反射时都是拿不到这些信息的,如执行 arg instanceof T; new T()都会报错,解决办法有构造器传入Class参数/其他接口方法等。
泛型边界:class Generator<T extends String>{} 这样在类方法中T才可以调用String的方法。
TODO15.9~
十六、数组
十七、容器深入研究
十八、I/O系统
十九、枚举
编译器创建的enum类都是继承自Enum类。
常量、switch、添加新方法、覆盖枚举方法、实现接口、使用接口组织/分类枚举、枚举集合EnumSet/EnumMap、枚举常量各自的方法。
TODO 19.10~19.11
二十、注解
三种标准注解,四种元注解。
嵌套注解,注解不能继承。
TODO 20.3~
二十一、并发