类与对象基础02

1、继承

1.1、为甚么要继承

  1. 利用继承,人们可以基于已存在的类构造一个新类
  2. 继承已存在的类就是复用(继承)这些类的方法和域。

1.2、如何实现继承

关键字 extends表示继承。

父类并不因为它优于子类或者拥有比子类更多的功能;

子类比超类拥有的功能更加丰富。

在通用扩展超类定义子类的时候,仅需要指出子类与超类的不同之处。因此在设计类的时候,应该将通用的方法放在超类。而将具有特殊用途的方法放到子类中。

1.3、继承特征

  • 子类继承父类的所有属性和方法(私有方法除外)

  • 子类不能访问父类的私有属性

  • 一般在父类中需要提供一个无参构造器,在子类中会默认调用父类的无参构造器

  • 可以显示在子类构造器的第一行调用父类构造器 super(参数)

  • Java中类不支持多继承,但是Java接口支持多继承


2、多态

2.1、方法重写

超类中的方法对于子类的情形并不适用,因此需要在子类中对方法进行重写。

  • 可以在子类中增加域,增加方法或覆盖超类的方法,但是不能删除继承的任何域与方法

2.2、子类构造器

  • super()实现对超类构造器的调用
  • 使用super()调用构造器的语句必须是子类构造器的第一条语句
  • 如果在子类构造器中没有显示的调用父类构造器,则将自动调用超类默认(没有参数)的构造器。若在超类中没有不带参数的构造器,则编译期报错
  • super关键字的两个用途:一是调用超类的方法;二是调用超类的构造器
  • this关键字的用途:一是引用隐式参数,二是调用该类的其他构造器

2.3、什么是多态

一个对象bianl(例如,变量e)可以只是多种实际类型的现象被称为多态(polymorphism);

在运行时能够自动的选择调用哪个方法的现象称为 动态绑定

 Manager boss = new Manager("Carl cracker",80000,1987,12,15);

        Employee[] staff = new Employee[3];
        staff[0] = boss; //父类引用变量指向子类对象
        staff[1] = new Employee("Harry Hacker",5000,1989,10,1);
        staff[2]= new Employee("Tony Tester",4000,1990,3,15);

        for(Employee e:staff){
            System.out.println(e.getName()+"--->"+e.getSalary());
        }

is-a规则的另一种表述法是置换法则它表明程序中出现超类对象的任何地方都可以用子类对象置换。

例如:可以将一个子类对象赋值给超类变量:

Employee e;//超类变量
e = new Employee()  ;  //超类变量引用超类对象
e = new Manager(); //超类变量引用子类对象;若子类中方法再重写,则e.f()出现多态

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BvtQHkiF-1579357724279)(images/20.png)]

2.4、理解方法调用

在Java中因为有继承,方法的重写因此需要研究下方法调用的具体流程:

  1. 编译期查看对象的声明类型和方法。假设调用x.f(param);编译器会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法(超类中的私有方法不可访问)

  2. ==编译期将查看调用方法时提供的参数类型。==如果在所有名为f的方法中存在一个与提供的参数类型完全匹配,就选择这个方法。这个过程被称为重载解析

  3. 如果是 private static final或者构造器,那么编译器可以准确的知道应该调用哪个方法,将此种调用方式称为 静态绑定

    与此对应的是,调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定

  4. 当程序运行,并且采用动态绑定调用时,虚拟机一定调用与x所引用对象的实际类型最合适那个类的方法。现在子类中确定待执行的方法,倘若子类中没有则在其父类中查找

  5. 每次调用方法都需要进行搜索,时间开销较大。因此,虚拟机预先为每个类创建了一个方法表,其中列出了所有方法的签名和实际调用的方法。这样一来,在真正调用方法时,虚拟机仅查这个表就行了。

动态绑定有一个非常重要的特性:无需要对现存的代码进行修改,就可以对程序进行扩展。

假设增加一个新类Executive,并且变量e有可能引用这个类的对象,我们不需要对包含调用e.getSalary()的代码进行重新编译。如果e恰好引用一个Executive的对象,就会自动调用Executive.getSalary()方法

2.5、阻止继承

有时候,可能希望阻止人们利用某个类定义子类。

不允许扩展的类被称为final类。

2.6、强制类型转换

当一个父类变量引用一个子类对象,需要使用父类变量调用子类中的特有方法:需要使用强制类型转换

有时候也可能需要将某个类的对象引用转换成另外一个对象的引用。对象的引用转换与数值表达式的类型转换类似,仅需要用一对圆括号将目标类名括起来:

Manager boss = (Manager)staff[0];  //staff[0]为Enmploy变量引用的Manager对象
  • 可以进行由下向上进行转换,不能进行由上向下的强制类型转换

  • 由上向下进行的类型转换,会产生一个ClassCastException

  • 在进行强制类型转换之前,先查看一下是否能能够成功的转换,这个过程简单的使用instanceOf操作符

3、抽象类

3.1、为什么需要抽象类

如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。

从某种角度看,祖先类更加通用,人们只将它作为派生其他类的基类,而不作为想使用特定的实例、

3.2、抽象类定义

  • 包含一个或多个抽象方法的类本身必须被声明为抽象的
  • 抽象类还可以包含具体数据和具体方法。
  • 建议将通用的域和方法(不管是否抽象的)放在超类(不管是否是抽象类)中
  • 抽象方法充当着占位的角色,它们的具体实现在子类中。
  • 扩展抽象类可以有两种选择:一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的。
  • 抽象类不能被实例化。也就是说:如果将一个类声明为abstract,就不能创建这个类的对象
  • 可以定义 抽象类的对象变量,但它只能引用非抽象子类的对象
public abstract  class Person {

    public Person(String name) {
        System.out.println("person类中的构造器");
        this.name = name;
    }

    public Person() {
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public abstract String getDescription();



}

3.3、抽象类的实现(使用)

4、接口

5、万类之祖–Object类

Object类是Java中所有类的始祖

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 {
  
    }

  
    public final void wait() throws InterruptedException {
       
    }

  
    protected void finalize() throws Throwable { }
}

5.1、equals方法

Object中的源码

 public boolean equals(Object obj) {
        return (this == obj);//两个引用是否引用相同的对象
    }

Object类中的equals方法用于检测一个对象是否等于另外一个对象;在Object中,这个方法将判断两个对象是否具有相同的引用

对于大多数类来说,这种比较时没有意义的。

经常需要检测两个对象状态的相等性,如果两个对象的状态相等,就认为这两个对象时相等的

若重写了equals方法必须重写hashCodse方法

编写一个完美equals方法的建议

  • 显示参数命名为otherObject,稍后需要将它转换成另外一个叫做other的变量

  • 检测this与otherObject是否引用同一个对象

    if(this==otherObject) return true;

  • 检测otherObhject是否为null,如果为null,则返回false

    if(otherObject==null) return false;

  • 比较this与otherObject是否属于同一个类,如果equals的语义在每个子类中有所改变,就是用getClass检测:

    if(getClass()!=otherObject.getClass()) return fasle;

    如果所有的子类都拥有统一的语义,就使用instanceOf检测:

    if(!(otherObject instanceOf className)) return false;

  • 将otherObject转换为相应的类型变量

    ClasName other = (ClassName) otherObject

  • 现在开始对所有需要比较的域进行比较。使用==比较基本类型域,使用equals比较对象域。如果所有的域都匹配,就返回true,否则返回fasle

        @Override
        public boolean equals(Object obj) {
    
            //如果当前对象和obj引用同一块区域则相等
            if(obj==this) return true;
    
            //加入待比较的对象为空 则直接返回false
            if(obj==null) return false;
    
            //假如lei不相等,则必不等
            if(getClass()!=obj.getClass()) return false;
    
            Employee e = null;
            //进行类型转换;这一步其实不用判断
            if(obj instanceof  Employee){
                e = (Employee)obj;
            }
    
            return  e.name.equals(this.name) &&
    
                    e.getSalary()==this.getSalary() &&
    
                    e.getHireDay().equals(this.getHireDay());
        }
    

    看看String中的equals方法

     /** The value is used for character storage. */
        private final char value[];
    
        public boolean equals(Object anObject) {
            //引用同一块内存地址则返回true
            if (this == anObject) {
                return true;
            }
            //是Stringde 类型
            if (anObject instanceof String) {
                //进行强制类型转换
                String anotherString = (String)anObject;
                //求字符串的长度
                int n = value.length;
                //两个字符床长度相等
                if (n == anotherString.value.length) {
                    char v1[] = value;//获取存储字符串的字符数组
                    char v2[] = anotherString.value;
                    int i = 0;
                    //从前往后依次比较字符数组中的字符
                    while (n-- != 0) {
                        if (v1[i] != v2[i])//不相等直接返回false,
                            return false;
                        i++;
                    }
                    return true; 
                }
            }
            //不是String类型对象,直接返回fasle
            return false;
        }
    

看看StringBuilder中的equals方法

        boolean flag =stringBuilder1.equals(stringBuilder2);//false ;Debug模式下可以看出直接调用Object类中的方法;并且在StringBuilder及其父类中没有重写equals方法

看看Date中的equals方法

 /**
     * Compares two dates for equality.
     * The result is <code>true</code> if and only if the argument is
     * not <code>null</code> and is a <code>Date</code> object that
     * represents the same point in time, to the millisecond, as this object.
     * <p>
     * Thus, two <code>Date</code> objects are equal if and only if the
     * <code>getTime</code> method returns the same <code>long</code>
     * value for both.
     *
     * @param   obj   the object to compare with.
     * @return  <code>true</code> if the objects are the same;
     *          <code>false</code> otherwise.
     * @see     java.util.Date#getTime()
     */
    public boolean equals(Object obj) {
        return obj instanceof Date && getTime() == ((Date) obj).getTime();
    }


5.2、hashCode方法

散列码(hash Code)是由对象化导出的一个整型值。、

Java中的hashCode方法就是根据一定的规则将于对象相关的信息(比如对象的存储地址,对象的字段等)映射为一个数值,这个数值就称作散列码值。

查看Object中hashCode方法

Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.

The general contract of hashCode is Whenever it is invoked on the same object more than once during an execution of a Java application,the `hashCode’ must consistently return the same integer ,provided no information used in equals comparisons on the object is modified.

This integer need not remain consistent from one execution of an application to another execution of the same application.

If two objects are equal according to the equals(Object),

the two objects must produce the same integer result.

It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCodmethod on each of the two objects must produce distinct integer results.

However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

//本地方法
    public native int hashCode();

概括以上的说法:

  1. 返回对一个对象的HashCode值,这对于hashMap有帮助
  2. 在同一个应用中,一次执行过程中,一个对象无论何时调用该方法都会返回同样的hashValue
  3. 对同一个应用的,多次执行中,hashValue不必相同
  4. equalse()方法不相等,hashCode()方法可能相等也可能不相等
  5. hashCode相等,equals()方法不一定相等
  6. equals()相等,hashCode()一定相等
  7. 对于不相等的对象,最好能计算出不同的hash值
  8. Equals与hashCode的定义必须一致:如果x.equals()返回true,那么x.hashCode()就必须与y.hashCode具有相同的值。例如,如果定义的Employee.equals比价雇员的ID,那么hashCode方法就需要散列ID

查看String的hashCode方法

 /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
 /** Cache the hash code for the string */
    private int hash; // Default to 0

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

查看Double的hashCode

    @Override
    public int hashCode() {
        return Double.hashCode(value);
    }


    /**
     * Returns a hash code for a {@code double} value; compatible with
     * {@code Double.hashCode()}.
     *
     * @param value the value to hash
     * @return a hash code value for a {@code double} value.
     * @since 1.8
     */
    public static int hashCode(double value) {
        long bits = doubleToLongBits(value);
        return (int)(bits ^ (bits >>> 32));
    }

5.3、toString方法

在Object中有一个重要的方法,就是toString方法

它用于返回表示对象值的字符串

Object中的toString

//默认打印所属类名及其散列码(散列码就表示为对象的一种标记)   
public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

建议为自定义的每一个类增加toString方法。方便调试

查看String的toString方法

   /**
     * This object (which is already a string!) is itself returned.
     *
     * @return  the string itself.
     */
    public String toString() {
        return this;////return this就是返回当前对象的引用(就是实际调用这个方法的实例化对象)
    }

6、对象包装器与自动装箱

6.1、为什么需要自动装箱

有时候,需要将int这样的基本类型转换为对象。

所有的基本类型都有一个与之对应的类

Integer------->int

byte----->Byte

short----->Short

char----->Character

double----->Double

假设想定义一个整型数组列表,而尖括号中的类型参数不允许是基本类型,也就是说不允许写成 ArrayList,这里就用到了Integer对象包装器类

ArrayList<Integer> arr = new ArrayList<>();

有一个很有用的特性,从而更加便于添加int类型的元素到ArrayList中,下面这个调用

list.add(3)

将自动变换成

list.add(Integer.valueOf(3))

这种变换被称为自动装箱(autoBoxing)

相反:将一个Integer对象赋给一个int值时,会自动的拆箱

int n = list.get(i);
//翻译成
int n = list.get(i).intvalue();

甚至在算数表达式中也能够自动的装箱和拆箱

Integer n = 34;
n++;
//编译期将自动的插入一条对象的拆箱命令,然后进行自增计算,最后再将结果装箱

自动装箱规范要求boolean、byte、char<=127,介于-128~127之间的short和int被包装到固定的对象之中,设计到缓冲

发布了107 篇原创文章 · 获赞 17 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ZHOUJIAN_TANK/article/details/104034563