effective java
学习别人的:
第2章 阐述何时以及如何创建对象,何时以及如何避免创建对象,如何确保它们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清除动作。
第1条 考虑用静态工厂方法代替构造器
- 静态工厂方法有名称
- 不必在每次调用它们的时候都创建一个新对象.
- 可以返回原类型的子类型对象.静态工厂方法还有一个好处,就是可以返回子类型,比如我们有一个Car类,其中有一个关键的参数叫做price,我希望price>30万的返回豪车子类,price<30万的返回买菜车子类
第2条 遇到多个构造器参数时要考虑用Builder
-
因为构造过程被分到了几次调用中,所以在构造过程中可能会处于不一致的状态。类无法仅仅通过检验构造器参数的有效性来保证一致性。[注]对于这句话,我个人的理解如下.比如上面的用户类,我们规定如果职位为Java开发工程师,则性别一定是男性。但是由于
setter
方法是分成两部进行设置值,而且设置值的顺序也是不一致的,所以就无法使两个参数同时满足上面的要求。 -
Builder模式的优势: 可读性增强; 可以有多个可变参数; 易于做参数检查和构造约束检查; 比JavaBeans更加安全; 灵活性: 可以利用单个builder构建多个对象, 可以自动填充某些域, 比如自增序列号.
Builder模式的不足: 为了创建对象必须先创建Builder, 在某些十分注重性能的情况下, 可能就成了问题; Builder模式较冗长, 因此只有参数很多时才使用。
第3条 用私有构造器或者枚举类型强化Singleton属性
- 从Java 1.5起, 可以使用枚举来实现单例: 只需要编写一个包含单个元素的枚举类型. 这种方法无偿地提供了序列化机制, 绝对防止多次实例化.
第6条 避免创建不必要的对象
- String s = new String(“bikini”); // Don’t do this
- 这段程勋的答案是正确的,但是比实际情况更慢一些,只因为打错了一个字符,变量sum被声明称Long而不是long,以为着程序构造了大约2^31个多余的Long实例。将sum上午声明从Long变成long,在我机器上运行的时间从8.952秒减少到了0.821秒。结论很明显,要优先考虑基本类型而不是装箱基本类型,要当心无意识地装箱。(4)对象重用在数据库连接池中非常有意义。
JSON是怎么做的?fastjson为什么有很多漏洞?
- AutoType 何方神圣?fastjson的主要功能就是将Java Bean序列化成JSON字符串,这样得到字符串之后就可以通过数据库等方式进行持久化了。但是,fastjson在序列化以及反序列化的过程中并没有使用Java自带的序列化机制,而是自定义了一套机制。其实,对于JSON框架来说,想要把一个Java对象转换成字符串,可以有两种选择:1、基于属性2、基于setter/getter
- 而我们所常用的JSON序列化框架中,FastJson和jackson在把对象序列化成json字符串的时候,是通过遍历出该类中的所有getter方法进行的。Gson并不是这么做的,他是通过反射遍历该类中的所有属性,并把其值序列化成json。
- 当一个类中包含了一个接口(或抽象类)的时候,在使用fastjson进行序列化的时候,会将子类型抹去,只保留接口(抽象类)的类型,使得反序列化时无法拿到原始类型。
- 在之后的几个版本中,黑客的主要的攻击方式就是绕过黑名单了,而fastjson也在不断的完善自己的黑名单。
- 因为fastjson自己定义了序列化工具类,并且使用asm技术避免反射、使用缓存、并且做了很多算法优化等方式,大大提升了序列化及反序列化的效率。
-
自己总结:
字符串
- m.put ( new PhoneNumbe(707,867,5309),“jenny”),此时是根据hash值来进行存储到散列桶的,因此需要重写hashCode的方法。
- 在实际应用中, toString 方法应该返回对象中包含的所有值得关注的信息,、
- 事实上,实现Cloneable 接口的类是为了提供一个功能适当的公有的cl 。ne 方法。为了达到这个目的,类及其所有超类都必须遵守一个相当复杂的、 不可实施的,并且基本上没有文档说明的协议。由此得到一种语言之外的( extralinguistic)机制:它无须调用构造器就可以创建对象。
- 因为Java 支持协变返回类型( covariant return type ) 。换句话说,目前覆盖方法的返回类型可以是被覆盖方法的返回类型的子类了。、
- 这是个根本的问题: 就像序列化一样, Cloneable 架构与引用可变对象的final 域的正常用法是不相兼容的,
类和接口
- 为了测试而将一个公有类的私有成员变成包级私有的,这还可以接受, 但是要将访问级别提高到超过它,这就无法接受了。
- 不可变对象
- 没有任何线程会注 意到其他线程对于不可变对象的影响。 所以, 不可变对象可以被自由地共享。
- 在设计新的类时,选择用静态工厂代替公有的构造器可以让你以后有添加缓存的灵活性,而不必影响客户端。
- 不仅可以共享不可变对象,甚至也可以共享它们的内部信息。
- 不可变的类变成 final 的另一种办法就是,让类的所有构造器都变成私有的或者包级私 有的,并添加公有的静态工厂( static factory)来代替公有的构造器
- 好的 API 文档应该描述一个给定的方法做了什么工作,而不是描述它是如何做到的。
泛型
-
泛型有子类型化( subtyping )的规则,List<String >是原生态类型List 的一个子类型,而不是参数化类型List
-
如果要使用泛型,但不确定或者不关心实际的类型参数,就可以用一个问号代替。例如,泛型Set<E >的无限制通配符类型为Set <?>(读作“某个类型的集合”) 。
-
无限制通配类型Set <?>和原生态类型Set 之间有什么区别呢?这个问号真正起到作用了吗?这一点不需要赘述,但通配符类型是安全的,原生态类型则不安全。由于可以将任何元素放进使用原生态类型的集合中,因此很容易破坏该集合的类型约束条件(如之前范例中所示的u 口saf eAdd 方法);但不能将任何元素(除了null 之外)放到c。llection < ?>中。如果尝试这么做,将会产生一条像这样的编译时错误消息:error: incompatibl e types: String can not be converted to CAP
-
必须在类文字( class l i te ra l ) 中使用原生态类型。规范不允许使用参数化类型(虽然允许数组类型和基本类型)[ JLS, 15.8.2 ] 。换句话说, List.class 、String.class 和int.class 都合法,但是List<String .class 和List<?>.class 则不合法。
-
由于泛型信息可以在运行时被擦除,因此在参数化类型而非无限制通配柯:类型上使用instanceof 操作符是非法的
-
II Legitima t e use of raw type - instanceof operator if (o i nstanceof Set) { II Raw t ype Set<?> s = (Set<?>) o; I I Wi l dca 「d type 注意,一旦确定这个o 是个Set ,就必须将它转换成通配符类型Set <?>,而不是转 换成原生态类型Set 。这是个受检的( checked )转换,因此不会导致编译时警告。
- 原生态类型只是为了与引人泛型之前的遗留代码进行兼容和互用而提供的。让我们做个快速的回顾:Set<Object >是个参数化类型,表示可以包含任何对象类型的一个集合; Set <?>则是一个通配符类型,表示只能包含某种未知对象类型的一个集合; Set 是一个原生态类型,它脱离了泛型系统。前两种是安全的,最后一种不安全。
- 如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下)才可以用一个@S uppre ss Warnings ( “ unchecked“ )注解来禁止这条警告。
- 这其中无论哪一种方法,都不能将String 放进Long 容器中,但是利用数组,你会在运行时才发现所犯的错误;而利用列表,则可以在编译时就发现错误。我们当然希望在编译时就发现错误。
方法
- 说概要描述是文档注释中的第一个句子( sentence ),这似乎有点误导人。规范指出,概要描述很少是个完整的句子。对于方法和构造器而言,概要描述应该是个完整的动词短语(包含任何对象),它描述了该方法所执行的动作。对于类、接口和域,概要描述应该是一个名词短语,它描述了该类或者接口的实例,或者域本身所代表的事物。
- API 有两个特征在文档中经常被忽视,即线程安全性和可序列化性。类或者静态方法是否线程安全,应该在文档中对它的线程安全级别进行说明。
通用编程
- 将局部变量的作用域最小化。要使局部变量的作用域最小化,最有力的方法就是在第一次要使用它的地方进行声明。
- 当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱。
- 基本类型要优先于装箱基本类型。基本类型更加简单,也更加快速。如果必须使用装箱基本类型,要特别小心! 自动装箱减少了使用装箱基本类型的烦琐性,但是并没有减少它的风险。
- 字符串不适合代替其他的值类型。
- 口损失了编译时类型检查的优势,包括异常检查。如果程序企图用反射方式调用不存在的或者不可访问的方法在运行时它将会失败,除非采取了特别的预防措施。
- Java Native Interface (刑I) 允许Java 应用程序调用本地方法( native method ),所谓本地方法是指用本地编程语言(比如C 或者C++)来编写的方法。它们提供了“访问特定于平台的机制”的能力,比如l访问注册表( registry )
- 当你确定类是私有的或包私有的 并且equals方法不被调用时 无需覆盖equals。
相关知识
简单工厂(静态工厂)
定义:由一个工厂对象决定创建出哪一种产品类的实例
类型:创建型,不属于GOF23种设计模式
适用场景:
- 工厂类负责创建的对象比较少
- 客户端(应用层)只知道传入工厂类的参数,对于如何创建对象(逻辑)并不关心
优点:
- 只需要传入一个正确的参数,就可以获取你所需要的对象而不需要知道其创建的细节
缺点:
-
如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下)才可以用一个@S uppre ss Warnings ( “ unchecked “ )注解来禁止这条警告。
-
这其中无论哪一种方法,都不能将String 放进Long 容器中,但是利用数组,你会在运行时才发现所犯的错误;而利用列表,则可以在编译时就发现错误。我们当然希望在编译时就发现错误。
-
数组是具体化的( reified) [ JLS , 4.7 ] 。因此数组会在运行时知道和强化它们的元素类型。如上所述,如果企图将String 保存到Lo呵数组中,就会得到一个ArrayStoreExcept 工on 异常。相比之下,泛型则是通过擦除( erasure ) [ JLS, 4.6 ] 来实现的。这意味着,泛型只在编译时强化它们的类型信息,并在运行时丢弃(或者擦除)它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码随意进行互用(详见第26 条),以确保在Java 5 中平滑过渡到泛型。
-
不可具体化的( non-reifiable )类型是指其运行时表示法包含的信息比它的编译时表示法包含的信息更少的类型。唯一可具体化的( reifiable )参数化类型是无限制的通配符类型,如List <?>和Map<?,?>
-
Favorites 实例是类型安全( typ巳safe )的: 当你向它请求String 的时候, 它从来不会返回一个Integer 给你。同时它也是异构的( heterogen eou s ) : 不像普通的映射,它的所有键都是不同类型的。因此,我们将Favorites 称作类型安全的异构容器( typ esafe heterogeneous contain巳r ) 。
-
cast 方法是Jav a 的转换操作符的动态模拟。它只检验它的参数是否为Class 对象所表示的类型的实例。如果是,就返回参数;否则就抛出ClassCastException 异.
枚举
-
枚举天生就是不可变的,因此所有的域都应该为final 的(详见第17 条) 。它们可以是公有的,但最好将它们做成私有的,并提供公有的访问方法(详见第16 条) 。
-
于是每个枚举常量都带有一组隐藏的行为,这使得枚举类型的类或者所在的包能够运作得很好,像其他的类一样,除非要将枚举方法导出至它的客户端,否则都应该声明为私有的,或者声明为包级私有的
-
因为枚举类型中的抽象方法必须被它的所有常量中的具体方法所覆盖。
-
我们真正想要的就是每当添加一个枚举常量时,就强和!选择一种加班报酬策略。幸运的是,有一种很好的方法可以实现这一点。这种想法就是将加班工资计算移到一个私有的嵌套枚举中,将这个策略枚举( strategy enum )的实例传到Payroll Day 枚举的构造器中。之后Payroll Day 枚举将加班工资计算委托给策略枚举, Payroll Day 中就不需要switch语句或者特定于常量的方法实现了。
-
P 「ivate static <T extends Enum
& Operation> void test(Class opEnumType , do ubl e x, double Y] opEηum Type 参数中公认很复杂的声明(<T extendsEnum & Operation> Class<T >)确保了Class 对象既表示枚举又表示Operation的子类型,这正是遍历元素和执行与每个元素相关联的操作时所需要的。 lambda 和 stream
-
基础接口作用于对象引用类型。Operator 接口代表其结果与参数类型一致的函数。Predicate 接口代表带有一个参数并返回一个boolean 的函数。Fun ct 工on 接口代表其参数与返回的类型不一致的函数。Suppl 工er 接口代表没有参数并且返回(或“提供”)一个值的函数。最后, Consumer 代表的是带有一个函数但不返回任何值的函数,相当于消费掉了其参数。
方法
-
采用了新的构造器和新的访问方法之后, Period 真正是不可变的了。不管程序员是多么恶意,或者多么不合格,都绝对不会违反“周期的起始时间不能晚于结束时间”这个约束条件。确实如此,因为除了Period 类自身之外,其他任何类都无法访问Period 实例中的任何一个可变域。这些域被真正封装在对象的内部。
-
在Java 8 中,还有第三种方法可以编写不能返回值的方法。Optinal<T >类代表的是一个不可变的容器,它可以存放单个非null 的T 引用,或者什么内容都没有。不包含任何内容的optional 称为空( empty ) 。非空的optio nal 中的值称作存在( present ) 。optional 本质上是一个不可变的集合,最多只能存放一个元素。Optio 口al<T >没有实现Collection
接口,但原则上是可以的。 -
我们已经讨论了返回optional ,以及返回之后对它们的处理方法。之所以还没有讨论到其他可能的用途,是因为optional 的大部分其他用途都还受到质疑。
-
尽量不要将optional 用作返回值以外的任何其他用途。
-
Javadoc 工具会把文档注释翻译成HTML ,文档注释中包含的任意HTML 元素都会出现在结果HTML 文档中。有时程序员还会把HTML 表格嵌入到它们的文档注释中,但是这种做法并不多见。 207页
通用方法
- 第一个缺点是,如果n 是一个比较小的2 的乘方,经过一段相当短的周期之后,它产生的随机数序列将会重复。第二个缺点是,如果n 不是2 的乘方,那么平均起来,有些数会比其他的数出现得更为频繁。如果n 比较大,这个缺点就会非常明显。
为什么
- 使用标准类库的第三个好处是, 它们的性能往往会随着时间的推移而不断提高,无须你做任何努力
- 每个程序员都应该熟悉j ava. lang 、j ava.util 、j ava.io 及其子包中的内容。
- ollections Framework (集合框架)和Stream 类库(详见第45条至第48 条)应该成为每一位程序员基本工具箱中的一部分,同样也应该成为j ava.util.、concurrent 中并发机制的组成部分。
- 如果你在Java 类库中找不到所需要的功能,下一个选择应该是在高级的第三方类库中去寻找,比如Google 优秀的开源Guava 类库[ Guava ] 。
并发
- 在这个多核的时代,过度同步的实际成本并不是指获取锁所花费的CPU 时间;而是指失去了并行的机会,以及因为需要确保每个核都有一个-致的内存视图而导致的延迟。
- Java 平台中已经增加了j ava.util.concurrent 。这个包中包含了一个Executor Framework 它是一个很灵活的基于接口的任务执行工具。它创建了一个在各方面都比本书第一版更好的工作队列,却只需要这一行代码:ExecutorService exec = Executors . newSingleThreadExecutor();
- 。对于间歇式的定时,始终应该优先使用System.nanoTime ,而不是使用System.currentTimeMillis 。因为System.nanoTime 更准确,也更精确,它不受系统的实时时钟的调整所影响。最后,注意本例中的代码并不能进行准确的定时,除非action 能完成一定量的工作,比如一秒或者一秒以上。众所周知,准确的微基准测试十分困难,最好在专门的框架如jmh 的协助下进行
- 本条目仅仅触及了并发工具的一些皮毛。例如,前一个例子中的那三个倒计数锁存器其实可以用一个CyclicBarrier 或者Phaser 实例代替。这样得到的代码更加简洁,但是理解起来比较困难。
简洁的代码,追求的目标,技术范儿
- 一个类为了可被多个钱程安全地使用,必须在文档中清楚地说明它所支持的线程安全性级别。
- 如果出于性能的考虑而需要对实例域使用延迟初始化, 就使用双重检查模式( doub lecheckidiom ) 。这种模式避免了在域被初始化之后访问这个域时的锁定开销(详见第79 条) 。这种模式背后的思想是: 两次检查域的值因此名字叫双重检查( double - ch巳ck ),第一次检查时没有锁定, 看看这个域是否被初始化了;第二次检查时有锁定。只有当第二次检查时表明这个域没有被初始化,才会对这个域进行初始化。因为如果域已经被初始化就不会有锁定,这个域被声明为volatile 就很重要了(详见第78 条) 。
- 保持可运行线程数量尽可能少的主要方法是,让每个线程做些有意义的工作,然后等待更多有意义的工作。如果线程没有在做有意义的工作,就不应该运行。