1. 类的定义
- 类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物;如:人类
- 实例对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为;如:小红、小明、小南
面向过程和面向对象
- 面向过程:当需要实现一个功能时,每个步骤都要亲自实现,详细处理每个细节
- 面向对象:当需要实现一个功能时,不需要关心具体的步骤,而是找一个已经具有该功能的人(或事物)来帮我实现
类定义格式
// 类可以实现对数据的封装,类由成员变量和成员方法组成
class className {
// 成员变量 成员变量是直接定义在类当中的,在方法外边
// 成员方法 成员方法不要写static关键字
}
示例:
class Student {
// 成员变量
String name;
int age;
// 成员方法
public void getName() {
System.out.println(this.name);
}
}
1.1 对象的使用
通常类不能直接使用,需要根据类创建一个对象来使用,使用步骤:
- 导包:
import 包名称.类名称
- 创建对象:
类名称 对象名 = new 类名称()
,如:Student s1 = new Student();
- 使用
- 使用成员变量:
对象名.成员变量名
- 使用成员方法:
对象名.成员方法名(参数)
- 使用成员变量:
// 实例化一个对象
类名 对象名 = new 类名();
对象名.成员变量;
对象名.成员方法();
示例:
public class ClassTest {
public static void main(String[] args) {
Student s1 = new Student();
System.out.println(s1.getName());
// 设置年龄
s1.setAge(18);
System.out.println(s1.getAge());
}
}
class Student {
// 成员变量
String name = "rose";
int age;
// 成员方法
public String getName() {
return this.name;
}
// 设置年龄
public void setAge(int age) {
this.age = age;
}
// 获取年龄
public int getAge() {
return this.age;
}
}
1.2 成员变量
若成员变量没有进行赋值,那么将会有一个默认值:
数据类型 | 默认值 | |
---|---|---|
基本类型 | 整数(byte,short,int,long ) | 0 |
浮点数(float,double ) | 0.0 | |
字符(char ) | '\u0000' | |
布尔(boolean ) | false | |
引用类型数组 | 类,接口 | null |
一个类也可以定义多个成员变量和方法,有 public、private
之分:
public
:类外部可以访问private
:私有,类外部不可访问,格式:private 数据类型 变量名;
- 权限修饰符,代表最小权限
- 可以修饰成员变量和成员方法
- 被修饰后的成员变量和成员方法,只能在本类中才能访问
public class ClassTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "rose";
}
}
class Person {
public String name;
public int age;
}
以上成员变量 name、age
是公共的,类外部可以访问,这就破坏了类的封装性,导致类外部可以修改,正确的写法应该是:
class Person {
// 当外部修改成员变量值的时候,会编译报错 p1.name = "rose";
private String name;
private int age;
}
1.3 成员变量和局部变量
- 成员变量
- 位置:在方法外部,直接写在类当中
- 作用范围:整个类全都可以通用
- 默认值:有默认值
- 内存位置:位于堆内存
- 生命周期:随着对象创建而诞生,随着对象被垃圾回收而消失
- 成员变量
- 位置:在方法内部
- 作用范围:只有方法当中才可以使用,出了方法就不能再用
- 默认值:无默认值
- 内存位置:位于栈内存
- 生命周期:随着方法进栈而诞生,随着方法出栈而消失
示例:
class Person {
public String name; // 成员变量
public int age;
public void setName(String name) {
String gender = "男"; // 局部变量
this.name = name;
}
2. 方法
2.1 private 修饰符
在类外部可以通过对象来修改成员变量,但是却无法阻止不合理的数据,比如:age
有可能被设置为负数,解决方案:
private
关键字修饰要修改的成员变量, 被修饰后的成员变量和成员方法,只能在本类中才能访问- 再通过
Getter/Setter
方法实现外部也可以访问和修改成员变量,同时也可以校验数据的合理性 - 那么可以通过
private
关键字和Getter/Setter
方法来实现间接修改,这样也可以在Getter/Setter
方法中校验数据的合理性
public class ClassTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setName("rose");
System.out.println(p1.getName());
}
}
class Person {
private String name;
private int age;
private boolean male; // bool 类型成员变量
// 设置名字
public void setName(String name) {
this.name = name;
}
// 访问名字
public String getName() {
return this.name;
}
public void setMale(boolean b) {
male = b;
}
// bool 类型 Getter 方式是 isXXX()
public boolean isMale() {
return male;
}
}
注意:
bool
类型Getter
方式是isXXX()
,而非Getter()
2.2 方法定义
语法格式:
修饰符 方法返回类型 方法名(方法参数列表) {
若干方法语句;
return 方法返回值;
}
-
修饰符:
public、private
-
方法返回类型:
String、int、void
等
注意:若没有返回值,可设置返回类型为
void
,可省略return
2.3 private 方法和 this 变量
有 public
方法就有 private
方法,同理它不允许外部调用,只能在类内部调用:
// private 方法
private int calcAge(int currentYear) {
return currentYear - this.age;
}
this 变量
this
代表所在类的当前对象的引用(地址值),即对象自己的引用,可解决成员变量和局部变量、参数的命名冲突,如:成员变量和局部变量名字一致
使用格式:
this.成员变量
示例:
// 成员变量和 setName 的参数名冲突,this.name 表示调用成员变量 name
class Person {
private String name;
// 设置名字
public void setName(String name) {
this.name = name;
}
// 访问名字
public String getName() {
return this.name;
}
}
若没有命名冲突可省略 this
,但是最好是显示地写上。
注意 :方法被哪个对象调用,方法中的
this
就代表那个对象。即谁在调用,this
就代表谁。
2.4 方法参数
方法可以接收 0 个或任意个参数,定义参数时需要数据类型,传递参数必须严格按照参数数据类型传递:
class Person {
public String name;
public int age;
// 执行需传入的参数必须为 String 类型
public void setName(String name) {
this.name = name;
}
// 无参数
public String getName() {
return this.name;
}
}
// 参数传递
Person p1 = new Person();
p1.setName("rose");
可变参数
当传入的参数不固定或不确定数目时,可使用可变参数:
// 格式 类型...
class Person {
public String[] names;
// 接收的是一个 String 数组
public void setName(String... names) {
this.names = names;
}
}
// 参数传递
Person p1 = new Person();
p1.setName();
p1.setName("rose");
p1.setName("rose", "lila");
2.5 参数绑定
- 基本类型参数的传递:是调用方值的复制。双方各自的后续修改,互不影响
- 引用类型参数的传递:调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方
- 注意:String的值是不可变的 ,对 String 重新赋值的时候,会重新创建一个变量的引用;
2.5.1 基本类型参数传递
public class ClassTest {
public static void main(String[] args) {
Person p1 = new Person();
int n = 20;
p1.setAge(n);
System.out.println(p1.getAge()); // 20
n = 18;
System.out.println(p1.getAge()); // 20
}
}
class Person {
private int age;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
修改外部的局部变量n
,不影响实例p
的age
字段,原因是setAge()
方法获得的参数,复制了n
的值,因此,p.age
和局部变量n
互不影响。
2.5.2 引用类型参数传递
public class ClassTest {
public static void main(String[] args) {
// Person p1 = new Person();
// int n = 20;
// p1.setAge(n);
// System.out.println(p1.getAge()); // 20
// n = 18;
// System.out.println(p1.getAge()); // 20
Person p2 = new Person();
String[] full_name = {"li", "xiaoer"};
p2.setName(full_name);
System.out.println(p2.getName()); // li xiaoer
full_name[1] = "da";
System.out.println(p2.getName()); // li da
}
}
class Person {
private int age;
private String[] names;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.names[0] + " " + this.names[1];
}
public void setName(String[] names) {
this.names = names;
}
}
数组 full_name
第 1 个元素修改,getName()
方法获取的 names
也跟着改变,这是因为两者指向的是同一变量,其内存地址是同一个。
2.5.3 引用类型之 String
类型参数传递(绑定)
public class ClassTest {
public static void main(String[] args) {
Person p2 = new Person();
String n1 = "rose";
p2.setName(n1);
System.out.println(p2.getName()); // rose
n1 = "lila";
System.out.println(p2.getName()); // rose
}
}
class Person {
private int age;
private String name;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
上面说引用类型的参数传递,调用方的变量,和接收方的参数变量,指向的是同一个对象。双方任意一方对这个对象的修改,都会影响对方;但是 String
类型是个特例。
**String的值是不可变的 ,对String 重新赋值的时候,会重新创建一个新的变量的引用;**所有当变量 n1 = "lila";
时,它会创建一个新的变量的引用,而 setName()
方法时获取的原先的变量引用,所以修改不会改变。
3. 构造方法
构造方法:可以实现在创建实例的时候,初始化实例字段,特性:
-
构造方法名称即为类名,无返回值,无
void
-
调用构造方法,必须用
new
操作符 -
可以有多个构造方法(有参数、无参数、不同参数),若没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句
public class StructMethod {
public static void main(String[] args) {
Person1 p = new Person1("rose", 18);
System.out.println(p.getAge());
System.out.println(p.getName());
Person1 p2 = new Person1("lila");
Person1 p3 = new Person1();
}
}
class Person1 {
private String name;
private int age;
// 默认构造方法
public Person1() {
}
// 自定义构造方法 1
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
// 自定义构造方法 2
public Person1(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public int getAge() {
return this.age;
}
}
多个构造方法调用:
Person1 p = new Person1("rose", 18);
Person1 p2 = new Person1("lila");
Person1 p3 = new Person1();
一个构造方法调用另一个构造方法:
this("john", 19); // 便于代码复用
4. 方法重载
在 Java
类中可以定义多个名称相同的方法,若只是参数不同,功能类似,那么可以将这类方法称为同名方法,也可以叫做 方法重载,一般而言这类方法返回类型应该相同:
class Book {
public void hello() {
System.out.println("Hello World!");
}
public void hello(String name) {
System.out.println("Hello " + name);
}
public void hello(String name, int age) {
System.out.println("Hello " + name + "You're age " + age);
}
}
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,调用起来更简单,如
String
类提供了多个重载方法indexOf()
5. Java 对象内存图
【JavaSE】Java面向对象的思想(类、对象、对象调用内存图)
一个 Java
对象的创建 --> 使用在内存中总共大概会开辟三块内存区域:栈内存Stack
、堆内存Heap
、方法区Method Area
- 方法调用的时候,该方法所需的内存空间在栈内存中分配,这个过程叫 压栈,方法结束后,该方法所属的内存空间释放,成为 出栈
- 栈中主要存储的是方法体当中的局部变量
- 方法的代码段以及整个类的代码段都被存储到方法区内存当中,在类加载的时候这些代码片会载入
- 程序执行
new
创建对象时,存储在 堆内存 当中,对象内部的实例变量
1、只有一个对象的内存图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yp5t6p8l-1673275268929)(http://img.midworld.top/img/01-只有一个对象的内存图.png)]
- 方法区:总共有两个类
Phone、Demo1PhoneOne
,方法有:main、call、send
- 栈内存:当执行主函数时,会在栈内存开辟一块空间;当使用对象去修改或访问成员变量时,会直接在堆内存中找到成员变量进行修改,而当调用成员方法时,通过堆内存中的成员方法内存地址找到相应的成员方法,此时成员方法会从方法区进入栈空间(压栈),执行完毕后,就立马出栈
- 堆内存:当
new
创建对象时,会在堆内存开辟一块空间
2、两个对象使用同一个方法的内存图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oAaANtDA-1673275268930)(http://img.midworld.top/img/02-两个对象使用同一个方法的内存图.png)]
创建两个对象情况和一个对象类似:
- 创建第二个对象是在堆内存中新建一个对象
new Phone();
赋给第二个对象名two
的地址也是一个新的地址值 - 对象访问成员变量,访问的是该对象自己的成员变量。
- 两个对象使用同一个方法,会根据地址值指向同一个方法。因为两个对象中的成员方法,保存的都是方法区中
Phone.class
中对应成员方法的地址值 - 两个对象访问成员变量,调用成员方法不会产生任何关联
3、两个引用指向同一个对象的内存图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CpKGyj4g-1673275268932)(http://img.midworld.top/img/03-两个引用指向同一个对象的内存图.png)]
-
Phone two =one;
是将one
中保存的对象地址值赋给的two
,two
也指向了该对象。 -
根据
one
和two
的地址值都可以访问到该同一对象的成员变量、成员方法