Java面向对象(中)| Java基础自学笔记

144 阅读10分钟

类中方法的使用

方法的重写

  • 子类继承父类后,可以对父类中同名同参数的方法进行覆盖操作

  • 重写以后,当创建子类对象以后,通过子类对象调用父类中同名同参数的方法时,实际执行的是子类重写父类的方法

  • 重写的规定

    • 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
    • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
      • 特殊情况:子类不能重写父类中声明为private的方法
    • 返回值类型
      • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
      • 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
      • 父类被重写的方法的返回值类型是基本数据类型,则子类重写的方法的返回值类型必须是相同类型
    • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型
    • 子类和父类中同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(非重写)
  • 重写表现为多态性

    • 重载,是指允许存在多个同名的方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。所以,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为 “早绑定” 或 “静态绑定”
    • 而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为 “晚绑定” 或 “动态绑定”

面向对象三大特征

封装与隐藏

继承

继承的好处

  • 减少了代码的冗余,提高了代码的复用性
  • 便于功能的扩展
  • 为之后多态性的使用,提供了前提

继承性的格式

  • class A extends B {}
  • A:子类、派生类、subclass
  • B:父类、超类、基类、superclass
  • 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性、方法
  • 子类继承父类后,还可以声明自己特有的属性或方法,实现功能的拓展

继承性的规定

  • 一个类可以被多个子类继承
  • Java中类的单继承性:一个类只能有一个父类
  • 子父类是相对的概念
  • 子类直接继承的父类称为直接父类,简介继承的父类称为间接父类
  • 子类继承父类后,就获取了直接父类以及所有间接父类中声明的属性和方法

Object类

  • 如果没有显式声明一个类的父类的话,则此类继承与java.lang.Object类

  • 所有的java类(除java.lang.Object类之外)都直接或间接的继承java.lang.Object类

  • Object类中的功能(属性、方法)就具有通用性

    • 属性:无
    • 方法:equals() / toString()
  • Object只声明了空参构造器

  • equals()

    • ==回顾

      • 可以使用在基本数据类型变量和引用数据类型变量
      • 如果比较的是基本数据类型:比较两个变量保存的数据是否相等(不一定类型相同)
      • 如果比较的是引用数据类型:比较两个对象的地址值是否相等,即两个引用是否指向同一个对象实体
      • 使用 “==” 时,必须保证符号左右两边变量类型一致(不一定类型相同)
    • equals() 方法使用

      • 是一个方法,而非运算符

      • 只适用于引用数据类型

      • Object类中equals() 的定义:

        public boolean equals(Object obj){
            return (this == obj);
        }
        

        说明:Object类中定义的 equals() 和 == 的作用是相同的:比较两个对象的地址值是否相同

      • 像String、Date、File、包装类等都重写了Object类中的 equals() 方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的 “实体内容” 是否相同

    • 通常情况下,我们的自定义的类如果使用 equals() 的话,也通常是比较两个对象的 “实体内容” 是否相同。那么,我们就需要对Object类中的 equals() 进行重写

      @Override
      public boolean equals(Object obj){
          if (this == obj) {
              return true;
          }
          
          if (obj instanceof Customer) {
              Customer cust = (Customer)obj;
              //比较两个对象的每个属性是否都相同
              if(this.age == cust.age  && this.name.equals(cust.name)) {
                  return true;
              }else{
                  return false;
              }
              //或
              //return this.age == cust.age && this.name.equals(cust.name);
          }
          
          return false;
      }
      
    • == 和equals() 的区别

      • == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
      • equals() 是属于java.lang.Object类里面的方法,如果该方法没有被重写过,默认也是==;String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点
      • 具体要看自定义类里有没有重写Object的equals方法判断
      • 通常情况下,重写equals方法,会比较类中的响应属性是否相等
  • toString()

    • 当输出一个对象的引用时,实际上就是调用当前对象的toString()

    • Object类中toString()的定义:

      public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
      
    • 像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用对象的toString()时,返回 “实体内容” 信息

    • 自定义类也可以重写toString()方法,当调用此方法时,返回对象的 “实体内容”

子类对象实例化全过程

  • 从结果上来看
    • 子类继承父类后,就获取了父类中声明的属性或方法
    • 创建子类对象后,在堆空间中,就会加载所有父类中声明的属性
  • 从过程上看
    • 当通过子类构造器创建子类对象时,一定会直接或间接的调用父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器为止。正因为加载过所有的父类结构,所以才可以看到内存中有父类的结构,子类对象才可以考虑进行调用

多态

  • 一个事物的多种形态
  • 何为多态性
    • 对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)
  • 多态的使用:虚拟方法调用
    • 有了对象的多态性以后,在编译器只能调用父类声明的方法,在运行期,实际执行的是子类重写父类的方法
  • 多态性的使用前提
    • 类的继承关系
    • 方法的重写
  • 对象的多态性只适用于方法,不适用于属性

向下转型

  • 为什么使用向下转型

    有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用

  • 如何使用向下转型

    使用强制转换符()

  • 使用时注意点

    • 使用强转时,可能出现ClassCastException异常
    • 在向下转型之前,先进行instanceof的判断

包装类(Wrapper)

  • Java中JUnit单元测试

    • 步骤

      1. 选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步

      2. 创建Java类,进行单元测试

        此时的Java类要求:① 类是public的 ② 此类提供无参的构造器

      3. 此类中声明单元测试方法

        此时的单元测试方法:方法的权限是public,没有返回值,没有形参

      4. 此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;

      5. 声明好单元测试方法以后,就可以在方法体内测试相关代码

      6. 写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test

    • 说明

      1. 如果执行结果没有任何异常:绿条
      2. 如果执行结果出现异常:红条
  • 包装类的使用

    • 针对八种基本数据类型定义相应的引用类型——包装类(封装类)

    • 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

      基本数据类型包装类
      byteByte
      shortShort
      intInteger
      longLong
      floatFloat
      doubleDouble
      booleanBoolean
      charCharacter
    • 基本数据类型、包装类、String三者之间的相互转换

      public class WrapperTest {
          
          //String类型 ---> 基本数据类型、包装类
          @Test
          public void test5(){
              String str1 = "123";
              int num2 = Integer.parseInt(str1);
              
              String str2 = "true1";
              boolean b1 = Boolean.parseBoolean(str2);
          }
          
          //基本数据类型、包装类 ---> String类型:
          @Test
          public void test4(){
              int num1 = 10;
              //方式1:连接运算
              String str1 = num1 + " ";
              //方式2:调用String的valueOf(Xxx xxx)
              float f1 = 12.3f;
              String str2 = String.valueOf(f1);
              
              Double d1 = new Double(12.4);
              String str3 = String.valueOf(d1);
              System.out.println(str2);
              System.out.println(str3);//"12.4"
          }
          
          /*
           * JDK 5.0新特性:自动装箱与自动拆箱
           */
          @Test
          public void test3() {
              //自动装箱:基本数据类型 ---> 包装类
              int num2 = 10;
              Integer in1 = num2;//自动装箱
              
              boolean b1 = true;
              Boolean b2 = b1;//自动装箱
              
              //自动拆箱
              System.out.println(in1.toString());
              int num3 = in1;//自动拆箱
              
          }
          
          //包装类 ---> 基本数据类型:调用包装类Xxx的xxxValue()
          @Test
          public void test2(){
              Integer in1 = new Integer(12);
              
              int i1 = in1.intValue();
              System.out.println(i1 + 1);
              
              Float f1 = new Float(12.3);
              float f2 = f1.floatValue();
              System.out.println(f2 + 1);
          }
          
          //基本数据类型 ---> 包装类:调用包装类的构造器
          @Test
          public void test1() {
              
              int num1 = 10;
              Integer in1 = new Integer(num1);
              System.out.println(in1.toString());
              
              Integer in2 = new Integer("123");	//字符串中必须纯数字
              System.out.println(in2.toString());
              
              Float f1 = new Float(12.3f);
              Float f2 = new Float("12.3");
              System.out.println(f1);
              System.out.println(f2);
              
              Boolean b1 = new Boolean(true);	//true
              Boolean b2 = new Boolean("TrUe");	//true
              Boolean b3 = new Boolean("true123");	//false
          }
      }
      

关键字

super

  • super可以用来调用属性、方法、构造器
  • 在子类的方法或构造器中,通过使用 “super.属性” 或 ”super.方法“ 的方式,显式的调用父类中声明的属性或方法,但通常情况下,习惯省略 “super”
  • 当子类和父类中定义了同名的属性时,想要在子类中调用父类中生命的属性,则必须显式的使用 “super.属性” 的方式,表明调用的是父类中声明的属性
  • 当子类重写了父类中的方法以后,想在子类的方法中调用父类中被重写的方法,则必须显式的使用 “super.方法” 的方式,表明调用的是父类中被重写的方法
  • 可以在子类的构造器中显式的使用 “super(形参列表)” 的方式,调用父类中声明的指定构造器
  • “super(形参列表)” 的使用,必须声明在子类构造器的首行
  • 在类的构造器中, “this(形参列表)” 和 “super(形参列表)” 只能二选一
  • 在构造器的首行,没有显式的声明 “this(形参列表)” 或 “super(形参列表)” ,则默认调用的是父类中空参的构造器:super()
  • 在类的多个构造器中,至少有一个类的构造器使用了 “super(形参列表)” , 调用父类中的构造器

instanceof

  • a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false

  • 为了避免在向下转型时出现ClassCastException的异常,在向下转型之前,先进行instanceof的判断,如果返回true就向下转型

  • 如果a instanceof A返回true,则a instanceof B也返回true。其中B是A的父类