细碎点集锦
- 程序是对象的集合,它们通过发送消息来告知彼此所要做的。要想请求一个对象,就必须对该对象发送一条消息。更具体的说,可以把消息想象为对某个特定对象的方法的调用
- 文档注释可以加html中的标签,使文档有更好的显示
- 静态导入可以方便一些方法的调用
例如:
Import static net.mindview.util.Print.*;
Print(“Hello,World”);
但使用不当很容易引起代码阅读性的降低和重名几率的提高,需要慎用。
- 如下代码可实现在一条语句对同一对象执行多次操作:a.method().method();
Public class a{
Public a method{
Return this;
}
}
- 执行顺序:静态初始化块>初始化块。初始化块总在构造器执行前执行,实际上编译后初始化块的内容会还原入每一个构造器
- 组合继承
当创建一个导出类(继承类)的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的。组合和继承都允许在新的类中放子对象,组合是显示的这样做,而继承则是隐式的这样做。
Is a 适合继承表达,has a适合组合
到底该用组合还是继承,一个最清晰的判断方法就是问一问自己是否需要从新类向基类进行向上转型 - 类的加载过程
类的代码在初次使用时才加载。这通常是指加载发生于创建类的第一个对象之时,但是访问static域或static方法时,也会发生加载
一个对象加载过程:
寻找class文件并加载进入内存(此类所有基类class文件一起加载)——》static初始化——》创建对象,初始化成员变量——》调用构造器(从基类往下) - String的+拼接会被编译器优化成StringBuilder的append()方法,但优化有限,大量拼接时会不断new StringBuilder,所有大量拼接的情况还是用StringBuilder好,编译后只会new 一个StringBuilder
注意,图省事用StringBuilder.append(a+”:”+b)会让编译器另外创建StringBuilder对象来处理括号内字符串拼接,所有还是用append好
(p284)
多态
class Super{
public void f() {
System.out.println("super.f()");
}
}
public class Sub extends Super{
@Override
public void f() {
System.out.println("sub.f()");
}
public static void main(String[] args) {
Super s=new Sub();
s.f();
}
}/*output:
Sub.f()
*//
问:为什么s调用f方法会是继承类中的f()方法而不是父类中的?
——将一个方法调用同一个方法的主体关联起来称为绑定。
若在程序执行前进行绑定,叫做前期绑定。
若在程序运行时根据对象的类型进行绑定,则称为后期绑定,或者动态绑定
Java中除了static方法和final方法外,其余方法都是后期绑定。
F方法调用时,会动态和对象s进行绑定,根据s对象中的类型信息(可以输出s对象查看),判断其为sub,所以调用了sub.f()。
class Super{
public int field=0;
public int getField(){
return field;
}
}
class Sub extends Super{
public int field=1;
public int getField(){
return field;
}
public int getSuperField(){
return super.field;
}
}
public class Test{
public static void main(String[] args) {
Super sup=new Sub();
System.out.println("sup.field="+sup.field+",sup.getField()="+sup.getField());
Sub sub=new Sub();
System.out.println("sub.field="+sub.field+",sub.getField="+sub.getField()+",sub.getSuperField()="+sub.getSuperField());
}
}/*Output:
sup.field=0,sup.getField()=1
sub.field=1,sub.getField=1,sub.getSuperField()=0
*/
只有普通的方法调用为多态,域和静态方法都不具有多态。
因为任何域访问操作都将由编译器解析。例如,如果你直接访问某个域,这个访问就将在编译期进行解析,直接返回原本对象的域
类型信息
- RTTI(Run-Time Type Identification):即运行时类型鉴定。Java中的工作原理在于,运行时可根据对象的Class类来知道其真实类型。
Java中主要有两种方式:普通的RTTI以及反射。区别在于,前者在编译时打开和检查.class文件获取Class,而反射是在运行时打开和检查.class文件 - 新的转型语法,对于无法使用普通转型的情况特别有用
class Father{}
class Son extends Father{}
public class Test {
public static void main(String[] args) {
Father f=new Son();
Class sonType=Son.class;
Son s=sonType.cast(f);
//等效于: Son s=(Son)f;
}
}
- 空对象思想 (p341)
使用内置的null来表示缺少对象时,每次都需要测试其是否为null。而null除了在操作时产生NullPointerException之外,没有其他任何行为。
通过引入空对象,执行所有方法时都返回表示“不存在”的信息。这样我们可以假设所有对象都是有效的,而不必浪费编程精力去检查null
interface Null{}
class Person{
public String name;
public String address;
//Person的空对象,单例模式
public static final Person NULL=new NullPerson();
public Person(String name,String address) {
this.name=name;
this.address=address;
}
public String whoAmI() {
return name+" "+address;
}
public static class NullPerson extends Person implements Null{
private NullPerson(){super("None", "None");}
public String whoAmI() {
return "NullPerson";
}
}
}
补充:Java8新特性-Optional
转载自http://www.blogbus.com/dreamhead-logs/235329092.html
java.lang.NullPointerException,只要敢自称Java程序员,那对这个异常就再熟悉不过了。为了防止抛出这个异常,我们经常会写出这样的代码:
Person person = people.find("John Smith");if (person != null) { person.doSomething();}
遗憾的是,在绝大多数Java代码里,我们常常忘记了判断空引用,所以,NullPointerException便也随之而来了。
“Null Sucks.”,这就是Doug Lea对空的评价。作为一个Java程序员,如果你还不知道Doug Lea是谁,那赶紧补课,没有他的贡献,我们还只能用着Java最原始的装备处理多线程。
"I call it my billion-dollar mistake.",有资格说这话是空引用的发明者,Sir C. A. R. Hoare。你可以不知道Doug Lea,但你一定要知道这位老人家,否则,你便没资格使用快速排序。
在Java世界里,解决空引用问题常见的一种办法是,使用Null Object模式。这样的话,在“没有什么”的情况下,就返回Null Object,客户端代码就不用判断是否为空了。但是,这种做法也有一些问题。首先,我们肯定要为Null Object编写代码,而且,如果我们想大规模应用这个模式,我们要为几乎每个类编写Null Object。
幸好,我们还有另外一种选择:Optional。Optional是对可以为空的对象进行的封装,它实现起来并不复杂。在某些语言里,比如Scala,Optional实现成了语言的一部分。而对于Java程序员而言,Guava为我们提供了Optional的支持。闲言少叙,先来如何使用Optional,完成前面的那段代码。
Optional person = people.find("John Smith");if (person.isPresent()) { person.get().doSomething();}
这里如果isPresent()返回false,说明这是个空对象,否则,我们就可以把其中的内容取出来做自己想做的操作了。
如果你期待的是代码量的减少,恐怕这里要让你失望了。单从代码量上来说,Optional甚至比原来的代码还多。但好处在于,你绝对不会忘记判空,因为这里我们得到的不是Person类的对象,而是Optional。
看完了客户端代码,我们再来看看怎样创建一个Optional对象,基本的规则很简单:
- 如果我们知道自己要封装的对象是一个空对象,可以用 Optional.absent();
- 如果封装的对象是一个非空对象,则可以用 Optional.of(obj);
- 如果不知道对象是否为空,就这样创建创建 Optional.fromNullable(obj);
有时候,当一个对象为null的时候,我们并不是简单的忽略,而是给出一个缺省值,比如找不到这个人,任务就交给经理来做。使用Optional可以很容易地做到这一点,以上面的代码为例:
Optional person = people.find("John Smith"); person.or(manager).doSomething()
说白了,Optinal是给了我们一个更有意义的“空”。
- 反射突破private限制
我们都知道,private的域和方法是只能在本类中访问的,但通过反射可以突破这一点
class HidenPerson{
private String name="jack";
private final String sex;
private String address="BeiJing";
public HidenPerson(String name, String address,String sex) {
this.name = name;
this.address = address;
this.sex=sex;
}
private void whoAmI() {
System.out.println(name+" | "+sex+" | "+address);
}
}
public class Main {
public static void main(String[] args) throws Exception {
HidenPerson hidenPerson=new HidenPerson("jack","BeiJing","boy");
Class hidenPersonType=HidenPerson.class;
//私有域和方法都不能在别的类中访问
// System.out.println(hidenPerson.name); compile error
// System.out.println(hidenPerson.sex); compile error
// System.out.println(hidenPerson.whoAmI()); compile error
//反射访问私有方法
Method m=hidenPersonType.getDeclaredMethod("whoAmI");
m.setAccessible(true); //设置该方法可访问
m.invoke(hidenPerson);
//反射访问并改变私有域
Field name=hidenPersonType.getDeclaredField("name");
name.setAccessible(true);
name.set(hidenPerson, "you are not jack");
m.invoke(hidenPerson);
//反射可访问私有final域,在使用构造器初始化域的情况下可以更改它;如果在声明sex时就赋值,则无法更改
Field sex=hidenPersonType.getDeclaredField("sex");
sex.setAccessible(true);
sex.set(hidenPerson, "gril");
m.invoke(hidenPerson);
}
}/*output:
jack | boy | BeiJing
you are not jack | boy | BeiJing
you are not jack | gril | BeiJing
*/
这段颠覆了我的三观,原来private的域和方法也不是安全的!而且,即使发布编译后的代码,也可以使用反编译命令javap -private 类名
轻松显示所有成员出来。或者使用反编译工具jd-gui更方便。
另外书中说明的是final的域是安全的,反射也不能更改。但笔者发现在使用构造器初始化域的情况下可以更改。(如上代码)
而如果声明时赋值则更改无效。private final String sex="boy";
。原因不明。