Java与面向对象

首先做一点说明:为了复习知识点,也是强迫自己认真读书,打算重新开始总结Java.主要参考<Java编程思想>第4版<Java核心技术 I>第10版.

Java语言是由詹姆斯·高斯林于1995年发明出来的一种编程语言,前身是Oak语言.

任职于太阳微系统的詹姆斯·高斯林等人于1990年代初开发Java语言的雏形,最初被命名为Oak,目标设置在家用电器等小型系统的编程语言,应用在电视机、电话、闹钟、烤面包机等家用电器的控制和通信。由于这些智能化家电的市场需求没有预期的高,Sun公司放弃了该项计划。随着1990年代互联网的发展,Sun公司看见Oak在互联网上应用的前景,于是改造了Oak,于1995年5月以Java的名称正式发布。Java伴随着互联网的迅猛发展而发展,逐渐成为重要的网络编程语言。       --* Java 词条

Java的语言风格接近C++,继承了其面向对象的思想,舍弃了容易出错的指针,改为引用.移除了C++中的运算符重载和多重继承特性,用接口取代;增加垃圾回收器功能。在Java SE 1.5版本中引入了泛型编程、类型安全的枚举、不定长参数和自动装/拆箱特性。

面向对象的部分主要参考<Java编程思想>,在书的基础上自己做一些润色.

第一个面向对象的语言叫做Simula,发行时间是1967年.Smalltalk是第二种,这种语言也是Java的启发语言之一.Smalltalk的设计者:Alan Curtis Kay总结了Smalltalk的五个特性:

1.万物皆为对象.

2.程序是对象的集合,它们通过发送消息来告知彼此所要做的.

3.每个对象都有自己的由其他对象所构成的存储.

4.每个对象都拥有其类型

5.某一特定类型的所有对象都可以接收同样的消息.

Booch对对象提出了更加简洁的描述:对象具有状态,行为标识.这意味着每一个对象都可以拥有其内部数据(它们给出了该对象的状态)和方法(它们产生的行为),并且每一个对象都可以唯一地与其他对象区分开来,具体来说,就是每个对象在内存中都有唯一的地址.

面向对象拥有三大特性:封装,继承多态.

封装

顾名思义,就是把这个类中的一些信息或实现方法隐藏起来,不让外人看到,只能自己看到(private).当然也不能完全隐藏,要不就只能自己跟自己玩了,所以一般还要提供大家都能调用的方法(public),来让外部程序来设置属性或者获得自己的一些信息:

public class Computer {
    /**
     * 主机或手机内部结构,你不必知道它的内部细节
     */
    private long cpu;
    private long memory;
    private long hardDrive;
    private String brand;
    private Object monitor;

    /**
     * 你可以定制组装自己的设备,如存储容量等(set).
     */
    public void setcpu(long cpu) { this.cpu = cpu; }
    public void setMenory(long memory) { this.memory = memory; }
    public void setHardDrive(long hardDrive) { this.hardDrive = hardDrive; }
    public void setBrand(String brand) { this.brand = brand; }
    public void setMonitor(Object monitor) { this.monitor= monitor; }

    /**
     * 通过输出设备获得你的主机或手机的信息(get).
     */
    public long getcpu() { return cpu; }
    public long getMenory() { return memory; }
    public long getHardDrive() { return hardDrive; }
    public String getBrand() { return brand; }
    public Object getMonitor() { return monitor; }
}

 对于该类中比较关键的部分,我们应该不能让外人修改它们,所以应该将其设计成private.

Java用三个关键字在类的内部设定边界:public,private,protected.这些访问指定词(也就是访问修饰符~)决定了紧跟其后被定义的东一可以被谁使用.public表示紧随其后的元素对任何人都是可用的,而private这个关键字表示除类型创建者和类型的内部方法之外的任何人都不能访问的元素.private就像你与客户端程序员之间的一堵砖墙,如果有人试图访问private成员,就会在编译时得到错误信息.protected关键字与private作用相当,差别仅在于继承的类可以访问protected成员,但是不能访问private成员.

Java还有一种默认的访问权限,当没有使用前面提到的任何访问指定词时,它将发挥作用.这种权限通常称为包访问权限,因为在这种权限下,类可以访问在同一个包(库构件)中的其他类的成员,但是在包之外,这些成员如同指定了private一样.

 对应到现实世界的话,就比如你面前的手机或者电脑:它们提供了操作它们的媒介(屏幕,按键;鼠标,键盘),以及获取它们信息的媒介(屏幕,喇叭;显示器).而手机主板是怎么设计的,主机硬件是怎么组装的,是怎么运行起来的,非专业人员不需要关心.对你来说,手机内部结构,主机内部结构就是private的,一般不允许对它们进行操作,否则会失去保修等.它们提供的供你操作的部分和获取信息的部分就是public的,你想怎么操作都是你的事情,它们只负责响应你的请求和给你展示信息:

Java与面向对象

 访问权限修饰符

  本类中 同包/同包子类 不同包子类 不同包非子类
private 可见 不可见 不可见 不可见
默认(default) 可见 可见 不可见 不可见
protected 可见 可见 可见 不可见
public 可见 可见 可见 可见

继承

对于继承来说,最好的例子就是你继承于你的父母(当然Java是不支持多重继承的,这里只是举个例子).

    class Father{}
    class Mother{}
    //切记Java是不支持多重继承的!!!这里只是举个例子.
    public class Me extends Father, Mother{}

换个角度来说,就继承来说,最经典例子莫过于Shape下的若干个类了.

package pack;

public class Shape {
    //默认访问权限修饰方法,只能被包内子类重写.
    void draw(int sides) {/*画一个图形,参数为边数.*/}
    void paint(String color) {/*上色.*/}
    void show(Shape shape) {/*展示画好的图形.*/}
    protected void pro(){/*protected方法,可以被非同包子类重写.*/}
    private void method() {/*父类私有方法.*/}
}

class Circle extends Shape{
    /**
     *可以显式地重写.
     */
    @Override
    void draw(int sides) {/*重写.*/}
    @Override
    void paint(String color) {/*重写.*/}
    @Override
    void show(Shape shape) {/*重写.*/}
    void method() {/*使用IDE可以发现并没有重写父类方法,强行添加@Override会提示错误.*/}
    /**不仅可以继承方法,还可以有自己独有的方法.*/
    void showPerimeter(int r) {System.out.println(r * 2 * 3.14);}
}

class Square extends Shape {
    void area(int sideLength) { System.out.println(sideLength * sideLength);}
    /**
     * 也可以不重写父类方法,依然可以调用.
     */
    public static void main(String[] args) {
        Shape square = new Square();
        square.draw(4);
        square.paint("yellow");
    }
}

//其他包子类
package another;
import pack.Shape;
class Sh extends Shape {
    /**
     * 在其他包可以重写protected方法
     */
    @Override
    protected void pro() { super.pro(); }
    /**
     * 没有重写标志,意味着默认访问修饰符对其他包的子类也是不可见的.
     * 添加@Override会提示错误.
     */
    void draw(int sides) {}
    void paint(String color) {}
}

我们管extends后面的类叫做"基类","超类"或"父类",extends前面的叫做"导出类","子类".

 

Java与面向对象

当继承现有类型时,也就创造了新的类型.这个新的类型不仅包括现有类型的所有成员(尽管private成员被隐藏了起来,且不可访问.),而且更重要的是它复制了基类的接口.也就是说,所有可以发送给基类的信息同样也可以发送给导出类对象.由于通过发送给类的消息的类型可知类的类型,所以也就意味着导出类基类具有相同的类型.对应到前面的代码,就是Circle也是一种Shape.

继承不是只能重写父类的方法,也可以有自己定义的方法.这也就说明了继承是"子类长得父类",而不是"子类跟父类一模一样".你跟你父母是各方面都很像,但是不跟任何一个人完全一样.需要注意的是,父类的private方法不能被子类重写,即使同名,也只能算是子类独有的方法,在IDE中可以发现没有重写的标志,原因很简单,private只有自己的类中可见.父类中的protected方法可以被任意包中的子类重写.

所有的类最终都继承于java.lang.Object类,我们打开官方API手册,左上角可以看到类名(class 类名),下面展示了继承的情况:

Java与面向对象Java与面向对象

这也就说明了所有的类都拥有Object类中的所有方法:

package java.lang;
/**
 * Class {@code Object} is the root of the class hierarchy. 
 * Every class has {@code Object} as a superclass. All objects, 
 * including arrays, implement the methods of this class.
 * Object是类层级的根
 * 所有的类都以它为父类.
 * 所有对象,包括数组,都实现这个类中的方法
 */
public class Object {
    private static native void registerNatives();
    static { registerNatives(); }
    public final native Class<?> getClass();
    public native int hashCode();
    public boolean equals(Object obj) { return (this == obj);}
    protected native Object clone() throws CloneNotSupportedException;
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }
        if (nanos > 0) {timeout++;}
        wait(timeout);
    }
    public final void wait() throws InterruptedException { wait(0); }
    protected void finalize() throws Throwable { }
}

多态

先来看两段代码:

1.

class Shape {
    void showShape(Shape shape){ System.out.println(shape); }
    public static void main(String[] args) {
        Circle circle = new Circle();
        Square square = new Square();
        Shape shape = new Shape();
        circle.showShape(circle);
        square.showShape(square);
        shape.showShape(shape);
    }
    @Override
    public String toString() { return "这是形状."; }
}

class Circle extends Shape{
    @Override
    void showShape(Shape shape) { super.showShape(shape); }
    @Override
    public String toString() { return "这是圆形."; }
}

class Square extends Shape{
    @Override
    public String toString() { return "这是矩形."; }
}

运行结果是(输出信息时会自动调用对象的toString().): 

这是圆形.
这是矩形.
这是形状.

当Circle被传入到预期接收Shape类的方法中,究竟会发生什么.由于Circle可以被showShape()看作是Shape,也就是说,showShape()可以发送给Shape信息,Circle都可以接收,那么,这么做是完全安全且合乎逻辑的.

将导出类看作是它的基类的过程称为向上转型(upcasting),为什么是向上,看看继承的那个示意图就明白了:下面的子类指向在上面的父类.转型这个词的灵感来自于模型铸造的塑模动作;而向上这个词来源于继承图的典型布局方式(就像继承的那个图片~):通常基类在顶部,而导出类在其下部散开,因此,转型为一个基类就是在继承图中向上移动,即"向上转型".

注意这些代码并不是说:"如果你是Circle,请这么做,如果你是Square,请那样做..."....这里所要表达的意思仅仅是:"你是一个Shape,我知道你可以showShape(),那么去做吧,但是要注意细节的正确性".

这里说了"向上转型",按规律来说一定会有"向下转型",就是父类往下发出箭头指向子类,我们来看看"向下转型"的代码:

Java与面向对象

错误信息翻译过来就是:不相容的类型, 需要的类型是:oop.Circle,发现的是:oop.Shape(父类),显然编译器不允许我们直接这么做,那么下面来看看IDE提供的修改方案:

Java与面向对象

1.转换至oop.Circle 

2.把new Shape()改为new Circle().

3.把变量circle的类型改变为Shape.

4.把circle的类型移植为oop.Shape.

这里就看看第1个是什么情况:

    public static void main(String[] args) {
        Circle circle = (Circle) new Shape();
        circle.showShape(circle);
    }

在后面加上了类型转换,但是这么运行会出问题:

Java与面向对象

 线程"main"中的异常 类型转换异常:Shape不能转换为Circle.

说得俗一点就是,你父母转换类型变成了你,但是你不能转型变回父母...

这里是简单提一下,后面会详细说说.

一点废话

我觉得<Java编程思想>对面向对象的思想的阐述还是十分到位的,虽然我参考的第4版只是基于JDK5的,现在已经有了JDK11,但是面向对象的思想似乎永远不会过时,毕竟面向对象是Java的核心,不会跟随JDK的版本更迭而改变.建议大家都看看<Java编程思想>的前2章,对理解面向对象非常有帮助.