书读得多而不思考,你会觉得自己知道的很多。

书读得多而思考,你会觉得自己不懂的越来越多。

                                                                                                                                                       ——伏尔泰


接口是一个契约,不仅仅约束着实现者,同时也是一个保证,保证提供的服务(常量、方法)是稳定的、可靠地,如果把实现代码写到接口中,那接口就绑定了可能变化的因素,这就会导致实现不稳定和可靠,是随时都可能被抛弃、被更改、被重构的。比如,匿名内部类实现其它接口。

接口中不能存在实现代码。

静态变量是在类初始化时首先被加载的,JVM会去查找类中所有的静态声明,然后分配空间,之后JVM会根据类中静态赋值(包括静态类赋值和静态块赋值)的先后顺序来执行。

再次重申变量要先声明后使用,这不是一句废话。

一个实例对象有两个类型:表面类型(Apparent Type)和实际类型(Actual Type),表面类型是声明时的类型,实际类型是对象产生时的类型。在向上转型中,表面类型是父类,而实际类型是子类。对于非静态方法,它是根据对象的实际类型来执行的,子类覆写父类方法,在向上转型时,实例对象如果调用该方法,执行的是子类的方法。而对于静态方法来说比较特殊,首先静态方法不依赖实例对象,它是通过类名访问的;其次,可以通过对象访问静态方法,如果是通过对象调用静态方法,JVM则会通过对象的表面类型查找到静态方法的入口,继而执行。在向上转型中,子类不能覆写父类的静态方法,只能自定义和父类静态方法签名相同的静态方法,在声明了向上转型的对象时,如果调用该方法,执行的是父类中的方法。

在子类中构建与父类相同的方法名、输入参数、输出参数、访问权限(权限可以扩大),并且父类、子类都是静态方法,此种行为叫做隐藏(Hide),它与覆写有两点不同:

1、表现形式不同。隐藏用于静态方法,覆写用于非静态方法。@Override可用于覆写

2、职责不同。隐藏的目的是为了抛弃父类静态方法,重现子类方法,期望父类的静态方法不要破坏子类的业务行为;覆写则是将父类的行为增强或减弱,延续父类的职责。

构造函数简化,再简化,应该达到“一眼洞穿”的境界。

不要在构造函数中声明初始化其他类,养成良好的习惯。

在Java中一共有四种类型的代码块:

1、普通代码块,方法后面使用“{}”括起来的代码片段,必须通过方法名调用执行。

2、静态代码块,在类中使用static修饰,并使用“{}”括起来的代码片段,用于静态变量的初始化或对象创建前的环境初始化。

3、同步代码块,使用synchronized关键字修饰,并使用“{}”括起来的代码片段,它表示同一时间只能有一个线程进入到该方法块中。

4、构造代码块,在类中没有任何的前缀或后缀,并使用“{}”括起来的代码片段。

构造代码块会在每个构造函数内首先执行,依托于构造函数的执行,可应用到:

1、初始化实例变量(Instance Variable)

2、初始化实例环境

构造代码块的特性:在每个构造函数中都运行和在构造函数中它会首先运行。如果遇到this()关键字,则不插入构造代码块,遇到super()会正常插入构造代码块。

静态内部类有两个优点:加强了类的封装性和提高了代码的可读性。

静态内部类与普通类的不同:

1、静态内部类不持有外部类的引用

2、静态内部类不依赖外部类

3、普通内部类不能声明static的方法和变量

匿名类虽然没有名字,但也是可以有构造函数的,它用构造函数块来代替。在初始化的时候只会调用父类的同参数构造,然后再调用自己的构造代码块。

多重继承指的是一个类可以同时从多于一个的父类那里继承行为与特性,此时可以考虑使用内部类。

如果一个类不允许实例化,就要保证“平常”渠道都不能实例化它。

一个类实现了Cloneable接口就表示它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。

Object提供的clone()方法中,拷贝规则是:

1、基本类型 如果变量是基本类型,则拷贝其值。

2、对象 如果变量是一个实例对象,则拷贝地址引用,也就是此时拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。

3、String字符串 拷贝的是一个地址引用,但是在修改时,会从字符串池中重新生成新的字符串,原有的字符串对象保持不变。

浅拷贝只是Java提供的一种简单拷贝机制,不便于直接使用。

覆写equals方法时遵循原则:

1、自反性原则,对于任何非空引用x,x.equals(x)应该返回true。(在覆写equals方法时,x不要做不必要的处理,比如调用trim方法去空格)

2、对称性原则,对于任何引用x和y的情况,如果x. equals(y)返回true,那么y. equals(x)也应该返回true。(在覆写equals方法时,最好判断null)

3、传递性原则,对于实例对象x、y、z来说,如果x. equals(y)返回true,y. equals(x)返回true,那么x. equals(z)也应该返回true。(建议在覆写equals方法时,使用getClass进行类型判断,而不要使用instanceof)

4、覆写equals方法必须覆写hashCode方法。

public class Person {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj != null && obj.getClass() == this.getClass()) {
            Person p = (Person) obj;
            if (p.getName() == null || name == null) {
                return false;
            } else {
                return name.equalsIgnoreCase(p.getName());
            }
        }
        return false;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(name).toHashCode();
    }

}

HashMap的底层处理机制是以数组的方式保存Map条目(Map Entry)的,这其中的关键是这个数组下标的处理机制:依据传入元素hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了Map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到Map条目的链表中。同理,检查键是否存在也是根据哈希码确定位置,然后遍历查找键值的。

在需要用到包的地方,就可以考虑一下package-info这个特殊类,也许能起到事半功倍的作用。