记录自己狭隘的理解,想要深入去看书
环境jdk8
文中缓存就是静态变量,或者方法中的集合Map等
有疑问的地方标记※
第二章:创建和销毁对象
1.静态工厂方法代替构造器
即静态方法返回本类的实例
优点:
1.静态方法有方法名称更加直观
2.不必每次都创建新的对象
2.1单例模式
2.2线程池、连接池。这种可复用且较消耗内存资源的对象
2.3固定范围内的对象
Integer的valueOf方法,在创建-128~127之间时不创建新的对象,直接从缓存中取
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
又如Boolean类的valueOf方法:
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
3.静态方法可以返回原返回类型的任何子类对象,返回对象具有灵活性
缺点:这俩缺点可以直接忽略,这也叫缺点么2233
- 查阅文档不方便,当你想创建对象时,如果有构造可以直接使用,但如果没有构造方法就要去一个一个看静态方法
- 类如果没有public/protected的构造(如单例模式需要将构造函数私有化),就不能被子类继承。但你可以使用装饰者模式,而不使用继承
2.构造函数有多个参数时,且参数个数不确定,且需要线程安全,推荐使用构建者
如:lombok的builder注解,就是构建着模式
比如:一个类有10个属性,11个构造方法,构造方法参数个数分别为0-10,就会发现构造方法特别多,而且参数顺序也是严格的
方案一:setter方法,先调用空参构造生成对象,之后设置属性,缺点设置属性的动作并不具有原子性,中间可以开启多线程!那么就出现线程安全问题
方案二:builder注解,就是构建着模式,创建对象和设置属性就是一个原子操作
其实一般情况下,不用这么搞!工作中方案一完全够用
1.你会发现工作中builder并不是全能的,(比如创建对象之后赋值操作)然后你就加上了setter,这样的话builder完全就是没什么用了
2.就是在防止别人创建对象时开启了线程,才会使用builder
3.builder注解和setter注解同时使用。我认为是比较愚蠢的
4.用idea查看builder注解的实体的.class文件,你就会发现多了很多代码,还有一个内部类,构建类!也就是创建对象时,先创建构建类实例,这TM的不恶心人么,浪费资源!也就是为了安全时才开启builder
3.private构造器或者枚举,实现Singleton单例
私有构造
/**
* 单例的Demo
* 但是AccessibleObject的 setAccessible方法,通过反射机制可以调用私有构造
*/
public class Singleton {
public static final Singleton INSTANCE = new Singleton();
private Singleton(){}
}
但可以使用反射调用私有构造
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//通过静态属性获取实例
Singleton singleton = Singleton.INSTANCE;
Singleton singleton1 = Singleton.INSTANCE;
//静态实例在类加载时就被创建,所以是单例,true
System.out.println(singleton == singleton1);
//通过反射调用私有构造方法
Class clazz = Singleton.class;
//获取构造方法
Constructor cons = clazz.getDeclaredConstructor();
//设置私有构造方法为可访问
cons.setAccessible(true);
//使用私有构造方法获取实例
Object obj = cons.newInstance();
//实例属于类Singleton,true
System.out.println(obj instanceof Singleton);
//破坏了单例,false
System.out.println(singleton == obj);
}
}
抵御反射攻击
public class Singleton implements Serializable {
public static final transient Singleton INSTANCE = new Singleton();
private Singleton(){
//当创建第二个实例就会抛异常
if(null!=INSTANCE){
throw new RuntimeException();
}
}
}
当然也可以使用静态方法实现
好处:灵活性,在不改变API的情况下,可以对方法进行修改
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton(){
if(null!=INSTANCE){
throw new RuntimeException();
}
}
//方法是灵活的,方法的实现可以修改
public static Singleton getInstance(){
return INSTANCE;
}
}
当单例的类需要可序列化,就需要实现Serializable接口
但序列化和反序列化都会创建一个新的实例
所以实例属性必须是瞬时的transient(不可序列化的属性)
static的属性也是不能被序列化的
public class Singleton implements Serializable{
//实现Serializable,之后加上transient修饰
private static final transient Singleton INSTANCE = new Singleton();
private Singleton() {
if (null != INSTANCE) {
throw new RuntimeException();
}
}
public static Singleton getInstance() {
return INSTANCE;
}
}
单例最佳实现。枚举,提供了序列化机制,还可以防止多次实例化
public enum EnumSingleton {
INSTANCE;
}
4.私有构造器,强化不可实例化的能力
工具类的实例化是没有意义的,但默认有无参构造
/**
* 工具类,内方法都是静态,无需实例
*/
public class UtilClass {
//私有构造,并在其调用时抛出异常
private UtilClass(){
throw new AssertionError();
}
}
这种代码怎么说。。。谁又会去创建工具类的实例呢?防止小白么?
5.避免创建不必要的对象
- String不要这么用,除非特殊需求
//每次执行都会创建一个新的对象,不要这么做,除非需求就是要两个不同的实例
String s = new String("str");
//每次执行,重用对象(在常量池中)
String s1 = "str";
-
方法中使用的对象都是同一个,可以提取成静态,避免每次调用都创建
-
优先使用基本数据类型,而不是包装类,要当心自动装箱
/**
* 计算和,
* sum被声明成了Long,而不是long,就会多创建大约2的31次方个实例
* 书里是这么写的:改成long,运行时间从43秒变成6.8秒
* 但我实际上运行时间是:5.8秒变成了0.7秒,但也是数量级别的优化,很强
*/
public static void sum() {
Long sum = 0L;
for (long i = 0; i < Integer.MAX_VALUE; i++) {
sum += i;
}
System.out.println(sum);
}
6.消除过期的对象引用(防止内存泄漏)
内存泄漏:就是对象不使用了,但对象的引用还在
- 当类自己管理内存时(操作原始数组元素时),应该小心内存泄漏问题
如:自己管理数组
/**
* 栈伪代码
* 不考虑数组越界,不考虑扩容,长度固定16
*/
public class Stack {
//定义一个长度为16的Object的数组
private Object[] element = new Object[16];
//已占用数组大小
private int size = 0;
//添加一个元素
public void push(Object e) {
element[size++] = e;
}
//删除一个元素
public Object pop(Object e) {
return element[--size];
}
}
比如现在长度是8,里面的值是1-8,在删除一个元素时,只是将size改为7,数组中元素还在。8还再,这个8就属于内存泄漏。优化后的代码如下
public Object pop(Object e) {
Object result= element[--size];
//防止内存泄漏,清空size处对象的引用
element[size] = null;
return result;
}
- 缓存:就是自己使用缓存时注意是否需要管理其生命周期
2.1 如果需要:使用线程定时检查,Timer、ScheduledThreadPoolExecutor等
或者在add时检查。如:LinkedHashMap利用removeEldestEntry方法※
其他复杂情况:使用Java.long.ref※
2.2 在缓存之外存在,对某个键的引用,可以用WeakHashMap:就是说WeakHashMap的键只有自己引用时就会被回收,被其他变量引用时才不会被回收 - 监听器、回调※
7.避免使用终结方法
就是不要使用finalize方法回收对象,危险,不稳定,降低性能
不合理的使用
- 即使你调用了,不保证及时执行甚至不执行(及时执行是GC算法的主要功能,但在不同JVM实现的又不一样。就是换个jvm就有可能无法运行)
- finalize方法线程优先级低。方法栈是进栈出栈的过程,可能遇到终结方法并不能及时执行,导致栈下面的方法不能及时出栈,然而其他方法又要进栈。就容易造成OOM,解决方法就是不使用finalize方法
- 异常在终结方法中,则异常的堆栈信息,都不会打印出来
- 影响程序效率。慢400多倍
合理的使用
- 流系列对象,显式终止是使用close方法(Timer/Connection也具有终结方法)
- 对象的本地对等体※,jvm不会对其回收,需要有显式的回收方法,终止方法可以是本地方法或者它调用本地方法
- 如果类(不是Object)有终结方法,且子类覆盖了终结方法,子类的终结方法必须手动调用超类的终结方法。并且在try中执行子类的终结方法,在finally中调用超类的终结方法,保证超类的终结方法一定会被执行,否则父类的终结方法永远执行不了
对于所有对象都通用的方法
8.覆盖equals时遵守通用约定
首先Object的hashCode方法的注释明确规定
equals相等hashCode必须相等
hashCode相等,equals可以不相等
所以重写equals必须重写hashCode,因为要保证规则一
Object的equals方法,如下,比较的是对象的地址值
public boolean equals(Object obj) {
return (this == obj);
}
不需要重写equals的情况
- 保证类的每个实例都是唯一的。
因为每次new就会在堆内存产生一个唯一的地址值,保证实例唯一,比较地址值即可。Object的equals方法比较的就是地址值,无需重写。如Thread,每次new都是一个新的线程,保证唯一性 - 不关心类是否提供了逻辑相等的测试功能。
如Random类,设计者不认为用户需要这种行为
//获取一个随机数
Random random = new Random();
int i = random.nextInt();
//下面的代码有什么意义呢
Random random1 = new Random();
System.out.println(random.equals(random1));
- 父类已经覆盖equals,子类就继承了父类的equals,无特殊需求的话,子类就没必要覆盖equals。如AbstractSet,AbstractList,AbstractMap覆盖了equals,子类无需实现
需要重写的情况
- private的类或者包级私有,那么它的equals永远不会被调用,这种情况必须覆盖,防止意外调用
未完持续。。。。