Java--3面向对象2【继承】【子父类中成员的特点】【子父类中构造函数的特点】 【final关键字】【抽象类】【接口】【多态】【instanceof操作符】



1.    继承
1). 继承的相关知识
(1). 继承的优势
[1]. 提高了代码的重用性
[2]. 让类与类之间产生了关系。只因为有了继承,才会有多态    (继承 == >多态)
注意:继承是类与类之间关系的一种,是一种所属关系,“isa”的关系。
(2). Java中继承的特点
[1]. 和C++相比,Java只支持单继承
[2]. 为了支持类似C++中的多继承的机制,Java采用多实现的方式来支持多继承。
(3). 如何使用是个继承体系的功能呢?
[1]. 先查阅父类的描述。因为父类中定义的是该类体系中的共性的功能
       通过了解父类的共性功能,就可以知道这个体系中,就可以使用这个体系中的基本功能。
[2]. 再查阅子类的描述。
(4). 类中的成员:
变量、函数和构造函数。
(5). 继承子类中关键字
[1]. this代表本类对象的引用
[2]. super代表父类对象的引用
2). 继承中子类、父类成员(变量/方法)的特点
(1). 子类、父类的成员变量全部存放在子类的内存区域
       【个人理解】由于类中成员函数存储位置是方法区,而不是堆内存,所以父类同名的方法不会存到子类的方法区对应的代码中。
(2). 子类和父类出现同名非私有成员的时候:
[ 对于父类的私有成员, 子类是无法直接访问到的 ]
       {1}.子类方法的内部要访问本类中的该成员,使用关键字this
       {2}.子类方法的内部要访问到从父类继承的该成员,使用的是关键字super
(3). 如果子类中的某个变量没有在父类中出现过,那么这个时候,使用this和super访问到的都是该子类成员变量。
3). 子类重写 (覆盖) 父类中方法
(1). 何时应用重写
[1]. 当子类继承了父类,这样子类就具备了父类的所有非私有的功能(方法)
[2]. 当子类同样具备和父类相同功能的函数,但是功能的实现却和父类中该功能的实现不一样的时候,这时候,没有必要在子类中定义新的功能,而是重写这个功能的实现。
       保留父类的功能定义(函数名),并重写功能的内容
---------------【子类继承类似生物中的遗传】
---------------【子类重写类似生物中的变异: 原有功能上的进化】
       ---------------【子类添加父类中没有功能类似生物中的变异: 新的功能】
(2). 重写的条件
[1].重写产生的时机:当满足子类中的函数的名字、参数列表和父类的某个非私有功能具有相同的函数名字、参数列表的时候,Java的编译器就会把子类这个同名同参数的功能作为父类对应功能的重写。为了保证重写的正确性(也就是Javac编译通过),还要满足以下两个条件:(所以,是否为重写,最大原则就是父类的该方法是非private的访问权限!!!)
[2]. 正确重写的条件1:重写的两个方法的其余部分满足以下条件:
       {1}.返回值类型一致、
{2}. 子类访问权限修饰符 >=父类的访问权限修饰符 (子类和父类中对应的方法都不能出现private!!!)
         {3}. 子类函数抛出的异常类型必须是父类函数抛出来的异常类型的子类或者子集
【注意】子类中的静态方法只能覆盖父类中的静态方法 ====>静态只能覆盖静态
(3). 重写跟重载的区别
[1]. 重载:只看是否是       相同的函数名和不相同的参数列表 (javac认定此时为重载)
                     其余部分无所谓是否一致。
[2]. 重写:只看子类、父类中是否是相同的函数名和参数列表(javac认定此时为重写)
                     如果是,函数剩下的部分必须满足正确重写的条件!!javac才能编译通过。
                     否则,javac认为这是错误的重写方式,编译报错!!
2.    子类对象的实例化过程
(1). 子类能覆盖父类的构造方法么
子类不能覆盖父类的构造函数。
原因:由于构造函数的名字是和类名是一样的。子类和父类类名又不相同,所以不满足子类父类之间的函数重载的充分条件,所以子类不能覆盖父类的构造函数。
(2). 隐式的super语句和显式super语句
[1]. 子类的构造函数中的第一行如果没有通过super语句显式调用父类构造函数的时候,JVM在执行的时候,会默认为子类的该构造函数的第一行加上代表空参数的父类构造函数的super语句: super()
【结论】子类的构造方法的第一行默认是super()
[2]. 由于子类的构造函数的第一行默认是super(),这要求父类的构造方法必须有相应的空参构造函数。如果父类没有相对应的空参构造函数,javac就会报错!!!
[3]. 当父类中没有空参构造函数的时候,子类的构造方法必须通过super(xxxx)语句指定要调用父类的哪一个构造函数来对自己进行初始化。
【举例1:子类调用默认的父类空参构造函数】
class Fu{
    Fu(){
        System.out.println("Fu runs");
    }
  
    Fu(int x){
        System.out.println("Fu runs..."+ x);
    }
}

class Zi extends Fu{
    Zi(){
        System.out.println("Zi runs");//没有指定,默认调用super();
    }
  
    Zi(int x){
        System.out.println("Zi runs..."+ x); //没有指定,默认调用super();
    }
}

class Test{
    public static void main(String[] args) {
        Ziz =new Zi();
        System.out.println("*****");
        ZizX =new Zi(4);
    }
}

运行结果:

【举例2:子类调用指定的父类构造函数】
class Fu{
    Fu(int x){
        System.out.println("Fu runs..."+ x);
    }
}

class Zi extends Fu{
    Zi(){
        super(3);
        System.out.println("Zi runs");
    }
  
    Zi(int x){
        super(x);
        System.out.println("Zi runs..."+ x);
    }
}

class Test{
    public static void main(String[] args) {
        Ziz =new Zi();
        System.out.println("*****");
        ZizX =new Zi(4);
    }
}


由于Fu类没有空参构造函数,所以继承自这个类的子类必须在子类的构造方法中的第一行指定要调用的父类的构造函数。
运行结果:

(3). 使用super语句的注意事项
[1]. super()语句必须放在子类构造函数的第一行。
       原因:先使用老的内容(代码复用)。如果不喜欢老的内容,再super语句之后再修改或者增加新的内容。非常类似生物学中的遗传和变异
[2]. 一个子类的构造函数中,只能有super()语句或者this()语句,两者不能同时存在
原因:
super()为了获取父类的财产,初始化父类的成员变量。继承老的东西,所以放在第一行;
this()为了在本次构造中进行更细致的构造,所以放在第一行;
如果一个构造方法中,出现了super()语句放在第一行,那么要求也在第一行的this()就不能出现。反之亦然。
[3]. 子类中至少有一个子类构造函数能访问父类的构造函数
举例:
class Fu{
    Fu(){}
  
    Fu(int x){
        System.out.println(x);
    }
}

class Zi extends Fu{
    Zi(){}
  
    Zi(int x){
        this();
        System.out.println("x="+ x);
    }
}

问题:Zi(int x)这个构造中第一行加了this(); 他这里面还有super()语句么?他还调用父类的构造函数么?
【1】. 这个构造函数的第一行是this(),那么这个构造方法中就没有super()语句了。
【2】. 但是,由于这个构造函数调用了this(),对的是Zi(){}。这个构造函数的第一行没有this语句,所以,JVM会为这个构造函数加上一个super()。这样 构造函数Zi(int x)通过调用Zi()达到了访问父类构造函数的目的。
(4). 总结super语句
【1】. 子类的所有构造函数都会默认访问父类空参的构造函数
【2】. 父类没有空参数构造函数时,必须手动为子类构造函数指定要调用的父类构造函数。
【3】. 当子类构造函数形成重载的情况时,如果某一个构造函数的第一行被this()语句占用,则这个子类的构造函数中必须保证至少有一个构造函数能够访问父类的构造函数。
【问题】父类的构造函数中有super()语句么?
有!这个super()语句是Object的默认空参构造函数 Object(){…..}
3.    子类this的真实指向(盲点)
1). 子类对象实例化过程 调用父类构造方法---- this的指向
(1). 误区:认为实例化的过程中,调用父类的构造也同时实例化了父类实例
【注意】子类通过自身构造方法实例化过程中再次调用父类的构造方法,实际上是通过父类的构造方法对父类的财产“成员变量”进行特有的初始化之后,再来继承父类的特有财产“成员变量”。
【注意】绝对不是实例化子类对象之后,又同时实例化相应的父类对象!!!!。
(2). 通过MyEclipse进行验证
[1]. 验证代码
class Fu {
    public Fu(){
       System.out.println(this);
    }
    public void showName(){
       System.out.println("Fu..." +this);
    }
}

class Zi extends Fu{
    public Zi(){
       System.out.println(this);
    }
    public void showNameII(){
       this.showName();
       super.showName();
    }
}

public class Test{
    public static void main(String[] args){
       Zizi =new Zi();
       zi.showNameII();
    }
}

[2]. 调试验证
执行 Zi zi =new Zi();时候,父类的构造中this指向的是Zi@834e7   是子类的实例。
说明:子类调用父类的构造方法,在父类中的方法的内部this仍然是子类的this,而不是父类的this.

2). 子类对象调用从父类继承的方法中---- this的指向
(1). 此时this同样是指向子类实例对象
由于构造子类实例的时候,仅仅是调用父类的构造但是并没有进行父类实例化。所以子类调用从父类继承的方法时候,在这个方法的内部this同样也是指向子类的实例。
(2). MyEclipse验证
执行到zi.showNameII();的时候,在Zi类的showNameII中又调用了从父类继承的showName方法,进入到Fu的showName()代码中的时候,this显示的值是Zi@834e7这个值,证明Fu中的方法被子类调用的时候,this仍然指向的Zi的实例

.    final关键字
1). final修饰范围
final可以修饰的元素是:类、函数和变量
(1). 被final修饰的类不可以被继承;
       继承的弊端:打破了OOP的封装性
       [1].子类可以重写父类
       要想子类不可以重写父类的成员,最简单的办法就是让这个类不能被继承。
       [2].子类可以访问父类的成员
(2). 被final修饰的函数不可以被重写;
(3). 被final修饰的变量不可以被修改值,是常量。
       被final修饰的变量是一个常量,只能被赋值一次。final即可以修饰成员变量,又可以修饰局部变量
【注意】如果是常量。常量的名字要求全部大写,单词之间用下划线“_”隔开
2). final修饰成员变量
【1】. final修饰成员变量的时候,此时这个成员变量就是成员常量。由于只能被赋值一次,所以所有的对象的这个成员常量的值都是一样的。为了节省内存,成员常量中的final通常也配合static一起修饰这个成员常量。“final static ….”
【2】. 全局常量:当在“final static ….”前面的访问修饰符public之后,这个常量的访问权限也是足够大的。
有了public,自然可以直接访问,所以不用对这个常量设置读取方法Getter。由于也不能赋值,所以,也没有Setter。
当构成“public finalstatic成员变量名”这个时候就是全局成员常量
2.    抽象类(abstract class)
(1). 抽象方法和抽象类产生的背景
当多个类出现相同的功能,但是功能的主体是不一样的,这时候,可以对这多个类的该功能进行向上抽取 ====》抽取到仅仅定义功能的名称,但是不抽取功能的实现体。
[1]. 此时就可以用abstract这个关键字来修饰这个方法====》抽象方法
[2]. 抽象方法必须定义在抽象类中====》抽象类
(2). 抽象类的特点
[1]. 抽象方法一定定义在抽象类中
[2]. 抽象方法和抽象类必须都被abstract关键字来修饰
[3]. 抽象类不可以通过new来建立实体
[4]. 抽象类中的方法要被调用,必须子类重写抽象类中的全部抽象方法之后,建立子类对象才能调用这个抽象类的方法。
[5]. 继承抽象类的子类如果没有完全覆盖所有的抽象方法,那么这个子类仍然是抽象类
(3). 特殊的抽象类 (没有抽象方法)
抽象类中没有定义任何抽象方法,目的就是不让建立子类对象。
e.g. Java核心类库中的WindowAdapter是一个抽象类,他全部重写了WindowEvent接口中的七个抽象方法,但是这些抽象方法在WindowAdapter中的实现体全部是空实现体。原因就是用户直接构建实现WindowEvent的子类的话,要重写全部的七个抽象方法。但是这些抽象方法未必都能用得上。所以,为了方便用户使用WindowEvent,特意定义了WindowAdapter以全部的空实现体来实现接口WindowEvent。但是,空的实现体所在的WindowAdapter如果实例化成相应的对象的话,是没有意义的,因为所有的成员方法都是空的实现体。所以这个没有抽象方法的WindowAdapter类被定义成抽象类。抽象类不能被new。但是继承这个WindowAdapter之后的子类,可以根据自己的需求来重写七个方法中的自己所需的部分,方便简化了开发。
(4). 多个类对应的功能向上抽取之后,如何确定是方法是否是抽象的?
[1]. 如果功能相同,但是功能的实现不同 ===>定义成抽象方法
[2]. 如果功能相同,功能的实现也相同 ===>定义成普通具体方法
3.    接口(interface)
1). 接口与类之间的关系
接口与类之间的关系就是:子类实现接口
(1). 接口中成员格式上的特点
[1]. 接口中可以定义常量和抽象方法
[2]. 接口中的成员都有固定的修饰符:
{1}. 接口中常量默认修饰符:public final static===>默认是全局常量
       这三个修饰符少些哪几个,系统就会补全哪几个
{2}. 接口中的方法是默认修饰符:public abstract===>默认是公共抽象方法
      这两个修饰符少些哪几个,系统就会补全哪几个
e.g.

(2). 接口实现类中的方法
[1]. 接口中的方法的默认访问修饰符是public
[2]. 接口实现子类的对应的方法的访问修饰符一定是 public
       所以接口的实现子类重写其抽象方法之后,一定要满足重写时候的访问权限的条件:
子类重写父类的方法的访问权限>= 父类/接口中的方法的访问权限
所以接口的实现子类的对应方法的访问权限 >= public
但是public是访问权限取值中的最大值,所以接口的实现子类中对应的重写的方法的访问权限一定是public,绝不能是default、private或者protected
(3). 多个接口和一个类的关系
[1]. 一个类可以实现多个接口
       {1}.这个是Java不直接支持多继承的一种转换形式。
{2}. Java支持一个类实现多个接口从而达到间接支持多继承的思想
[2]. 当多个接口存在共同的抽象方法的时候并且都被同一个实现类实现,那么这个实现类对这多个接口的公共方法仅仅实现一次即可。
(4). 接口的语义
[1]. class A extends B: 继承的语义
       A is aB
[2]. class A implements C: 继承的语义
       A is like aC
(5). 设计时:抽象类和接口中功能定义
[1]. 基本功能定义在抽象类/类中
[2]. 扩展功能定义在接口中
2). 接口与接口之间的关系
(1). 接口与接口之间可以是继承关系
(2). 接口之间支持多继承!!!!!
interface A{
    void methodA();
}

interface B{
    void methodB();
}

interface C extends A,B{
    void methodC();
}

(3). 接口在多继承的时候,要保证如果多个接口中存在同名的方法的时候,必须保证返回值一致,否则编译报错。
{1}. 错误案例1:
interface A{
    void methodA();
}

interface B{
    void methodB();
}
 
interface C extends A,B{
    void methodC();
    int methodA();
//编译报错。因为是继承,并且同方法名和参数列表  JavaC认为是重
//写但是返回值类型不一样,所以编译不通过
}
 
{2}. 错误案例2
interface A{
    void methodA();
}

interface B{
    void methodB();
    int methodA();
}
 
interface C extends A,B{
//上面定义接口C报错!!!继承两个接口中的方法,这说明C中存在两个方法
//一个叫void methodA(){…}
//另一个叫int methodA(){…}
//但是 同方法名,同参数列表 这时候,java认为C继承到的一个方法是对继承到的另一
//个方法的重写。但是由于返回值类型不一致,所以编译报错!!
    void methodC();
}
 
【面试题】Java支持多继承么?
Java是支持多继承的!但是这种多继承仅仅是存在Java中的接口之间的。除此之后,Java没有直接的多继承。
Java类之间是通过一个类实现多个接口来体现多继承的思想。

1.    多态基本使用
多态可以理解为:事物存在多种体现形态
1). 多态的基本体现
(1). 父类的引用指向了子类的对象
(2). 父类的引用作为函数的参数,可以接收子类对象
e.g.
class Animal{}
class Cat extends Animal{}
class Dog extends Animal{}

Animal ani =newCat();
从这句的语义理解:把猫传给一个动物的名字,说明这个猫也是动物的一种。(多种形态)
*******************************************************************************
但是反过来:
Cat cat =new Animal();
把动物传给一个猫的名字,说明这个动物也是猫的一种,不符合实际意义,编译出错。
2). 多态使用的前提
(1). 前提I:类和类或者类和接口之间存在关系
[1]. 类之间要么有继承关系
[2]. 要么类实现接口
(2). 前提II:在前提I的基础之上,类之间/类和接口之间的方法存在关系
存在类之间的继承关系或者类实现了接口之后,子类必须还覆盖了父类或者接口中的相应的方法。
3). 多态的好处和弊端
[1]. 多态的好处:多态大大提高了程序的可扩展性。
[2]. 多态的弊端:父类引用指向了子类的实例,这时候使用父类的引用只能访问子类从父类继承或者重写的成员,而不能通过父类的引用调用子类特有的成员。
4). 使用多态的注意事项
(1). 向上转型和向下转型
[1]. 向上转型:把子类的实例赋值给父类的引用 (也就是父类的引用指向了子类的实例)
       向上转型可以通过Java自动完成。
[2]. 向下转型:把父类的引用强制转换成子类的引用
       向下转型通过手动强制类型转化来完成,如果不进行强制类型转换,会编译报错。
【注意】多态自始至终都是将子类的堆内存对象做着变化
不建议:把父类的实例强制转换赋值给子类的引用。
***********这样做,编译能通过(符合语法规则就能通过),但是运行时候,直接抛出ClassCastException异常!!!***********
举例:
class Animal{
    public void show(){
        System.out.println("Show Animal");
    }
}
class Cat extends Animal{
    public void show(){
        System.out.println("Show Cat");
    }
}
class Dog extends Animal{
    public void show(){
        System.out.println("Show Dog");
    }
}

public class TestAnimal{
    public static void main(String[] args) {
        Animalani =new Cat();
        Cat  cat=(Cat)new Animal();//子类引用指向了父类的实例,Javac通过!!
        cat.show();
    }
}

运行的时候,cat.show();抛出了异常:

2.    instanceof操作符
1). 用途
(1). 使用格式:a instanceofB
       a:是某个类型的实例的引用变量
       B:某个类的类名
(2). 用途:用于判定引用变量a指向的实例是否是类型B的一个实例
(3). instanceof 是一个操作符,返回值是boolean类型的值
2). 使用场景
[1]. 如果一个类的子类类型很少,可以用instanceof操作符来判断指向子类实例的父类引用是否是父类的一个实例。instanceof引用到多态中
[2]. 当这个父类的子类类型很多的时候,instanceof使用起来是比较麻烦的,因为需要亲自列出很多判断。

    多态中成员的特点
1). 多态中重名非静态成员方法的特点
当父类引用指向子类对象的时候,多态中成员函数的特点:
[1]. 编译通过的关键
       {1}.查看父类引用变量所属的类是否有调用该类存在的方法。
{2}. 如果调用了该类不存在的方法,编译不能通过
[2]. 运行期间
       JVM运行子类对象自身的方法。
【简单归纳】非静态成员函数在多态调用时候,编译能否通过看左边,运行结果看右边
e.g.1
class Fu{
    void method1(){
        System.out.println("fu method1");
    }
  
    void method2(){
        System.out.println("fu method2");
    }
}

class Zi extends Fu{
    void method1() {
        System.out.println("zi method1");
    }

    void method3() {
        System.out.println("zi method3");
    }
}
 
Zi z =new Zi();
z.method1();    //运行自身的method1
z.method2();    //从父类继承而来,运行父类的method2
z.method3();    //运行自身的method3
打印结果:

e.g.2 如果调用改成以下:
Fu f =new Zi();
f.method1();
f.method2();
f.method3();
这样编译会报错:因为编译的时候,javac不会考虑行与行之间的关系,仅仅检查每一行的语法是否正确。编译的时候,Fu f =new Zi();符合向上类型转换,正确。
但是赋值语句右边的new Zi()是运行时候才执行的,所以编译期间,无法知道将其赋给父类引用的时候,父类引用是否真的能找到父类中没有的方法method3。所以f.method3();产生编译错误。
2). 多态中重名非成员变量的特点
【结论】多态中,子父类中出现同名的成员变量时候,无论是编译还是运行,都参考的是赋值左边(也就是引用变量所属的类)
【面试题目】
class Fu{
    int num =5;
    void method1(){
    }
    void method2(){      
    }
}
class Zi extends Fu{
    int num =8;
    void method1(){
    }
  
    void method2(){      
    }
}

测试类:
class DuotaiDemo{
    public static void main(String[] args) {
        Fuf =new Zi();
        System.out.println(f.num);
        Ziz =new Zi();
        System.out.println(z.num);
    }
}
打印结果是什么?
根据上面的结论:多态中父子类出现同名变量的时候,无论是编译还是运行的时候,都是以等式左边的父类引用为主,不会出现覆盖的情况。
3). 多态中重名静态成员方法的特点
这种情况主要是面试的时候使用。
class Fu{
    static void method4(){
        System.out.println("Fu static method4");
    }
}

class Zi extends Fu{
    static void method4(){
        System.out.println("Zi static method4");
    }
}

class DuotaiDemo{
    public static void main(String[] args) {
        Fuf =new Zi();
        Zi z =new Zi();
        f.method4();
        z.method4();
    }
}


这里面要重点看得是:
f.method4();    打印结果:Fu static method4
z.method4();    打印结果:Zi static method4

【误区】认为子类和父类的同名静态函数会发生覆盖X 不会发生覆盖!!
(1). 当子类父类中同名的静态函数的处理方式
[1]. 结论:通过MyEclipse可以验证出来,子类父类同名静态方法是不会发生覆盖的
[2]. 知识点1:对静态成员的调用方式有两种:
{1}. 类名.静态成员 (根本调用方式)
{2}. 类的引用变量.静态成员 (另一种调用方式)
方式{1}是最根本的方式。因为静态成员加载是通过类名加载的。
方式{2}是要查找到引用变量的类型,再通过找到的类名进行加载。
[3]. 知识点2:对非静态成员的调用方式仅仅有一种,那就是:通过对象引用.来调用
       但是注意:运行的时候,对象引用去调用这个方法的时候,真正的还是通过对象的引用去找这个引用指向的真正的堆内存中的对象,再由这个对象去调用本身存在的方法。
所以:f.method1();打印的结果是 Zi method1. //因为f真正指向的是Zi类的内存对象
     f.method4();打印的结果是 Fu staticmethod4
     原因:f.method4();运行的时候,会去找f对应的类是Fu,由Fu来调用method4()。
    所以相当于:Fu.method4() 所以打印结果是 Fu staticmethod4
[4]. 【绝招】当程序中涉及到静态的调用的时候,一定要检查一下,相应的静态调用是不是通过最根本的类名.静态成员加载的。如果是,继续分析。
****如果不是,首先应该把这种静态成员的调用改成最根本的静态成员调用的方式:类名.静态成员。****
然后再进行分析
4). 子父类中重名的成员的结论
记住即可。
应用场合:在父类的引用指向子类的对象的时候会用到以下结论
[1]. 子类父类出现同名的成员变量的时候,无论是否是静态成员还是非静态成员,编译和运行看得全部是引用所在的类。
       非静态成员变量因为内存存储的原因,不会发生覆盖
       静态成员变量根本就不会存在覆盖一说
[2]. 子类父类出现同名的成员函数的时候,静态非静态调用有区别:
{1}. 静态方法由于不存在覆盖的概念,所以,编译、运行仅仅看父类引用所在的类的类型。
{2}. 非静态方法是:编译时候看父类引用所在的类型,运行时候看引用指向的对象的类型。
【静态成员使用时候的绝招:一律把引用.静态成员的调用方式改成类名.静态成员的方式】
【静态成员(变量+方法)+非静态成员变量看父类引用类型,非静态成员方法编译引用类型,运行子类对象类型】

.    Object
1). Object类的基本特点
(1). Object类在Java整个继承体系中的位置
[1]. Object类位于整个Java继承体系的根节点。
[2]. 所有的类都直接或者间接地继承了这个类。
===>所有的类都具有Object类中定义的属性和方法
[3]. 有的时候,尽管没有写一个类具体继承哪个类,这个类也会默认继承Object类。
【默认继承】指的就是某个类不通过关键字extends就可以继承Object这个类。
(2). Object类的构造方法
查询Object类的源码可知:Object类中没有显式的构造方法。这样可以推断出,JVM会为Object添加上一个默认的空参构造方法。public Object(){}
【注意】由于Object是所有类的根父类,Object类中的第一行没有super()语句
2).Object类的常用方法
(1). equals()方法
[1]. equals()方法的作用:Java认为所有的对象都具有比较性,都可以比较两个对象是否相同
[2]. equals()方法的源码:
public boolean equals(Object obj) {
       return (this == obj);
}
所以:原生的Object的equals方法是***调用比较运算符 ==***比较两个对象的地址是否一致。
[3]. equals()方法的参数:是Object类本身的引用
由于equals(Object obj)参数来的事根父类Object的引用 obj, 所以Object类的对象可以和任意类的对象进行比较。比较的是二者的地址值是否一致。
【注意】:单纯的比较两个对象的地址是没有意义的,所以子类经常重写Object类的equals方法来进行根据特殊条件进行对象的比较。
[4]. 子类重写Object类的equals方法需要注意的事项
重写的时候一定要注意equals方法的参数是Object obj类型的引用变量。绝对不可以是其他类型的变量。因为这样的话,和父类Object的equals方法名相同,但是参数列表不同,javac认为是重载。当底层方法真的要调用equals方法的时候,不会调用你重载的equals方法,使得原本打算重载的equals方法失去了意义。
public boolean equals(Demo d){ //错误的重写!!!

}
这样不是重写Object类的方法,一旦这样写完,底层调用不到这个重写的
public boolean equals(Object obj){//正确的重写

}
(3). hashCode()方法
[1]. hashCode()方法的作用:每个对象都有自己的内存地址值,可以映射为Hash码。Java中用hashCode()方法获取对象在内存中映射的哈希码。
[2] hashCode()方法的源码:
public native int hashCode();
这个方法是一个本地化方法,用native关键字来修饰。这个方法要调用操作系统底层的代码。分配内存地址是有OS来决定的。所以是本地化方法,看不到里面的源码。
(2). toString()方法
[1]. toString()方法的作用:打印这个对象的hash码值和类名
[2]. toString()方法的源码:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
{1}. hashCode()返回的是哈希码的十进制值,这里面将其转化为十六进制的字符串。
{2}. Object的toString返回值是getClass()和hashCode()拼接出来的









猜你喜欢

转载自zhyp29.iteye.com/blog/2305350