学习java最核心最重要的就是要理解面向对象。
1. 类与对象
1.1 介绍
类是抽象的,概念的,代表一类事物,比如人类,猫类,狗类.., 即它是数据类型。
对象是具体的,实际的,代表一个具体事物,,是实例,具体的张三、李四、小花猫、大黄狗。
类是对象的模板,对象是类的一个个体,对应一个实例。
1.2 类和对象的内存分配机制
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象(Cat cat,数组等)
- 方法区:常量池(常量,比如字符串),类加载信息
2. 成员方法
人类:除了有一些属性外(年龄,姓名...),人类还有一些行为,比如:说话、跑步、玩游戏、听音乐,这些就要用成员方法才能完成。
class Person {// 属性String name;int age;// 方法(成员方法)//1. public 表示方法是公开//2. void : 表示方法没有返回值//3. speak() : speak 是方法名, () 形参列表//4. {} 方法体,可以写我们要执行的代码public void speak() {System.out.println("我今天真的有好好吃饭!");}
}
好处
- 提高代码的复用性
- 可以将实现的细节封装起来,然后供其它用户来调用
3. 成员方法传参机制
3.1 基本数据类型的传参机制
示例
public static void main(String[] args) {int a = 10;int b = 20;// 创建 AA 对象 名字 objAA obj = new AA();obj.swap(a, b); //调用 swapSystem.out.println("main 方法 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// 完成了 a 和 b 的交换int tmp = a;a = b;b = tmp;System.out.println("\na 和 b 交换后的值\na=" + a + "\tb=" + b);//a=20 b=10}
}
结论:
基本数据类型,传递的是值(值拷贝),形参的改变不影响实参。
3.2 引用数据类型的传参机制
B 类中编写一个方法 test100,可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化?
B 类中编写一个方法 test200,可以接收一个 Person(age,sal)对象,在方法中修改该对象属性,看看原来的对象是否会变化?
public class MethodParameter02 {public static void main(String[] args) {// 测试1// B b = new B();// int[] arr = {1, 2, 3};// b.test100(arr);//调用方法// System.out.println(" main 的 arr 数组 ");// //遍历数组// for(int i = 0; i < arr.length; i++) {// System.out.print(arr[i] + "\t");// }// System.out.println();// 测试2Person p = new Person();p.name = "jack";p.age = 10;b.test200(p);//测试题, 如果 test200 执行的是 p = null ,下面的结果是 10//测试题, 如果 test200 执行的是 p = new Person();..., 下面输出的是 10System.out.println("main 的 p.age=" + p.age);//10000}
}class Person {String name;int age;
}class B {public void test200(Person p) {//p.age = 10000; //修改对象属性//思考p = new Person();p.name = "tom";p.age = 99;//思考//p = null;}//B 类中编写一个方法 test100,//可以接收一个数组,在方法中修改该数组,看看原来的数组是否变化public void test100(int[] arr) {arr[0] = 200;//修改元素//遍历数组System.out.println(" test100 的 arr 数组 ");for(int i = 0; i < arr.length; i++) {System.out.print(arr[i] + "\t");}System.out.println();}
}
结论:
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
3.3 成员方法返回类型是引用类型应用实例
1)编写类 MyTools 类,编写一个方法可以打印二维数组的数据。
2) 编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象, 注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同
public class MethodExercise02 {public static void main(String[] args) {Person p = new Person();p.name = "milan";p.age = 100;//创建 toolsMyTools tools = new MyTools();Person p2 = tools.copyPerson(p);//到此 p 和 p2 是 Person 对象,但是是两个独立的对象,属性相同System.out.println("p 的属性 age=" + p.age + " 名字=" + p.name);System.out.println("p2 的属性 age=" + p2.age + " 名字=" + p2.name);//这里老师提示: 可以同 对象比较看看是否为同一个对象System.out.println(p == p2);//false}
}class Person {String name;int age;
}class MyTools {//编写一个方法 copyPerson,可以复制一个 Person 对象,返回复制的对象。克隆对象//注意要求得到新对象和原来的对象是两个独立的对象,只是他们的属性相同//编写方法的思路//1. 方法的返回类型 Person//2. 方法的名字 copyPerson//3. 方法的形参 (Person p)//4. 方法体, 创建一个新对象,并复制属性,返回即可public Person copyPerson(Person p) {//创建一个新的对象Person p2 = new Person();p2.name = p.name; //把原来对象的名字赋给 p2.namep2.age = p.age; //把原来对象的年龄赋给 p2.agereturn p2;}
}
4. 方法重载
允许同一个类中,多个同名方法的存在,但要求形参列表不一致。(同名不同参)
- calculate(int n1, int n2) // 两个整数的和
- calculate(int n1, double n2) // 一个整数,一个 double 的和
- calculate(double n2, int n1) // 一个 double ,一个 Int 和
5. 可变参数
java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。
语法
访问修饰符 返回类型 方法名(数据类型... 形参名) {}
代码示例
public class VarParameter01 {public static void main(String[] args) {HspMethod m = new HspMethod();System.out.println(m.sum(1, 5, 100)); //106System.out.println(m.sum(1,19)); //20}
}class HspMethod {public int sum(int... nums) {int res = 0;for(int i = 0; i < nums.length; i++) {res += nums[i];}return res;}
}
注意:
- 可变参数的实参可以为0个或任意多个。
- 可变参数的实参可以为数组。
- 可变参数的本质就是数组。
- 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后。
- 一个形参列表中只能出现一个可变参数。
6. 作用域
基本使用
- 全局变量:也就是属性变量,作用域为整个类体。
- 局部变量:除了属性之外的其它变量,作用域为定义它的代码块中。
全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值。
注意事项
- 属性和局部变量可以重名,访问时遵循最近原则。
- 在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名。
- 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中。
- 作用域范围不同:全局变量/属性可以被本类使用,或其他类使用(通过对象调用);局部变量:只能在本类中对应的方法中使用。
- 修饰符不同:全局变量/属性可以加修饰符;局部变量不可以加修饰符。
7. 构造方法/构造器
构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。
public class Constructor01 {public static void main(String[] args) {//当我们 new 一个对象时,直接通过构造器指定名字和年龄Person p1 = new Person("smith", 80);System.out.println("p1 的信息如下");System.out.println("p1 对象 name=" + p1.name);//smithSystem.out.println("p1 对象 age=" + p1.age);//80}
}//在创建人类的对象时,就直接指定这个对象的年龄和姓名
class Person {String name;int age;//构造器//1. 构造器没有返回值, 也不能写 void//2. 构造器的名称和类 Person 一样//3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样public Person(String pName, int pAge) {System.out.println("构造器被调用~~ 完成对象的属性初始化");name = pName;age = pAge;}
}
注意:
- 一个类可以定义多个不同的构造器,即构造器重载(如,定义一个构造器,用来创建对象的时候,只指定人名,不指定年龄)。
- 构造器名和类名要相同。
- 构造器没有返回值。
- 构造器是完成对象的初始化,并不是创建对象。
- 在创建对象时,系统自动的调用该类的构造方法。
- 如果没有定义构造器,系统会自动给类生成一个默认的无参构造器(也叫默认构造器)。
- 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下。
8. 对象创建的流程分析
class Person {int age;String name;Person(int dAge, String dName) {name = dName;age = dAge;}
}Person p = new Person("18", "李嘉图");
流程分析:
- 加载Person类信息(Person.class),只会加载一次。
- 在堆中分配空间(地址)。
- 完成对象的初始化(构造器的初始化,age = 18,name = 李嘉图)。
- 把对象在堆中的地址(引用),返回给p。
从JVM角度:
-
加载类:首先,JVM需要加载对象所属的类。这涉及到查找类文件(.class文件或.jar文件),然后将其加载到JVM的内存中。加载类是由类加载器(ClassLoader)完成的。
-
链接:链接包含三个阶段:验证、准备和解析。
- 验证:验证被加载的类文件是否符合JVM规范,例如文件格式、字节码等。
- 准备:为类的静态变量分配内存,并将其初始化为默认值(例如,对于int类型的静态变量,默认值为0)。
- 解析:将符号引用转换为直接引用。
-
初始化:初始化类的静态变量和静态代码块。这一步是由JVM执行的,而不是由类的构造器执行的。
-
创建对象:一旦类被加载、链接和初始化,就可以创建该类的对象了。在堆内存中分配对象所需的内存,并调用相应的构造器来初始化对象的实例变量。对象的内存分配涉及到垃圾回收的问题,所以Java的对象内存管理相对复杂。
-
返回引用:对象的引用(或指针)被返回给调用者,调用者可以使用这个引用来访问和操作对象的实例变量和方法。
9. this 关键字
虚拟机给每个对象分配this,代表当前对象。
public Person(String name, int age){//this.name 就是当前对象的属性 namethis.name = name;//this.age 就是当前对象的属性 agethis.age = age;
}
this图解
this使用注意:
- this关键字用来访问本类的属性、方法、构造器。
- this用于区分当前类的属性和局部变量。
- 访问成员方法的语法:this.方法名(参数列表)
- 访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放在第一条语句)
- this不能在类定义的外部使用,只能在类定义的方法中使用。