首页 > 编程语言 >算法细节系列(1):Java swap

算法细节系列(1):Java swap

时间:2023-07-10 20:32:22浏览次数:48  
标签:Java int 算法 integerA swap public 引用


算法细节系列(1):Java swap

问题

在C++中,swap算法可以用指针来实现,因此在Java中,如果采用如下代码来对两个数字进行交换时,也不会影响两个对象的值。

public class TestSwap {

    public static void main(String[] args) {
        int a =2;
        int b =3;
        System.out.println("交换前 -> "+a+":"+b);
        swap(a, b);
        System.out.println("交换后 -> "+a+":"+b);
    }

    public static void swap(int a,int b){
        int temp =a;
        a = b;
        b = temp;
    }
}

console输出结果为:

交换前 -> 2:3
交换后 -> 2:3

原因

在Java中,对基本类型的操作都是传值操作,在进入swap语句中时,我把a的值传给了新的变量a,而非原先的变量a。这在C++中,也是一个道理。但你又问了,Java不是实现了引用嘛,看如下代码

/**引用传递**/
StringBuffer sb = new StringBuffer("Hello");
addStr(sb);
System.out.println(sb);

public static void addStr(StringBuffer sb){
    sb.append(",my name is demon!");
}

console输出结果为:

Hello,my name is demon!

StringBuffer是Java中的类,定义了一个addStr()方法,并把sb对象,传入到方法中去,并且成功的改变了sb的值,这就说明进入方法体后,方法体中的局部变量sb指向了存放“Hello”的对象内存去了,不管这个指向是引用还是指针,起码引用传递能够改变原先变量sb的值嘛。

没错,但在Java中,对类有两类划分,一类为基本类型,即我们见到的int,char,byte….等等这些小写开头的基本数据类型,还有一类在Java类库中都是以class关键字定义的类型,该类型为引用类型,即只有这些以class定义的类型才能够真正实现引用传递,而基本类型传递的只是参数值而已。

于是我便突发奇想,嗯,在《Thinking in Java》中提到,每个基本类型都有对应的包装类,即Int ->Integer char -> Character,包装类都是用class定义的引用类型,于是咱们来试试对包装类的参数传递是什么结果?

Integer integerA = new Integer(2);
Integer integerB = new Integer(3);

System.out.println("交换前 -> "+integerA+":"+integerB);
swap(integerA, integerB);
System.out.println("交换前 -> "+integerA+":"+integerB);

public static void swap(Integer a,Integer b){
    Integer temp =a;
    a = b;
    b = temp;
}

console输出结果:

交换前 -> 2:3
交换前 -> 2:3

大失所望,怎么回事啊,包装类不是引用类型嘛,为何依旧无法实现swap的正确传值?思考了很久,结合自己封装的包装类是如何实现swap的,得出了比较合理的解释,其实引用本身是对一个类的别名!它不像C++中的指针那样,如果指针指向某个内存单元,对它赋值就能直接改变内存单元的值,对内存单元的操作,直接由指针指向来完成。在Java中,上述版本的swap方法,显然并没有对引用指向的内存单元进行操作,而只是改变了引用的指向。

进入swap方法体后,a = integerA,b =integerB,引用a和引用b,copy了实际变量integerA和integerB,也就是说,虽然方法体内完成了对引用的交换,但a和b分别为躺在内存中的实际数据2和3的另外一个指向罢了。方法体中完成了交换,却不影响integerA和integerB的指向。那跟基本类型的值传递有何区别,基本类型的传递是拷贝内存单元的实际数据,即内存单元中存在两份一模一样的数据,分别由变量a和方法体内的a表示,而引用传递,在内存单元中只存放了一份实际数据,只是变量integerA和方法体内的a均指向该内存单元。

而明显的,只有当方法体中的a对包装类的值进行改变时,才能够影响integerA中内存单元的值。而如果只是简单的对引用进行操作,那么即使是包装类也无法产生效果,从sb中也可以看出,它append了”my name is demon.”这就是对指向该内存单元的数据做了改变,由此改变了原先sb的值。

因此,我们得出一个解决方案,封装自己的包装类,实现引用传递,并且在引用传递的过程中,要改变实际内存单元的值。所以在包装类中我们需要有一个改变类中基本类型的方法。这也就解释了为什么Integer无法实现swap的原因了,不变类中的基本类型是final类型的,即它只允许初始化一次,后续是不能被改变的,否则就不符合int基本类型的性质,且你在Integer官方提供的方法中也无法找到改变初始值的方法。

自己实现的MyInteger实现数据交换:

public class MyInteger {

    private int num;

    public MyInteger(int num){
        this.num = num;
    }

    public int getNum() {
        return this.num;
    }

    public void setNum(int num){
        this.num = num;
    }

    @Override
    public String toString() {
        return num+"";
    }
}

/**引用传递+改变内存单元**/
MyInteger integerA = new MyInteger(2);
MyInteger integerB = new MyInteger(3);
System.out.println("交换前 -> "+integerA+":"+integerB);
swap(integerA, integerB);
System.out.println("交换前 -> "+integerA+":"+integerB);

public static void swap(MyInteger a,MyInteger b){
    MyInteger temp =new MyInteger(a.getNum());//拷贝一份新的值
    a.setNum(b.getNum());;//a为b的值
    b.setNum(temp.getNum());//b为a的值
}

console输出结果:

交换前 -> 2:3
交换前 -> 3:2

欣喜若狂,反复倒腾,总算改变了变量a和b的值。oh,my god!但自己的包装类是没法进行Java自动装箱滴,显然类的封装破坏了基本类型的约束,因此该包装是失败的,在实际过程,我们很有可能因为使用这个类导致一些看不到的bug,因此针对swap方法,我们再提出一种新的解决方案,上述解决方案只为了帮助理解引用,指针,参数传递,引用传递的概念。

正解

在很多排序算法中,它们是这么干的!

int[] data = {2,3};
System.out.println("交换前 -> "+data[0]+":"+data[1]);
swap(data, 0,1);
System.out.println("交换前 -> "+data[0]+":"+data[1]);

public static void swap(int[] data, int a, int b) {  
    int t = data[a];  
    data[a] = data[b];  
    data[b] = t;  
}

这可是结合了指针概念和排序算法的实际应用啊,咱们来谈谈该设计哲学,首先,大多数的排序算法的输入均为一个数组,各种基本类型的组合。因此,swap基本都用在排序算法中,而对数组的传递,实际背后原理是对指针的应用,所以该方法是奏效的。

参考资料

  • Swap in JAVA, 不是想象中的简单
  • Java传参的值传递和引用传递问题


标签:Java,int,算法,integerA,swap,public,引用
From: https://blog.51cto.com/u_16184402/6680339

相关文章

  • Java图片加水印
    分为文字水印和透明图片水印packagecom.qms.report.util;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.File;importjava.io.IOException;publicclassImageWatermark{/***@paramwaterMarkType......
  • 每日汇报 第三周第二天 JAVA中的异常处理
    今日所学:掌握什么是异常;区分运行时异常和非运行时异常;掌握try、catch、finally后面代码块发挥的作用;掌握运用try-catch-finally捕获异常;掌握如何运用throws关键字在方法上抛出异常;掌握使用throw关键字为异常创建对象,进而主动印发某种异常明日计划:JAVA中的枚举与泛型......
  • java内存问题排查
    1.查看进程  输入:jps-v  输出:1jar-Xms2g-Xmx2g-XX:SurvivorRatio=4-XX:MetaspaceSize=256M-XX:MaxMetaspaceSize=256M-XX:MaxDirectMemorySize=256M-Dfile.encoding=UTF-8-Duser.timezone=GMT+08  可以查看机器上的java进程,1是进程ID,jar是进程名称,后面是一......
  • 文心一言 VS 讯飞星火 VS chatgpt (57)-- 算法导论6.4 1题
    文心一言VS讯飞星火VSchatgpt(57)--算法导论6.41题一、参照图6-4的方法,说明HEAPSORT在数组A=(5,13,2,25,7,17,20,8,4)上的操作过程。文心一言:下面是HEAPSORT在数组A=(5,13,2,25,7,17,20,8,4)上的操作过程:调用BUILD-MAX-HEAP函数,将数组A转换为最大堆。将堆顶元......
  • java串口通信
    java串口通信importjava.io.*;importjava.util.*;importgnu.io.*;publicclassSerialTestimplementsSerialPortEventListener{SerialPortserialPort;//串口名称privatestaticfinalStringPORT_NAME="/dev/ttyUSB0";//波特率priv......
  • JAVA集成velocity实现对已有模板替换(占位符变量)替换
      平时我们如果有一些简单的模板替换需求,比如有个txt文件,或者代码生成模板文件要根据传入的变量替换成具体的值就可以使用这个框架 依赖<dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId>......
  • java类和对象学习总结
    当一个引用赋值为null的时候,就代表这个引用不指向任何的对象引用不能指向引用,只能说引用指向了另一个引用的对象.一个引用不能指向多个对象this引用的学习:代表的是当前对象的引用,每一个成员方法的第一个参数默认是thisthis.year this.month   加上this代表给当前的对象......
  • JavaScript加密:常见加密种类、优缺点和代码示例
    当涉及到JavaScript加密时,有多种加密算法和技术可供选择。下面我将列举一些常见的加密种类、它们的优缺点,并提供一些代码案例作为参考。对称加密算法:对称加密算法使用相同的密钥进行加密和解密。常见的对称加密算法包括AES(AdvancedEncryptionStandard)和DES(DataEncryptionS......
  • Java实现浏览器端大文件分片上传实例
    ​ 上周遇到这样一个问题,客户上传高清视频(1G以上)的时候上传失败。一开始以为是session过期或者文件大小受系统限制,导致的错误。查看了系统的配置文件没有看到文件大小限制,web.xml中seesiontimeout是30,我把它改成了120。但还是不行,有时候10分钟就崩了。同事说,可能是客户这里......
  • 面试进阶齐飞!Github一天万赞的阿里Java系统性能优化有多牛?
    前两天在知乎上看到一个问答,说的是:一个Java程序员具备什么样的素质和能力才可以称得上高级工程师?这个问题也引发了我的一些思考,可能很多人会说,“作为高级工程师,基础得过硬、得熟练掌握一门编程语言、至少看过一个优秀开源项目的源代码、有过高并发/性能优化的工作经验、沟通能力......