学习-Java类和对象之对象引用之坐标系中两点距离计算

本文介绍了Java中对象引用的概念,对象比较的方法,以及垃圾收集机制,重点讲解了引用计数法和可达性分析在内存管理中的应用,以求解两点间距离问题为实例,展示了分代收集算法在不同内存区域的选择和优化。
摘要由CSDN通过智能技术生成

第1关:学习-Java类和对象之对象引用之坐标系中两点距离计算

任务描述

本关任务:已知两个点 A、B 以及坐标分别为(2,3) 、(8,-5) ,求 A 和 B 两点之间的距离。

两点间的距离计算公式:设两个点A、B以及坐标分别为(x1​,y1​)、(x2​,y2​),则A和B两点之间的距离为:∣AB∣=(x1​−x2​)2+(y1​−y2​)2​。

相关知识

为了完成本关任务,你需要掌握:

  1. 对象的引用和对象;
  2. 对象的比较;
  3. 垃圾收集机制。
对象的引用和对象

首先我们来看一个例子:

 
  1. public class Demo{
  2. //默认构造方法
  3. public Demo{ }
  4. }

接下来,我们用 Demo 类来创建一个对象。

 
  1. Demo demo=new Demo();

我们来对这条语句进行一个解析:

  1. 右边的“new Demo”,是以 Demo 类为模板,在堆空间里创建一个 Demo 对象;
  2. 末尾的()意味着,在对象创建后,立即调用 Demo 类的构造函数,对刚生成的对象进行初始化;
  3. 左边的“Demo demo”创建了一个 Demo 类引用变量,它存放在栈空间中。也就是用来指向 Demo 对象的对象引用;
  4. “=”操作符使对象引用指向刚创建的那个 Demo 对象。

当然这条语句我们也可以写成:

 
  1. Demo demo; //创建对象引用,存储在栈内存
  2. demo=/*将对象引用指向对象*/new Demo(); //创建对象,存储在堆内存

对象与引用的关系:

  • 一个对象引用可以指向一个对象;

     
      
    1. Demo demo; //一个对象引用
    2. demo=new Demo(); //一个对象引用指向一个对象
  • 也可以不指向对象;

     
      
    1. Demo demo; //创建对象引用,但是没有指向对象
  • 一个对象可以被一个对象引用;

     
      
    1. Demo demo; //创建对象引用
    2. demo=new Demo(); //创建对象,并被一个对象引用指向
  • 也可以被多个对象引用同时引用。

     
      
    1. Demo demo1,demo2,demo3; //创建多个对象引用
    2. demo1=new Demo();
    3. demo2=demo1;
    4. demo3=demo2; //创建对象,并被多个对象引用指向

引用只是存放一个对象的内存地址,并非存放一个对象, 就好比钥匙与房子的关系,钥匙相当于引用,房子相当于对象,钥匙只是能打开房子,而并非就是一个房子。创建一个引用对象就是创建一把钥匙而并非是造了一个房子。

对象的比较

在 Java 之中不仅仅存在两个数字与两个字符串之间的比较,还存在两个对象之间的比较。

众所周知,两个数字之间的比较我们使用“==”,两个字符串之间的比较我们使用“equals()”,那么两个对象之间如何进行比较呢?“==”比较的是两个对象引用的地址是否相等,而“equals()”比较的是两个对象引用的内容是否相等,而既然要进行两个对象之间的比较,那么就必须要实现两个对象之间所有属性内容的比较。

例子:

 
  1. public class Person {
  2. private String name;
  3. private int age;
  4. public Person(String name, int age) {
  5. this.name = name;
  6. this.age = age;
  7. }
  8. public boolean compare(Person per) {//此时有两个对象:this表示当前对象,另外一个是参数传递
  9. if (this == per){
  10. return true;//如果自己和自己比较
  11. }
  12. if (per == null){
  13. return false;//若传入的为空
  14. }
  15. if (this.name.equals(per.name) && this.age == per.age) {//此时per对象已经在类的内部,可以直接利用
  16. return true;
  17. }
  18. return false;
  19. }
  20. public static void main(String args[]) {
  21. Person perA = new Person("123", 20);
  22. Person perB = new Person("123", 20);//将对象拥有的属性进行完整比对
  23. if (perA.compare(perB)) {
  24. System.out.print("两个对象相等!");
  25. } else {
  26. System.out.print("两个对象不相等!");
  27. }
  28. }
  29. }

执行结果:

 
  1. 两个对象相等!
垃圾收集机制

自动垃圾回收是一种在堆内存中找出哪些对象在被使用,还有哪些对象没被使用,并且将后者删掉的机制。所谓使用中的对象(已引用对象),指的是程序中有指针指向的对象;而未使用中的对象(未引用对象),则没有被任何指针给指向,因此占用的内存也可以被回收掉。

在用 C 之类的编程语言时,程序员需要自己手动分配和释放内存。而 Java 不一样,它有垃圾回收器,释放内存由回收器负责。本文接下来将介绍垃圾回收机制的基本过程。

既然我们要做垃圾回收,首先我们得搞清楚垃圾的定义是什么,哪些内存是需要回收的。

Java 中标记垃圾的算法主要有两种,引用计数法和可达性分析算法。我们首先来介绍引用计数法。

引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。

如果该对象被其它对象引用,则它的引用计数加 1,如果删除对该对象的引用,那么它的引用计数就减 1,当该对象的引用计数为 0 时,那么该对象就会被回收。当对象被赋值为 null 是,该对象的引用计数会变为 0 。

但是它有一个缺点:无法检测出循环引用的情况,导致内存泄露。

我们举一个简单的例子。

 
  1. public class MyObject {
  2. public MyObject childNode;
  3. }
 
  1. public class ReferenceCounterProblem {
  2. public static void main(String[] args) {
  3. MyObject object1 = new MyObject();
  4. MyObject object2 = new MyObject();
  5. object1.childNode = object2;
  6. object2.childNode = object1;
  7. }
  8. }

从上述代码中我们可以看出,object1 和 object2 并没有任何价值,但是他们循环引用,造成内存泄露。

可达性分析算法是从离散数学中引入的,也是如今正在使用的策略,程序把所有的引用关系看作一张图(DOM 图类似),从一个节点 GC ROOT 开始,寻找对应的引用节点,找到这个节点以后,继续寻找这个节点的引用节点,就如递归思想一般,遍历所有,当所有的引用节点寻找完毕之后,剩余的节点则被认为是没有被引用到的节点,即无用的节点,无用的节点将会被判定为是可回收的对象。

通过可达性算法,成功解决了引用计数所无法解决的问题-“循环依赖”,只要你无法与 GC Root 建立直接或间接的连接,系统就会判定你为可回收对象。那这样就引申出了另一个问题,哪些属于 GC Root。

在 Java 语言中,可作为 GC Root 的对象包括以下 4 种:

  • 虚拟机栈中的引用对象;

  • 方法区中的常量引用对象;

  • 方法区中的类静态属性引用对象;

  • 本地方法栈中的引用对象。

在确定了哪些垃圾可以被回收后,垃圾收集器要做的事情就是开始进行垃圾回收,但是这里面涉及到一个问题是:如何高效地进行垃圾回收。

标记-清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为 2 部分,先把内存区域中的这些对象进行标记,哪些属于可回收标记出来,然后把这些垃圾拎出来清理掉。清理掉的垃圾就变成未使用的内存区域,等待被再次使用。

这逻辑再清晰不过了,并且也很好操作,但它存在一个很大的问题,那就是内存碎片。当我们将垃圾回收完,内存会被切分成很多段。

我们知道开辟内存空间时,需要的是连续的内存区域,如果这时候我们需要一个 2M 的内存区域,其中有 2 个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。

复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。

它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

保证了内存的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。

但是它也很明显的暴露了另一个问题,假设我有一个 140 平米的房子,那么就只能当 70 平米的小两房来使,代价实在太高。

标记-整理算法(Mark-Compact)的标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,再清理掉端边界以外的内存区域。

标记-整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。

看起来很美好,但它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。

分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述 3 种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。

对象存活周期的不同将内存划分为几块。一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用适当的收集算法。

在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。

而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记-整理算法来进行回收。

编程要求

仔细阅读右侧编辑区内给出的代码框架及注释,按照提示编写程序代码。

测试说明

平台将使用测试集运行你编写的程序代码,若全部的运行结果正确,则通关。 可在右侧“测试结果”区查看具体的测试集详情。


开始你的任务吧,祝你成功!

源代码:

/**
 * 任务:已知两个点 A、B 以及坐标分别为(2,3) 、(8,-5) ,求 A 和 B 两点之间的距离。
 * 类名为:Distance
 */

public class Distance {

// 请在下面的Begin-End之间按照注释中给出的提示编写正确的代码

/********** Begin **********/

    /**
     * 定义一个静态方法,该方法计算坐标两点的距离,携带四个参数,分别为x1、y1、x2、y2的值
     * 将距离结果返回,返回类型为double
     */
    static double point(double x1,double y1,double x2,double y2)
    {
        double d;
        d=Math.sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
        return d;
    }
    public static void main(String[]args)
    {
        System.out.printf("A、B两点的距离为%.6f",Distance.point(2,3,8,-5));
    }


    // 定义主方法


    // 通过类名.方法名的方式调用计算两点间距离的方法,分别将A、B的x1、y1、x2、y2的值传入该方法中

    // 不换行输出,输出格式: A、B两点的距离为xx


/********** End **********/

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值