很多前辈,老师,面试官考校Java菜鸟的时候时常会说:“面向对象的三大特性是什么?” ‘封装,继承,多态“,看过Java的各种教科书扉页的人相信都能轻松答出来吧。
但是所谓三特性是为何出现?为何如此重要?
----------------------------------------------------------------
复用类:顾名思义,便是使用类而不破坏现有的程序代码。Bruce书中给出的话很直观:
一、继承和组合
1、只需在新的类中产生现有类的对象,由于新的类是由现有类的对象所”组成“,所以称为”组合“,当年我学高中化学的时候,我就在”组成“和”构成“上的定义纠结了许久。
【构成仅用于形容精细的,微小的,精微的,微观的,具体的,精密的,针对性强的事物。 组成:我们说一个大的机器由几个部件组成,而不能说由几个部件构成】
所以就像是这样
public class Source{
private int id;
private String name;
private VeryBigObject fat;
private ....;
}
所谓组成而已,这个在无尽的娱乐编程阶段已经写得足够多了。
2、这种方法更细致一些,他们按照现有类的类型来创建新类。无需改变现有类的形式,采用现有类的”形式“并向其中添加新代码,这种神奇的形式被称为继承。extends。
一些比较基本的概念:
构造过程是从基类向外扩散的,所以基类在导出类构造器可以访问他之前,就已经完成了初始化。
带参数的构造器必须显式调用super(type1 param1,....);
3、第三种关系俗称代理(Proxy),Bruce:"这是继承和组合之间的中庸之道。我们将一个成员对象置于所要构造的类中,但同时在新类中暴露了该成员的所有方法"。
给出的例子
public 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() {}
}
他包含多个控制飞船的方法,类似于控制器,接下来我们应当构造飞船的本身的实体类
public class SpaceShip extends SpaceShipControls {
private String name;
public SpaceShip(String name) { this.name = name; }
public String toString() { return name; }
public static void main(String[] args) {
SpaceShip protector = new SpaceShip("NSEA Protector");
protector.forward(100);
}
}
更准确的讲,此时SpaceShip并非是真正的Controls类型,同时SpaceShipControls方法都在SpaceShip中暴露了出来。而代理的方法如下
public class SpaceShipDelegation {
private String name;
private SpaceShipControls controls =
new SpaceShipControls();
public SpaceShipDelegation(String name) {
this.name = name;
}
// 代理方法
public void back(int velocity) {
controls.back(velocity);
}
public void down(int velocity) {
controls.down(velocity);
}
public void forward(int velocity) {
controls.forward(velocity);
}
public void left(int velocity) {
controls.left(velocity);
}
public void right(int velocity) {
controls.right(velocity);
}
public void turboBoost() {
controls.turboBoost();
}
public void up(int velocity) {
controls.up(velocity);
}
public static void main(String[] args) {
SpaceShipDelegation protector =
new SpaceShipDelegation("NSEA Protector");
protector.forward(100);
}
}
看起来麻烦了很多,但是这种方法可以将control对象很好的"转递",而其接口由此也就和使用继承得到的接口相同了。
实际上我看到代理的时候并不觉得这是什么有用的东西。也许会在类型信息上有些作用?嘛嘛……
接着就是所谓的在组合和继承之间选择
组合是显式的这样做,而继承是隐式的这样做。
Bruce是这样建议的:组合技术通常用于想在新类中使用现有类的功能而非接口这样的情形。即在新类中嵌入(或者说放置)某个对象,然后使用它,但是新类的用户看到的只是为新类所定义的接口,而非所嵌入对象的接口。
所以我个人的理解就是 在组合情况下,当前类并不需要组合基类的前世今生买单,而是仅仅将其作为一个工具,private 对象类型 名字; 仅仅这样定义就可以了,这仅仅是一个Has-a关系(有一个)。
而关于继承,Bruce说道:使用某个现有类,并开发它的一个特殊版本。这是一个is-a关系(是一个)。
二、protected关键字
一直以来我都觉得他很鸡肋,没有用的到的地方。但是它的作用并不小:就类用户而言 他是private的,而对于此类的派生类(包括同一个包的类)而言,它是可以访问的。
这是保留“更改底层实现“的一种权力。例如说机器人的下属各个不同种类的机器人的类型名字,在抽象类定义的时候就有必要将其声明为protected。
三、向上转型
List <Object> list = new ArrayList<Object>(); 这是种什么东西? 对象上转型~!刚刚学习编程的我这样回答道。
但是为啥要上转型?Bruce的概括很准确:为了表现新类和基类之间的关系。
例如如下的代码:
class Instrument {
public void play() {}
static void tune(Instrument i) {
// ...
i.play();
}
}
// Wind objects are instruments
// because they have the same interface:
public class Wind extends Instrument {
public static void main(String[] args) {
Wind flute = new Wind();
Instrument.tune(flute); //上转型
}
}
将导出类转换为基类,以便实现公用(或者是早就定义好了的方法),这便是对象上转型的作用。当我们出现大量相同种类的方法时。
不可能依照这几个相似类型对象而重载方法N多次,此时便是对象上转型发挥威力的时候了
import static net.mindview.util.Print.*;
class Stringed extends Instrument {
public void play(Note n) {
print("Stringed.play() " + n);
}
}
class Brass extends Instrument {
public void play(Note n) {
print("Brass.play() " + n);
}
}
public class Music2 {
public static void tune(Wind i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Stringed i) {
i.play(Note.MIDDLE_C);
}
public static void tune(Brass i) {
i.play(Note.MIDDLE_C);
}
public static void main(String[] args) {
Wind flute = new Wind();
Stringed violin = new Stringed();
Brass frenchHorn = new Brass();
tune(flute); // No upcasting
tune(violin);
tune(frenchHorn);
}
}
多冗余啊?!但是如果我们这样
public static void tune(Instrument i){
<span style="white-space:pre"> </span>i.play(Note.MIDDLE_C);
}
就可以了~!简直天才!咳咳。具体的上转型的问题第八章多态还会继续说明。
四、final关键字
final关键字经常被误用(说的就是我),使用final关键字无非就是为了不想改变:只有两种理由”设计或效率“
final关键字用于:1、一个永不改变的编译时常量。2、一个在运行时被构造器或其他什么东西定义的值,而我们不希望他改变。
对于空白final来说,必须要在构造器里进行声明。否则就会报编译期间错误。
对于基本类型来说,final很直观
但是如果我们final一个对象。我们无法把当前的对象改为指向另一个对象,但是我们却可以修改对象本身。
例如说你娶了个死肥死肥由黑又胖满脸大包丑女。婚都结完了,房子都买完了,你才发现对方不化妆竟然连头猪都看不下去,还没法离婚,这就是final了。
但是哥们你可以带她去做美容,去做健身,最后变成个又白身材又好的美女,此时她还是她,对象的引用没有改变,而自身的属性却改变了。
这一性质同样适用于数组,它也是对象。
而关于误用final 基于现在的情况,JVM虚拟机基于hotspot技术可以优化那些内嵌调用。所以就不需要final来进行优化了。只有在要明确禁止覆盖的时候才设置final比较明智。
五、初始化和类的加载
Java的对象基本上都是惰性加载,即在该文件只在需要程序代码时才会被加载。一般的来说,类的代码在初次使用时才加载。但是当访问static域时,也会发生加载
实际上,构造器也是static方法。更准确的讲,类是在任何static成员被访问时加载的。
熟悉包括继承在内的初始化全过程。
import static net.mindview.util.Print.*;
class Insect {
private int i = 9;
protected int j;
Insect() {
print("i = " + i + ", j = " + j);
j = 39;
}
private static int x1 =
printInit("static Insect.x1 initialized");
static int printInit(String s) {
print(s);
return 47;
}
}
public class Beetle extends Insect {
private int k = printInit("Beetle.k initialized");
public Beetle() {
print("k = " + k);
print("j = " + j);
}
private static int x2 =
printInit("static Beetle.x2 initialized");
public static void main(String[] args) {
print("Beetle constructor");
Beetle b = new Beetle();
}
}
/* Output:
static Insect.x1 initialized
static Beetle.x2 initialized
Beetle constructor
i = 9, j = 0
Beetle.k initialized
k = 47
j = 39
*///:~
从基类中获得加载,一层一层过去,根基类的static初始化,然后是下一个导出类,一次类推。这种方式很重要。
然后类加载完毕,对象可以被创建。基本类型将被设为默认值,对象=null,然后基类的构造器将被调用。和导出类的构造器一样,将会以相同的顺序经历相同的过程
最后实例变量将会按次序初始化。最后构造器的其余部分将执行。
====================================================================================================================================
2014年6月18日 Hope6537