Java SE基础1:面向对象三大基本特性

封装:将数据和行为组合成一个对象,并向对象的使用者隐藏实现细节

  • 类的细节

    • 一个对象变量并没有实际包含一个对象,Java中任何对象变量的值都是对存储在另外一个地方的一个对象的引用,new操作符的返回值也是一个引用

      • 如果需要返回一个可变数据域的拷贝,就应该使用clone()

      • 局部变量不会自动初始化为null,必须通过new或将他们设置为null来初始化

    • 构造器

      • 如果构造器中没有显式给域赋初值,那么就会被自动赋予初值:数值为0,布尔值为false,对象引用为null

      • 构造器的具体处理步骤

        1. 所有数据初始化为默认值(0,false或null)

        2. 按照类声明中出现的次序,执行所有域初始化语句和初始化块

        3. 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体(其他构造器调用必须在第一行,如果想同时调用父类构造器和本类其他构造器会报错,可以另外定义函数解决)

        4. 执行本构造器主体

    • 方法与静态方法

      • 方法

        • 除了有括号内的显式参数,还有出现在方法名前的类对象,被称为隐式参数this它是调用该方法的对象本身的引用,有些人把隐式参数称为方法调用的目标或者接收者

        • 方法参数

          • 按值调用:方法接收的是调用者提供的值

          • 按引用调用:方法接收的是调用者提供的变量地址

          • Java总是采用按值调用,方法不能修改传递给它的任何参数变量的值

            • 然而方法参数有两种类型:基本数据类型和对象引用

            • 对于基本数据类型,方法无法修改参数的值

            • 而对象引用可以被方法修改对象中的实例域(这里的参数是对象地址,而修改的是对象的值)

            • 验证Java按值引用的例子:无法通过swap(Object o1, Object o2)来交换这两个对象

        • 方法签名:方法名和方法参数类型构成方法签名,返回类型不是方法签名的一部分

      • 静态方法

        • 是一种不能操作对象的方法,它属于类而不属于类的对象。可以看作是一种没有隐式参数this的方法,静态方法可以访问自身类中的静态域

        • 不推荐使用对象来调用静态方法,使用对象调用时没有多态,引用是什么类型就会去调对应类型的静态方法

    • static实例域

      • 属于类而不属于类对象

      • 每个类中只有一个这样的域,所有的类对象共享

    • final实例域和方法

      • 定义为final的实例域在构建对象时必须初始化,并且在之后的操作中,不能再对它进行修改

      • 这里的修改指的是不能将变量中的对象引用指向其他对象,但是被引用对象的状态是可以改变的

      • 声明为final的方法子类不能覆盖

      • 总结

        • 修饰类:不能被继承

        • 修饰方法:不能被覆盖

        • 修饰变量:不能被改变

继承:复用已存在类的域和方法

  • super关键字

    • 使用super来调用父类方法

    • super与隐式参数this不同,因为super并不是一个对象的引用,它只是一个指示编译器调用父类方法的特殊关键字

    • 使用super调用构造器的语句必须是子类构造器的第一条语句

  • 抽象类

    • 包含一个或多个抽象方法的类必须声明为抽象的

    • 除了抽象方法,抽象类还可以包含具体数据和具体方法

    • 抽象类不能被实例化,但是可以定义一个抽象类的对象变量,它只能引用一个非抽象的子类对象

  • getClass()

    • 返回一个对象所属的类

    • 返回的是变量实际指向的对象的类,比如父类变量引用子类对象,那么返回的其实是子类(和多态一样实际操作的对象是隐含参数this)

  • equals()

    • 编写一个完美的equals()方法

      1. 检测隐式参数this和显式参数otherObject是否引用同一个对象

      2. 检测otherObject是否为null

      3. 比较隐式参数this和显式参数otherObject是否是同一个类

        • 若equals语义在每个子类中有所改变,就用getClass()是否是相同的(子)类

        • 否则用instanceof 是否是同一个父类

      4. 开始比较域,基本类型用==,对象域使用equals()

    • 如果在子类中重新定义equals,就要在其中包含调用super.equals()

    • 如果重新定义equals,那么必须重新定义hashCode方法

      • 因为equals与hashcode的定义必须一致,如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()具有相同的值

      • null安全的方法Objects.hashCode(obj),参数为null会返回0

      • 组合多个散列值的方法,调用Objects.hash(param1, param2...),这个方法会对各个参数调用Objects.hashCode(),并组合这些散列值

      • 数组类型的域,可以使用Arrays.hashCode()方法

      • 标准库中你会看到很多成对出现的接口和使用工具类,如Object/Objects, Collection/Collections, Path/Paths等

    • int的==陷阱

      • 整形变量使用 == 运算符比较时会进行自动装箱,使用Integer.valueof()来创建Integer实例,然后就是两个Integer对象的比较了

      • 而整数类型在-128127之间时,会使用缓存(**在IntegerCache类中有一个Integer数组,用以缓存当数值范围为-128127时的Integer对象**)

      • 如果已经创建了一个相同的整数,那么创建一个新的相同整数时不会使用new关键字,而是用已经缓存的对象,两个对象指向同一地址

      • 所以此时 == 会相等

  • 覆盖父类的方法

    • 子类覆盖父类方法,可见性不能比父类方法可见性低

    • 原因是这样与多态冲突:假设现在有一个父类变量指向一个子类对象,调用子类中重写的方法。如果此时子类中可见性更低,那么方法就无法被访问,这样多态就失效了

多态:一个对象变量可以指示多种实际类型的现象

  • 动态绑定:运行时能自动地选择调用哪个方法的现象。

    • 比如父类和子类都有getSalary()方法,现声明一个父类对象Employee e,而将它指向一个子类对象引用Manager m。那么调用e.getSalary()时实际调用的是Manager的方法(因为是通过this自引用来调用对象的方法)
  • 若父类变量指向子类对象引用,调用子类定义而父类未定义的方法是非法的,因为此方法不是父类的方法

    • 理解方法调用
      1. 编译器查看对象的声明类型和方法名,这一步将得到该类和父类中public的所有名为f的方法(所有可能被调用的方法)

      2. 将调用时提供的方法参数与上一步找到的所有方法参数匹配,这被称为重载解析。若找到多个匹配则会报错。

        • 重载调用哪个方法,和参数的引用类型相关,和实际指向的类型无关
      3. 根据隐式参数的实际类型确定调用方法指令

      4. 动态绑定确认最适合的被调方法

  • 总结:能够调用哪些方法取决于引用,实际调用的方法取决于引用指向的对象,无论调用哪个方法,都是在一个实际的对象上执行的,即隐含参数this执行

posted @ 2020-07-13 15:19  codespoon  阅读(174)  评论(0编辑  收藏  举报