编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 1.1String深入理解原理
  • 1.2浮点型数据深入研究
  • 1.3数据装箱和拆箱原理
  • 1.4泛型由来和设计思想
  • 1.5加密和解密设计和原理
  • 2.1面向对象设计思想
  • 2.2抽象类和接口设计
  • 2.3封装和继承设计思想
  • 2.4复用和组合设计思想
  • 2.5对象和引用设计思想
  • 3.1IO流设计思想和原理
  • 3.2为何设计序列化数据
  • 3.3各种拷贝数据比较
  • 3.4高效文件读写的原理
  • 4.1反射性能探索和优化
  • 4.2为何要设计注解思想
  • 4.3动态代理的设计思想
  • 4.4SPI机制设计的思想
  • 4.5异常设计和捕获原理
  • 4.6虚拟机如何处理异常
  • 4.7四种引用设计思想
  • 5.1线程的前世今生探索
  • 5.2线程通信的设计思想
  • 5.3线程监控和Debug设计
  • 5.4线程和JVM之间联系
  • 5.5线程池使用技巧介绍
  • 5.6线程池设计核心原理
  • 5.7线程如何最大优化
  • 6.1多线程并发经典案例
  • 6.2并发安全前世今生
  • 6.3线程安全如何保证
  • 6.4变量的线程安全探索
  • 6.5并发上下文切换原理
  • 6.6理解CAS设计和由来
  • 6.7协程设计思想和原理
  • 6.8事物并发模型解读
  • 6.9并发设计模型研究
  • 6.10并发编程数据一致性
  • 6.11锁问题的定位和修复
  • 6.12多线程如何性能调优
  • 7.1类的加载过程和原理
  • 7.2对象布局设计的原理
  • 7.3双亲委派机制设计思想
  • 7.5代码攻击和安全防护
  • 7.6设计动态生成Java类

2.5对象和引用设计思想

目录介绍

  • 01.对象和引用概念
  • 02.对象和引用的区别
  • 03.理解对象和引用
  • 04.基本类型和引用类型
  • 05.Java是什么传递
  • 06.通过案例验证传递
  • 07.Java中只有值传递

01.对象和引用概念

1.1 通过案例了解对象和引用

  • 在Java中万物皆对象,比如我们定义一个简单的动物类:
    class Animal {
        String count;
        String weight;
    }
  • 有了这个Animal类之后,我们可以来创建一个Animal对象:
    Animal an = new Animal();
  • 我们把编写这个语句的动作就称作创建一个对象,细化这个动作为:
    • 1.右面的”new Animal”,是以Animal类为模板的,在堆空间里创建一个Animal对象;
    • 2.末尾的”( )”代表着:在对象创建之后,立即调用Animal类的构造函数,对新生成的对象进行初始化。(如果没构造函数,Java会有一个默认的构造函数的);
    • 3.左面的”Animal an”创建了一个Animal类引用变量。即以后可以用来指向Animal对象的对象引用;
      1. “=” 操作符使对象引用指向刚才创建的那个Animal对象。
  • 拆分开也就是:等同于
    Animal an;
    an = new Animal();
  • 有两个实体:一个是对象引用变量;一个是对象本身。在java中,都是通过引用来操纵对象的,这也是两者的区别。

1.2 一个引用指向多个对象

  • 再看一个例子:
    Person person;
    person = new Person("张三");
    person = new Person("李四");
    • 这里让person先指向了“张三”这个对象,然后又指向了“李四”这个对象。也就是说,Person person,这句话只是声明了一个Person类的引用,它可以指向任何Person类的实例。这个道理就和下面这段代码一样:
    int a;
    a=2;
    a=3;
    • 这里先声明了一个int类型的变量a,先对a赋值为2,后面又赋值为3.也就是说int类型的变量a,可以让它的值为2,也可以为3,只要是合法的int类型的数值即可。
  • 也就是说,一个引用可以指向多个对象。

1.3 一个对象被多个引用所指

  • 一个对象可不可以被多个引用所指呢?答案当然是可以的。
    Person person1 = new Person("张三");
    Person person2 = person1;
    • person1和person2都指向了“张三”这个对象。

02.对象和引用的区别

  • 1、关联性:
    • 当对象的引用变量指向对象时,两者就互相联系起来,改变引用的属性,就会改变对象的属性;
    • 如果同一个对象被多个引用变量引用的话,则这些引用变量将共同影响这个对象本身;
    • 在java中,都是通过引用来操纵对象的。
  • 2、差异性:
    • 一个对象可以被不同的引用变量来操纵,同时一个引用变量也可以指向不同的对象,但是同一时刻下只能指向一个对象。
    • 从存储空间上来看,对象和引用也是相互独立的,对象一般存储在堆中,而引用存储在堆栈中(存储速度而更快)。

03.理解对象和引用

  • 所以理解好下面这两句话的真正含义非常重要
    Case cc=new Case();
    
    Case cc;
    cc=new Case();
    • 注意,栈内存储的除了基本类型的变量(String,int这种类型的变量)还会存储对象的引用变量。java中,引用变量实际上是一个指针,它指向的是堆内存中对象实例。
  • 可以分为4个步骤
    • 1.在栈内存里面开辟了空间给引用变量cc,这时cc=null
    • 2.new Case()在堆内存里面开辟了空间给Case类的对象,这个对象没有名字
    • 3.Case()随即调用了Case类的构造函数
    • 4.把对象的地址在堆内存的地址给引用变量cc
  • 这样我们就明确了:
    • Java中,这里的“=”并不是赋值的意思,而是把对象的地址传递给变量;
    • 对象创建出来,其实连名字都没有,因此必须通过引用变量来对其进行操作。

04.基本类型和引用类型

  • Java中的数据类型分为两种为基本类型和引用类型。
    • 1、基本类型的变量保存原始值,所以变量就是数据本身。
      • 常见的基本类型:byte,short,int,long,char,float,double,Boolean,returnAddress。
    • 2、引用类型的变量保存引用值,所谓的引用值就是对象所在内存空间的“首地址值”,通过对这个引用值来操作对象。
      • 常见的引用类型:类类型,接口类型和数组。
  • 基本类型(primitive types)
    • primitive types 包括boolean类型以及数值类型(numeric types)。numeric types又分为整型(integer types)和浮点型(floating-point type)。整型有5种:byte short int long char(char本质上是一种特殊的int)。浮点类型有float和double。
  • 引用类型(reference types)
    • ①接口 ②类 ③数组
  • 基本数据类型和引用数据类型传递区别
    • 程序设计语言中有关参数传递给方法的两个专业术语是:
      • 按值调用:表示方法接收的是调用者提供的值。
      • 按引用调用:表示方法接收的是调用者提供的变量的地址。
    • 在Java中 不存在按引用调用,也就是说,假如方法传递的是一个引用数据类型,那么可以修改 引用所指向的对象的属性,但不能 让引用指向其它的对象。
  • 传递基本数据类型
    public static void methodBasic() {
        int lNum = 3;
        methodRunInner(lNum);
        Log.d("CopySample", "After methodRunInner, lNum=" + lNum);
    }
    
    private static void methodRunInner(int lNum) {
        lNum++;
        Log.d("CopySample", "methodRunInner, lNum=" + lNum);
    }
  • 传递引用数据类型
    • 当传递的是引用数据类型,可以在函数中修改该引用所指向的对象的成员变量的值,如下所示:
    public static void methodRef() {
        NumHolder holder = new NumHolder();
        holder.num = 3;
        methodRunInner(holder);
        Log.d("CopySample", "After methodRunInner, holder.num=" + holder.num);
    }
    
    private static void methodRunInner(NumHolder holder) {
        holder.num++;
        Log.d("CopySample", "methodRunInner, holder.num=" + holder.num);
    }
    
    private static class NumHolder {
        int num;
    }

05.Java是什么传递

5.1 什么是值传递

  • 在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。
  • 比如String类,设计成不可变的,所以每次赋值都是重新创建一个新的对象,因此是值传递!

5.2 什么是引用传递

  • 引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间。
  • 引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。
  • 在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

5.3 得出结论

  • 得出以下结论:
    • 基本数据类型传值,对形参的修改不会影响实参;
    • 引用类型传引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。
    • String, Integer, Double等immutable的类型特殊处理,可以理解为传值,最后的操作不会修改实参对象。
  • 如何理解引用类型的按值传递?
    • 引用类型的按值传递,传递的是对象的地址。只是得到元素的地址值,并没有复制元素。比如数组,就是引用传递,假如说是值传递,那么在方法调用赋值中,将实参的值复制一份传递到函数中将会非常影响效率。

06.通过案例验证传递

  • 倘若“将一个对象赋值给另一个对象”,实际是将“引用”从一个地方复制到另一个地方.

6.1 测试基本类型传递数据

  • 先测试基本类型传递
    public void test1(){
        int i = 50;
        testBasicType(i);
        System.out.println("main 基本类型 m3 :"+i);//50
    }
        
        
    //基本类型的参数传递
    //可以理解为传值,最后的操作不会修改实参对象
    public static void testBasicType(int m) {
        System.out.println("main 基本类型 m1 :" + m);//m=50
        m = 100;
        System.out.println("main 基本类型 m2 :" + m);//m=100
    }
    
    //打印值
    2021-08-27 14:50:38.207 21148-21148/com.ycbjie.ychybrid I/System.out: main 基本类型 m1 :50
    2021-08-27 14:50:38.207 21148-21148/com.ycbjie.ychybrid I/System.out: main 基本类型 m2 :100
    2021-08-27 14:50:38.207 21148-21148/com.ycbjie.ychybrid I/System.out: main 基本类型 m3 :50

6.2 测试引用类型传递数据

  • 再测试引用类型传递
    StringBuffer sb;
    public void test1(){
        StringBuffer sMain = new StringBuffer("init");
        System.out.println("main 引用类型 s1 :" + sMain.toString());//init
        add(sMain);
        System.out.println("main 引用类型 s3 :" + sMain.toString());//init_add
        changeRef(sMain);
        System.out.println("main 引用类型 s5 :" + sMain.toString());//init_add
        sb = sMain;
        System.out.println("main 引用类型 s7 :" + sb.toString());//init_add
        sb = null;
        System.out.println("main 引用类型 s6 :" + sMain.toString());//init_add
        System.out.println("main 引用类型 s8 :" + sb);//null
    }
    
    //参数为对象,不改变引用的值
    //s即sMain指向的对象执行了append方法,在原来的字符串上加了段“_add”
    public static void add(StringBuffer s) {
        s.append("_add");
        System.out.println("main 引用类型 s2 :" + s.toString());//init_add
    }
    
    //参数为对象,改变引用的值
    //引用变量指向了一个新的对象,已经不是sMain指向的对象了
    public static void changeRef(StringBuffer s) {
        s = new StringBuffer("Java");
        System.out.println("main 引用类型 s4 :" + s.toString());//Java
    }
  • 结果打印值
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s1 :init
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s2 :init_add
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s3 :init_add
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s4 :Java
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s5 :init_add
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s7 :init_add
    2021-08-27 14:57:52.370 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s6 :init_add
    2021-08-27 14:57:52.371 1719-1719/com.ycbjie.ychybrid I/System.out: main 引用类型 s8 :null
  • 传递过来的明明是对象的引用,为什么就是值得传递呢?
    • 因为传递之前,被传的就是个引用啊,我们所谓的“传地址”,在传之前,那可是一个实例,传过来的是实例的地址。
    • 这里传递的值,从始至终就是个地址,sMain就是个地址,传给s还是个地址。你们感受下:
    • s2那里输出的结果会是“init_add”。而s4那里s引用了一个新的对象,根本没有进行参数的传递,它和之前的sMain没有关系了。

6.3 看一个综合案例

  • 看下面代码案例
    private void test1(){
        Demo demo = new Demo();
        demo.change(demo.str, demo.ch);
        Log.d("yc---",demo.str);
        Log.d("yc---", Arrays.toString(demo.ch));
        //打印值
        //yc---: hello
        //yc---: [c, b]
    }
    
    public class Demo {
        String str = new String("hello");
        char[] ch = {'a', 'b'};
        public void change(String str, char[] ch) {
            str = "ok";
            ch[0] = 'c';
        }
    }

07.Java中只有值传递

  • 在日常编码中,会经常看到如下现象:
    • 1、对于基本类型参数,在方法体内对参数进行重新赋值,不会改变原有变量的值。
    • 2、对于引用类型参数,在方法体内对参数进行重新赋予引用,不会改变原有变量所持有的引用。
    • 3、方法体内对参数进行运算,不会改变原有变量的值。
    • 4、对于引用类型参数,方法体内对参数所指向对象的属性进行操作,将改变原有变量所指向对象的属性值。
  • 记住一句话
    • java中只有值传递,基本类型传递的是值的副本,引用类型传递的是引用的副本。
贡献者: yangchong211
上一篇
2.4复用和组合设计思想
下一篇
3.1IO流设计思想和原理