美文网首页
Java核心技术——面向对象与类

Java核心技术——面向对象与类

作者: Jacquie葭葵 | 来源:发表于2019-01-04 14:51 被阅读0次

    面向对象与类

    面向过程

    ​ 通过设计过程(即算法)求解问题。算法是第一位的,数据结构是第二位的。适于解决小的问题,优化算法。

    面向对象

    ​ 把数据放在第一位,然后再去考虑操作数据的算法。面对规模较大的问题,只需要通过访问过这个数据的少量方法中锁定bug。

    类 class

    ​ 类是构造对象的模板。封装了数据和行为。由类 构造(construct)对象的过程 称为 创建类实例(instance)

    封装 encapsulation

    ​ 有时被称为数据隐藏,关键在于绝对不能让类中的方法直接地访问其他类的实例域。为的是使对象成为“黑盒”,提高重用性和可靠性。

    类之间的关系

    • 依赖 dependence(uses-a)—— 即耦合,A类的方法操纵B类的对象,则A依赖于B
    • 聚合 aggregation(has-a)—— 即关联,A类的对象里包含着B类的对象,即B对象是A的成员
    • 继承 inheritance(is-a)—— A类是B类的一种特例,A继承了B原有的东西,多了一些特殊的属性和方法

    对象

    对象的三个特性
    • 行为(behavior)—— 方法、行为(从同一个类实例化出的不同对象,方法相同
    • 状态(state)—— 数据、实例域(可能不同
    • 对象标识(identity)—— 唯一性(一定不同
    对象与对象变量

    ​ 只声明没有new,此时为对象变量。只有将构造器new出的对象赋给它,才是一个可多次使用的对象,即Object object = new Object(); 等号左边是对象变量,右边为实例对象

    局部变量不会自动地初始化为null,必须通过new或者设为null进行初始化

    编译器自动编译

    ​ 键入命令:

    ​ javac A.java

    ​ 如果A里面使用了B类

    ​ 编译器会查找名为 B.class 的文件。如果没找到会自动搜索 B.java 自动编译。

    ​ 如果 B.java 的版本比已有的 B.class 的版本新,还会对其重新编译。

    构造器

    ​ 构造器与类同名没有返回值。作用是将实例域初始化为希望的状态。

    ​ 所有Java对象都是在堆中构造的。构造器总是伴随着new操作符被调用。不能对一个已存在的对象调用构造器来重新设置实例域。

    //正确用法
    Employee jams = new Employee("James Bomd",100000,1950,1,1);
    //错误用法,编译出错
    james.Employee("James Bomd2",250000,1950,1,1);
    

    get/set 访问器

    ​ 即Java Bean的写法。维护了封装的优点

    ​ 注意不要写返回引用可变对象的访问器方法,如 Date。

    ​ 如果需要返回一个可变对象的引用,应该首先对它进行克隆。像这样写get

    public Date getHireDay(){
        return (Date)hireDay.clone();
    }
    

    ​ 这样调用get得到的就不是类变量(私有的)hireDay,而是它的克隆,一个新的内容一样的不同Date。此时对新Date的修改不会影响这个类的私有成员。

    final 不可变实例域

    ​ 必须确保在每个构造器执行时,final值被设置,并且对象构建后不会再被修改。

    ​ final大多用于基本类型(primitive)或 不可变类(immutable)域。

    不可变类

    ​ 所谓不可变类即实例创建一旦完成,就不能修改其成员变量值。所以线程安全,线程间可以共享。如:String;基本数据类型对应的包装类。

    从 String 看不可变性如何做到

    ​ 参考:openJDK8源码;JAVA不可变类(immutable)机制与String的不可变性;理解serialVersionUID

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        
        //所谓的值。用来存储字符的数组
        private final char value[];
    
        //缓存字符串的HashCode
        private int hash; // Default to 0
    
        //反序列化时用来校验版本
        private static final long serialVersionUID = -6849794470754667710L;
    
        //序列化用的流
        private static final ObjectStreamField[] serialPersistentFields =
            new ObjectStreamField[0];
            
        /** 新建新的同值对象,只是这个值多了个引用指向它
            value[]本身不存在两份,以减少回收*/
        public String(char value[]) {
            this.value = Arrays.copyOf(value, value.length);
        }
    
    JAVA序列化

    ​ 进程远程通信时,JAVA对象与字节序列转化的过程。
    serialVersionUID:(继承了Serializable接口就需要定义)
    ​ 根据类名、接口名、成员方法及属性等生成的64位哈希值。
    ​ 用来验证版本一致,证明可以反序列化。
    ​ 若字节流与本地的不相同,则InvalidCastException

    深复制 / 浅复制

    先试着解释一下,A复制出B
    浅复制(shallow copy):即不会递归复制,对基本数据类型是值拷贝,对象只是引用拷贝
    ​ 所以对B中的对象进行修改,还是会影响A。
    深复制: 把A所引用的对象也都复制了一遍,这时B完全独立了

    可以看ArrayList.clone()中的解释

        /** 
         * Returns a shallow copy of this <tt>ArrayList</tt> instance.  (The
         * elements themselves are not copied.)
         *
         * @return a clone of this <tt>ArrayList</tt> instance
         */
        public Object clone() {
            try {
                ArrayList<?> v = (ArrayList<?>) super.clone();
                v.elementData = Arrays.copyOf(elementData, size);
                v.modCount = 0;
                return v;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError(e);
            }
        }
    

    String的构造和ArrayList.clone()都用到了Arrays.copyOf()

    public static char[] copyOf(char[] original, int newLength) {
            char[] copy = new char[newLength];
            /** 这里的System.arraycopy()是native*/
            System.arraycopy(original, 0, copy, 0,
                             Math.min(original.length, newLength));
            return copy;
        }
    

    如何实现深复制呢?可以借用序列化!

    static 静态域(类域)

    每个类中只有一个这样的域。而每个对象对于所有的实例域却都有自己的一份拷贝。
    

    静态常量

    public static final double PI = 3.14159265358979323846;
    

    如果static被忽略,PI就变成了Math类的一个实例域。需要通过Math类的对象访问PI,并且每个对象都有它自己的一份PI拷贝。

    静态方法

    ​ 可由类名直接调用,是一种不能向对象实时操作的方法。可以认为静态方法是没有this的方法。不能访问实例域,但是可以访问静态域(类域)。

    ​ 使用静态方法的情景

    1. 所需参数都通过显式参数(参数列表)提供。方法不需要访问对象状态。
    2. 方法只需要访问类的静态域。
    3. 实现工厂方法
    4. main方法
    
    main方法

    ​ main不对任何对象进行操作,将执行并创建程序所需对象。

    ​ 每个类都可以有一个main方法。以最外层的类的main优先,小的类中的main不会被执行,但是可善用于单元测试。

    方法参数

    ​ 方法得到的是所有参数值的一个拷贝,对普通变量,方法无法改变其值。但是将对象引用(对象变量)作为参数,则方法拷贝的也是引用,指向的是同一个对象,对其状态的修改自然也会真的改变这个对象。但是方法不能使对象参数引用一个新对象。因为方法只能修改拷贝来的引用的引用值,让它指向新对象,不能修改原来模板引用值。

    对象构造

    重载 Overload

    ​ 多个方法有相同的名字,不同的参数列表。编译器按方法签名(方法名与参数列表)来匹配相应方法。不能有两个同名同参数但返回值不同的方法

    默认域初始化

    ​ 如果没有自己实现构造器,编译器会自动添一个无参构造器,将没有显式赋初值的实例域在这里显式的设为默认值。但是如果自己写了含参构造器,那构造对象时就不能不传参数。此时,没有初值的实例域依然会被赋默认值,但编译器不会再补充无参的构造器了

    动态构造技巧

    动态初始化实例域
    class Employee{
        private static int nextId;//默认初始化为0
        private int id = assignId();//等待调用赋值
        
        private static int assignId(){
            int r = nextId;
            nextId++;
            return r;
        }
    }
    
    调用另一个构造器
    public Employee(double s){
        //调用 Employee(String,double)
        this("Employee #"+nextId,s);
        nextId++;
    }
    

    Pakage 包

    ​ 从编译器的角度来看,嵌套的包没有任何关系。例如,java.util与java.util.jar。每一个都拥有独立的类的集合。

    import导入

    ​ 导入包:需要注意只能导入一个包,不能java.*java.*.*。此外,通过import staticjava.lang.System.lei导入静态域/方法

    类注释

    ​ JDK自带javadoc工具,可以由源文件生成HTML文档

    相关文章

      网友评论

          本文标题:Java核心技术——面向对象与类

          本文链接:https://www.haomeiwen.com/subject/uwahrqtx.html