Effective Java 笔记(一)

Stella981
• 阅读 492

创建和销毁对象
NO.1 考虑用静态工厂方法代替构造函数

静态工厂方法好处:
(1)构造函数有命名的限制,而静态工厂方法有自己的名字,更加易于理解。
(2)静态工厂方法不必在每次调用它们的时候创建一个新的对象。这种做法对于一个要频繁创建相同对象的程序来说,可以极大的提高性能。它使得一个类可以保证是一个singleton;他使非可变类可以保证“不会有两个相等的实例存在”。

java内存分成4块,一块是堆,用来存放new的对象,一个是栈,用来存放局部变量,然后就是静态变量区,存放常量,静态变量,以及代码区,存放代码,所以很说静态变量区是和堆栈并列的,并不从属的。

(3)静态工厂方法在选择返回类型时有更大的灵活性。使用静态工厂方法,可以通过调用方法时使用不同的参数创建不同类的实例,还可以创建非公有类的对象,这就封装了类的实现细节。
(4)在创建参数化类型实例的时候,他们使代码变的更加简洁。
例如:

public static Boolean valueOf(boolean f){
    return b ? Boolean.TRUE : Boolean.FALSE; }

Map<String,List<String>> m=HashMap.newInstance();

静态工厂方法的主要缺点在于类如果不含公有的或者受保护的构造器,就不能被子类化。静态工厂方法的第二个缺点在于它们与 其他的静态方法实际上没有任何区别,我们需要遵守标准的命名习惯弥补这一劣势,例如valueOf、of、getInstance、 newInstance、getType、newType。
NO.2 遇到多个构造器参数时要考虑用构建器

当有多个构造方法,一般是参数大于4个的时候,建议使用Builder模式。Builder模式既能保证像重叠构造器模式那样的安全性,也能保证像JavaBean模式那么好的可读性。

NO.3 用私有构造器或者枚举类型强化Singleton属性

NO.4 通过私有构造器强化不可实例化的能力

在面向对象程序设计中,假如存在太多只有静态属性和静态方法的类;那么,面向对象的思想可能在这会损失殆尽。

但是,并不能说面向对象的程序中就不应该出现只有静态属性和静态方法的类,相反,有时候我们还必须写这样的类作为工具类。这样的类怎么实现呢?有人 可能会把该类定义成抽象类(Abstract class),的确,抽象类是不可以实例化的,但是别忘了还有继承,继承了抽象类的子类在实例化时候,默认是会先调用父类无参数的构造函数的 super();,这时候,父类不是也被实例化了嘛?

其实我们可以这样做,把该类的构造函数定义为私有的(private),而类的内部又不调用该构造函数的话,就成功了。这样带来的后果就是该类成了 final的,不可能再被任何类继承了,要被继承,得提供一个公有(public)的或者保护(protect)的构造函数,这样才能被子类调用。

public class UtilityClass {
    //Suppress default constructor for noninstantiability.
    private UtilityClass() {
        throw new AssertionError();
    }
}

这样定义之后,该类将不会再被外部实例化了,否则会产生编译错误。然而这样的定义带来的最直接的负面影响是该类将不能再被子类化。
NO.5 避免创建不必要的对象

如果一个对象是不可变的,那么他总是可以被重用的,如:

//不推荐,”test”本来就是一个String实例,如果此方法在一个循环中或者被频繁的调用,将会严重影响性能。

String s = new String("test");

//推荐方式

String s = "test";

由于String被实现为不可变对象,JVM底层将其实现为常量池,既所有值等于”test”的String对象实例共享同一对象地址,而且还可以保证,对于所有在同一JVM中运行的代码,只要他们包含相同的字符串字面常量,该对象就会被重用。
对于提供静态方法和构造函数的非可变类,推荐使用静态方法,这样可以避免重复创建对象,如:Boolean.vauleOf(String)方法优于构造函数Boolean(String)

类初始化的顺序: 先初始化父类的静态代码 —> 初始化子类的静态代码 —> 初始化父类的非静态代码 —> 初始化父类构造函数
—> 初始化子类非静态代码 —>初始化子类构造函数。

NO.6 消除过期的对象引用

内存泄漏问题:如果一个对象的引用被无意识的保留起来,那么垃圾回收机制是不会去处理这个对象,而且也不会去处理被这个对象引用的其它对象。 比如堆栈的弹出栈元素方法。

public Object pop(){

   if(size == 0){
       throw new EmptyStackException();
   }

    Object result = elements[--size];
    //自减后把原来的引用置为null
    elements[size] = null;
    return result;
}

内存泄露常出现在:
(1)类是自己管理内存。
(2)缓存,由于缓存没有及时清除无用的条目而出现,可以使用weakHashMap来避免这种情况
(3)监听器和其他回调,确保回调立即被当做垃圾回收的最佳方法是只保留它们的弱引用(weak reference).
Java回调机制:A发送消息给B,B处理好A要求的事情后,将结果返回给A,A再对B返回的结果来做进一步的处理。

清理过期对象引用的好处是:如果在以后又被使用到该引用,最会抛下NullPointException而不是让程序继续错误的运行下去,尽可能早的监测出程序中的错误总是有好处的。
方法:重新使用这个已经指向一个对象的引用,或结束其生命周期。

NO.7 避免使用终结方法

对于所有对象都通用的方法
NO.8 覆盖equals时请遵守通用约定

(1) equals方法一般用于“值类”的情形,比如Integer,Date,目的是为了比较两个指向值对象的引用的时候,希望它们的逻辑是否相等而不是它们是否指向同一个对象。

约定:
a. 自反性 对任意的对象必须和它自身相等。对值引用x x.equals(x) 一定返回true
b. 对称性 对任意的值引用x,y,如果x.equals(y) 一定有y.equals(x)
c. 传递性 对任意的值引用x,y,z,如果x.equals(y),y.equals(z) 一定有x.equals(z)
d. 一致性 对于任何非null的引用x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致地返回true

实现高质量equals方法的诀窍:
(1)使用==操作符检查”参数是否为这个对象的引用”。
(2)使用instanceof操作符检查”参数是否为正确的类型”,如果不是,则返回false。
(3)把参数转换成为正确的类型。
(4)对于该类中的每个”关键”域,检查参数中的域是否与该对象中对应的域相匹配。

@Override public boolean equals(Object o) {
        if (o == this) 
            return true;

        if (!(o instanceof MyType))
            return false;

        MyType myType = (MyType)o;
        return objField.equals(o.objField) && intField == o.intField 
            && Double.compare(doubleField,o.doubleField) == 0 
            && Arrays.equals(arrayField,o.arrayField);
    }

NO.9 覆盖equals时总要覆盖hashCode

相等的对象必须要有相等的散列码,如果违反这个约定可能导致这个类无法与某些散列值得集合结合在一起使用,所以在改写了equals方法的同时一定要重写hashCode方法以保证一致性。

@Override public int hashCode() {
        int result = 17;
        result = 31 * result + areaCode;
        result = 31 * result + prefix;
        result = 31 * result + lineNumber;
        return result;
    }

NO.10 始终要覆盖toString

toString返回值中包含所有信息

NO.11 谨慎地覆盖clone

NO.12 考虑实现Comparable接口
java.lang.Comparable 接口

类和接口
NO.13 使类和成员的可访问性最小
信息隐藏是软件程序设计的基本原则之一,面向对象又为这一设计原则提供了有力的支持和保障。优势:
(1)更好的解除各个模块之间的耦合关系
(2)最大化并行开发
(3)性能优化和后期维护
(4)代码的高可复用性
NO.14 在公有类中使用访问方法而非公有域
如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示方法的灵活性。
如果类是包级私有的,或者私有的嵌套类,直接暴露他的数据域并没有本质的错误。因为这些代码被限定在包中或外围类中。
NO.15 使可变性最小化
使类成为不可变类应遵循以下五条原则:
(1)不要提供任何会修改对象状态的方法
(2)保证类不会被扩展,即声明为final类,或将构造函数定义为私有
(3)使所有的域都是final的
(4)使所有的域都成为私有的
(5)确保对于任何可变组件的互斥访问。
不可变对象本质上是线程安全的,他们不要求同步。不可变对象可以自由地共享。
NO.16 复合优先于继承
NO.17 要么为继承而设计,并提供文档说明,要么就禁止继承
NO.18 接口优先于抽象类

(1)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
(2)抽象类可以有构造方法,子类通过构造方法链调用构造方法,但不能实例化;接口没有构造方法,不能实例化。
(3)实现接口的类必须实现接口的所有方法,而抽象类不需要。
(4)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。
深入理解Java的接口和抽象类
NO.19 接口只用于定义类型
接口不应该被用来导出常量。
NO.20 类层次优于标签类
NO.21 用函数对象表示策略
NO.22 优先考虑静态成员类
在Java中嵌套类主要分为四种类型,下面给出这四种类型的应用场景。
(1)静态成员类,静态嵌套类:
static嵌套类通过写出封装的类名来进行实例化和访问其内部成员:

OuterClass.StaticNestedClass nestedObject =
new OuterClass.StaticNestedClass();

(2)非静态成员类,成员内部类:

OuterClass outerObject=new OuterClass();
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

(3)匿名类,匿名内部类:
匿名内部类就是没有名字的局部类。它不使用关键字class, extends,implements以及构造函数。它通常作为方法的一个参数传入,比如在android开发中对一个Button添加一个OnClickListener监听器。匿名内部类隐匿的继承了一个父类或者实现了一个接口。
(4)局部类,局部内部类:
定 义在方法内部的类叫作“局部内部类”。它的作用域仅限于方法作用域内,只能在方法的作用域内定义和实例化,是用处最小的类类型。和局部变量一样,它不能被 修饰为private, public, protected和static的,并且只能访问方法内部定义的final变量。

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java日期时间API系列31
  时间戳是指格林威治时间1970年01月01日00时00分00秒起至现在的总毫秒数,是所有时间的基础,其他时间可以通过时间戳转换得到。Java中本来已经有相关获取时间戳的方法,Java8后增加新的类Instant等专用于处理时间戳问题。 1获取时间戳的方法和性能对比1.1获取时间戳方法Java8以前
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
2年前
JS 对象数组Array 根据对象object key的值排序sort,很风骚哦
有个js对象数组varary\{id:1,name:"b"},{id:2,name:"b"}\需求是根据name或者id的值来排序,这里有个风骚的函数函数定义:function keysrt(key,desc) {  return function(a,b){    return desc ? ~~(ak
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这