构造器初始化
初始化顺序
先根据成员变量定义顺序初始化成员变量,再调用构造函数。
package constructorInitialization;
class Window{
Window(int marker){
System.out.println("Window(" + marker +")");
}
}
class House{
Window w1 = new Window(1);
House(){
System.out.println("House()");
w2 = new Window(22); // reinitialize
}
Window w2 = new Window(2);
}
public class OrderOfInitialization {
public static void main(String[] args) {
new House();
}
}
静态数据的初始化
先初始化静态成员变量(只在第一次使用类的时候搞一次),再初始化非静态成员变量,最后执行构造函数。
package constructorInitialization;
class Bowl{
Bowl(int marker){
System.out.println("Bowl(" + marker + ")");
}
}
class Table{
static Bowl bowl1 = new Bowl(1);
Table(){
System.out.println("Table()");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard{
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
// 要调用 main 方法,就得先加载 StaticInitialization 类,并先完成初始化。
public static void main(String[] args) {
System.out.println("Creating new Cupboard() in main...");
new Cupboard();
System.out.println("Creating new Cupboard() in main...");
new Cupboard();
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
静态初始化块
package constructorInitialization;
class Cup{
Cup(int marker){
System.out.println("Cup(" + marker + ")");
}
}
class Cups{
static {
cup = new Cup(1); // 2.调用构造函数初始化
}
static Cup cup; // 1. 初始化为 null
Cups(){
System.out.println("Cups()");
}
}
public class ExplicitInitialization {
public static void main(String[] args) {
new Cups();
new Cups(); // 静态代码块只会搞一次,静态成员也只在第一次搞的时候初始化一次
}
}
普通初始化块
package constructorInitialization;
class Mug{
Mug(int marker){
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs{
{
mug = new Mug(1);
}
Mug mug;
Mugs(){
System.out.println("Mugs()");
}
public static void main(String[] args) {
new Mugs();
new Mugs();
}
}
可变形参列表
下面似乎方法重载的很好!如果调用 f() 编译器就傻眼了!
package constructorInitialization;
public class OverloadVariableArguments {
static void f(Character... args){
System.out.println("f(Character... args)");
for(Character character : args){
System.out.print(character + " ");
}
System.out.println();
}
static void f(Integer... args){
System.out.println("f(Integer... args)");
for(Integer integer : args){
System.out.print(integer + " ");
}
System.out.println();
}
public static void main(String[] args) {
f(1, 2, 3);
f('a', 'b', 'c');
}
}
下面给其中一个方法添加非可变参数似乎可以重载好!然报错如下图!
package constructorInitialization;
public class OverloadVariableArguments {
static void f(Character... args){
System.out.println("f(Character... args)");
for(Character character : args){
System.out.print(character + " ");
}
System.out.println();
}
static void f(int i, Character... args){
System.out.println("f(int i, Character... args)");
System.out.println("i="+i);
for(Character integer : args){
System.out.print(integer + " ");
}
System.out.println();
}
public static void main(String[] args) {
f(1, 'a');
f('a', 'b', 'c');
}
}
给每个方法都添加非可变参数,可完美重载!!
package constructorInitialization;
public class OverloadVariableArguments {
static void f(float f, Character... args){
System.out.println("f(float f, Character... args)");
System.out.println("f="+f);
for(Character character : args){
System.out.print(character + " ");
}
System.out.println();
}
static void f(int i, Character... args){
System.out.println("f(int i, Character... args)");
System.out.println("i="+i);
for(Character integer : args){
System.out.print(integer + " ");
}
System.out.println();
}
public static void main(String[] args) {
f(1, 'a', 'b', 'c');
f(1.0f, 'a', 'b', 'c');
f('a', 'b', 'c');
}
}
代理
组合和继承之间的中庸之道!可以只暴露被组合/继承对象的部分方法。
package constructorInitialization;
class SpaceshipControls{
void up(int velocity){
}
void down(int velocity){
}
void left(int velocity){
}
void right(int velocity){
}
void forward(int velocity){
}
void back(int velocity){
}
void turboBoost(int velocity){
}
}
public class SpaceshipDelegation {
private String name;
private SpaceshipControls spaceshipControls = new SpaceshipControls();
private SpaceshipDelegation(String name){
this.name = name;
}
// 可选择性地暴露 SpaceshipControls 的部分方法
public void up(int velocity){
spaceshipControls.up(velocity);
}
public void forward(int velocity){
spaceshipControls.forward(velocity);
}
public static void main(String[] args) {
SpaceshipDelegation spaceshipDelegation = new SpaceshipDelegation("xxx");
spaceshipDelegation.forward(10);
}
}
继承与初始化
每个类的编译代码都存在于它自己的独立文件中,该文件只在需要使用程序代码时才会被加载。一般来说,类的代码在初次使用时才加载。这通常是指加载发生于创建类的第一个对象之时,但是当访问 static 域或方法时也会发生加载。
package constructorInitialization;
class Insect{
private int i = 9;
protected int j;
Insect(){
System.out.println("i="+i+", "+"j="+j);
j = 39;
}
private static int x1 = printInit("static Insect.x1 initialized"); // step1, x1=47
static int printInit(String s){
System.out.println(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle(){
System.out.println("k="+k+", j="+j);
}
private static int x2 = printInit("static Beetle.x2 initialized"); // step2, x2=47
public static void main(String[] args){
System.out.println("Beetle constructor");
Beetle b = new Beetle();
}
}
多态
方法调用绑定和向上转型
将一个方法调用同一个方法主体关联起来成为绑定。若在程序执行前进行绑定(编译器和连接程序实现),叫做前期绑定(C 语言就是前期绑定)。运行时根据对象的类型进行绑定就是后期绑定(动态绑定)。
Java 中除了 static 方法和 final 方法(private 方法属于 final 方法)之外,其他所有方法都属于后期绑定。
package constructorInitialization;
import java.util.Random;
class Shape{
public void draw(){
System.out.println("Shape.draw()");
}
}
class Triangle extends Shape{
public void draw(){
System.out.println("Triangle.draw()");
}
}
class Circle extends Shape{
public void draw(){
System.out.println("Circle.draw()");
}
}
class RandomShapeGenerator{
private Random random = new Random(47);
public Shape next(){
switch (random.nextInt(2)){
default:
case 0: return new Triangle();
case 1: return new Circle();
}
}
}
public class Shapes {
private static RandomShapeGenerator gen = new RandomShapeGenerator();
public static void main(String[] args) {
Shape[] s = new Shape[6];
for(int i = 0; i < s.length; i++){
s[i] = gen.next();
s[i].draw();
}
}
}
注意1:试图覆盖私有方法
package constructorInitialization;
public class PrivateOverride {
private void f(){
System.out.println("private void f()");
}
public static void main(String[] args) {
PrivateOverride po = new Derived();
po.f(); // d
}
}
class Derived extends PrivateOverride{
// 该方法不会覆盖 PrivateOverride 的 f() (private)
public void f(){
System.out.println("public void f()");
}
}
注意:域(成员变量)不具有多态性
package constructorInitialization;
class Super{
public int filed = 0;
public int getFiled(){
return filed;
}
}
class Sub extends Super{
public int field = 1;
public int getField(){
return field;
}
public int getSuperField(){
return super.filed;
}
}
public class FieldAccess {
public static void main(String[] args) {
Super sup = new Sub();
System.out.println("sup.field="+sup.filed+", sup.getField()="+sup.getFiled());
Sub sub = new Sub();
System.out.println("sub.field="+sub.field+", sub.getField()="+sub.getField()+", sub.getSuperFiled()="+sub.getSuperField());
}
}
注意:静态方法不具有多态性
package constructorInitialization;
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());
}
}
构造器和多态性
构造器的调用顺序
package constructorInitialization;
import sun.util.resources.cldr.lu.CalendarData_lu_CD;
class Meal{
Meal(){
System.out.println("Meal()");
}
}
class Bread{
Bread(){
System.out.println("Bread()");
}
}
class Cheese{
Cheese(){
System.out.println("Cheese()");
}
}
class Lettuce{
Lettuce(){
System.out.println("Lettuce()");
}
}
class Lunch extends Meal{
Lunch(){
System.out.println("Lunch()");
}
}
class PortableLunch extends Lunch{
PortableLunch(){
System.out.println("PortableLuhch()");
}
}
public class Sandwich extends PortableLunch{
private Bread bread = new Bread();
private Cheese cheese = new Cheese();
private Lettuce lettuce = new Lettuce();
public Sandwich(){
System.out.println("Sandwich()");
}
public static void main(String[] args) {
new Sandwich();
}
}
继承与清理
dispose 的顺序与初始化顺序相反,类似于 C++ 的析构函数。子类可能需要调用基类的方法来销毁自己,所以先销毁子类,再销毁父类。
package constructorInitialization;
class Characteristic{
private String s;
Characteristic(String s){
this.s = s;
System.out.println("Creating Characteristic " + s);
}
protected void dispose(){
System.out.println("disposing Characteristic " + s);
}
}
class Description{
private String s;
Description(String s){
this.s = s;
System.out.println("Creating Description " + s);
}
protected void dispose(){
System.out.println("disposing Description " + s);
}
}
class LivingCreature{
private Characteristic p = new Characteristic("is live");
private Description t = new Description("Basic Living Creature");
LivingCreature(){
System.out.println("LivingCreature()");
}
protected void dispose(){
System.out.println("LivingCreature dispose");
p.dispose();
t.dispose();
}
}
class Animal extends LivingCreature{
private Characteristic p = new Characteristic("has heart");
private Description t = new Description("Animal not Plant");
Animal(){
System.out.println("Animal()");
}
protected void dispose(){
System.out.println("Animal dispose");
p.dispose();
t.dispose();
super.dispose();
}
}
class Amphibian extends Animal{
private Characteristic p = new Characteristic("can live in water");
private Description t = new Description("Both water and land");
Amphibian(){
System.out.println("Amphibian()");
}
protected void dispose(){
System.out.println("Amphibian dispose");
p.dispose();
t.dispose();
super.dispose();
}
}
public class Frog extends Amphibian{
private Characteristic p = new Characteristic("croak");
private Description t = new Description("Eats Bugs");
public Frog(){
System.out.println("Frog()");
}
protected void dispose(){
System.out.println("Frog dispose");
p.dispose();
t.dispose();
super.dispose();
}
public static void main(String[] args) {
Frog frog = new Frog();
System.out.println("Bye");
frog.dispose();
}
}
如果多对象共享某个对象,就不能用上述方式销毁了!应该引用计数。
package constructorInitialization;
class Shared{
private int refcount = 0;
private static long counter = 0;
private final long id = counter++;
public Shared(){
System.out.println("Creating " + this);
}
public void addRef(){
refcount++;
}
protected void dispose(){
if(--refcount == 0){
System.out.println("disposing " + this);
}
}
@Override
public String toString() {
return "Shared " + id;
}
}
class Composing{
private Shared shared;
private static long counter = 0;
private final long id = counter++;
public Composing(Shared shared){
System.out.println("Creating " + this);
this.shared = shared;
this.shared.addRef();
}
protected void dispose(){
System.out.println("disposing " + this);
shared.dispose();
}
@Override
public String toString() {
return "Composing " + id;
}
}
public class ReferenceCounting {
public static void main(String[] args) {
Shared shared = new Shared();
Composing[] composings = {
new Composing(shared), new Composing(shared), new Composing(shared)};
for(Composing composing : composings){
composing.dispose();
}
}
}
构造器内部调用动态绑定方法
如果调用构造器内部的一个动态绑定方法,就要用到那个方法的被覆盖后的定义。然而,这个调用的效果可能相当难预料,因为被覆盖的方法在对象被完全构造之前就会被调用,可能造成一些难于发现的隐藏错误。
编写构造器有一条有效的准则:“用尽可能简单的方法使对象进入正常状态;如果可以的话,避免调用其他方法”。在构造器内唯一能够安全调用的那些方法是基类中的final/private 方法,这些方法不能被覆盖,因此也就不会出现下面代码令人惊讶的结果!
package constructorInitialization;
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(6);
}
}
向下转型
package constructorInitialization;
class Useful{
public void f(){
}
}
class MoreUseful extends Useful{
public void f(){
}
public void g(){
}
}
public class RTTI{
public static void main(String[] args) {
Useful[] x = {
new Useful(), new MoreUseful()};
x[0].f();
x[1].f();
// x[1].g(); // 编译错误!这样只能调用子类覆盖的方法
((MoreUseful)x[1]).g(); // 向下转型!
// ((MoreUseful)x[0]).g(); // RTTI
}
}
类型信息
Java 在运行时识别类的信息的方式主要有 RTTI(Run-Time Type Identification)和反射,RTTI 假定在编译时已经知道了所有的类型,反射允许我们在运行时发现和使用类的信息。
RTTI
package type;
import java.util.Arrays;
import java.util.List;
abstract class Shape {
void draw() {
System.out.println(this + ".draw()");
}
public abstract String toString();
}
class Circle extends Shape {
public String toString(){
return "Circle";
}
}
class Square extends Shape{
public String toString(){
return "Square";
}
}
class Triangle extends Shape{
public String toString(){
return "Triangle";
}
}
public class Shapes {
public static void main(String[] args) {
List<Shape> shapeList = Arrays.asList(new Circle(), new Square(), new Triangle());
for(Shape shape : shapeList){
shape.draw();
}
}
}
Class 对象
要理解 RTTI 在 Java 中的工作原理,首先必须知道类型信息在运行时是如何表示的。这项工作是由 Class 对象完成的,它包含了与类有关的信息。事实上,Class 对象就是用来创建类的所有“常规”对象的。Java 使用 Class 对象来执行其 RTTI,即使你正在执行的是类似转型这样的操作。
每个类都有一个 Class 对象,每编译一个新类就会产生一个 Class 对象(更恰当地说,是被保存在一个同名的 .class 文件中)。所有的类都是在第一次使用时,类加载器负责动态加载(非加载整个类,类各部分分别用到时才加载)到 JVM 中。当程序创建第一个对类的静态成员的引用时,就会加载这个类(这表明构造器是隐式的 static 方法)。
一旦某个类的 Class 对象载入内存,它就被用来创建这个类的所有对象。
package type;
class Candy {
static {
System.out.println("Loading Candy");
}
}
class Gum {
static {
System.out.println("Loading Gum"); // Gum.class 不会执行这句
}
}
public class SweetShop {
public static void main(String[] args) {
new Candy();
try{
Class clazz = Class.forName("type.Gum");
System.out.println(clazz.getSimpleName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
为了使用类而做的准备工作包含三个步骤:
- 加载。类加载器查找字节码,并利用字节码创建一个 Class 对象。
- 链接。验证字节码。为静态域分配内存,并且如果必需的话,将解析这个类创建的对其它类的所有引用。
- 初始化。如果该类有父类,则对父类初始化,执行静态初始化块。
package type;
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) throws Exception{
Class initable = Initable.class;
System.out.println(Initable.staticFinal);
System.out.println(Initable.staticFinal2);
System.out.println(Initable2.staticNonFinal);
Class initable3 = Class.forName("type.Initable3");
System.out.println(Initable3.staticNonFinal);
}
}
instanceof 与 Class 的不同
instanceof 和 isInstance() 的结果一致。equals() 和 ==
的结果一致。instanceof 保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”,而如果用==
比较实际的 Class 对象,就没有考虑继承——它或者是这个确切的类型,或者不是。
package type;
class Base {
}
class Derived extends Base {
}
public class FamilyVSExactType {
static void test (Object x) {
System.out.println("Testing x of type " + x.getClass());
System.out.println("x instanceof Base " + (x instanceof Base));
System.out.println("x instanceof Derived " + (x instanceof Derived));
System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));
System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));
System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));
System.out.println("x.getClass().equals(Base.class) " + (x.getClass().equals(Base.class)));
System.out.println("x.getClass().equals(Derived.class) " + (x.getClass().equals(Derived.class)));
}
public static void main(String[] args) {
test(new Base());
System.out.println();
test(new Derived());
}
}
反射:运行时的类信息
RTTI 和反射真正的区别在于,对RTTI来说,编译器在编译时打开和检查 .class 文件(换句话说,我们可以用”普通“方式调用对象的所有方法);而对于反射来说, .class 文件在编译时是不可获取的,所以是在运行时打开和检查 .class 文件。
某对象的确切类型在编译时已知,才可通过 RTTI 识别对应的类型。但假设获取了一个指向某个并不在你的程序空间中的对象的引用,事实上,在编译时你的程序根本没法获知这个对象所属的类。例如,假设你从磁盘文件或网络连接中获取一串字节,并且你被告知这些字节代表了一个类,既然这个类在编译器为你的程序生成代码之后很久才会出现,那么怎样才能使用这样的类呢?反射。
书中举了个代码例子,大概就是在运行时为 main 方法传入 args 参数,显然,在编译的时候 args 参数是不知的,编译器无法知道 args 的类型,只有在运行时传入了 args 参数后,才可查看 args 参数对应的是什么类,这里就要反射!先获取 Class 对象,从而获取这个类的构造器、方法、属性等信息。
动态代理
下面展示一个代理的简单例子,SimpleProxy 就是 RealObject 的代理,其实只是对 RealObject 的简单封装!
package type;
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 proxied;
public SimpleProxy(Interface proxied) {
this.proxied = proxied;
}
@Override
public void doSomething() {
System.out.println("SimpleProxy doSomething");
proxied.doSomething();
}
@Override
public void somethingElse(String arg) {
System.out.println("SimpleProxy somethingElse " + arg);
proxied.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());
System.out.println();
consumer(new SimpleProxy(new RealObject()));
}
}
下面展示 Java 动态代理
package type;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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 DynamicProxyHandler implements InvocationHandler {
private Object proxied;
public DynamicProxyHandler(Object proxied) {
this.proxied = proxied;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("*** proxy: " + proxy.getClass() + ", method: " + method.getName() + ", args " + args);
if(args != null) {
for(Object arg : args) {
System.out.println(arg);
}
}
return method.invoke(proxied, 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);
System.out.println();
Interface proxy = (Interface) Proxy.newProxyInstance(
Interface.class.getClassLoader(),
new Class[]{
Interface.class},
new DynamicProxyHandler(real)
);
consumer(proxy);
}
}