Java-Day-10(Object 常用类 + JDK 源码 + 断点调试)

发布时间 2023-04-17 23:48:37作者: 朱呀朱~

Java-Day-10

Object 常用类

equals 方法

  • == 与 equals

    • == 是一个比较运算符

      • 既可以判断基本类型,又可以判断引用类型
      • 如果判断基本类型,判断的是值是否相等
      • 如果判断引用类型,判断的就是地址是否相同,即判断是否是一个对象
    • equals 是 Object 类中的方法,只能判断引用类型

      • 默认判断的是地址是否相等 ( Object 里的 equals 可以看源码分析,实际就是 == ),但子类中往往重写该方法,用于判断内容是否相等 ( String、Integer 里的 equals 是重写了 Object 源码里的 equals )
    • 例:

      Person p1 = new Person();
      p1.name = "zhu";
      
      Person p2 = new Person();
      p2.name = "zhu";
      
      System.out.println(p1 == p2);  // F
      System.out.println(p1.name.equals(p2.name)); // T
      System.out.println(p1.equals(p2)); // F,因为Person对象没对equals方法重写
      
  • Object 里的 equals

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

    仿 String 重写 — 能判断 Person 类的属性相等的话对象 equals 就返回 true

    public boolean equals(Object obj) {
        // 两者同一个对象,就一定是true
        if(this == obj){
            return true;
        }
    	if(obj instanceof Person){
            // 向下转型,因为想要得到 obj 的各个属性
            Person p = (Person)obj;
            return this.name.equals(p.name) && this.age == p.age;
        }
        return false;
    }
    
    // main中初始化过了的person1和2
    System.out.println(person1.equals(person2));
    
  • 小练习

    int n1 = 65;
    float n2 = 65.0f;
    System.out.println(it == fl); // T,值相等
    
    char ch1 = 'A';
    char ch2 = 65;
    System.out.println(ch1 == ch2); // T,A的ASCII码为65
    
    String str1 = new String("hello");
    String str2 = new String("hello");
    System.out.println(str1.equals(str2)); // T,因为String重写了此方法,值相等也可以true
    

hashCode 方法

  • 集合部分再详细讲解

  • 暂且需要记住的结论

    • 作用是为了提高具有哈希结构的容器的效率

    • 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的

    • 两个引用,如果指向的是不同对象,则哈希值是不一样的

    • 哈希值主要根据地址来的 ( 针对不同的对象返回不同的整数 ),但不能等价于地址

      // 查看某一对象的哈希值
      System.out.println(对象名.hashCode());
      
    • 在讲到集合的时候,需要的话也会重写 hashCode()

toString 方法

  • 默认返回:全类名 + @ + 哈希值的十六进制
    • 子类往往重写 toString 方法,用于返回对象的属性信息
  • 一般可以直接在 alt + insert —> toString 方法直接导入方法,返回当前对象的所有属性名 + 值
  • 当直接输出一个对象时,toString 方法会被默认的调用

finalize 方法

  • 当对象被回收时,系统自动调用该对象的 finalize 方法
    • 子类可以重写该方法,做一些释放资源的操作
  • 什么时候被回收:当某个对象没有任何引用时 ( new 了的对象变成仅一个栈名头,无任何指向,可以理解为 = null 了时 ) ,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象 ( 把 new 时开辟的堆里的空间给销毁了,就可以重又能被分配了 ),在销毁该对象前,会先调用该对象的 finalize 方法 ( 默认 Object 类里的,也可以选择重写在里面编写自己的业务逻辑代码,比如释放资源:数据库连接、打开文件 ... )
  • 但不是某一个对象变成垃圾后就会立即被 finalize 回收,那样要实时监控很麻烦,而是垃圾回收机制的调用是由系统来决定的,它自己自带一套算法 ( GC ),jvm 时细讲
    • 但想要测试,一般情况下也可以通过 System.gc() 来主动触发垃圾回收机制,但 gc() 执行时不会造成阻塞,代码仍会继续往下走
    • 但实际开发时,几乎不会运用 finalize

JDK 源码

  • 用 IDEA 举例,一般来说 IDEA 配置好 JDK 后,jdk 的源码也就配置好了

  • 如果没有的话点击菜单 File —> Project Structure —> SDKs —> Sourcepath 然后点击绿色加号加进去

    image-20230415202409500

  • 在需要查看某个方法源码时,将光标放在该方法,ctrl + b 即可,或者在方法上点击右键 —> Go To —> Declaration or...

断点调试

(debug)

  • 了解

    • 断点调试一步一步看源码执行的过程,从而发现错误所在
    • 断点调试过程中是运行状态,是以对象的运行类型来执行的
    • 断点调试也能帮助我们查看 java 底层源代码的执行过程
  • 快捷键

    • F7 — 跳入 — 方法内
    • F8 — 跳过 — 逐行执行代码
    • shift + F8 — 跳出 — 跳出方法
    • alt + shift + F7 — 强制跳入
    • F9 — resume program — 执行到下一个断点
  • 断点使用

    • 左键单击出红点,即一个断点,可点出多个

      image-20230415220533257

    • 右键 Debug

      image-20230415220727781

    • 打开 Debug 页面

      image-20230415221158462

    • Debug 开始,F8 一步步执行,蓝底 — 到哪一行了 ( 运行的下一行 )

      红底 — 断点行处

      image-20230415221830305

    • 遇到 System 控制台输出语句就点击 Console 查看控制台

    • 代码运行完成后就自行跳出 Debug

  • 在 Debug 追源码

    • 在某想要查看的方法所在行按 F7,若只是继续执行了此方法没进去方法源码就

      • alt + shift + F7 强制执行,就能进入所在行的方法的源码处了

      • Setting —> Build,Execution,Deployment —> Debugger —> Stepping —> Do not step into the classes 中取消勾选 java.* 和 javax.*,其他随意,Apply —> OK,就可进入源码了

        image-20230416000943712

    • 有时 F7 进方法后,还有一层要再 F7 才能看到真正执行的方法所在,一直追到最里层的代码实现处

      • F7 多层跳进后,要 shift + F8 才能一层层跳出返回到想要的那一层
  • F9 直接执行下一个断点

    • 断点在 Debug 过程中可以动态地下断点
    • 利用此动态下断点,可以在系统代码里下一个断点,F9 没能执行到所下断点处,即可判断代码不会执行所下断点处的代码,走的是另一条路,从而可以更好的理解复杂性代码
  • 小练习

    • 追踪对象创建的过程
      • 对象 new 创建的代码行处下断点,F7 进入 ( 强制进入 ),到 loadClass 加载类方法处,跳出,再进入,就到构造器方法处, 默认初始化后进行显式初始化把属性值填入
      • 跳入方法后记得跳出,跳入后可以再选择逐行执行
    • 追踪动态访问机制
      • 了然子父类方法调用与属性的变化