Effective Java这本书针对的是有经验的程序员,本书共计78个条目,目的是告诉大家如何进行更加有效的编程
静态工厂方法的优点
静态工厂方法的缺点
当一个对象中字段很多,那么需要实例化时如果每种不同的对象都写一个对象的构造器来进行实例化会使得代码十分冗余,如果使用无参构造器,然后使用setter方法来给每个字段设置值,会导致这个对象字段可变且不安全,所以需要考虑使用构建器.构建器代码如下所示
// 工厂实例类代码
public class FactoryDemo {
private final Long ID;
private final String adress;
private final int age;
private final String name;
public static class Builder {
private final int age;
private final String name;
private Long ID = 0L;
private String adress = "";
public Builder(int age, String name) {
this.age = age;
this.name = name;
}
public Builder ID(Long ID){
this.ID = ID;
return this;
}
public Builder adress(String adress){
this.adress = adress;
return this;
}
public FactoryDemo build(){
return new FactoryDemo(this);
}
}
private FactoryDemo(Builder builder){
this.ID = builder.ID;
this.adress = builder.adress;
this.age = builder.age;
this.name = builder.name;
}
}
// 客户端调用代码
FactoryDemo build = new FactoryDemo.Builder(18, "大爷").ID(35062819920914L).adress("福建漳州").build();
//方式1
public class Elvis {
public static fianl Elvis INSTANCE = new Elvis();
private Elvis(){}
}
//方式2
public class Elvis {
public static fianl Elvis INSTANCE = new Elvis();
private Elvis(){}
public static Elvis getInstance(){
return INSTANCE;
}
}
public enum Elvis {
INSTANCE;
//业务逻辑方法
public void run(){
System.out.println("处理业务逻辑");
}
}
对于工具类而言,是不需要被实例化的,这个时候需要使用私有化构造器的方式来强化它不可被实例化的能力.不要选择不写,这样当该类被调用的时候,编译器会自动生成一个无参构造函数,或者当子类继承它并实例化子类的时候,该类也会被实例化,这样的操作都是不合理的
public class Test {
public static void main(String[] args) {
Long sum = 0L;
long begin = System.currentTimeMillis();
for(int i = 0 ;i<=100000;i++){
sum += 1;
}
long end = System.currentTimeMillis();
System.out.println("Long总耗时:"+(end-begin));//5ms
long sum1 = 0L;
long begin1 = System.currentTimeMillis();
for(int i = 0 ;i<=100000;i++){
sum1 += 1;
}
long end1 = System.currentTimeMillis();
System.out.println("long总耗时:"+(end1-begin1));//0ms
}
}
public class Test {
public static void main(String[] args) {
long begin = System.currentTimeMillis();
for(int i = 0 ;i<=10000;i++){
String str = new String("sb");
}
long end = System.currentTimeMillis();
System.out.println("new总耗时:"+(end - begin));//2ms
long begin1 = System.currentTimeMillis();
for(int i = 0 ;i<=10000;i++){
String str = "sb";
}
long end1 = System.currentTimeMillis();
System.out.println("非new总耗时:"+(end1 - begin1));//0ms
}
}
备注:以上所说的情况仅仅只是针对于需求当中该对象是只需要被初始化一次,后续不需要再进行更改的情况下可以使用,如果是该对象在程序运行过程中需要被修改的,那么则不适用此规则
在JVM当中栈空间弹出的对象引用为过期引用,这一类的过期引用不会再被GC回收,那么这样的过期对象引用越来越多则会导致内存泄漏(Disk paging),严重可能导致OOM,具体代码示例如下
//以下这段代码主要问题在于pop出栈的对象,还对原有的对象在内存中的地址/存在着引用,所以需要对已经出栈的对象做空引用的处理
public class StackDemo {
private Object[] elements;
private int size = 0;
private final int DEFAULT_INITIAL_CAPACITY = 16;
/**
* 功能描述:
* 〈初始化栈〉
*
* @params : []
* @return :
* @author : cwl
* @date : 2019/5/5 16:59
*/
public StackDemo(){
elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
/**
* 功能描述:
* 〈压栈〉
*
* @params : [o]
* @return : void
* @author : cwl
* @date : 2019/5/5 16:58
*/
public void push(Object o){
ensureCapacity();
elements[size++] = o;
}
/**
* 功能描述:
* 〈出栈〉
*
* @params : []
* @return : java.lang.Object
* @author : cwl
* @date : 2019/5/5 16:59
*/
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
return elements[size--];
}
/**
* 功能描述:
* 〈自动扩容〉
*
* @params : []
* @return : void
* @author : cwl
* @date : 2019/5/5 16:59
*/
private void ensureCapacity() {
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}
public Object pop(){
if (size == 0){
throw new EmptyStackException();
}
elements[size] =null;//将出栈的元素设置为空引用
return elements[size--];
}
大多数情况下finally代码块是用来搭配一些资源的打开没有关闭,在这里进行最终的关闭的.但是finally代码块有时候并不会非常及时的去关闭资源,而且会极其消耗JVM的性能,但是如果如果需要搭配FileInputStream/FileOutputStream/Timer/Connection等,自带colse方法时,可以考虑避免文件过多的被打开而导致内存不足,而在finally代码块当中去执行这样的操作
摘自Object规范[JavaSE6]第二条:
结论:如果没有覆盖hashCode方法,那么当使用HashMap,HashSet和HashTable这种键值对结构时比较就会不准确.详情请看以下代码
public class PhoneNumber {
private final short areaCode;
private final short prefix;
private final String lineNumber;
public PhoneNumber(int areaCode, int prefix, String lineNumber) {
rangeCheck(areaCode,999,"areaCode");
this.areaCode = (short) areaCode;
this.prefix = (short) prefix;
this.lineNumber = lineNumber;
}
private void rangeCheck(int arg, int max, String name) {
if(arg < 0 || arg > max){
throw new IllegalArgumentException(name + ":" +arg);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber that = (PhoneNumber) o;
return areaCode == that.areaCode &&
prefix == that.prefix &&
lineNumber.equals(that.lineNumber);
}
}
当你想要尝试搭配map集合使用时,如果没有覆盖hashCode方法,那么当你用相同的对象去get集合Map当中的value值的时候,返回的将会是null,因为他们的hashCode值时不一样的,所以这个时候就需要我们去覆盖hashCode方法,这个方法涉及到hashCode值的计算方式,这里我们不做深究,建议直接调用父类的即可,在PhoneNumber当中添加代码示例1的方法
Map<PhoneNumber,String> numberStringMap = new HashMap<>();
numberStringMap.put(new PhoneNumber(180,286,"6849"),"Jenny");
//代码示例1:
@Override
public int hashCode() {
return Objects.hash(areaCode, prefix, lineNumber);
}
这一条的目的非常简单,覆盖toString只是为了让我们更加方便观看而已,Object类的toString是全限定雷鸣+地址值