Java编程思想重点笔记(Java开发必看)
Java编程思想,Java学习必读经典,无论是初学者仍是大牛都值得一读,这里总结书中的重点知识,这些知识不只常常出如今各大知名公司的笔试面 试过程当中,并且在大型项目开发中也是经常使用的知识,既有简单的概念理解题(好比is-a关系和has-a关系的区别),也有深刻的涉及RTTI和JVM底层 反编译知识。java
final方法会使编译器生成更有效的代码,这也是为何说声明为final方法能在必定程度上提升性能(效果不明显)。
若是某个方法是静态的,它的行为就不具备多态性:
class StaticSuper {
public static String staticGet() {
return "Base staticGet()";
}程序员
public String dynamicGet() { return "Base dynamicGet()"; }
}算法
class StaticSub extends StaticSuper {
public static String staticGet() {
return "Derived staticGet()";
}编程
public String dynamicGet() { return "Derived dynamicGet()"; }
}设计模式
public class StaticPolymorphism {数组
public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); }
}安全
输出:
Base staticGet()
Derived dynamicGet()cookie
构造函数并不具备多态性,它们其实是static方法,只不过该static声明是隐式的。所以,构造函数不可以被override。网络
在父类构造函数内部调用具备多态行为的函数将致使没法预测的结果,由于此时子类对象还没初始化,此时调用子类方法不会获得咱们想要的结果。app
class Glyph {
void draw() {
System.out.println("Glyph.draw()");
}
Glyph() {
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after 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); }
}
输出:
Glyph() before draw()
RoundGlyph.draw(). radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(). radius = 5
为何会这样输出?这就要明确掌握Java中构造函数的调用顺序:
(1)在其余任何事物发生以前,将分配给对象的存储空间初始化成二进制0;
(2)调用基类构造函数。从根开始递归下去,由于多态性此时调用子类覆盖后的draw()方法(要在调用RoundGlyph构造函数以前调用),因为步骤1的缘故,咱们此时会发现radius的值为0;
(3)按声明顺序调用成员的初始化方法;
(4)最后调用子类的构造函数。
只有非private方法才能够被覆盖,可是还须要密切注意覆盖private方法的现象,这时虽然编译器不会报错,可是也不会按照咱们所指望的来执行,即覆盖private方法对子类来讲是一个新的方法而非重载方法。所以,在子类中,新方法名最好不要与基类的private方法采起同一名字(虽然不要紧,但容易误解,觉得可以覆盖基类的private方法)。
Java类中属性域的访问操做都由编译器解析,所以不是多态的。父类和子类的同名属性都会分配不一样的存储空间,以下:
// Direct field access is determined at compile time.
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 FieldAccess {
public static void main(String[] args) { Super sup = new Sub(); System.out.println("sup.filed = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.filed = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); }
}
输出:
sup.filed = 0, sup.getField() = 1
sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0
Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并不是Super版本的field域,所以为了获得Super.field,必须显式地指明super.field。
基类和子类有着彻底相同的接口,这样向上转型时永远不须要知道正在处理的对象的确切类型,这经过多态来实现。
is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,可是他还具备由额外方法实现的其余特性。
缺点就是子类中接口的扩展部分不能被基类访问,所以一旦向上转型,就不能调用那些新方法。
一种是“传统的”RTTI,它假定咱们在编译时已经知道了全部的类型,好比Shape s = (Shape)s1;
另外一种是“反射”机制,它运行咱们在运行时发现和使用类的信息,即便用Class.forName()。
其实还有第三种形式,就是关键字instanceof,它返回一个bool值,它保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”。而若是用==或equals比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。
工做原理
要理解RTTI在Java中的工做原理,首先必须知道类型信息在运行时是如何表示的,这项工做是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。Java送Class对象来执行其RTTI,使用类加载器的子系统实现。
不管什么时候,只要你想在运行时使用类型信息,就必须首先得到对恰当的Class对象的引用,获取方式有三种:
(1)若是你没有持有该类型的对象,则Class.forName()就是实现此功能的便捷途,由于它不须要对象信息;
(2)若是你已经拥有了一个感兴趣的类型的对象,那就能够经过调用getClass()方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。Class包含颇有有用的方法,好比:
package rtti;
interface HasBatteries{}
interface WaterProof{}
interface Shoots{}
class Toy {
Toy() {}
Toy(int i) {}
}
class FancyToy extends Toy
implements HasBatteries, WaterProof, Shoots {
FancyToy() {
super(1);
}
}
public class RTTITest {
static void printInfo(Class cc) { System.out.println("Class name: " + cc.getName() + ", is interface? [" + cc.isInterface() + "]"); System.out.println("Simple name: " + cc.getSimpleName()); System.out.println("Canonical name: " + cc.getCanonicalName()); } public static void main(String[] args) { Class c = null; try { c = Class.forName("rtti.FancyToy"); // 必须是全限定名(包名+类名) } catch(ClassNotFoundException e) { System.out.println("Can't find FancyToy"); System.exit(1); } printInfo(c); for(Class face : c.getInterfaces()) { printInfo(face); } Class up = c.getSuperclass(); Object obj = null; try { // Requires default constructor. obj = up.newInstance(); } catch (InstantiationException e) { System.out.println("Can't Instantiate"); System.exit(1); } catch (IllegalAccessException e) { System.out.println("Can't access"); System.exit(1); } printInfo(obj.getClass()); }
}
输出:
Class name: rtti.FancyToy, is interface? [false]
Simple name: FancyToy
Canonical name: rtti.FancyToy
Class name: rtti.HasBatteries, is interface? [true]
Simple name: HasBatteries
Canonical name: rtti.HasBatteries
Class name: rtti.WaterProof, is interface? [true]
Simple name: WaterProof
Canonical name: rtti.WaterProof
Class name: rtti.Shoots, is interface? [true]
Simple name: Shoots
Canonical name: rtti.Shoots
Class name: rtti.Toy, is interface? [false]
Simple name: Toy
Canonical name: rtti.Toy
(3)Java还提供了另外一种方法来生成对Class对象的引用,即便用类字面常量。好比上面的就像这样:FancyToy.class;来引用。
这样作不只更简单,并且更安全,由于它在编译时就会受到检查(所以不须要置于try语句块中),而且它根除了对forName方法的引用,因此也更高效。类字面常量不只能够应用于普通的类,也能够应用于接口、数组以及基本数据类型。
注意:当使用“.class”来建立对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操做)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。
为了使用类而作的准备工做实际包含三个步骤:
这一点很是重要,下面经过一个实例来讲明这二者的区别:
package rtti;
import java.util.Random;
class Initable {
static final int staticFinal = 47;
static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);
static { System.out.println("Initializing Initable"); }
}
class Initable2 {
static int staticNonFinal = 147;
static { System.out.println("Initializing Initable2"); }
}
class Initable3 {
static int staticNonFinal = 74;
static { System.out.println("Initializing Initable3"); }
}
public class ClassInitialization {
public static Random rand = new Random(47); public static void main(String[] args) { // Does not trigger initialization Class initable = Initable.class; System.out.println("After creating Initable ref"); // Does not trigger initialization System.out.println(Initable.staticFinal); // Does trigger initialization(rand() is static method) System.out.println(Initable.staticFinal2); // Does trigger initialization(not final) System.out.println(Initable2.staticNonFinal); try { Class initable3 = Class.forName("rtti.Initable3"); } catch (ClassNotFoundException e) { System.out.println("Can't find Initable3"); System.exit(1); } System.out.println("After creating Initable3 ref"); System.out.println(Initable3.staticNonFinal); }
}
输出:
After creating Initable ref
47
Initializing Initable
258
Initializing Initable2
147
Initializing Initable3
After creating Initable3 ref
74
RTTI的限制?如何突破? — 反射机制
若是不知道某个对象的确切类型,RTTI能够告诉你,可是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,也就是在编译时,编译器必须知道全部要经过RTTI来处理的类。
能够突破这个限制吗?是的,突破它的就是反射机制。
Class类与java.lang.reflect类库一块儿对反射的概念进行了支持,该类库包含了Field、Method以及Constructor类(每一个类都实现了Member接口)。这些类型的对象是由JVM在运行时建立的,用以表示未知类里对应的成员。这样你就可使用Constructor建立新的对象,用get()/set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还能够调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被彻底肯定下来,而在编译时不须要知道任何事情。
当经过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪一个特定的类(就像RTTI那样),在用它作其余事情以前必须先加载那个类的Class对象,所以,那个类的.class文件对于JVM来讲必须是可获取的:要么在本地机器上,要么能够经过网络取得。因此RTTI与反射之间真正的区别只在于:对RTTI来讲,编译器在编译时打开和检查.class文件(也就是能够用普通方法调用对象的全部方法);而对于反射机制来讲,.class文件在编译时是不可获取的,因此是在运行时打开和检查.class文件。
下面的例子是用反射机制打印出一个类的全部方法(包括在基类中定义的方法):
package typeinfo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.regex.Pattern;
// Using reflection to show all the methods of a class.
// even if the methods are defined in the base class.
public class ShowMethods {
private static String usage =
"usage: \n" +
"ShowMethods qualified.class.name\n" +
"To show all methods in class or: \n" +
"ShowMethods qualified.class.name word\n" +
"To search for methods involving 'word'";
private static Pattern p = Pattern.compile("\\w+\\."); public static void main(String[] args) { if(args.length < 1) { System.out.println(usage); System.exit(0); } int lines = 0; try { Class<?> c = Class.forName(args[0]); Method[] methods = c.getMethods(); Constructor[] ctors = c.getConstructors(); if(args.length == 1) { for(Method method : methods) { System.out.println(p.matcher(method.toString()).replaceAll("")); } for(Constructor ctor : ctors) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); } lines = methods.length + ctors.length; } else { for(Method method : methods) { if(method.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(method.toString()).replaceAll("")); lines++; } } for(Constructor ctor : ctors) { if(ctor.toString().indexOf(args[1]) != -1) { System.out.println(p.matcher(ctor.toString()).replaceAll("")); lines++; } } } } catch (ClassNotFoundException e) { System.out.println("No such Class: " + e); } }
}
输出:
public static void main(String[])
public final native void wait(long) throws InterruptedException
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public boolean equals(Object)
public String toString()
public native int hashCode()
public final native Class getClass()
public final native void notify()
public final native void notifyAll()
public ShowMethods()
interface Interface {
void doSomething();
void somethingElse(String arg);
}
class RealObject implements Interface {
@Override public void doSomething() { System.out.println("doSomething."); } @Override public void somethingElse(String arg) { System.out.println("somethingElse " + arg); }
}
class SimpleProxy implements Interface {
private Interface proxy; public SimpleProxy(Interface proxy) { this.proxy = proxy; } @Override public void doSomething() { System.out.println("SimpleProxy doSomething."); proxy.doSomething(); } @Override public void somethingElse(String arg) { System.out.println("SimpleProxy somethingElse " + arg); proxy.somethingElse(arg); }
}
public class SimpleProxyDemo {
public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { consumer(new RealObject()); consumer(new SimpleProxy(new RealObject())); }
}
输出:
doSomething.
somethingElse bonobo
SimpleProxy doSomething.
doSomething.
SimpleProxy somethingElse bonobo
somethingElse bonobo
动态代理
Java的动态代理比代理的思想更向前迈进了一步,由于它能够动态地建立代理并动态地处理对所代理方法的调用。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class DynamicProxyHandler implements InvocationHandler {
private Object proxy; public DynamicProxyHandler(Object proxy) { this.proxy = proxy; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("*** proxy: " + proxy.getClass() + ". method: " + method + ". args: " + args); if(args != null) { for(Object arg : args) System.out.println(" " + arg); } return method.invoke(this.proxy, args); }
}
public class SimpleDynamicProxy {
public static void consumer(Interface iface) { iface.doSomething(); iface.somethingElse("bonobo"); } public static void main(String[] args) { RealObject real = new RealObject(); consumer(real); // insert a proxy and call again: Interface proxy = (Interface)Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[]{ Interface.class }, new DynamicProxyHandler(real)); consumer(proxy); }
}
输出:
doSomething.
somethingElse bonobo
*** proxy: class typeinfo.$Proxy0. method: public abstract void typeinfo.Interface.doSomething(). args: null
doSomething.
*** proxy: class typeinfo.$Proxy0. method: public abstract void typeinfo.Interface.somethingElse(java.lang.String). args: [Ljava.lang.Object;@6a8814e9
bonobo
somethingElse bonobo
即时编译器技术 — JIT
Java虚拟机中有许多附加技术用以提高速度,尤为是与加载器操做相关的,被称为“即时”(Just-In-Time,JIT)编译器的技术。这种技术能够把程序所有或部分翻译成本地机器码(这原本是JVM的工做),程序运行速度所以得以提高。当须要装载某个类时,编译器会先找到其.class文件,而后将该类的字节码装入内存。此时,有两种方案可供选择:
(1)一种就是让即时编译器编译全部代码。但这种作法有两个缺陷:这种加载动做散落在整个程序生命周期内,累加起来要花更多时间;而且会增长可执行代码的长度(字节码要比即时编译器展开后的本地机器码小不少),这将致使页面调度,从而下降程序速度。
(2)另外一种作法称为惰性评估(lazy evaluation),意思是即时编译器只在必要的时候才编译代码,这样,从不会被执行的代码也许就压根不会被JIT所编译。新版JDK中的Java HotSpot技术就采用了相似方法,代码每次被执行的时候都会作一些优化,因此执行的次数越多,它的速度就越快。
访问控制权限
Java访问权限修饰词:public、protected、包访问权限(默认访问权限,有时也称friendly)和private。
包访问权限:当前包中的全部其余类对那个成员具备访问权限,但对于这个包以外的全部类,这个成员倒是private。
protected:继承访问权限。有时基类的建立者会但愿有某个特定成员,把对它的访问权限赋予派生类而不是全部类。这就须要 protected来完成这一工做。protected也提供包访问权限,也就是说,相同包内的其余类均可以访问protected元素。 protected指明“就类用户而言,这是private的,但对于任何继承于此类的导出类或其余任何位于同一个包内的类来讲,它倒是能够访问的”。好比:
基类:
package access.cookie;
public class Cookie {
public Cookie() {
System.out.println("Cookie Constructor");
}
void bite() { // 包访问权限,其它包即便是子类也不能访问它
System.out.println("bite");
}
}
子类:
package access.dessert;
import access.cookie.Cookie;
public class ChocolateChip extends Cookie {
public ChocolateChip() { System.out.println("ChocolateChip constructor"); } public void chomp() { bite(); // error, the method bite() from the type Cookie is not visible }
}
能够发现子类并不能访问基类的包访问权限方法。此时能够将Cookie中的bite指定为public,但这样作全部的人就都有了访问权限,为了只容许子类访问,能够将bite指定为protected便可。
类内全部private 方法都自动成为final。因为咱们不能访问一个private 方法,因此它绝对不会被其余方法覆盖(若强行这样作,编译器会给出错误提示)。可为一个private方法添加final指示符,但却不能为那个方法提供任何额外的含义。
通常来讲,内部类继承自某个类或实现某个接口,内部类的代码操做建立它的外围类的对象。因此能够认为内部类提供了某种进入其外围类的窗口。
内部类最吸引人的缘由是:每一个内部类都能独立地继承自一个(接口的)实现,因此不管外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
若是没有内部类提供的、能够继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了“多重继承”。也就是说,内部类容许继承多个非接口类型。
考虑这样一种情形:若是必须在一个类中以某种方式实现两个接口。因为接口的灵活性,你有两种选择:使用单一类或者使用内部类。但若是拥有的是抽象的类或具体的类,而不是接口,那就只能使用内部类才能实现多重继承。
使用内部类,还能够得到其余一些特性:
String类型 — 不可变
用于String的“+”与“+=”是Java中仅有的两个重载过的操做符,而Java并不容许程序员重载任何操做符。
考虑到效率因素,编译器会对String的屡次+操做进行优化,优化使用StringBuilder操做(javap -c class字节码文件名 命令查看具体优化过程)。这让你以为能够随意使用String对象,反正编译器会为你自动地优化性能。但编译器能优化到什么程度还很差说,不必定能优化到使用StringBuilder代替String相同的效果。好比:
public class WitherStringBuilder {
public String implicit(String[] fields) {
String result = "";
for(int i = 0; i < fields.length; i++)
result += fields[i];
return result;
}
public String explicit(String[] fields) {
StringBuilder result = new StringBuilder();
for(int i = 0; i < fields.length; i++)
result.append(fields[i]);
return result.toString();
}
}
运行javap -c WitherStringBuilder,能够看到两个方法对应的字节码。
implicit方法:
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #16 // String
2: astore_2
3: iconst_0
4: istore_3
5: goto 32
8: new #18 // class java/lang/StringBuilder
11: dup
12: aload_2
13: invokestatic #20 // Method java/lang/String.valueOf:(
Ljava/lang/Object;)Ljava/lang/String;
16: invokespecial #26 // Method java/lang/StringBuilder.”<
init>”:(Ljava/lang/String;)V
19: aload_1
20: iload_3
21: aaload
22: invokevirtual #29 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #33 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
28: astore_2
29: iinc 3, 1
32: iload_3
33: aload_1
34: arraylength
35: if_icmplt 8
38: aload_2
39: areturn
public java.lang.String implicit(java.lang.String[]);
Code:
0: ldc #16 // String
2: astore_2
3: iconst_0
4: istore_3
5: goto 32
8: new #18 // class java/lang/StringBuilder
11: dup
12: aload_2
13: invokestatic #20 // Method java/lang/String.valueOf:(
Ljava/lang/Object;)Ljava/lang/String;
16: invokespecial #26 // Method java/lang/StringBuilder.”<
init>”:(Ljava/lang/String;)V
19: aload_1
20: iload_3
21: aaload
22: invokevirtual #29 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
25: invokevirtual #33 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
28: astore_2
29: iinc 3, 1
32: iload_3
33: aload_1
34: arraylength
35: if_icmplt 8
38: aload_2
39: areturn
能够发现,StringBuilder是在循环以内构造的,这意味着每通过循环一次,就会建立一个新的StringBuilder对象。
explicit方法:
public java.lang.String explicit(java.lang.String[]);
Code:
0: new #18 // class java/lang/StringBuilder
3: dup
4: invokespecial #45 // Method java/lang/StringBuilder.”<
init>”:()V
7: astore_2
8: iconst_0
9: istore_3
10: goto 24
13: aload_2
14: aload_1
15: iload_3
16: aaload
17: invokevirtual #29 // Method java/lang/StringBuilder.ap
pend:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: pop
21: iinc 3, 1
24: iload_3
25: aload_1
26: arraylength
27: if_icmplt 13
30: aload_2
31: invokevirtual #33 // Method java/lang/StringBuilder.to
String:()Ljava/lang/String;
34: areturn
}
能够看到,不只循环部分的代码更简短、更简单,并且它只生成了一个StringBuilder对象。显式的建立StringBuilder还容许你预先为其指定大小。若是你已经知道最终的字符串大概有多长,那预先指定StringBuilder的大小能够避免屡次从新分配缓冲。
所以,当你为一个类重写toString()方法时,若是字符串操做比较简单,那就能够信赖编译器,它会为你合理地构造最终的字符串结果。可是,若是你要 在toString()方法中使用循环,那么最好本身建立一个StringBuilder对象,用它来构造最终的结果。
System.out.printf()和System.out.format()方法模仿自C的printf,能够格式化字符串,二者是彻底等价的。
Java中,全部新的格式化功能都由java.util.Formatter类处理。
String.format()方法参考了C中的sprintf()方法,以生成格式化的String对象,是一个static方法,它接受与Formatter.format()方法同样的参数,但返回一个String对象。当你只需使用format()方法一次的时候,该方法很方便。
import java.util.Arrays;
import java.util.Formatter;
public class SimpleFormat {
public static void main(String[] args) { int x = 5; double y = 5.324667; System.out.printf("Row 1: [%d %f]\n", x, y); System.out.format("Row 1: [%d %f]\n", x, y); Formatter f = new Formatter(System.out); f.format("Row 1: [%d %f]\n", x, y); String str = String.format("Row 1: [%d %f]\n", x, y); System.out.println(str); Integer[][] a = { {1, 2, 3}, {4, 5, 6}, {7, 8, 3}, {9, 10, 6} }; System.out.println(Arrays.deepToString(a)); }
}
实现Externalizable代替实现Serializable接口来对序列化过程进行控制,Externalizable继承了Serializable接口,同时增添了两个方法:writeExternal()和readExternal()。
二者在反序列化时的区别:
这些方法必须含有下列准确的签名:
private void writeObject(ObjectOutputStream stream)
throws IOException;
private void readObject(ObjectInputStream stream)
throws IOException, ClassNotFoundException