05 - 面向对象编程(基础部分一)
一、类与对象
1)类是抽象的,概念的,代表一类事物,比如人类,猫类..,即它是数据类型。
2) 对象是具体的,实际的,代表一个具体事物, 即是实例。
3) 类是对象的模板,对象是类的一个个体,对应一个实例。
单独定义变量
//第 1 只猫信息
String cat1Name = "小白";
int cat1Age = 3;
String cat1Color = "白色";
//第 2 只猫信息
String cat2Name = "小花";
int cat2Age = 100;
String cat2Color = "花色";
//....很多猫
使用(面向对象)OOP定义
//使用 OOP 面向对象解决
//定义一个猫类 Cat -> 自定义的数据类型
class Cat {
//属性/成员变量
String name; //名字
int age; //年龄
String color; //颜色
//行为
//比如爱运动 ,爱吃鱼
public void like() {
//爱吃鱼
}
}
//1. new Cat() 创建一只猫(猫对象)
//2. Cat cat1 = new Cat(); 把创建的猫赋给 cat1
//3. cat1 就是一个对象
Cat cat1 = new Cat();
cat1.name = "小白";
cat1.age = 3;
cat1.color = "白色";
//创建了第二只猫,并赋给 cat2
//cat2 也是一个对象(猫对象)
Cat cat2 = new Cat();
cat2.name = "小花";
cat2.age = 100;
cat2.color = "花色";
//怎么访问对象的属性呢
System.out.println("第 1 只猫信息" + cat1.name + " " + cat1.age + " " + cat1.color + " " + cat1.weight);
//cat2类似
属性/成员变量/字段
从概念或名称上看:成员变量 = 属性 = field(字段) (即成员变量是用来表示属性的)
class Car {
String name;//属性,成员变量,字段 field
double price;
String color;
String[] master;//属性可以是基本数据类型,也可以是引用类型(对象,数组)
}
属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类 的 int age 就 是属性
使用细节:
1) 属性的定义语法同变量一样。
示例:访问修饰符 属性类型 属性名;
访问修饰符: 控制属性的访问范围;
有四种访问修饰符 public,proctected,默认,private
2) 属性的定义类型可以为任意类型,包含 基本类型 或 引用类型。
3) 属性如果不赋值,有默认值,规则和数组一致。
具体说: int 0,short 0,byte 0,long 0,float 0.0
double 0.0,char \u0000,boolean false,String null
对象的创建
1) 先声明再创建。
Cat cat; //声明对象 cat
cat = new Cat(); //创建
2) 直接创建 Cat cat = new Cat();
对象的访问
基本语法:
对象名.属性名;
比如:
cat.name;
cat.age;
cat.color;
类和对象的内存分配机制
Java 内存的结构分析
1)栈: 一般存放基本数据类型(局部变量)
2)堆: 存放对象(Cat cat , 数组等)
3)方法区: 常量池(常量,比如字符串),类加载信息
基本数据类型在内存中存在形式
1)Java基本类型数据有8种:int,short,byte,long,float,double,char,boolean。
2)例如定义一个 int a = 3;
表示 a 指向一个 int 类型的引用,并且指向 3 这个字面值。
这些字面值的数据,由于大小可知,生存期可知
(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了)
出于追求速度的原因,就存在于栈中。
3)并且栈数据具有共享性。
例如定义两个数据 int a = 4;int b = 4;
编译器先处理int a = 4;首先它会在栈中创建一个变量为 a 的引用,然后查找有没有字面值为 4 的地址。
如果没找到,就开辟一个存放 4 这个字面值的地址,然后将a指向4的地址。
接着再创建完 b 这个引用变量后,由于在栈中已经有 4 这个字面值,便将 b 直接指向 4 的地址。
这样,就出现了 a 与 b 同时均指向 4 的情况。
4)但是不同于对象的地址拷贝。假如修改 b 的值为 6 , a 的值不变仍然为 4 ,不会因为 b 的值修改而改变。
因为在修改 b 的值为 6 的时候,会重新搜索栈中是否有 6 的字面值。
如果有那就直接将字面量为 6 的地址赋值给 b ,如果没有就会在栈中创建一个为 6 的字面量。
因此修改 b 的值并不会影响 a 的值,a 仍然指向为 4 的字面量。
总结:基本类型数据存在于栈中,如果两个不同变量指向相同的字面量地址。
如果其中一个修改值,并不会影响另一个变量的值。
对象在内存中存在形式
1)首先使用 Cat cat;
声明一个对象 cat 时,将在 栈内存 为对象的引用变量 cat 分配内存空间。
2)但 Cat 的值为空,称 Cat 是一个空对象。
空对象不能使用,因为它还没有引用任何“实体”。
3)对象实例化时的内存模型 当执行 cat = new Cat(“小白”, 12, “白色”); 时
在 堆内存 中为类的成员变量 name,age,color 分配内存(字符串会定义在方法区的常量池中)
并将其初始化为各数据类型的默认值
4)接着进行显式初始化(类定义时的初始化值);
5)最后调用构造方法,为成员变量赋值。
返回堆内存中对象的引用(相当于首地址)给栈中的引用变量 cat,以后就可以通过cat来引用堆内存中的对象了。
对象创建的流程简单分析
Person p = new Person();
p.name = “jack”;
p.age = 10;
1) 先加载 Person 类信息(属性和方法信息, 只会加载一次)
2) 在堆中分配空间, 进行默认初始化(看规则)
3) 把地址赋给 p , p 就指向对象
4) 进行指定初始化, 比如 p.name =”jack”; p.age = 10;
二、成员方法
基本介绍
在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外(年龄,姓名…),我们人类还有一些行为。比如:可以说话、跑步…,通过学习,还可以做算术题。这时就要用成员方法才能完成。
成员函数的作用
-
提高代码的复用性。
-
可以将实现的细节封装起来,然后供其他用户来调用即可。
成员方法的定义
访问修饰符 返回数据类型 方法名(形参列表..) {
//方法体
语句;
return 返回值;
}
1)形参列表:表示成员方法的输入 cal(int n), getSum(int num1, int num2)
2)返回数据类型:表示成员方法的输出, void 表示没有返回值
3)方法主体:表示为了实现某一功能代码块
4)return 语句不是必须的。
使用细节
访问修饰符
访问修饰符的作用是控制方法使用的范围。
如果不写则为默认访问
有四种访问修饰符: [public,protected,默认,private]
返回数据类型
1) 一个方法最多有一个返回值 [思考,如何返回多个结果 返回数组 ]。
2) 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)。
3) 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值。
而且要求返回值类型必须和 return 的 值 类型一致或兼容。
4) 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;
方法名
遵循驼峰命名法,最好见名知义,表达出该功能的意思即可,比如:得到两个数的和 getSum()
形参列表
1)一个方法可以有 0 个参数,也可以有多个参数,中间用逗号隔开。
比如 getSum(int n1, int n2)
2)参数类型可以为任意类型,包含基本类型或引用类型
比如 printArr(int[][] map)
3)调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数!
4)定义方法时的参数称为形式参数,简称形参。
方法调用时的传入参数称为实际参数,简称实参。
实参和形参的类型要一致或兼容,个数、顺序必须一致!
方法体
方法体里面编写完成功能的语句,可以为输入、输出、变量、运算、分支、循环、方法调用。
但是不能再定义方法,也就是说 方法不能嵌套!
//方法调用细节
//1. 同一个类中的方法调用:
// 直接调用即可。
// 比如:A类的 sayOk() 调用 print()
Class A {
Public void print(int n) {
System.out.println(“输出:” + n);
}
Public void sayOk() {
Print(10);
}
}
//2. 跨类中的方法调用:
// 需要通过对象名调用。
// 比如:B类的 sayHello() 调用 A类的 print()
Class B {
Public void sayHello() {
A a = new A();
A.Print(10);
}
}
成员方法传参机制
1)基本类型数据传参机制
class AA {
public void swap(int a, int b) {
System.out.println("交换前的值:" + "a = " + a + "b = " + b);
int temp = a;
a = b;
b = temp;
System.out.println("交换后的值:" + "a = " + a + "b = " + b);
}
}
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println("调用swap函数前的值:" + "a = " + a + ", b = " + b);
AA aa = new AA();
aa.swap(a, b);
System.out.println("调用swap函数后的值:" + "a = " + a + "b = " + b);
}
//结果:
//调用swap函数前的值:a = 10, b = 20
//交换前的值:a = 10, b = 20
//交换后的值:a = 20, b = 10
//调用swap函数后的值:a = 10, b = 20
结论:
基本类型数据,传递的是值(值拷贝),形参的任何改变不影响实参数。
2)引用类型数据传参机制
2.1)参数为数组
class BB {
public void changeArr(int[] arr) {
arr[0] = 4;
}
}
public static void main(String[] args) {
int[] a = new int[4];
a = {0, 1, 2, 3};
BB bb = new BB();
bb.changeArr(a);
for(int i = 0; i < a.length; i++) {
System.out.println(a[i])
}
}
结论:
在参数为数组时候,形参的改变会影响实参。
在方法中修改该数组,原来的数组会变化,数组传递的是地址。
2.2)参数为对象
public class TestClass {
public static void main(String[] args) {
// 测试
B b = new B();
// 测试
Person p = new Person();
p.name = "jack";
p.age = 10;
b.test200(p);
//三种情况:
// 如果 test200 执行的是 p.age = 10000,下面的结果是 10000
// 如果 test200 执行的是 p = null ,下面的结果是 10
// 如果 test200 执行的是 p = new Person();..... 下面输出的是 10
System.out.println("main 的 p.age=" + p.age);//10
}
}
class Person {
String name;
int age;
}
class B {
public void test200(Person p) {
//思考1:
//p.age = 10000; //修改对象属性
//思考2:
// p = new Person();
// p.name = "tom";
// p.age = 99;
//思考3:
p = null;
}
}
第一种:
p.age = 10000; 因为引用类型传递的是地址,所以修改p.age的值,实参也会改变。
第二种:
p = null; 相当于形参断开了和堆内存中实参地址的联系,但是栈内存中的实参还是连接着,所以输出的是 10。
第三种:
p = new Person(); 相当于在堆内存中重新开了一个空间,和实参一点关系都没有,肯定不会改变。
结论:
在方法中修改该对象属性,原来的对象会变化,对象传递的是地址
总结:引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
注:本博客引用韩顺平老师Java课程
Record Our Mind
本文作者:SpockC
本文链接:https://www.cnblogs.com/SpockC/p/15779539.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步