Java基础6
类与对象
类:具有相同特征的事物的抽象描述,是抽象的、概念上的定义。
对象:实际存在的该类事物的每个个体,是具体的,因而也称为实例。
面向过程编程(POP) vs 面向对象编程(OOP)
面向过程:以“函数”为组织单位,是一种“执行者思维”,适合解决简单问题。拓展能力差、后期维护难度较大。
面向对象:以“类”为组织单位,每种事物都具备自己的“属性”和“行为/功能”。是一种“设计者思维”,适合解决复杂问题、可维护性高。
两者之间的关系:我们千万不要把面向对象和面向过程对立起来,他们是相辅相成的,面向对象离不开面向过程。
类
类是一组相关属性和行为的集合,这也是类最基本的两个成员。
属性: 该类事物的状态信息。对应类中的成员变量 成员变量<==>属性<==>Field
行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的成员方法 (成员)方法<==>函数<==>Method
类的实例化
类的实例化<==> 创建类的对象 <==> 创建类的实例
格式: 类类型 对象名 = 通过new创建的对象实体
举例:
Phone p1 = new Phone();
Scanner scan = new Scanner(System.in);
String str = new String();
面向对象完成具体功能的操作的三步骤:
① 创建类,并设计类的内部成员(属性、方法)
② 创建类的对象,比如Phone p1 = new Phone();
③ 通过对象,调用其内部声明的属性或方法,完成相关的功能
public class Phone { // 属性 String name; //品牌 double price; //价格 //方法 public void call(){ System.out.println("手机能够打电话!"); } public void sendMessage(String message){ System.out.println("发送信息"+message); } } public class PhoneTest { public static void main(String []args) { //创建Phone对象 Phone p1 = new Phone(); //通过phone的对象,调用其内部的属性或方法 //格式: "对象.属性" 或"对象.方法" p1.name = "huawei"; p1.price = 4999; System.out.println("name = "+p1.name+", price = "+p1.price); //调用方法 p1.call(); p1.message("有内鬼!终止交易!!"); } }
类中对象的内存解析
对象在内存中分配涉及的内存结构:
- 栈(stack) :方法内定义的变量,存储在栈中。
- 堆(heap) : new出来的结构(比如:数组实体、对象的实体)。包括对象中的属性。
- 方法区(method area) : 存放类的模板。比如:Person类的模板。
说明:
Person P1 = new Person(); Person P2 = new Person();
创建类的多个对象时,每个对象在堆空间中有一个对象的实体。每个对象实体中保存着一份类的属性。如果修改某一个对象的某属性值时,不会影响其他对象的此属性。
Person P3 = new Person(); Person P3 = P1;
此时的P1,P3两个变量指向了堆空间中的同一个对象实体(P1,P3保存的地址值相同)。如果通过其中某一个对象变量修改对象的属性时,会导致另一个对象变量此属性的值变化。
类的成员之一:属性
变量的分类:
角度一 按照数据类型来分:基本数据类型(8种)、引用数据类型(数组、类、接口、枚举、注解、记录)
角度二 按照变量在声明中的位置的不同: 成员变量(或属性)、局部变量(方法内、方法形参、构造器内、构造器形参、代码块内等)
成员变量 vs 局部变量
相同点:
>变量声明的格式相同: 数据类型 变量名 = 变量值
>变量都有其有效的作用域,出了作用域就会失效。
>变量必须先声明后赋值,再使用。
不同点:
① 类中声明的位置不同: 属性:声明在类内,方法外的变量 局部变量:声明在方法、构造器内部的变量
② 在内存中分配的位置不同: 属性:随着对象的创建,存储在堆空间中 局部变量:存储在栈空间中
③ 生命周期: 属性:随着对象的创建而创建,随着对象的消亡而消亡 局部变量:随着方法对应的栈帧入栈,局部变量会在栈中分配;随着方法对应的栈帧出栈,局部变量消亡
④ 作用域: 属性:在整个类的内部都是有效的 局部变量:仅限于声明此局部变量所在的方法(或构造器、代码块)中
⑤ 是否可以有权限修饰符进行修饰:
都有哪些权限修饰符: public、protected、缺省、private。(用于表明所修饰的结构可调用的范围大小)
属性:是可以使用权限修饰符进行修饰的。 局部变量:不能使用任何权限修饰符进行修饰
⑥ 是否有默认值: 属性:都有默认初始化值。意味着,如果没有给属性进行显示初始化赋值,则会有默认初始化值 局部变量:都没有默认初始化值。意味着在使用局部变量之前,必须要显示的赋值,否则报错。
//属性(或成员变量) String name; int age; char gender; //方法 public void eat(){ String food = "宫保鸡丁" // 局部变量 System.out.println("我喜欢吃"+food); } public void sleep(int hour){ //形参: 同属于局部变量 //编译不通过,因为超出了food变量的作用域 System.out.println("我喜欢吃"+food); System.out.println("人不能少于"+hour+"小时的睡眠"); }
案例:
1.声明一个MyDate类型,有属性:年year , 月month , 日day
2.声明一个Employee类型,包含属性:编号、姓名、年龄、薪资、生日(MyDate类型)
3.在EmployeeTest测试类中的main( )中,创建两个员工对象,并为他们的姓名和生日赋值,并显示
class MyDate{ int year; int month; int day; } class Employee{ int id; String name; int age; double salary; MyDate birthday; //可以用引用类型(类)作为属性的类型! } public class EmployeeTest{ public static void main(String[] args){ //创建一个Employee的实例 Employee emp1 = new Employee(); emp1.id = 1002; emp1.name = "小明"; // 也可以写 emp1.name = new String("小明"); emp1.age = 24; emp1.salsry = 9000; emp1.birthday = new MyDate(); emp1.birthday.year = 2000; emp1.birthday.month = 1; emp1.birthday.day = 24; /* 另一种写法: * MyDate mydate1 = new MyDate(); * emp1.birthday = mydate1; */ System.out.println("id = " +emp1.id + ", name = "+ emp1.name+ ", age = "+emp1.age + ", salary = "+ emp1.salary +
", biethday = [" +emp1.birthday.year+ "年"+emp1.birthday.month + "月"+emp1.birthday.day+"日]"); } }
类的成员变量之二:方法
方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为"函数" 或 "过程"
将功能封装为方法的目的: 实现代码重用、减少冗余、简化代码
注意:
①Java里的方法不能独立存在,所有的方法必须定义在类里。
②方法内可以调用其他方法和属性。
③方法内不可以定义方法
方法声明的格式
权限修饰符 [其他修饰符] 返回值类型 方法名(形参列表){ //方法头
//方法体
}
权限修饰符: private / 缺省 / protected / public
返回值类型:描述当调用完此方法时,是否需要返回一个结果。 需要在方法内部配合使用“return + 返回值类型的变量或常量”
分类:
> 无返回值类型:使用void表示即可。比如: System.out.println(x)的println(x)方法
> 有具体的返回值类型: 需要指明返回的数据的类型。可以是基本数据类型,也可以是引用数据类型。
方法名: 属于标识符,需要满足标识符的规定和规范,要做到“见名知意”
形参列表: 形参,属于局部变量,且可以声明多个。 格式: (形参类型1 形参1,形参类型2 形参2,···)
分类:
>无形参列表: 不能省略这一对()。
>有形参列表: 根据方法调用时,需要的不确定的变量的类型和个数,确定形参的类型和个数。
方法体: 当我们调用一个方法时,真正执行的代码。体现了此方法的功能。
return关键字
作用: ①结束一个方法 ②结束一个方法的同时,可以返回数据给方法的调用者
方法调用的内存解析
- 形参:方法在声明时,一对()内声明的一个或多个形式参数,简称为形参。
- 实参:方法在被调用时,实际传递给形参的变量或常量,就称为实际参数,简称实参。
对象数组
数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用类型中的类时,就是对象数组。
举例:
String[] , Person[] , Student [] , Customer[]等
class Student{ int number; int sate; int score; public void PrintOut(){ System.out.println("学号:"+number+",年级:"+state+",分数:"+score); } } public class StudentTest{ public static void main(String[] args){ //创建Student[] Student[] students = new Student[20]; //使用循环赋值: for (int i = 0; i < students.length; i ++){ students[i] = new Student(); //这一句忘记写就会是面向对象里面的空指针问题 students[i].number = i+1; students[i].state = int(Math.random()*6+1); students[i].score = int(Math.random()*101); } //打印年级为3的学生信息: for (int i = 0; i < students.length; i ++){ if (students[i].state == 3){ students[i].PrintOut(); } } //冒泡排序 for (int i = 0; i < students.length-1; i ++){ for (int j = 0; j < students.length-1-i; j ++){ if (students[j].score > students[j+1].score){ Student stu = students[j]; students[j] = students[j+1]; students[j+1] = stu; } } } //打印所有学生信息 for (int i = 0; i < students.length; i ++){ students[i].PrintOut(); } } }
方法的重载
定义:在一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。
总结:“两同一不同” 同一个类、相同的方法名 参数列表不同(① 参数个数不同 ② 参数类型不同)
注意: 方法的重载与形参的名、权限修饰符、返回值类型都没有关系
练习: 判断与void show (int a,char b,double c){}构成重载的有:
1)void show(int x, char y, double z){} //不是 因为形参列表都相同
2) int show (int a, double c, char b){} //是的!不是因为前面的返回值类型是int 而是因为后面的形参列表是 int double char 跟上面不一样
3) void show (int a, double c, char b){} //是的 后面的形参列表是int , double ,char
4) double show (int x, char y, double z){} //不是! 虽然返回值类型不一样但是并不看返回值类型!!而是看后面的形参列表一样
可变个数形参的方法
在调用方法时,可能会出现方法形参的类型是确定的,但是参数的个数不确定。此时,我们就可以使用可变个数形参的方法。
格式:(参数类型 ... 参数名)
说明:
① 可变个数形参的方法在调用时,针对于可变的形参赋的实参个数可以为0个、1个或多个
② 可变个数形参的方法与同一个类中,同名的多个方法之间可以构成重载
③ 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载
④ 可变个数的形参必须声明在形参列表的最后
⑤ 可变个数的形参最多在一个方法的形参列表中出现一次
public class ArgsTest { public static void main(String []args) { ArgsTest test = new ArgsTest(); test.print(); //输出 1111 test.print(1); //输出 1111 test.print(1,2); //输出 1111 } //可变个数形参方法: public void print(int ... nums){ System.out.println("1111"); } /* public void print(inti){ //这个和上面的可变个数形参是属于重载 System.out.println("2222"); } public void print(int[] nums) //这里的方法和上面的可变个数形参是一样的,编译不会通过! */ }
方法的值传递机制
1. 对于方法内声明的局部变量来说,如果出现赋值操作
> 如果是基本数据类型的变量,则将此变量保存的数据值传递出去。
> 如果是引用数据类型的变量,则将此变量保存的地址值传递出去。
2. 概念
形参:在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。
实参:在调用方法时,方法名后面括号()中使用的值/变量/表达式称为实际参数,简称实参。
3.方法的参数的值传递机制 实参给形参赋值的过程:
> 如果形参是基本数据类型的变量,则将实参保存的数据值赋给形参。
> 如果形参是引用数据类型的变量,则将实参保存的地址值赋给形参。
注意:Java中的参数传递机制是:值传递(不是引用传递)
public static void main(String[] args){ ValueTransferTest test = new ValueTransferTest(); // 1.对于基本数据类型的变量来说 int m = 10; test.method1(m); System.out.println("m = "+m); //这里得到的结果是10,方法里面改变的结果并不会影响main里面的m,只是把m的值赋给了形参 } public void method1(int m){ m++; }
public static void main(String[] args){ Person P = new Person(); // 1.对于引用数据类型的变量来说 P.age = 10; test.method2(P); System.out.println(P.age); //这里得到的结果是11,把P的地址赋给了形参,所以main里面的值跟着一起改变 } public void method2(Person P){ P.age ++; }
现实中交换数值的常见错误:
//在冒泡排序中交换数组中的两个值 用方法写 //swap(arr[i], arr[i+1]); 这种方法是错误的,并不会交换数组中值 swap(arr, i , j); //这样把引用数据类型传入进去就将地址传入了 可以达到交换数组值的目的 public void swap(int i, int j){ int temp = i; i = j; j = temp; } public void swap(int[] arr, int i, int j){ int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; }
递归方法
自己调用自己的现象就称为“递归” 分类: 直接递归、间接递归
注意:递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环‘慢得多’,所以在使用递归时要慎重
在要求高性能的情况下尽量避免使用递归,递归调用既花时间又‘耗内存’。考虑使用循环迭代
public class PassObject { public static void main(String[] args){ PassObject P1 = new PassObject(); // 计算 1~n 的和,使用递归完成 int n = 5; int sum = P1.getSum(n); System.out.println(sum); } public int getSum(int n){ if (n == 1){ return 1; }else{ return n + getSum(n-1); } } }
package关键字的使用
说明:package 称为‘包’ 用于指明该文件
注意:一个源文件只能有一个声明包的package语句,package语句作为Java源文件的第一条语句出现,若缺省该语句,则指定为无名包
包名: 属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意
- 包通常使用所在公司域名的倒置: com.atgujiu.XXX
- 取包名不要使用 " java.xx "包
包对应于文件系统的目录,package语句中用 " . " 来指明包(目录)的层次, 每 . 依次就表示一层文件目录
同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下可以定义同名的结构。
包的作用
- 包可以包含类的子包,划分“项目层次”,便于管理
- 帮助‘管理大型软件’系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
- 解决‘类命名冲突’的问题
- 控制‘访问权限’
import关键字的使用
import:导入
import语句显式引入指定包下所需要的类,相当于 'import语句告诉编译器到哪里去寻找类'
import语句,声明在包的声明和类的声明之间
如果使用 ' a.* '导入结构,表示可以导入a包下的所有的结构。如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的哪个类。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具