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对象的对象引用;
- “=” 操作符使对象引用指向刚才创建的那个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、引用类型的变量保存引用值,所谓的引用值就是对象所在内存空间的“首地址值”,通过对这个引用值来操作对象。
- 常见的引用类型:类类型,接口类型和数组。
- 1、基本类型的变量保存原始值,所以变量就是数据本身。
- 基本类型(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中只有值传递,基本类型传递的是值的副本,引用类型传递的是引用的副本。