面向对象

介绍

面向对象三大特征: 封装, 继承, 多态.

类里面能写哪些东西:

public class 类名 {
1. 成员变量 (代表属性, 一般是名词)
2. 成员方法 (代表行为, 一般是动词)
3. 构造器
4. 代码块
5. 内部类
}

通过类得到对象:

类名 对象名 = new 类名();

使用对象:

对象名.成员变量; // 访问属性
对象名.方法名; // 访问行为

定义一个类:

public class Phone {
// 属性 (成员变量)
String brand;
double price;
// 行为 (方法)
public void call() {
System.out.println("手机在打电话");
}
public void playGame() {
System.out.println("手机在玩游戏");
}
}

使用这个类:

public class PhoneTest {
public static void main(String[] args) {
// 创建手机对象
Phone p = new Phone();
// 给手机赋值
p.brand = "华为";
p.price = 6999.9;
// 获取手机对象中的值
System.out.println(p.brand);
System.out.println(p.price);
// 调用手机的方法
p.call();
p.playGame();
Phone p2 = new Phone();
p2.brand = "小米";
p2.price = 1999.9;
System.out.println(p2.brand);
System.out.println(p2.price);
p2.call();
p2.playGame();
}
}

执行结果:

华为
6999.9
手机在打电话
手机在玩游戏
小米
1999.9
手机在打电话
手机在玩游戏

用来描述某一个事物的类称为 Javabean 类, 在 Javabean 类中, 是不写 main() 方法的.

带 main() 方法的类, 称为测试类.

可以在测试类中创建 Javabean 类的对象并赋值调用.

类名采用大驼峰命名规则.

一个 Java 源文件可以写多个类, 但是只能有一个 public 类, 且 public 类的类名必须和文件名相同. 实际开发中, 一般一个源文件只写一个类.

public是一个访问修饰符 (access modifier) , 访问修饰符用于控制程序的其他部分对这段代码的访问级别.

成员变量的完整定义格式为:

修饰符 数据类型 变量名称 = 初始值;

一般都不给成员变量以初始值, 因为各个对象的成员变量的值都可能不相等, 且如果不给成员变量赋值的话, 成员变量都有默认值.

默认值:

基本数据类型:

  • byte, short, int, long: 0

  • float, double: 0.0

  • char: 空格

  • boolean: false

引用类型:

  • String, 类, 接口, 数组: null

程序示例:

Javabean 类:

public class Default {
byte b;
short s;
int i;
long L;
char c;
boolean boo;
float f;
double d;
String st;
}

测试类:

public class DefaultTest {
public static void main(String[] args) {
Default test = new Default(); // 如果此处写 Default test; 则会报错 java: 可能尚未初始化变量test
System.out.println(test.b);
System.out.println(test.s);
System.out.println(test.i);
System.out.println(test.L);
System.out.println(test.c);
System.out.println(test.boo);
System.out.println(test.f);
System.out.println(test.d);
System.out.println(test.st);
}
}

执行结果:

0
0
0
0
false
0.0
0.0
null

封装

private 是一个修饰符, 可以修饰成员, 例如成员变量和成员方法等, 用 private 修饰的成员, 只能在本类中访问.

public 也是一个修饰符, 也可以修饰成员, 例如成员变量和成员方法等, 用 public 修饰的成员, 可以在所有的类中访问.

程序示例:

Javabean 类:

public class Girlfriend {
String name;
String sex;
private int age;
public void setAge(int a) {
if (a < 18 || a > 50)
System.out.println("非法数据. ");
else age = a;
}
public int getAge() {
return age;
}
}

测试类:

public class GirlfriendTest {
public static void main(String[] args) {
Girlfriend g1 = new Girlfriend();
g1.sex = "女";
g1.name = "小团团";
g1.setAge(20);
System.out.println(g1.getAge());
System.out.println(g1.sex);
System.out.println(g1.name);
}
}

执行结果:

20
小团团

用 public 或 private 修饰成员变量时不影响它的默认值.

private 可以防止给成员变量一个不恰当的值.

程序示例:

Javabean 类:

public class GF {
// 成员变量
private int age;
private String name;
private String gender;
// getter 和 setter
public void setAge(int age) {
if (age < 18 || age > 30) {
System.out.println("年龄不合适");
} else {
this.age = age;
}
}
public int getAge() {
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
// 成员方法
public void playGame() {
System.out.println("GF is playing game.");
}
public void eat() {
System.out.println("GF is eating.");
}
}

测试类:

public class GFtest {
public static void main(String[] args) {
GF fg = new GF();
fg.setName("Hello");
fg.setAge(20);
fg.setGender("nv");
fg.eat();
fg.playGame();
System.out.println(fg.getAge());
System.out.println(fg.getGender());
System.out.println(fg.getName());
}
}

执行结果:

GF is eating.
GF is playing game.
20
nv
Hello

Javabean 类的成员变量都应该被定义为私有的, 即被 private 修饰的, 同时提供对应的 get 和 set 方法, 外界对成员变量的操作 (包括赋值和获取值) 都应该通过 get 和 set 方法进行而不是直接操作这些私有的成员变量.

定义在方法里面的变量叫做局部变量 (包括定义在方法头的形参变量) , 定义在方法外面类里面的变量叫做成员变量. 遵循就近原则, 如果局部变量和成员变量同名, 则在方法内局部变量覆盖成员变量. 如果不想覆盖, 即在方法中使用同名的成员变量, 需要在成员变量前面加关键字 this.

this 的本质: 代表方法调用者的地址.


图 1

图 2

图 3

程序示例 1:

Javabean 类:

public class Girlfriend {
private int age; // 成员变量
public int getAge() {
int age = 10; // 局部变量
return age; // 返回局部变量
}
public int getAge1() {
int age = 10; // 局部变量
return this.age; // 返回成员变量
}
}

测试类:

public class GirlfriendTest {
public static void main(String[] args) {
Girlfriend g1 = new Girlfriend();
System.out.println(g1.getAge()); // 10
System.out.println(g1.getAge1()); // 0, 0 是默认值
}
}

程序示例 2:

Javabean 类:

public class Girlfriend {
private String name;
private int age;
public void setName(String name) {
name = name;
}
public void setName1(String name) {
this.name = name;
}
public void setAge(int a) {
age = a;
}
public String getName() {
String name = "小团团";
return name;
}
public String getName1() {
String name = "小张";
return this.name;
}
public int getAge() {
int age = 10;
return age;
}
}

测试类:

public class GirlfriendTest {
public static void main(String[] args) {
Girlfriend g1 = new Girlfriend();
g1.setName("小明");
g1.setName1("小李");
g1.setAge(20);
System.out.println(g1.getAge()); // 10
System.out.println(g1.getName()); // 小团团
System.out.println(g1.getName1()); // 小李
}
}

如果不发生成员变量和局部变量同名的情况, this 写不写都一样.

程序示例:

Javabean 类:

public class Girlfriend {
private int age;
public int getAge() {
return age;
}
public int getAge1() {
return this.age;
}
}

测试类:

public class GirlfriendTest {
public static void main(String[] args) {
Girlfriend g1 = new Girlfriend();
System.out.println(g1.getAge()); // 0
System.out.println(g1.getAge1()); // 0
}
}

构造方法

构造方法也叫构造器或构造函数.

作用: 在创建对象时, 给成员变量进行初始化 (即赋值) .

构造方法的格式:

public class Student {
// 构造方法
修饰符 类名(参数) {
方法体;
}
}

注意:

  • 方法名必须和类名完全相同.

  • 没有返回值类型, 连 void 也不能有.

  • 没有返回值, 不能由 return 语句带回结果数据, 即使是 return; 也不能有.

使用空参构造方法时, 成员变量被初始化为默认值. 空参构造方法的方法体一般都是空着, 什么都不写.

可以自定义带参构造方法. 在方法体内, 可以给成员变量赋值.

构造方法在创建对象时由虚拟机调用, 不能手动调用构造方法.

每创建一次对象, 就会调用一次构造方法.

如果没有自定义的构造方法, 那么虚拟机会自动添加一个空参构造方法.

如果自定义了有参构造, 那么虚拟机将不再提供默认的无参构造, 在创建对象时, 将不能再使用无参构造, 如果还需要使用无参构造, 则需要我们自己手动书写无参构造.

因此, 建议如果写了自定义的有参构造, 那么不管是否会用到无参构造, 都再手动书写一个无参构造.

带参构造和无参构造是构造方法的重载.

带参构造一定是带全部参数.

如果在 Javabean 类中, 定义的类只有有参构造, 没有无参构造, 在测试类中, 定义类的对象时, 不传递参数, 想要调用无参构造, 这时会报错, 因为在 Javabean 类中, 已经没有无参构造了, 如果还需要无参构造, 就需要自己去写无参构造了.


图 1

程序示例:

Javabean 类:

public class Girlfriend {
private int age;
private String name;
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public String getname() {
return name;
}
// 空参构造, 虚拟机自动添加的构造方法就和这个长得一样.
public Girlfriend() {
}
// 带参构造
public Girlfriend(String name, int age) {
this.name = name;
this.age = age;
}
}

测试类:

public class GirlfriendTest {
public static void main(String[] args) {
Girlfriend g1 = new Girlfriend();
System.out.println(g1.getAge()); // 0
System.out.println(g1.getname()); // null
Girlfriend g2 = new Girlfriend("小张", 23);
System.out.println(g2.getAge()); // 23
System.out.println(g2.getname()); // 小张
g2.setAge(25); // 进行修改
g2.setName("小王");
System.out.println(g2.getAge()); // 25
System.out.println(g2.getname()); // 小王
}
}

标准的 Javabean 类

类名采用大驼峰.

所有成员变量都用 private 修饰.

提供至少两个构造方法: 无参构造方法和带全部参数的构造方法.

成员方法: 提供对应到每一个成员变量的 setXXX() / getXXX(). 如果有其他行为, 也需要写上.

快捷键: alt + insert 或者 alt + fn + insert

插件 PTG 可以 1 秒生成标准 Javabean, 快捷键: ctrl + shift + ,

按下 alt + insert 或者 alt + fn + insert 之后显示:


图 1

回车, 表示选择 Construcor, 显示:


图 2

点击 Select None, 表示空参构造, 如果不想点击鼠标, 可以按两下 tab, 再按下回车. 第一次按下 tab, 表示不选择任何成员变量, 再按下 tab, 表示选择 Select None, 然后按下回车. 这样就生成了空参的构造方法.

再次按快捷键 alt + insert 或者 alt + fn + insert, 还是出现图 1, 回车, 表示选择 Construcor, 然后出现图 2, 然后按 Ctrl + a, 全选所有的成员变量, 再按下回车, 表示选择 OK 选项, 则创建出全参构造方法.

再次按快捷键 alt + insert 或者 alt + fn + insert, 还是出现图 1, 选择 Getter and Setter, 回车, 得到图 3, 然后按 Ctrl + a, 全选所有的成员变量, 再按下回车, 表示选择 OK 选项, 则创建出全部成员变量的 getter 方法和 setter 方法.


图 3

对象内存图

对象是 new 出来的, 对象中存放的是地址.

程序示例:

Javabean 类:

public class Phone {
// 属性 (成员变量)
String brand;
double price;
// 行为 (方法)
public void call() {
System.out.println("手机在打电话");
}
public void playGame() {
System.out.println("手机在玩游戏");
}
}

测试类:

public class PhoneTest {
public static void main(String[] args) {
// 创建手机对象
Phone p = new Phone();
System.out.println(p); // classes.Phone@776ec8df
}
}

new 一个对象要发生的事情至少包括以下七个步骤:

Student s = new Student(); // new 一个对象
  1. 加载 Student.class 字节码文件. 因为用到了这个类, 所以要加载这个类的字节码文件

  2. 申明等号左侧的局部变量

  3. 在堆内存中开辟一个空间

  4. 默认初始化

  5. 显示初始化 (在类的定义中给定初始值即为显式初始化)

  6. 构造方法初始化 (在类中定义了有参构造方法时执行这一步)

  7. 将堆内存中的地址值赋值给左边的局部变量

可以让两个变量指向同一个对象:

Javabean 类:

public class Student {
String name;
}

测试类:

public class StudentTest {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "小明";
Student s2 = s1;
System.out.println(s1.name + " " + s2.name); // 小明 小明
s2.name = "小张";
System.out.println(s1.name + " " + s2.name); // 小张 小张
s1 = null;
// System.out.println(s1.name); // Exception in thread "main" java.lang.NullPointerException: Cannot read field "name" because "s1" is null
System.out.println(s2.name); // 小张
}
}

一个对象的内存图 的分析过程:

下图 的代码中, 首先是将 TestStudent 类的字节码文件加载到方法区中, 并将 main() 方法在其中进行临时存储.


图 1

接下来, 虚拟机会自动调用程序的主入口 main() 方法, 于是, main() 方法被加载到栈里面.


图 2

然后开始执行 main() 方法里面的代码. 第一句是 Student s = new Student();, 创建一个 Student 类的对象 s.

创建这个对象时, 遵循上面所说的创建一个对象要经历的 7 个步骤.

第一步是将类 Student 的 class 文件加载到方法区, 且这个空间内有着这个类的全部信息, 比如所有的成员变量 name, age, 所有的成员方法 study() 等.


图 3

第二步是申明局部变量, 也就是创建对象这条语句 Student s = new Student(); 左边的代码 Student s, 在 main() 方法中就开辟了一个空间, 这个空间的名字就叫做 s. 这个空间可以存储 Student 这个类的对象的地址值.


图 4

第三步, 在堆内存中开辟一块空间, 也就是创建对象这条语句 Student s = new Student(); 右边的代码 new Student(). 堆内存里面的空间都是有地址值的, 假设这块空间的地址值为 001, 这块空间里面会把 Student 这个类的所有的成员变量都拷贝一份放过来 (从方法区中拷贝, 因为这些信息存储在方法区中) . 除此之外 001 这块空间还存储有 Student 这个类的所有成员方法的地址, 这个地址是为了以后用对象调用方法的时候能找到对应的方法, 等于说并没有复制成员方法, 成员方法还是在方法区中, 只是复制了成员变量到堆内存中. 此时这个地址为 001 的空间就是我们所说的对象. 但是这个对象现在还没有创建完毕. 因为此时的成员变量 nameage 都还没有值. 赋值操作就是接下来的 4, 5, 6 三个步骤.


图 5

第四步, 首先是默认初始化, name 默认初始化为 null, age 默认初始化为 0.


图 6

第五步, 是显示初始化, 如果 Student 类的定义中, 成员变量时直接给定了值, 即 String name = "zhangsan";int age = 10;, 那么这就叫做显示初始化, 此时上一步里面默认初始化的 null0 在这一步中就会被 zhangsan10 代替, 但是这里 Student 类并没有写这样的显示初始化的代码, 那么显式初始化可以忽略.

第六步, 是构造方法初始化, 在创建实例 s 时, 即 Student s = new Student(); 这条语句中, 小括号中什么都没写, 就表示现在调用的是空参构造, 且是虚拟机提供的默认的空参构造方法, 这个默认的空参构造方法的方法体是空的, 没有任何语句, 所以空参构造是可以忽略的. 但是如果此时用的是有参构造, 那么在构造方法初始化这一步, 就会有值代替默认初始化中的 null0. 从此也可以看出, 构造方法只是创建对象的多个步骤中的一个步骤而已.

第七步, 即把堆内存中的地址赋值给左边的局部变量. 即把地址 001 通过等号运算符即赋值运算符赋值给了左边的变量 s. 此时 s 这个变量的空间里面就会存储地址值 001. 于是变量 s 也可以通过地址值 001 找到堆内存中的空间.


图 7

至此, 创建对象的这条语句, 即 Student s = new Student();, 就执行完了.

如果直接打印 s, 就是打印 s 中记录的地址值 001.

s.name 就表示 001 这个内存空间里面的 name, 就找到了堆内存里面的 name.

如果执行语句 s.name = "aqiang";, 则改变的也是堆内存里面的 name.

语句 s.study(); 就是找 001 这个空间里面的 study(), 但是找到的是这个成员方法的地址, 再用这个地址, 找到了方法区里面的成员方法 study(). 此时 study() 方法就会被加载进栈. study() 方法执行完毕后, 就会出栈. 此时整个 main() 方法就执行完毕了, 于是 main() 方法也出栈. 既然 main() 方法都出栈了, 那么 main() 里面的变量自然也就没有了. 于是 s 也就没有了, 于是也就没有变量再使用堆内存中的这块原本地址值为 001 的这一块空间了. 专业点说就是没有变量指向这块空间了, 那么这块空间也会消失.


图 8 完整的示意图

图 9 main 方法执行结束

两个对象的内存图 的分析过程:

创建第一个对象和第二个对象的过程和上述分析一个对象的内存图 的过程是一样的.

执行 Student s2 = new Student(); 语句时, Student.class 文件不需要再加载一次, 因为已经存在于方法区中了, 直接用就可以了.


图 10 完整的示意图

图 11 main 方法执行结束

图 12

图 13

图 14 main 方法执行结束

图 15

图 16
posted @   有空  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
点击右上角即可分享
微信分享提示