Java基础知识学习笔记

学习Java语言特性时的笔记,基本没什么参考价值,只是知识点是初学时容易忽略的,整理把它丢在这吧。

继承相关

继承中super与this不是类似的概念,super不是一个对象的引用,不能将super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。super在构造器中的应用有不同的含义。即super具有两个作用:

  • 调用超类的方法
  • 调用超类的构造器

this的两个用途:

  • 引用隐式参数
  • 调用该类其他的构造器

上述两个关键字使用方法很相似,调用构造器的语句只能作为另一个构造器的第一条语句出现。构造参数既可以传给本类(this)的其他构造器,也可以传给超类(super)的构造器。

阻止继承:final类和final方法

编写一个完美的equals方法:

+ 显示参数命名为otherObject,稍后需要将它转换为另一个叫做other的变量。
+ 检测this与otherObject是否引用同一个对象
if(this == otherObject) return true;
+ 检测otherObject是否为null,是则返回false
if(otherObject == null) return false;
+ 比较this与otherObject是否属于同一个类
if(getClass() != otherObject.getClass()) return false;
如果所有子类具有统一语义,可使用instanceof检测:
if(!(otherObject instanceof ClassName)) return false;
+ 将otherObject转换为相应的类类型变量,因为otherObject是Object类型的变量:
ClassName other = (ClassName) otherObject;
+ 对需要进行比较的域进行比较;
return field1 == other.field1
&& Objects.equals(field2,other.field2)
&& ...

继承设计的技巧

  • 将公共操作和域放在超类
  • 不要使用受保护的域
  • 使用继承实现“is-a”关系
  • 除非所有的继承都有意义,否则不要轻易使用继承
  • 在覆盖方法的时候不要改变预期的行为
  • 使用多态而非类型信息
    对于下面这种形式的代码:
1
2
if(x is of type1) action1(x);
else if(x is of type2) action2(x);

都应该考虑动态多态性。
action1和action2是相同的概念吗?如果是,就应该为这个概念定义一个公共的方法,将其放在两个类的超类或接口中,然后调用x.action()

  • 不要过多的使用反射

final关键字

final关键字:final通常指“这是无法改变的”.不想做改变的理由有两个:设计或效率。

  • final数据
    • 一个永远不改变的编译时常量
    • 一个在运行时被初始化的值,而你不希望它被改变。
      当对对象引用而不是基本数据类型时,其含义会有一点点令人迷惑。对基本类型,final使得数值恒定不变;对于引用对象,final是引用恒定不变对数组来说具有同样意义。
  • 访问权限修饰符、static与final结合
    • 定义为public,则可以被用于包外;
    • 定义为static,则强调只有一份;
    • 定义为final,则说明它是一个常量。当然带恒定初始值的final static基本类型全用大写字母命名,并且词与词之间用下划线隔开。
  • 空白final:java允许生成“空白final”,所谓空白final是指被声明为final但又为给定初始值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。如此一来,一个final域可以根据对象而有所不同,却又保持其恒定不变的特性,提供了更大的灵活性。
  • final参数:java允许在参数列表中以声明的方式将参数指明为final.这就意味着你无法在方法中更改参数引用所指向的对象。可读不可改。
  • final方法,使用final方法的原因:
    • 把方法锁定。
    • 效率。
  • final和private关键字:类中所有的private方法都隐式地指定为final,由于无法取用private方法,所以也就无法覆盖它。对private添加final并不会增加额外的意义。
  • final类:final类禁止继承,所以final类中的所有方法隐式地指定为final,因为无法覆盖它们。

构造器内部的多态方法的行为

如果构造器的内部再调用正在构造的对象的某个动态绑定方法时,那会发生什么情况呢?我们来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Glyph {
void draw(){
System.out.println("Glyph.draw()");
}

Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() before draw()");
}
}

class RoundGlyph extends Glyph {
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = " + radius);
}
void draw(){
System.out.println("RoundGlyph.draw(), radius = " + radius);
}
}

public class PolyConstructors {
public static void main(String[] args) {
new RoundGlyph(5);
}
}

输出结果:

1
2
3
4
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5

综上分析,你可以发现初始化的过程其实是这样的:
(1)在其他任何事物发生之前,将分配给对象的存储空间初始化为二进制零;
(2)调用基类构造器。此时,调用被覆盖后的draw()方法(要在调用RoundGlyph构造器之前调用),由于步骤一的缘故,我们会发现radiu的值为0.
(3)按照声明的顺序调用成员的初始化方法。
(4)调用导出类的构造器主体。

编写构造器有一条有效的准则:
用尽量可能简单的方法使对象进入正常状态;如果可以的话,尽量避免调用其它的方法。

instanceof与Class的等价性

== 和 equals() 来检查Class对象是否相等;
instanceof和isInstance()生成结果一样,equals()和 == 也一样;
instanceof保持了类型的概念:你是这个类吗,或者你是这个类的派生类吗?而用 == 比较实际的对象,就没有考虑继承。

RTTI与反射之间的区别

RTTI:编译器在编译时打开和检查.class文件
反射:.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件的。