Java中的类、对象、方法、传参机制

前言

  • java 设计者引入 类与对象(OOP) ,根本原因就是现有的技术,不能完美的解决新的新的需求。
  • 在本文中只整理了面对对象编程基础其中第一部分,讲解了:
  1. 什么是类和对象、成员变量和成员方法;
  2. 怎么创建和使用类和对象、成员变量和成员方法;
  3. 最最重要的是整理和总结了类和对象在内存中的存在形式,类和对象的内存分配机制,方法的调用机制原理,成员方法传参机制…这些底层原理;
  • 只要理解并掌握了以上的内存中各种底层机制原理,便可以更好地学习接下来更加复杂的面对对象编程的学习内容了。大家一起加油!

一、类与对象

  • 一个程序就是一个世界,有很多事物,每个事务都可以对应一个对象,每个对象有着自己的[属性,行为])。
  • 把一些对象的相同属性和行为提取出来,便可以创建一个类。 例如:将地球上所有猫拥有的的相同属性(年龄…)和行为(奔跑、捕猎…)提取出来,便可以创建一个猫类。只要我们新创建了一个猫类对象,该对象就会拥有猫类中的所有属性和行为。

1. 类和对象的关系示意图

  • 如下图所示:
    在这里插入图片描述
    在这里插入图片描述
  • 说明:从猫类到猫类对象,有几种说法:(1)创建一个猫类对象;(2)实例化一个猫类对象;(3)把猫类实例化;这几个说法不同,但本质上是一样的。
  • 类和对象的区别和联系:
  1. 类是抽象的,概念的,代表一类事物,比如人类,猫类…, 即它是我们自定义的一种数据类型;可以类比数组;
  2. 对象是具体的,实际的,代表一个具体事物, 即 对象=实例;
  3. 类是对象的模板,对象是类的一个个体,对应着一个实例。

2. 对象在内存中存在形式(重要!)

  • 图示如下:
    在这里插入图片描述
  • 补充:Java 内存的结构分析:
    1)栈: 一般存放基本数据类型和局部变量 ;
    2)堆: 存放对象的内存空间、引用数据类型,如,数组;
    3)方法区:常量池(常量,比如字符串)、 类加载信息。
  • 解释:在新创建一个猫类对象时:(结合上图理解!)
  1. 首先在内存中的方法区加载了Cat类的信息,包括属性信息和方法信息,只会加载一次;
  2. 接着在堆内存中开辟了一个内存空间,空间大小是由类中的属性个数决定的;这个内存空间是真正的猫类对象cat;
  3. 该对象空间中初始划分有几个空间块(属性个数),存储数据的值为默认值;
  4. 然后把该对象空间的地址赋值给栈内存中的 cat (cat 只是一个对象名),让 cat 可以引用该对象的内存空间;
  5. 然后给该对象的指定属性初始化(cat.name = “小白”…);
  6. 基本类型的数据直接存储在对象堆内存空间的空间块中;
  7. String类型的数据则会在方法区中开辟一个常量池,然后在池中新开辟一个空间,用来存储String类型的数据;
  8. 最后将常量池中存储String类型数据的空间地址,赋值给对象堆内存空间中的对应空间块,让其可以引用。

3. 属性/成员变量/字段

  • 基本介绍:
  1. 从概念或叫法上看: 成员变量 = 属性 = field(字段) (即 成员变量是用来表示属性的,授课中,统一叫 属性);
  2. 属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象、数组)。比如我们前面定义猫类 的 age 就是属性。
  • 注意事项和细节说明:
  1. 属性的定义语法同变量,示例:

访问修饰符 属性类型 属性名;

  • 这里简单的介绍访问修饰符: 控制属性的访问范围 ;
  • 有四种访问修饰符 public, proctected, 默认, private ,后面会详细介绍 ;
  1. 属性的定义类型可以为任意类型,包含基本类型或引用类型 ;
  2. ==属性如果不赋值,有默认值,规则和数组一致。具体如下: ==

int 0,short 0,byte 0,long 0,float 0.0,double 0.0,char \u0000,boolean false,String null。

4. 如何创建对象

  1. 先声明再创建:
    Cat cat ; // 声明对象 cat
    cat = new Cat(); // 创建 = 给对象在堆内存中开辟了空间
  2. 直接创建:
    Cat cat = new Cat(); // 声明对象 cat,并同时给对象在堆内存中开辟了空间

5. 如何访问属性

  • 基本语法:
    对象名.属性名;
    举例:cat.name ; cat.age; cat.color;

6. 类和对象的内存分配机制(重要!)

看一个思考题:我们定义一个人类(Person)(包括 名字,年龄)。
回答下图问题:
在这里插入图片描述

  • 内存图如下:
    在这里插入图片描述
  • Java 创建对象的流程简单分析:
  1. 先加载 Person 类信息(属性和方法信息, 只会加载一次) ;
  2. 在堆中分配空间, 进行默认初始化(看规则) ;
  3. 把地址赋给 p , p 就指向对象;
  4. 进行指定初始化, 比如 p.name = ”jack” 、p.age = 10;

再看一个练习题,并分析画出内存布局图,进行分析
如下图:
在这里插入图片描述

  • 内存分析图如下:
    在这里插入图片描述
  • 解释:
  1. 执行b = a 时,将a 的内存地址赋值给b (引用传递),此时b 指向了a 的内存空间;
  2. 执行b.age = 200,则改变了该内存空间中的值,所以a.age 也同时变成了200;
  3. 接着执行了 b = null,此时b 将指向一个空值;
  4. 所以最后输出 b.age 时,会报错,因为b 已经没有指向任何内存空间(对象)了,自然没有age 这个属性。

二、成员方法

  • 在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名…),我们人类还有一些行为,比如:说话、跑步…这时就要用成员方法才能完成这些行为。
  • 成员方法的好处:
    1. 提高代码的复用性 ;
    2. 可以将实现的细节封装起来,然后供其他用户来调用即可。

1. 方法快速入门

  • 创建一个Person 类,在里面定义几个成员方法:
  1. 添加 speak 成员方法,输出 “我是一个好人”
  2. 添加 cal01 成员方法,可以计算从 1+…+1000 的结果
  3. 添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+…+n 的结果
  4. 添加 getSum 成员方法,可以计算两个数的和
  • 代码如下:
// 主类
public class Method01 { 
	//编写一个 main 方法 
	public static void main(String[] args) { 
		//方法使用 
		//1. 方法写好后,如果不去调用(使用),不会输出 
		//2. 先创建对象 ,然后调用方法即可 
		Person p1 = new Person(); 
		p1.speak(); //调用 speak 方法 
		p1.cal01(); //调用 cal01 方法 
		p1.cal02(5); //调用 cal02 方法,同时传入 n = 5 
		p1.cal02(10); //调用 cal02 方法,同时传入 n = 10 
		
		//调用 getSum 方法,同时传入 num1=10, num2=20
		//把方法 getSum 返回的值,赋给变量 returnRes 
		int returnRes = p1.getSum(10, 20); 
		System.out.println("getSum 方法返回的值=" + returnRes); 
	} 
}

// 创建Person类
class Person { 
	String name; 
	int age; 
	//方法(成员方法) 
	
	//添加 speak 成员方法,输出 “我是一个好人” 
	//1. public 表示方法是公开 ;
	//2. void : 表示方法没有返回值 ;
	//3. speak() : speak 是方法名,() 里面是形参列表 ;
	//4. {} 方法体,可以写我们要执行的代码 ;
	//5. System.out.println("我是一个好人"); 表示我们的方法就是输出一句话 。
	public void speak() { 
		System.out.println("我是一个好人"); 
	}
	
	//添加 cal01 成员方法,可以计算从 1+..+1000 的结果 
	public void cal01() { 
		//循环完成
		int res = 0; 
		for(int i = 1; i <= 1000; i++) { 
			res += i; 
		}
		System.out.println("cal01 方法 计算结果=" + res); 
	}
	
	//添加 cal02 成员方法,该方法可以接收一个数 n,计算从 1+..+n 的结果; 
	// (int n) 形参列表, 表示当前有一个形参 n, 可以接收用户输入 。
	public void cal02(int n) { 
		//循环完成 
		int res = 0; 
		for(int i = 1; i <= n; i++) { 
			res += i; 
		}
		System.out.println("cal02 方法 计算结果=" + res); 
	}

	//添加 getSum 成员方法,可以计算两个数的和 ;
	//1. public 表示方法是公开的 ;
	//2. int :表示方法执行后,返回一个 int 值 ;
	//3. getSum 方法名 ;
	//4. (int num1, int num2) 形参列表,2 个形参,可以接收用户传入的两个数 。
	//5. return res; 表示把 res 的值, 返回 
	public int getSum(int num1, int num2) {
		int res = num1 + num2; 
		return res; 
	} 
}

2. 方法的调用机制原理(重要!)

  • 以上面Person类中的 getSum 方法为例,解释方法的调用机制。
  • 执行流程图如下:
    在这里插入图片描述
  • 解释:
  1. 每次调用一个方法,虚拟机都会在栈内存中开辟一个独立的空间,方法之间不会相互影响。上图中,程序首先执行main 方法,在栈内存中开辟了一个main 栈空间;
  2. 在main 栈中,创建了一个Person 类的对象p1,此时在堆内存中开辟了一个空间,并把该空间的地址返回给 p1;
  3. 接着,p1 调用了getSum 方法,并传入了10、20 这两个实参,此时虚拟机在栈内存中开辟了一个独立的getSum 栈空间,程序跳到该栈空间中执行,直到getSum 方法执行完毕或者遇到 return 语句才会退出该栈空间,回到main 栈空间;
  4. 在getSum 栈中,执行了形参的赋值,相当于执行了:
    int num1 = 10;
    int num2 = 20;
    int res = num1 + num2;
    return res;
    getSum 方法执行结束,程序退出该栈并返回main 栈,继续执行main 栈剩下的语句;
  5. 程序返回main 栈后,声明了int 类型变量 returnRes 来接收getSum 方法返回的int 类型的值,然后输出;
  6. 最后,main 方法也执行完毕,退出main 方法,相当于退出了程序,程序终止。

3. 成员方法的定义

  • 基本语法:
访问修饰符 返回数据类型 方法名(形参列表..{
	方法体 语句; 
	return 返回值; 
}
  • 解释:
  1. 形参列表:表示成员方法需要传入的数据,例如, cal(int n) , getSum(int num1, int num2),这里的n、num1、num2 都是在调用方法时需要传入的数据;
  2. 返回数据类型:表示成员方法返回的数据, void 表示没有返回值;
  3. 方法主体:表示为了实现某一功能的代码块;
  4. return 语句不是必须的。

4. 注意事项和使用细节

  • ==注意事项: ==
  1. 调用带参数的方法时,一定要对应着形参列表传入 相同类型或兼容类型 的实参,且实参和形参的个数、顺序必须一致;
  2. 方法不能嵌套定义;
  • 细节:
  1. 一个方法最多有一个返回值;若想返回多个结果,可以返回一个数组;
  2. 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象);
  3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return + 返回值,而且要求返回值类型必须和 return 的值类型一致或兼容;
  4. 如果方法返回值类型是 void,则方法体中可以没有 return 语句,或者 只写 return;
  5. 方法命名:遵循驼峰命名法,最好见名知义,表达出该功能的意思即可, 比如得到两个数的和 用 getSum, 开发中按照规范。
  • 补充细节,如下图所示:
    在这里插入图片描述
    在这里插入图片描述
  • 代码说明:
// 定义一个A类
class A { 
	//同一个类中的方法调用:直接调用即可 
	
	public void print(int n) { 
		System.out.println("print()方法被调用 n=" + n);
	}
	
	public void sayOk() { 
		//sayOk 调用 print(直接调用即可) 
		print(10); // 不用另外创建A 类对象
		System.out.println("继续执行 sayOK()~~~"); 
	}
	
	//跨类中的方法 A 类调用 B 类方法:需要通过对象名调用 
	public void m1() { 
		//必须先创建一个 B类对象, 然后才能调用B类中的方法 
		System.out.println("m1() 方法被调用"); 
		B b = new B(); 
		b.hi(); 
		System.out.println("m1() 继续执行:)"); 
	} 
}
// 定义一个B类
class B { 
	public void hi() { 
		System.out.println("B 类中的 hi()被执行"); 
	} 
}

三、成员方法传参机制(非常非常重要!)

1. 基本数据类型的传参机制

  • 举例如下:
    在这里插入图片描述
  • 代码分析:
public class MethodExercise01 {
    public static void main(String[] args) {
        // 创建一个AA 类的对象aa
        AA aa = new AA();
        int a = 10;
        int b = 20;
        aa.swap(a, b);// 调用AA 类中的swap 方法,看看会不会影响到main 方法中的a、b。
        System.out.println("a=" + a + "b=" + b);// 输出: a = 10, b = 20,结论是不会影响。
    }
}

class AA {
	// 基本数据类型的传参机制,值传递,两个方法是独立的栈空间,不会相互影响。
    public void swap (int a, int b) {
        System.out.println("\na和b交换前的值\na=" + a + "\tb=" + b);
        // 输出: a = 10 ,b = 20
        int tmp;
        tmp = a;
        a = b;
        b = tmp;
        System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b);
        // 输出: a = 20, b = 10
    }
}
  • 解释:
  1. 基本数据类型的传参机制是值传递/值拷贝,这是大前提;
  2. 在main 方法中调用了swap 方法后,会在栈内存中新建一个独立的swap 栈空间,程序跳到swap 栈中执行;
  3. 在swap 栈中隐形地执行了 int a = a(前者是swap 栈中新声明的变量a,后者是main 栈中传递给 swap 栈 的a,两者不同),由于基本类型变量的赋值方式的值传递,因此新声明的a 只是得到了main 方法中a 的值,也就是10,两个方法中的a 是相互独立的,只是变量名相同。同理,变量b 也是如此;
  4. 因此,在swap 栈中的a、b 这两个变量交换了值,但是当退出swap 栈返回 main 栈后,a、b 的值没有任何改变。

2. 引用数据类型的传参机制

2.1 引用数据类型的传参机制

  • 案例引入:

1. AA 类中编写一个方法 test,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?答案:会变化

  • 代码分析:

public class MethodExercise01 {
    public static void main(String[] args) {
        // 创建一个AA 类的对象aa
        AA aa = new AA();
        int[] arr = new int[3];
        arr[0] = 10;
        aa.test(arr);
        System.out.println("main方法中arr[0]= " + arr[0]);
        // arr[0] = 200,被test 方法影响
    }
}

class AA {
	//引用数据类型的传参机制,引用传递
    public void test (int[] arr) {
        arr[0] = 200;
        System.out.println("test方法中arr[0]= " + arr[0]);
        // arr[0] = 200
    }
}
  • 解释:
  1. 引用数据类型的传参机制是引用传递/地址拷贝,这是大前提;
  2. 在main 方法中调用了test 方法后,会在栈内存中新建一个独立的test 栈空间,程序跳到test 栈中执行;
  3. 在test 栈中隐形地执行了 int[] arr = arr(前者是test 栈中新声明的数组arr,后者是main 栈中传递给 test 栈 的数组arr,两者不同),由于引用类型变量的赋值方式的引用传递,因此新声明的arr 得到了main 方法中arr 的地址,此时这两个arr 都可以改变存在于堆内存中的数组空间。这两个方法中的a 是相互独立的,只是变量名相同,但是它们对于内存空间的改变是相互影响的。
  4. 因此,在test 栈中的arr 改变了堆内存空间中arr[0] 的值,当退出test 栈返回 main 栈后,main 栈的 arr[0] 也会发生改变。
  • 结论及示意图:
    • 结论:引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
    • 示意图(网课老师的图,有点潦草):
      在这里插入图片描述

2.2 对象的传参机制

2.在AA 类中编写一个方法 test200,可以接收一个 Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否变化。答案:会变化。

  • 当传入的实参是一个对象时,其传参机制类似于引用类型的传参机制,可以类比分析。这里只放示意图。
  • 示意图如下:
    在这里插入图片描述

2.3 思考题

若在test200 方法中执行了下面的语句,会对main 方法中原来的对象有影响吗?

  1. p = null;
  2. p = new Person();
  • 第一条语句:p = null;
  1. 相当于在test200 方法中 隐式地执行了 Person p = p,再执行 p = null 这两条语句;
  2. test200 方法中的新创建了一个Person 类对象 p ,一开始其指向 main 方法中 的p 对象所指向的地址空间,但接着 新建的p = null, 也就是新建的 p 指向了空值;
  3. 所以最后 test200 方法中新建的p 对main 方法中原来的 p 指向的内存空间是不会有任何影响的。
  • 示意图如下:
    在这里插入图片描述

  • 第二条语句:p = new Person();

  1. 相当于在test200 方法中 隐式地执行了 Person p = p;再执行 p = New Person() 这两条语句;
  2. 此时在 test200 方法中的新创建了一个Person 类对象 p,其指向一个新开辟的堆内存空间;
  3. 所以最后 test200 方法中新建的 p 对main 方法中原来的 p 指向的内存空间是不会有任何影响的。
  • 示意图如下:
    在这里插入图片描述

3. 对象克隆

  • 克隆对象, 要求得到新对象和原来的对象是两个独立的对象(拥有独立的堆内存空间),只是他们的属性相同。

举例:编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。

  • 代码如下:

public class MethodExercise01 {
    public static void main(String[] args) {
        // 创建一个AA 类的对象aa
        AA aa = new AA();
        
        Person01 p = new Person01();
        p.name = "钢铁侠";
        p.sal = 3000;
        
        // 创建一个新的Person01类的对象p1 接收克隆出来的对象(内存空间)
        Person01 p1 = aa.copyPerson(p);// p 和 p1 是两个独立的对象,只是他们的属性相同。
        System.out.println("copy方法中这个超级英雄是:" + p1.name + "他的工资是:" + p1.sal);
    }
}

class AA {
	// 克隆对象
    public Person01 copyPerson (Person01 p) {
    	//创建一个新的对象/开辟了一个新的堆内存空间
        Person01 p1 = new Person01();
        p1.name = p.name;//把原来对象的名字赋给 p1.name
        p1.sal = p.sal;//把原来对象的工资赋给 p1.sal
        return p1;
    }
}

class Person01 {
    String name;
    int sal;
}
  • 示意图如下:
    在这里插入图片描述
  • 对象克隆的分析类比上面的对象的传参机制的思考题2,这里就不赘述了。

总结

  • 本文是小白博主在学习B站韩顺平老师的Java网课时整理的学习笔记,在这里感谢韩顺平老师的网课,如有有兴趣的小伙伴也可以去看看。
  • 第三篇第一部分的学习总结就结束啦,明天接着学习第三篇的第二部分内容。如果本文有什么错漏的地方,欢迎大家批评指正!一起加油!!
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林二月er

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值