目录
1.练一练:定义一个宠物类,属性包括名字,出生日期,性别。有吃和跑的行为。再编写测试程序,创建宠物对象,访问宠物的属性,调用宠物吃和跑的方法。
1. 定义类的语法格式:
[修饰符列表] class 类名{
类体 = 属性 + 方法;
// 属性(实例变量),描述的是状态
// 方法,描述的是行为动作
}
2. 为什么要定义类?
因为要通过类实例化对象。有了对象,让对象和对象之间协作起来形成系统。
3. 一个类可以实例化多个java对象。(通过一个类可以造出多个java对象。)
4. 实例变量是一个对象一份,比如创建3个学生对象,每个学生对象中应该都有name变量。
5. 实例变量属于成员变量,成员变量如果没有手动赋值,系统会赋默认值
数据类型 | 默认值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0L |
float | 0.0F |
double | 0.0 |
boolean | false |
char | \u0000 |
引用数据类型 | null |
1. 定义类(类属于引用数据类型)
类是对象的模板或蓝图,定义了对象的属性(字段)和行为(方法)。
示例:
// 定义一个类
class Dog {
// 字段(属性)
String name;
int age;
// 方法(行为)
void bark() {
System.out.println(name + " is barking!");
}
void sleep() {
System.out.println(name + " is sleeping.");
}
}
2. 创建对象
对象是类的实例。通过 new 关键字创建对象。
语法:
ClassName objectName = new ClassName();
示例:
// 定义一个类
class Dog {
// 字段(属性)
String name;
int age;
// 方法(行为)
void bark() {
System.out.println(name + " is barking!");
}
void sleep() {
System.out.println(name + " is sleeping.");
}
}
public class Main {
public static void main(String[] args) {
// 创建对象
Dog myDog = new Dog();
// 访问对象的字段
myDog.name = "Buddy";
myDog.age = 3;
// 调用对象的方法
myDog.bark(); // 输出:Buddy is barking!
myDog.sleep(); // 输出:Buddy is sleeping.
}
}
3. 访问对象的字段和方法
通过对象名后跟点号(.)可以访问对象的字段和方法。
访问字段:
objectName.fieldName;
调用方法:
objectName.methodName();
示例:
// 定义一个类
class Dog {
// 字段(属性)
String name;
int age;
// 方法(行为)
void bark() {
System.out.println(name + " is barking!");
}
void sleep() {
System.out.println(name + " is sleeping.");
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "Buddy";
myDog.age = 3;
// 访问字段
System.out.println("Dog's name: " + myDog.name); // 输出:Dog's name: Buddy
System.out.println("Dog's age: " + myDog.age); // 输出:Dog's age: 3
// 调用方法
myDog.bark(); // 输出:Buddy is barking!
myDog.sleep(); // 输出:Buddy is sleeping.
}
}
4. 构造方法
构造方法用于初始化对象的状态。构造方法的名字必须与类名相同,且没有返回类型。
默认构造方法
如果没有定义构造方法,Java 会提供一个默认的无参构造方法。
自定义构造方法
可以定义带参数的构造方法,方便初始化对象。
示例:
class Dog {
String name;
int age;
// 无参构造方法
Dog() {
name = "Unknown";
age = 0;
}
// 带参构造方法
Dog(String name, int age) {
this.name = name;
this.age = age;
}
void bark() {
System.out.println(name + " is barking!");
}
}
public class Main {
public static void main(String[] args) {
// 使用无参构造方法
Dog dog1 = new Dog();
System.out.println("Dog1's name: " + dog1.name); // 输出:Dog1's name: Unknown
System.out.println("Dog1's age: " + dog1.age); // 输出:Dog1's age: 0
// 使用带参构造方法
Dog dog2 = new Dog("Buddy", 3);
System.out.println("Dog2's name: " + dog2.name); // 输出:Dog2's name: Buddy
System.out.println("Dog2's age: " + dog2.age); // 输出:Dog2's age: 3
dog2.bark(); // 输出:Buddy is barking!
}
}
5. 对象的生命周期
对象的生命周期包括以下几个阶段:
(1).创建:通过 new 关键字创建对象。
(2).使用:通过对象访问字段和方法。
(3).销毁:当对象不再被引用时,Java 的垃圾回收器(Garbage Collector)会自动回收对象占用的内存。
示例:
public class Main {
public static void main(String[] args) {
// 创建对象
Dog myDog = new Dog("Buddy", 3);
// 使用对象
myDog.bark(); // 输出:Buddy is barking!
// 对象不再被引用
myDog = null;
// 垃圾回收器会回收 myDog 对象占用的内存
}
}
6. 静态(变量)字段、方法与实例变量、方法的区别
1.实例变量与静态变量
以下是静态变量和实例变量的主要区别:
(1). 所属对象不同
静态变量:属于类,是类级别的变量,也被称为类变量。它不依赖于类的实例而存在,在内存中只有一份,被该类的所有实例对象共享。
实例变量:属于类的实例对象,每个对象都有自己独立的实例变量副本。不同对象的实例变量可以有不同的值。
(2). 内存分配与生命周期不同
静态变量:在类加载时分配内存空间,并且在整个程序运行期间一直存在,直到程序结束才会被销毁。
类级别的变量:它属于类本身,而不是类的某个具体对象。无论创建多少个该类的对象,静态变量在内存中只有一份,被所有对象共享。
生命周期:静态变量的生命周期与类相同,随着类的加载而初始化,随着类的卸载而销毁。
实例变量:当创建类的实例对象时,才会为实例变量分配内存空间,对象被销毁时,实例变量所占用的内存也会被回收。
(3). 访问方式不同
静态变量:可以通过类名直接访问,也可以通过对象名访问,但一般推荐使用类名访问,以体现其类属性的特点。例如:ClassName.staticVariable 或 objectName.staticVariable(不推荐)。
实例变量:只能通过对象名来访问,例如:objectName.instanceVariable。
(4). 使用场景不同
静态变量:常用于存储一些全局共享的数据,如系统配置信息、常量、计数器等,这些数据在整个程序运行过程中不需要为每个对象单独存储,且需要被多个对象或方法共享和访问。
实例变量:用于存储与对象自身状态相关的数据,不同对象的实例变量值可能不同,以体现对象的个性特征。例如,一个学生类中的学生姓名、年龄等信息,每个学生对象的这些信息都可能不同,就适合用实例变量来存储。
例如:
public class Student {
// 静态变量,记录学生总数
public static int totalStudents;
// 实例变量,学生姓名
private String name;
public Student(String name) {
this.name = name;
// 每创建一个学生对象,学生总数加1
totalStudents++;
}
public static void main(String[] args) {
Student s1 = new Student("张三");
Student s2 = new Student("李四");
// 通过类名访问静态变量
System.out.println("学生总数:" + Student.totalStudents);
// 通过对象名访问实例变量
System.out.println("学生1的姓名:" + s1.name);
}
}
示例代码2:
public class MyClass {
// 静态变量
static int staticVar = 0;
// 实例变量
int instanceVar = 0;
public static void main(String[] args) {
// 访问静态变量
MyClass.staticVar = 10;
// 创建对象
MyClass obj1 = new MyClass();
MyClass obj2 = new MyClass();
// 访问实例变量
obj1.instanceVar = 5;
obj2.instanceVar = 15;
System.out.println("Static Variable: " + MyClass.staticVar); // 输出 10
System.out.println("Instance Variable of obj1: " + obj1.instanceVar); // 输出 5
System.out.println("Instance Variable of obj2: " + obj2.instanceVar); // 输出 15
}
}
2.实例方法与静态方法
实例方法和静态方法的主要区别如下:
(1). 所属范围
实例方法:属于对象,操作对象的实例变量。
静态方法:属于类,不依赖对象实例。
(2). 调用方式
实例方法:通过对象调用。
静态方法:通过类名直接调用,也可通过对象调用(不推荐)。
(3). 访问权限
实例方法:可以访问实例变量和静态变量。
静态方法:只能访问静态变量,不能直接访问实例变量。
(4). 生命周期
实例方法:与对象生命周期相同。
静态方法:与类生命周期相同。
不依赖对象实例:可以直接通过类名来调用,而不需要先创建类的对象实例。例如,如果有一个类MyClass,其中有一个静态方法staticMethod,可以使用MyClass.staticMethod()来调用,而不需要new MyClass()创建对象后再调用。
共享性:静态方法属于类本身,而不是类的某个特定对象。这意味着无论创建多少个该类的对象,静态方法在内存中只有一份,被所有对象共享。
(5). 用途
实例方法:用于操作对象的状态或行为。
静态方法:用于工具方法或全局操作,如工具类方法。
示例代码:
public class MyClass {
// 实例变量
int instanceVar = 0;
// 静态变量
static int staticVar = 0;
// 实例方法
public void instanceMethod() {
System.out.println("This is an instance method.");
System.out.println("Instance Variable: " + instanceVar);
System.out.println("Static Variable: " + staticVar);
}
// 静态方法
public static void staticMethod() {
System.out.println("This is a static method.");
// System.out.println("Instance Variable: " + instanceVar); // 错误:静态方法不能访问实例变量
System.out.println("Static Variable: " + staticVar);
}
public static void main(String[] args) {
// 调用静态方法
MyClass.staticMethod();
// 创建对象
MyClass obj = new MyClass();
// 调用实例方法
obj.instanceMethod();
}
}
7. 对象的引用
对象是通过引用操作的。多个引用可以指向同一个对象。
示例:
// 定义一个类
class Dog {
// 字段(属性)
String name;
int age;
public Dog(String buddy, int i) {
}
// 方法(行为)
void bark() {
System.out.println(name + " is barking!");
}
void sleep() {
System.out.println(name + " is sleeping.");
}
}
public class Main2{
public static void main(String[] args) {
Dog dog1 = new Dog("Buddy", 3);
Dog dog2 = dog1; // dog2 和 dog1 指向同一个对象
dog2.name = "Max";
System.out.println("Dog1's name: " + dog1.name); // 输出:Dog1's name: Max
System.out.println("Dog2's name: " + dog2.name); // 输出:Dog2's name: Max
}
}
运行结果:
总结
操作 | 语法或示例 | 说明 |
---|---|---|
定义类 | class ClassName { ... } | 定义对象的模板 |
创建对象 | ClassName obj = new ClassName(); | 使用 new 关键字创建对象 |
访问字段 | obj.fieldName; | 访问对象的字段 |
调用方法 | obj.methodName(); | 调用对象的方法 |
构造方法 | ClassName() { ... } | 初始化对象的状态 |
静态字段/方法 | static int count; 或 static void method() | 属于类,通过类名访问 |
对象引用 | Dog dog2 = dog1; | 多个引用可以指向同一个对象 |
8.JVM内存分析(需精通)
1.对象与引用
1.对象的内存分析(对象与引用)
new运算符会在JVM的堆内存中分配空间用来存储实例变量。new分配的空间就是Java对象。
在JVM中对象创建后会有对应的内存地址,将内存地址赋值给一个变量,这个变量被称为引用。
Java中的垃圾回收(GC)主要针对的是JVM的堆内存。
2.空指针异常是如何发生的?
3.方法调用时参数是如何传递的?将变量中保存的值复制一份传递过去。
4.初次认识this关键字:出现在实例方法中,代表当前对象。“this.”大部分情况下可以省略。 this存储在实例方法栈帧的局部变量表的0号槽位上。
import java.lang.String;
public class StudentTest01 {
public static void main(String[] args) {
// 局部变量
int i = 10;//因为在大括号里面(方法体里)定义的
// 通过学生类Student实例化学生对象(通过类创造对象)
// Student s1; 是什么?s1是变量名。Student是一种数据类型名。属于引用数据类型。
// s1也是局部变量。和i一样。
// s1变量中保存的是:堆内存中Student对象的内存地址。
// s1有一个特殊的称呼:引用
// 什么是引用?引用的本质上是一个变量,这个变量中保存了java对象的内存地址。
// 引用和对象要区分开。对象在JVM堆当中。引用是保存对象地址的变量。
Student s1 = new Student();
// 访问对象的属性(读变量的值)
// 访问实例变量的语法:引用.变量名
// 两种访问方式:第一种读取,第二种修改。
// 读取:引用.变量名 s1.name; s1.age; s1.gender;
// 修改:引用.变量名 = 值; s1.name = "jack"; s1.age = 20; s1.gender = true;
System.out.println("姓名:" + s1.name); // null
System.out.println("年龄:" + s1.age); // 0
System.out.println("性别:" + (s1.gender ? "男" : "女"));
// 修改对象的属性(修改变量的值,给变量重新赋值)
s1.name = "张三";
s1.age = 20;
s1.gender = true;
System.out.println("姓名:" + s1.name); // 张三
System.out.println("年龄:" + s1.age); // 20
System.out.println("性别:" + (s1.gender ? "男" : "女")); // 男
// 再创建一个新对象
Student s2 = new Student();
// 访问对象的属性
System.out.println("姓名=" + s2.name); // null
System.out.println("年龄=" + s2.age); // 0
System.out.println("性别=" + (s2.gender ? "男" : "女"));
// 修改对象的属性
s2.name = "李四";
s2.age = 20;
s2.gender = false;
System.out.println("姓名=" + s2.name); // 李四
System.out.println("年龄=" + s2.age); // 20
System.out.println("性别=" + (s2.gender ? "男" : "女")); // 女
}
}
// 通过学生类Student实例化学生对象(通过类创造对象)
// Student s1; 是什么?s1是变量名。Student是一种数据类型名。属于引用数据类型。
// s1也是局部变量。和i一样。
// s1变量中保存的是:堆内存中Student对象的内存地址。
// s1有一个特殊的称呼:引用
// 什么是引用?引用的本质上是一个变量,这个变量中保存了java对象的内存地址。
// 引用和对象要区分开。对象在JVM堆当中。s1引用是保存对象地址的变量。
Student s1 = new Student();
// 访问对象的属性(读变量的值)
// 访问实例变量的语法:引用.变量名
// 两种访问方式:第一种读取,第二种修改。
// 读取:引用.变量名 s1.name; s1.age; s1.gender;
// 修改:引用.变量名 = 值; s1.name = "jack"; s1.age = 20; s1.gender = true;
2.空指针异常的发生
public class PetTest02 {
public static void main(String[] args) {
// 创建宠物对象
Pet dog = new Pet();
// 给属性赋值
dog.name = "小黑";
dog.birth = "2012-10-11";
dog.sex = '雄';
// 读取属性的值
System.out.println("狗狗的名字:" + dog.name);
System.out.println("狗狗的生日:" + dog.birth);
System.out.println("狗狗的性别:" + dog.sex);
dog = null;
// 注意:引用一旦为null,表示引用不再指向对象了。但是通过引用访问name属性,编译可以通过。
// 运行时会出现异常:空指针异常。NullPointerException。这是一个非常著名的异常。
// 为什么会出现空指针异常?因为运行的时候会找真正的对象,如果对象不存在了,就会出现这个异常。
//System.out.println("狗狗的名字:" + dog.name);
// 会出现空指针异常。
dog.eat();
// 会出现空指针异常。
//dog.run();
}
}
运行结果:
(1).执行 // 创建宠物对象
Pet dog = new Pet();
(2).// 给属性赋值
dog.name = "小黑";
dog.birth = "2012-10-11";
dog.sex = '雄';
(3).执行
dog = null;
错误点:
在代码中,首先创建了 Pet 类的对象 dog,并为其属性赋值。但之后将 dog 对象赋值为 null,此时 dog 不再指向任何对象。之后又尝试调用 dog.eat() 方法,这就导致了 NullPointerException(空指针异常),因为 null 对象无法调用任何方法。
同理,若执行注释掉的 dog.run() 方法,也会引发相同的异常,因为 dog 已经不指向任何对象了。
3.方法调用时参数是如何传递的?
传入的是基本数据类型,没有传入地址值
(1).面试题:判断该程序的输出结果
/**
* 面试题:分析以下程序输出结果
*/
public class ArgsTest02 {
public static void main(String[] args) {
User u = new User();
u.age = 10;
// u是怎么传递过去的。实际上和i原理相同:都是将变量中保存的值传递过去。
// 只不过这里的u变量中保存的值比较特殊,是一个对象的内存地址。
add(u);
System.out.println("main-->" + u.age); // 11
}
public static void add(User u) { // u是一个引用。
u.age++;
System.out.println("add-->" + u.age); // 11
}
}
运行结果:
对下面知识不懂的可以回看我以前的文章
结合代码和图片分析
方法调用过程
【1】main 方法调用:
JVM 为 main 方法创建一个栈帧,局部变量 i 的值为 10。
调用 add 方法时,将 i 的值(10)复制一份传递给 add 方法。
【2】add 方法执行:
JVM 为 add 方法创建一个栈帧,局部变量 i 的初始值为 10(从 main 方法传递的值)。
执行 i++ 后,add 方法栈帧中的 i 值变为 11。
打印 add--->11。
【3】add 方法返回:
add 方法执行完毕后,其栈帧被销毁。
控制权返回到 main 方法,main 方法栈帧中的 i 值仍然是 10。
打印 main--->10。
总结:
变量传递和作用域 当在 main 方法中调用 add(i) 时,实际上是将 i 的值(即 10)复制了一份传递给 add 方法。这里涉及到 Java 中参数传递的机制,基本数据类型(如 int)是按值传递的。
在 add 方法中,形参 i 是一个局部变量,它与 main 方法中的 i 是不同的变量,只是名字相同而已。在 add 方法中对 i 的修改不会影响到 main 方法中的 i。
(2).面试题:分析以下程序输出结果
public class User {
int age;
}
/**
* 面试题:分析以下程序输出结果
*/
public class ArgsTest02 {
public static void main(String[] args) {
User u = new User();
u.age = 10;
// u是怎么传递过去的。实际上和i原理相同:都是将变量中保存的值传递过去。
// 只不过这里的u变量中保存的值比较特殊,是一个对象的内存地址。
add(u);
System.out.println("main-->" + u.age); // 11
}
public static void add(User u) { // u是一个引用。
u.age++;
System.out.println("add-->" + u.age); // 11
}
}
运行结果:
分析:u存的是地址
【1】内存模型分析(结合图示)
堆(heap):在堆中创建了一个 User 类型的对象,该对象具有一个 int 类型的 age 属性,初始值为 11(图示中显示)。
虚拟机栈(VM Stack):main 方法栈帧:其中有一个 User 类型的引用变量 u,它的值是堆中 User 对象的内存地址(图示中为 0x12)。
add 方法栈帧:当调用 add 方法时,会创建一个新的栈帧,其中也有一个 User 类型的引用变量 u,这个 u 和 main 方法栈帧中的 u 指向堆中同一个 User 对象(因为传递的是对象的引用,即内存地址)。
【2】输出结果分析
当执行 add 方法时,通过引用 u 找到堆中的 User 对象,并将其 age 属性值增加 1,此时 age 变为 11,所以 add 方法中输出 add--->11。
由于 main 方法中的 u 和 add 方法中的 u 指向同一个 User 对象,所以当 add 方法修改了对象的 age 属性后,在 main 方法中再次访问 u.age 时,其值也已经变为 11,因此 main 方法中输出 main--->11。
(3).总结
上一个示例(ArgsTest01)中传递的是基本数据类型 int,是按值传递,方法内部对参数的修改不会影响到外部变量的值。
而这个示例(ArgsTest02)传递的是对象引用,虽然也是按值传递(传递的是引用的值,即内存地址),但由于多个引用指向同一个对象,所以通过一个引用对对象的修改会影响到其他引用所看到的对象状态。
4.this内存存放位置
public class Student {
// 属性,实例变量,学生姓名
String name;
// 方法:学习的行为(实例方法)
public void study(){
// this 本质上是一个引用。
// this 中保存的也是对象的内存地址。
// this 中保存的是当前对象的内存地址。
//System.out.println(this.name + "正在努力的学习!");
// this. 是可以省略。默认访问的就是当前对象的name。
System.out.println(name + "正在努力的学习!!!!!!!");
}
}
public class StudentTest {
public static void main(String[] args) {
// 创建学生对象
Student zhangsan = new Student();
// zhangsan 是引用。通过“引用.”来访问实例变量。
zhangsan.name = "张三";
System.out.println("学生姓名:" + zhangsan.name);
// 让张三去学习。
zhangsan.study();
// 创建一个新的学生对象
Student lisi = new Student();
lisi.name = "李四";
System.out.println("学生的姓名:" + lisi.name);
// 让李四去学习。
lisi.study();
}
}
运行结果:
分析:
9.练习题
1.练一练:定义一个宠物类,属性包括名字,出生日期,性别。有吃和跑的行为。再编写测试程序,创建宠物对象,访问宠物的属性,调用宠物吃和跑的方法。
宠物类(Pet类)的定义:
public class Pet {
// 宠物的名字,实例变量
private String name;
// 宠物的出生日期,实例变量
private String birthDate;
// 宠物的性别,实例变量
private String gender;
// 构造方法,用于初始化宠物对象的属性
public Pet(String name, String birthDate, String gender) {
this.name = name;
this.birthDate = birthDate;
this.gender = gender;
}
// 宠物吃的行为,实例方法
public void eat() {
System.out.println(name + "正在吃东西。");
}
// 宠物跑的行为,实例方法
public void run() {
System.out.println(name + "正在跑。");
}
// 获取宠物名字的方法,实例方法
public String getName() {
return name;
}
// 获取宠物出生日期的方法,实例方法
public String getBirthDate() {
return birthDate;
}
// 获取宠物性别的方法,实例方法
public String getGender() {
return gender;
}
}
测试程序(PetTest类):
public class PetTest {
public static void main(String[] args) {
// 创建一个宠物对象,传入名字、出生日期、性别
Pet myPet = new Pet("旺财", "2020-05-10", "雄性");
// 访问宠物的属性
System.out.println("宠物的名字:" + myPet.getName());
System.out.println("宠物的出生日期:" + myPet.getBirthDate());
System.out.println("宠物的性别:" + myPet.getGender());
// 调用宠物吃的方法
myPet.eat();
// 调用宠物跑的方法
myPet.run();
}
}