ClassAndObject -类与对象 - java(万字篇)

x33g5p2x  于2021-11-22 转载在 Java  
字(21.9k)|赞(0)|评价(0)|浏览(476)

类与对象的初步认知

  1. C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。
  2. JAVA是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。
  3. 面向过程注重的是过程,在整个过程中所涉及的行为,就是功能。
  4. 面向对象注重的是对象,也就是参与过程所涉及到的主体。是通过逻辑将一个个功能实现连接起来

面向对象概念

  1. 1.面向对象是思考问题的一种思考方式,是一种思想。
  2. 2.类就是一类对象的统称。对象就是这一类具体化的一个实例。
  3. 3.面向对象的好处:将复杂的事情变简单了,只要面对一个对象就行。

面向对象设计

  1. 面向对象设计把握一个重要的经验:谁拥有数据,谁对外提供操作这些数据(私有)的方法!
  2. (被动的一方是数据的拥有者,主动的一方是执行者)
  3. 开发时:找对象,建对象,用对象,并维护对象之间的关系。

简而言之

  1. 面向对象就是用代码(类)来描述客观世界的事物的一种方式. 一个类主要包含一个事物的属性和行为

图 1

图 2

通过 图1 和 图2 ,我们更加清楚 面向对象 与 面向过程的区别。

那么我们问题是︰对象从何而来,从类来,你可以想象成一个类型,假设我们都是人,额,就这样吧。那么我们要定义一个类,这个类我们要定义出来,该怎么定义呢?

类和类的实例化

  1. 类就是一类对象的统称。对象就是这一类具体化的一个实例

创建类 ( 基本语法 )

  1. class <class_name>{
  2. field;//成员属性
  3. method;//成员方法
  4. }
  5. 实例化对象
  6. <class_name> <对象名> = new <class_name>();

class为定义类的关键字,ClassName为类的名字,{}中为类的主体(类体)。
类中的元素称为:成员属性(成员变量)。类中的函数称为:成员方法。

代码如下

  1. 注意类的定义是写在我们文件类的外面(初识Java中,我们说了类名要和文件相同,所以我写成文件类,方便你们区别)
  2. class Person{
  3. // 现在我们定义了 一个类 Person (注意类名要大驼峰结构,这个初识Java的时候就讲过了)
  4. // 类里面包含字段(又称 属性 和 成员变量) 和 方法
  5. // 字段又称属性,属性什么意思,比如人的属性
  6. public String name;// 每个人都有自己的名字
  7. public int age; // 年龄
  8. // 这两个成员变量虽然可以赋值,但不推荐,跟前面 数组文章中的不规则二维数组是一样的
  9. // 可以 根据后期的需要, 进行赋值的,而且你总不能每个人的名字和年龄都一样把。
  10. // 注意 创建在 在类的内部,方法的外部的变量,被称为 成员变量(属性/字段)
  11. // 定义写到方法的内部 是 局部变量
  12. 另外 成员变量 又可分为 普通成员变量(该程序的成员变量就是普通的) 静态成员变量(static
  13. // 而方法(又称 成员方法) 可以解析为 人的行为
  14. // 比如说吃
  15. public void eat(){
  16. System.out.println(name + "正在吃饭。");
  17. }
  18. public void sleep(){
  19. System.out.println(name + "睡着了。");
  20. }
  21. }
  22. public class ClassAndObject {
  23. public static void main(String[] args) {
  24. // 我们经过图1,得出的结论是 有了类才有对象
  25. // 现在我们有了类,那么怎么产生对象的呢?
  26. // 通过 new 去实例化一个对象,也就是说去产生一个对象
  27. Person person = new Person();
  28. // 注意此时的变量是由类定义的,由类定义的变量,都是引用变量,意味着初值可以赋 null
  29. // Person person = null;
  30. // 而且 前面也说,同一个类,我们可以产生很多对象
  31. // 意味着 我们生成 很多人 person
  32. Person person2 = new Person();
  33. Person person3 = new Person();
  34. Person person4 = new Person();
  35. Person person5 = new Person();
  36. // .
  37. // .
  38. // .
  39. }
  40. }

访问 类的成员变量(通过引用来访问)

基本语法

  1. 引用(由类定义的定义的类名).普通成员变量名

代码实例1

  1. class Person{
  2. public String name;// 每个人都有自己的名字
  3. public int age; // 年龄
  4. public void eat(){
  5. System.out.println(name + "正在吃饭。");
  6. }
  7. public void sleep(){
  8. System.out.println(name + "睡着了。");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. Person person = new Person();
  14. // 普通成员变量的访问需要 通过对象 的引用(person)来访问的
  15. System.out.println(person.name);// name 是引用类型,存的是首字符地址
  16. System.out.println(person.age);// int 整形
  17. }// 图 3
  18. }// 图4 , 图中的默认值,是针对于 成员变量,不包括局部变量,而且局部变量不初始化是无法使用的

图 3

图4(成员变量和引用的默认值)

代码实例2

  1. class Person{
  2. public String name;// 每个人都有自己的名字
  3. public int age; // 年龄
  4. public void eat(){
  5. System.out.println(name + "正在吃饭。");
  6. }
  7. public void sleep(){
  8. System.out.println(name + "睡着了。");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. Person person = new Person();
  14. person.name = "author";
  15. person.age =10;
  16. System.out.println(person.name);
  17. System.out.println(person.age);
  18. Person person2 = new Person();
  19. System.out.println(person2.name);// name 是引用类型,存的是首字符地址
  20. System.out.println(person2.age);// int 整形
  21. }// 图 5
  22. }// 图解6

图5

图6

代码实例3 (调用成员方法)

成员方法也和成员变量一样,可分为 普通成员方法 和 静态成员方法(static)

普通成员方法跟普通成员变量是一样的,通过对象的引用来调用
  1. class Person{
  2. public String name;// 每个人都有自己的名字
  3. public int age; // 年龄
  4. public void eat(){
  5. // eat 和 sleep 可以称其为 方法片段,方法片段 是存储在方法区中
  6. // 如果方法里 创建了 int a 或者 其他类型的变量,在该方法被调用时,
  7. // 方法会在栈上开辟内存(没被调用,就放在方法区里)
  8. // 那么方法的空间就会分出4byte来存储它
  9. System.out.println(name + "正在吃饭。");
  10. }
  11. public void sleep(){
  12. System.out.println(name + "睡着了。");
  13. }
  14. }
  15. public class ClassAndObject {
  16. public static void main(String[] args) {
  17. Person person = new Person();
  18. person.name = "author";
  19. person.age =10;
  20. person.eat();
  21. person.sleep();
  22. }// 图 7
  23. }

图 7

类的成员

  1. 类的成员可以包含以下:字段、方法、代码块、内部类和接口等
  2. 本文主要讲字段、方法,代码块。

字段 等价于 (属性/成员变量)

成员变量又可分为两类: 普通成员变量,静态成员变量

代码样本1

  1. class Person{
  2. // 普通成员变量 都是属于对象的
  3. public String name;// 引用类型(存的字符串首字符地址),默认值为null
  4. public int age;// 默认值为 0
  5. // 静态成员变量
  6. public static int count;// 默认值为 0
  7. }
  8. public class ClassAndObject {
  9. public static void main(String[] args) {
  10. Person person = new Person();
  11. person.age = 10;// 访问普通成员
  12. person.count = 1;
  13. System.out.println(person.age);
  14. System.out.println(person.count);
  15. }
  16. }// 图 8

图8

代码样本2(访问普通成员变量)

  1. class Person{
  2. // 普通成员变量 都是属于对象的
  3. public String name;// 引用类型(存的字符串首字符地址),默认值为null
  4. public int age;// 默认值为 0
  5. }
  6. public class ClassAndObject {
  7. public static void main(String[] args) {
  8. Person person = new Person();
  9. person.age++;
  10. System.out.println(person.age);
  11. System.out.println("======================");
  12. Person person2 = new Person();
  13. person2.age++;
  14. System.out.println(person2.age);
  15. }
  16. }// 图 9,10

图9

图10

代码样本3(访问静态成员变量)

  1. class Person{
  2. // 普通成员变量 都是属于对象的
  3. public String name;// 引用类型(存的字符串首字符地址),默认值为null
  4. public int age;// 默认值为 0
  5. public static int count;
  6. }
  7. public class ClassAndObject {
  8. public static void main(String[] args) {
  9. Person person = new Person();
  10. person.age++;
  11. person.count++;
  12. System.out.println(person.age);
  13. System.out.println(person.count);
  14. System.out.println("======================");
  15. Person person2 = new Person();
  16. person2.age++;
  17. person2.count++;
  18. System.out.println(person2.age);
  19. System.out.println(person2.count);
  20. }
  21. }// 图 11,12

图11

图12

代码样本4(由图12得出的结论,修改样本3)

  1. class Person{
  2. // 普通成员变量 都是属于对象的
  3. public String name;// 引用类型(存的字符串首字符地址),默认值为null
  4. public int age;// 默认值为 0
  5. public static int count;
  6. }
  7. public class ClassAndObject {
  8. public static void main(String[] args) {
  9. Person person = new Person();// 没意义,静态成员变量属于类,所以实不实例化一个对象。没有影响
  10. Person.count++;
  11. System.out.println(Person.count);
  12. System.out.println("======================");
  13. //Person person2 = new Person(); 没意义
  14. Person.count++;
  15. System.out.println(Person.count);
  16. }
  17. }// 图 13

图13

代码样本5 (调用【静态成员方法/类方法】)

  1. class Person{
  2. // 静态成员方法/类方法
  3. public static void staticFunc(){
  4. System.out.println("static::func()");
  5. }
  6. }
  7. public class ClassAndObject {
  8. public static void main(String[] args) {
  9. Person.staticFunc();
  10. }
  11. }// 图 14

图14

注意事项 1(能不能在方法中 创建一个 被 static修饰的变量)

  1. class Person{
  2. // 普通成员办法
  3. public void eat(){
  4. static int size = 0;// 该写法是错的
  5. }
  6. // 静态成员方法/类方法
  7. public static void staticFunc(){
  8. static int size2 = 0;// 该写法是错了
  9. }
  10. }// 图 15

图15

总结:

  1. 无论是 普通成员方法还是静态成员方法,都不能在其内部创建一个被static修饰的变量
  2. 因为 static修饰了的变量,该变量就属于类了(类变量/静态变量)。
  3. 而你把一个(类变量/静态成员变量)写在方法里,就意味着属于方法(是一个局部变量,不再是 类变量了),而不属于类
  4. 所以冲突了,不合适,导致编译器报错。
  5. 还有一个原因
  6. eat来说, eat是普通成员方法,是需要对应的引用来调用的。但是如果可以定义static的变量, 通过 类名Person 就能调用。
  7. 一个需要对象,不需要类;另一个需要类,不要有对象,两者就像在这里卡bug一样存在冲突。所以这种写法是错的
  8. 而静态成员方法中,之所以不能创建一个被 static修饰的变量,是因为,你是在内部定义的。在内部定义,就意味着属于方法,而不属于类,所以错的
  9. 总得来说: 只要是被 static 修饰的变量/方法,都是属于类的。

注意事项 2(能不能在方法中调用方法)

普通成员方法 调用动态成员方法

  1. class Person{
  2. public String name = "author";
  3. public void eat(){
  4. staticFunc();// 可以这样写,没有警告
  5. System.out.println(name+"正在吃饭.");
  6. }
  7. public static void staticFunc(){
  8. System.out.println("static::func()");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. Person p = new Person();
  14. p.eat();// 图 16
  15. }
  16. }由图得知,普通成员方法 可以调用 动态成员方法 ,因为普通成员方法是依赖对象的,静态成员方法 不依赖对象
  17. 所以 调用普通成员方法,new一个对象,对于静态成员方法来说没有任何影响,你用你的引用,我用我的类
图16

&****

静态成员方法/类方法 调用 普通成员方法

  1. class Person{
  2. public String name = "author";
  3. public void eat(){
  4. System.out.println(name+"正在吃饭.");
  5. }
  6. public static void staticFunc(){
  7. eat();// error 图17
  8. System.out.println("static::func()");
  9. }
  10. }
图17

由图17得知,静态成员方法 是无法调用 普通成员方法的
原因也很简单,静态成员方法不需要对象,而普通成员方法需要对象
静态成员通过类名来调用,因此不需要new对象,那么 eat() 谁调?
没有对象。不能调,所以编译器会报错。
当然硬要调用也行, 在 eat()前面 new一个对象,用该对象的引用 来调用 普通成员方法 eat()

总结

  1. 普通成员方法(有对象) 能调用 静态成员方法(单身贵族)
  2. 静态成员方法(单身贵族) 不能调用 普通成员方法(没对象)
  3. 现在知道前面博客的一些例题,使用方法的时候,为什么都用static 因为被static修饰的方法是可以直接调用的,不需要new对象
  4. 不是new对象麻烦,而是当时知识储备有限,明白了吧。

这里讲一个dan疼的面试题

main 函数为什么是静态的?
首先 main 函数 是不是静态都可以!!!
这个东西取决于 JVM 的规则。
main 之所以 是 静态的,是因为 在设计 JVM的时候,就这么设计的
所以 main 只有被static 修饰的时候,JVM才认为它是main函数

举个例子:
你去超市买东西,要付钱吧。你总不能拿东西不给钱,给警察抓吧?
买东西要给钱 和 在Java中 main方法需要 static 修饰 是一样的
是默认的规则,是你必须要遵循的。 当然 你非要 不使用用 static修饰main函数
没关系,问题就在于怎么new一个对象 辅助 编译器 在编译器 进入main函数,
而且,还没考虑 main函数 是程序运行一开始就要进入,因为main函数是程序的入口
想想就麻烦,我是做不出来。
总之做一个安分守己的好公民,买东西要给钱;写main方法 使用 static来修饰

在这里们再稳固一下知识

  1. class Person{
  2. public String name = "author";
  3. public void eat(){
  4. staticFunc();
  5. System.out.println(name+"正在吃饭.");
  6. }
  7. public static void staticFunc(){
  8. System.out.println("static::func()");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. Person p = null;// 这个引用 不指向 任何的对象
  14. // 两个引用指向同一个对象
  15. Person p2 = new Person();
  16. Person p3 = p2;// p3 这个引用 指向 p2 指向的对象
  17. // 一个引用能不能指向多个对象?
  18. Person person = new Person();
  19. person = new Person();
  20. person = new Person();// 这个是person真正指向的对象
  21. // 答案是不能,person 此时指向的对象是最后一次 new的对象
  22. }
  23. }

引用 一定是在栈上吗?

  1. 答案是 不是

代码实例

  1. class Person{
  2. public String name = "author";
  3. public void eat(){
  4. staticFunc();
  5. System.out.println(name+"正在吃饭.");
  6. }
  7. public static void staticFunc(){
  8. System.out.println("static::func()");
  9. }
  10. }
  11. public class ClassAndObject {
  12. Person person = new Person();
  13. public static void main(String[] args) {
  14. ClassAndObject classAndObject = new ClassAndObject();
  15. }
  16. }// 图 18

图18

总结 static 关键字

  1. 1、修饰属性
  2. 修饰属性,Java静态属性和类相关, 和具体的实例无关. 换句话说, 同一个类的不同实例共用同一个静态属性
  3. 2、修饰方法
  4. 如果在任何方法上应用 static 关键字,此方法称为静态方法。
  5. 静态方法属于类,而不属于类的对象。
  6. 可以直接调用静态方法,而无需创建类的实例。
  7. 静态方法可以访问静态数据成员,并可以更改静态数据成员的值。

注意事项1: 静态方法和实例无关, 而是和类相关. 因此这导致了两个情况:

  1. 1. 静态方法不能直接使用非静态数据成员或调用非静态方法(非静态数据成员和方法都是和实例相关的).
  2. 2. thissuper两个关键字不能在静态上下文中使用(this 是当前实例的引用, super是当前实例父类实例的引用,
  3. 是和当前实例相关).

注意事项2

  1. 1. 我们曾经写的方法为了简单(不用new对象), 都统一加上了 static. 但实际上一个方法具体要不要带 static, 都需要是情形而定.
  2. 2. main 方法为 static 方法.

注意事项3

  1. 1 final(使其具有常量属性)修饰的成员变量,与成员变量是不是存储在方法区或者堆上是无关的
  2. 简单来说: 一个对象存储到哪里 你是否被final修饰无关
  3. 还是那句话,凡是被 static 修饰的成员变量或者成员方法(又称静态成员变量和静态成员方法),都是存储在方法区中
  4. 没有被static修饰的成员变量 或者 方法(又称:普通成员变量 普通成员方法)
  5. 都需要通过new对象,来实体化对象,通过指向对象的引用来调用我们普通成员变量和方法。

注意事项4

  1. 以后在工作中,遇到被static修饰的成员变量和方法,意味着只有成员方法和变量一份,不能轻易动,因为你不知谁使用了它。不知道它涉及范围有多广
  2. 能在不使用 static 的情况,完成程序的编程,就尽量不要使用。(就像C语言的全局变量一样,多了不安全,牵扯太多,又不能随便动)
  3. 反正以后只要遇到static修饰的,你就当祖宗供着就行了。

在这里大家想想如果我们的类体中,字段/属性/成员变量,越来越多, 如果我们想打印一下内容,要写很多方法,很麻烦

先来看看下面的代码,我们一步步来看

代码样本1

  1. class Person{
  2. public String name;
  3. public int age;
  4. public static int count;
  5. public void eat(){
  6. System.out.println(name+"正在吃放");
  7. }
  8. public void print(){
  9. System.out.println("姓名"+name+"年龄"+age);
  10. }
  11. }
  12. public class ClassAndObject {
  13. public static void main(String[] args) {
  14. Person person = new Person();
  15. System.out.println(person);// 图 19
  16. }
  17. }
图19

程序结果图解流程图
点击 println 》 Ctrl+点击,进入println函数,得图20
图20

点击 valueOf 》 Ctrl+点击,进入valueOf函数,得图21
图21

点击 toString 》 Ctrl+点击,进入toString函数,得图22
图22

通过我们层层解析,我发现它最后是通过 toString 来转换

  1. 那么我们可不可以 自己写一个 toString 方法
  2. 答案是可以的

代码样本2

  1. class Person{
  2. public String name;
  3. public int age;
  4. public static int count;
  5. public void eat(){
  6. System.out.println(name+"正在吃放");
  7. }
  8. public void print(){
  9. System.out.println("姓名"+name+"年龄"+age);
  10. }
  11. public String toString(){// 这里我们写的toString 方法 ,返回一个字符串
  12. return "author";
  13. }
  14. }
  15. public class ClassAndObject {
  16. public static void main(String[] args) {
  17. Person person = new Person();
  18. System.out.println(person);// 图 23
  19. }
  20. }
图23

由图23得知,结果很有意思 输出的是我们自己写 toString 返回值
这里涉及到 动态绑定,这个后面讲到相应的内容,我们再来讲这个
你只需要知道,我们在打印数据时,如果我们写了一个toString的方法
编译器,就会执行我们所写的 toString 方法,而不是系统默认的toString

执行我们所写的 toString方法 的 前提是 toString 方法 的名字不能变,是定死了的,缺胳膊少腿 或者 画蛇添足都是不行的

  1. 例如
  2. public String toString1(){
  3. return "author";
  4. }
  5. public String toStrin(){
  6. return "author";
  7. }
  8. 你可以仔细去看看图23 我们的写法是与编译器默认的toString 写法是相同的

也就是说我们可以不用调用方法,就能输出一样的效果

代码样本(打印成员变量的值)

  1. class Person{
  2. public String name;
  3. public int age;
  4. public static int count;
  5. public void eat(){
  6. System.out.println(name+"正在吃放");
  7. }
  8. public void print(){
  9. System.out.println("姓名:"+name+" 年龄:"+age);
  10. }
  11. public String toString(){// 这里我们写的toString 方法 ,返回一个字符串
  12. return"姓名:"+name+" 年龄:"+age;
  13. }
  14. }
  15. public class ClassAndObject {
  16. public static void main(String[] args) {
  17. Person person = new Person();
  18. System.out.println(person);// 图 24
  19. }
  20. }
图24

代码样本1~3,只是来说明 println 函数 是通过 调用 toString 方法来实现的

有的人可能会说,这跟我们 通过对象去调用成员方法,有什么区别?

  1. 都是自己手敲的。
  2. 区别就在于 toString函数 可以 通过快捷键和鼠标来让编译器自动生成
  3. 操作流程图: 25 26 27 28

图25

图26

图27

图28

代码如下

  1. class Person{
  2. public String name;
  3. public int age;
  4. public static int count;
  5. public void eat(){
  6. System.out.println(name+"正在吃放");
  7. }
  8. public void print(){
  9. System.out.println("姓名:"+name+" 年龄:"+age);
  10. }
  11. @Override// 这个类似一个检查功能,检查我们要重写的东西,与原来的一不一样
  12. public String toString() {
  13. return "Person{" +
  14. "name='" + name + '\'' +
  15. ", age=" + age +
  16. '}';
  17. }// 这个功能 涉及 我们后面的内容 "重写", 这里暂时不讲,有个概念就行
  18. }
  19. public class ClassAndObject {
  20. public static void main(String[] args) {
  21. Person person = new Person();
  22. System.out.println(person);// 图 29
  23. }
  24. }
图29

总结:

toString 方法会在 println 的时候被自动调用.
将对象转成字符串这样的操作我们称为序列化(把一个对象转换成字符串). 反序列化(把字符串转换成对象)
toString 是 Object 类提供的方法, 我们自己创建的 Person 类默认继承自 Object 类, 可以重写 toString 方法
我们自己写的转换字符串方法. (关于继承和重写这样的概念, 我们后面会重点介绍).
@Override 在 Java 中称为 “注解”, 此处的 @Override 表示下面实现的 toString 方法是重写了父类的方法. 关于注解后面的课程会详细介绍…
IDEA快速生成Object的toString方法快捷键:alt + f12 / 0 (insert)

但是现在有一个问题

上代码

  1. class Person{
  2. // 如果有人 在某天,改变了 Person 类中 成员变量名
  3. // name 》 myName
  4. public String myName;
  5. public int age;
  6. public static int count;
  7. // 然后就 只把 Person 类 中的 name 都改 myName
  8. // 虽然这样做 在自己的类中,不影响 person类中的程序运行
  9. public void eat(){
  10. System.out.println( myName+"正在吃放");
  11. }
  12. public void print(){
  13. System.out.println("姓名:"+ myName+" 年龄:"+age);
  14. }
  15. @Override
  16. public String toString() {
  17. return "Person{" +
  18. "name='" + myName + '\'' +
  19. ", age=" + age +
  20. '}';
  21. }
  22. }
  23. public class ClassAndObject {
  24. public static void main(String[] args) {
  25. Person person = new Person();
  26. person.name = "bit";//图30
  27. // 但是 如果有其他类,在类体中调用 或者 访问 该成员变量,就存在着问题
  28. // 因为 别人调用语法: 引用.成员变量名, 而你改名了,别人不知道啊
  29. // 所以 别人也要改,但是如果调用它的类有很多,那么一个个改名,就不现实
  30. }
  31. }
图30

在这里引入一个概念 封装

什么叫封装?

  1. <<代码大全>> 开篇就在讨论一个问题: 软件开发的本质就是对程序复杂程度的管理. 如果一个软件代码复杂程
  2. 度太高, 那么就无法继续维护. 如何管理复杂程度? 封装就是最基本的方法.
  3. 在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
  4. 封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
  5. 这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度

private实现封装

  1. private/ public 这两个关键字表示 "访问权限控制" .
  2. public 修饰的成员变量或者成员方法, 可以直接被其他类的调用者使用.
  3. private 修饰的成员变量或者成员方法, 不能被其他类的调用者使用.
  4. 换句话说, 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员. 从而让类调用者以更低的成本来使用类.

代码实例

  1. class Person{
  2. private String name;// 此时 name 被 private 所修饰(被private包装了)
  3. // 普通成员变量 name,非本类内部,不可调用
  4. // 图31,由图得知 name已经不能 其他类的调用者所调用
  5. public int age;
  6. public static int count;
  7. private void eat(){
  8. System.out.println( name+"正在吃放");
  9. } // 普通成员方法 eat,非本类内部,不可调用
  10. // 图31,由图得知 eat已经不能 其他类的调用者所调用
  11. public void print(){
  12. System.out.println("姓名:"+ name+" 年龄:"+age);
  13. }
  14. @Override
  15. public String toString() {
  16. return "Person{" +
  17. "name='" + name + '\'' +
  18. ", age=" + age +
  19. '}';
  20. }
  21. }
  22. public class ClassAndObject {
  23. public static void main(String[] args) {
  24. Person person = new Person();
  25. person.name = "bit";
  26. person.age = 10;
  27. person.eat();
  28. }
  29. }
图31

总结

  1. private 不光能修饰字段, 也能修饰方法
  2. 通常情况下我们会把字段设为 private 属性, 但是方法是否需要设为 public,
  3. 就需要视具体情形而定. 一般我们希望一个类只提供 "必要的" public 方法, 而不应该是把所有的方法都无脑设为 public.

那么这样写的好出在哪?虽然说安全性很强,但是我要用里面的东西,怎么办?

这时就需要 getter和setter方法 来创建公开的2个接口,用来输出和输入

代码实例

  1. class Person{
  2. private String name;// null
  3. public int age;// 0
  4. public static int count; // 0
  5. public String getName(){
  6. // 读取 Person 类中的 普通成员变量存储的数据,将其返回(输出)
  7. return name;
  8. }
  9. public void setName(String myName){
  10. // 输入 一个 字符串 来修改 Person 类中 name 的 值
  11. this.name = myName;
  12. }
  13. public void eat(){
  14. System.out.println( name+"正在吃放");
  15. }
  16. public void print(){
  17. System.out.println("姓名:"+ name+" 年龄:"+age);
  18. }
  19. @Override
  20. public String toString() {
  21. return "Person{" +
  22. "name='" + name + '\'' +
  23. ", age=" + age +
  24. '}';
  25. }
  26. }
  27. public class ClassAndObject {
  28. public static void main(String[] args) {
  29. Person person = new Person();
  30. person.setName("author");
  31. System.out.println(person.getName());
  32. // 图 32,由图可知 赋值 和 读值 成功了,没问题
  33. }
  34. }

图32

由 图 32 ,得出结论 private 和 (setter,getter)结合使用 是没问题的,成功解决了 类的实现者,被修改成员变量的名称,而无法进行访问的问题。
然而还是存在某种问题,如果你修改 setter 和 getter的方法名,一样会出现问题
但是一般人不会去改,人家要改你也防不住
所以做人要厚道 不要去改。 这是默认的道德规则。

setter 和 getter 方法 和 前面 toString 一样,有快捷的方法,让编译器自动为我们添加

  1. toString方法一样的流程,仔细 观察 26 的选项中 Getter and Setter 选项
  2. 选择它,之后就图 27 一样,自己去选择成员变量 去生成对应 Getter and Setter 的方法
  3. 这里我就不再说明

当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例的引用.

  1. class Person{
  2. private String name;// null
  3. private int age;// 0
  4. public static int count; // 0
  5. public String getName(){
  6. return name;
  7. }
  8. public void setName(String name){
  9. name = name;
  10. }
  11. public int getAge() {
  12. return age;
  13. }
  14. public void setAge(int age) {
  15. this.age = age;
  16. }
  17. public void eat(){
  18. System.out.println( name+"正在吃放");
  19. }
  20. public void print(){
  21. System.out.println("姓名:"+ name+" 年龄:"+age);
  22. }
  23. @Override
  24. public String toString() {
  25. return "Person{" +
  26. "name='" + name + '\'' +
  27. ", age=" + age +
  28. '}';
  29. }
  30. }
  31. public class ClassAndObject {
  32. public static void main(String[] args) {
  33. Person person = new Person();
  34. person.setName("author");
  35. System.out.println(person.getName());
  36. person.setAge(10);
  37. System.out.println(person.getAge());
  38. }
  39. }// 图 33

图33

总结

  1. getName 即为 getter 方法, 表示获取这个成员的值.
  2. setName 即为 setter 方法, 表示设置这个成员的值
  3. set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例的引用.
  4. 不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.

构造方法

  1. 构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作

语法规则

  1. 1.方法名称必须与类名称相同
  2. 2.构造方法没有返回值
  3. 3.每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)

那么构造方法是干嘛的?在了解构造方法是干嘛的之前,需要了解一个对象的产生(也就是说对象的实例化/new的执行过程)

new 执行过程

  1. 1.为对象分配内存空间
  2. 2.调用合适的构造方法

上面这两步完成之后,我们的对象才真正产生了。(意味着调用完构造方法之后,我们的对象才真正产生了
注意 合适 这两个字,意味着构造方法不止一个。就好比鞋子很多,但要挑合适的

现在我们来写一个构造方法,代码如下

  1. class Person{
  2. private String name;
  3. private int age;
  4. public Person(){// 构造方法
  5. System.out.println("Person()::不带参数的构造方法");
  6. }
  7. }
  8. public class ClassAndObject {
  9. public static void main(String[] args) {
  10. // 前面我们也说了,构造方法 是在实例化对象(new对象)的过程中,会调用合适的构造方法
  11. // 所以 我们想要调用 构造方法时,只需要new对象就行了。
  12. Person person = new Person();// 图34
  13. }
  14. }

图34

很多人可能会问,你有输出,怎么说是不带参数的构造方法?这只是让你理解 在new对象的过程中,会调用构造方法。

再来看看这个程序

  1. class Person{
  2. private String name;
  3. private int age;
  4. // public Person(){// 构造方法
  5. // System.out.println("Person()::不带参数的构造方法");
  6. // }
  7. }
  8. public class ClassAndObject {
  9. public static void main(String[] args) {
  10. Person person = new Person();// 图35
  11. // 由图得知,当我们将自己写构造方法屏蔽时,照理说 是不能 new 对象的
  12. //因为 new 需要调用构造方法,才能实现的
  13. // 而现在代码执行完成,那么就意味着编译器在编译 执行new对象语句时,发现我们没有写构造方法
  14. // 会自动帮我们生成一个无参数参数的构造方法,如下所示,所以才没有任何的输出结果
  15. 例如
  16. public Person(){
  17. }
  18. }
  19. }

图35

得出结论:

  1. 如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数
  2. 也就是说一个类至少有一个构造方法,就算你没有写。

另外注意一点 如果我们写的 类中构造方法被private所修饰,那么其他类中 是不可使用 该类 去new 对象

  1. 当然 构造方法 所处类的内部是可以被调用的,因为 private 就是 所修饰部分 私有化,只有内部才能使用。外者不能使用
  2. 既然 构造方法 可以私有化,肯定由它的作用: 实现 单例模式 大前提,现在不讲,还不会

代码如下

  1. class Person{
  2. private String name;
  3. private Person(){
  4. System.out.println("不带参数的构造方法");
  5. }
  6. Person p = new Person();
  7. }
  8. public class ClassAndObject {
  9. public static void main(String[] args) {
  10. Person person = new Person();
  11. }// 图 36
  12. }
图36

现在我们来写一写 多个构造方法,加深对 “合适” 的理解

  1. class Person{
  2. private String name;
  3. public Person(){
  4. System.out.println("不带参数的构造方法");
  5. }
  6. public Person(String name){
  7. this.name = name;// 给Person类中 成员变量传参,类似 Setter 方法
  8. System.out.println("Person(String)::带一个String类型参数的构造方法");
  9. }
  10. 注意 this 代表当前的对象 这种说法是错误的
  11. 因为 如果你要调用当前对象的前提是产生一个对象(调用完合适的构造方法才能实例化对象)
  12. 而我们现在这个程序,构造方法里就用this了,说明此时的 this 就不能代表当前的对象了
  13. 只能说完成 new的执行过程的第一步: 为对象分配内存,有了内存就有了地址,this此时代表当前对象的引用
  14. }
  15. public class ClassAndObject {
  16. public static void main(String[] args) {
  17. // 调用不带参数构造方法
  18. Person person = new Person();
  19. System.out.println("=================");
  20. // 调用 带一个 参数的构造方法
  21. Person person1 = new Person("author");
  22. }// 图 37
  23. }

图37

你们有没有发现 构造方法支持重载. 规则和普通方法的重载一致
构造方法重载规则:类名相同,参数的类型和个数,两者中,至少有一个不同项。
调用构造方法时,编译器会自动筛选调用合适的构造方法

注意: 若类中定义了构造方法,则默认的无参构造将不再生成.

  1. class Person{
  2. private String name;
  3. // public Person(){
  4. // System.out.println("不带参数的构造方法");
  5. // }
  6. public Person(String name){
  7. this.name = name;// 给Person类中 成员变量传参,类似 Setter 方法
  8. System.out.println("Person(String)::带一个String类型参数的构造方法");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. // 调用不带参数构造方法
  14. Person person = new Person();
  15. System.out.println("=================");
  16. // 调用 带一个 参数的构造方法
  17. Person person1 = new Person("author");
  18. }// 图 38
  19. }

图38

总得来说 构造方法的意义是:用来构造对象的。
之前我们写的程序都没有写构造方法,但是现在我刚才讲了编译器会自动帮我们生成一个无参数的构造方法
你只需要要明白,没有构造方法 是无法 实例化一个对象的。

在我们这篇博客代码中 this 频繁出现。现在我们就来讲解一下this的用法

  1. 1.this.data 调用当前的对象的 属性/字段/成员变量
  2. 2.this.func() 调用当前的对象的方法
  3. 3.this() 调用当前对象的其他构造方法

第一种我就不说了,前面已经见过它的应用了,我来讲第二种

  1. class Person{
  2. private String name;
  3. public void eat(){
  4. System.out.println(name + "正在吃饭");
  5. }
  6. public void print(){
  7. this.eat();// 调用当前对象的eat方法,这里重复强调一个问题 静态成员方法中不能使用this
  8. // 写错也没关系,编译器会提示你写错了(红色警告波浪线)
  9. System.out.println("姓名:" + name);
  10. }
  11. }

&ensp;

第三种 this() 调用当前对象的构造方法

  1. class Person{
  2. private String name;
  3. public Person(){
  4. this();
  5. // 注意不能这么去写,因为 this() 表示调用当前对象的构造方法
  6. // 而现在Person 就是当前的构造方法,两者嵌套使用,会造成死循环
  7. // Person(){} 调用 this(), this() 调用 Person(){}
  8. // 而且编译器也出现 红色波浪线警告 图39
  9. System.out.println("不带参数的构造方法");
  10. }
  11. }

图39

那么 this() 在上述情况下 该怎么使用?

  1. 调用 其他 有参数的构造方法就可以了

代码如下:

  1. class Person{
  2. private String name;
  3. public Person(){
  4. this("author");
  5. System.out.println("不带参数的构造方法");
  6. }
  7. public Person(String name){
  8. System.out.println("带有一个参数的构造方法");
  9. }
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. // 现在我们来通过new对象,来调用无参数的构造方法
  14. // 在进入无参数的方法后,执行第一条语句,就是this("author")
  15. // 意思是 调用带有一个参数的构造方法
  16. // 调用完成之后,再执行 无参数的构造方法 的 输出语句
  17. // 也就是说 先打印 "带有一个参数的构造方法" , 后打印 "不带参数的构造方法"
  18. Person person = new Person();// 图40
  19. }
  20. }
图40

但是 注意一点 this()在构造方法中 去调用 其他构造方法时,只能放在该构造方法的第一句的位置,this()才能使用( 图41 )。而且 this() 这种方法,只能用于构造方法中。

图41

代码块

  1. 本地代码块
  2. 实例代码块
  3. 静态代码块
  4. 同步代码块
  5. 我们主要讲解 实例 静态代码块。

不过本地代码块 其实我们遇到过,在方法中,书写两个花括号,就是我们所说的 本地代码块

案例

  1. public class ClassAndObject {
  2. public static void main(String[] args) {
  3. {
  4. }
  5. }
  6. }

什么是实例代码块? 什么是静态代码块?

  1. 在类中,方法外,书写 两个花括号, 就是我说的 实例代码块
  2. 而静态代码块就是 在实例代码块的基础上,加上static 修饰
  3. 也就是说 static 去修饰这两个花括号

代码如下:

  1. class Person{
  2. private String name;
  3. {
  4. System.out.println("实例代码块");
  5. }
  6. static{
  7. System.out.println("静态代码块");
  8. }
  9. public Person(){
  10. System.out.println("不带参数的构造方法");
  11. }
  12. }
  13. 此时 我们就应该去想一下,这些代码块怎么被调用的?
  14. public class ClassAndObject {
  15. public static void main(String[] args) {
  16. 当我们main函数里什么都没有写的时候,程序能运行,但没有结果显示,
  17. 42
  18. 现在 我们来 实体化一个对象,而且我们知道 它一定会调用 不带参数的构造方法
  19. 但是当我们运行程序
  20. 43
  21. 而且请注意 静态 实例 代码块的位置(实例在前,静态在后)
  22. 但是最后输出结果 确实 静态代码块,先执行。
  23. 也就意味着 静态 实例 的运行顺序,不受 代码块的位置 影响。(默认静态代码块先执行,实例代码块后执行)
  24. 至于构造方法,我们得知它并不是第一个被执行的。
  25. Person person = new Person();
  26. System.out.println("==================");
  27. // 此时,我们再来 new 一个对象
  28. Person person1 = new Person();// 图44
  29. // 有输出结果得知,虽然我们new了两次对象,但静态代码块只会被执行一次
  30. // 也就说 静态代码块的执行 跟 我们 new几次对象没关系
  31. }
  32. }
图42

图43

图44

再来看一个点

如果我们不去new 对象,而是 去访问 静态成员变量会如何?

代码如下
  1. class Person{
  2. private String name;
  3. public static int count;
  4. {
  5. System.out.println("实例代码块");
  6. }
  7. static{
  8. System.out.println("静态代码块");
  9. }
  10. public Person(){
  11. System.out.println("不带参数的构造方法");
  12. }
  13. }
  14. public class ClassAndObject {
  15. public static void main(String[] args) {
  16. System.out.println(Person.count);// 图45
  17. // 由图得知,静态代码是不需要借助 new 对象,就可以被执行。(且只被执行一次)
  18. // 也就是说 静态代码块 在加载类的时候(你要访问类体,肯定是要加载类的),被执行。
  19. // 类的加载 属于JVM当中知识。留着将JVM的时候再讲。
  20. }
  21. }
图 45

那么 实例 和 静态代码块的作用是什么?

实例 和 静态 代码块 用来实例化(初始化) 数据成员

代码如下:
  1. class Person{
  2. private String name;
  3. public static int count=5;
  4. {
  5. this.name = "author";
  6. System.out.println("实例代码块");
  7. }
  8. static{// 注意 static 可不能 使用this
  9. // 因为 被 static 修饰的函数,都是 属于类的,不属于对象
  10. count =10;// 对静态成员变量 初始化没问题
  11. System.out.println("静态代码块");
  12. }
  13. }
  14. 那么当中就存在这一个疑问,静态代码中的count==10,和 成员变量的count == 5,那个先初始化?
  15. 最终的结果是 默认值 5 还是 10
  16. public class ClassAndObject {
  17. public static void main(String[] args) {
  18. System.out.println(Person.count);// 图46
  19. }
  20. }
图46

由图46得知, 静态代码块 是后初始化 count 的值。
难道 这就说明, 静态代码块初始化数据的优先级 低于 静态成员变量初始化 吗?
不一定!

来看看这个程序(此时 静态成员变量初始化 放在 静态代码块下方)

  1. class Person{
  2. private String name;
  3. static{
  4. count =10;
  5. System.out.println("静态代码块");
  6. }
  7. public static int count = 20 ;// 你可以发现静态变量的定义写在 使用者块下面,也能被使用
  8. 切记 一定要对count进行赋值,只有赋值才是初始化,
  9. // 如果 你直接 像 图 48 这样用,那么 count 处于一种未初始化的状态,此时再被 静态方法块所初始化。其值就是10了
  10. }
  11. public class ClassAndObject {
  12. public static void main(String[] args) {
  13. System.out.println(Person.count);// 图47
  14. }
  15. }
图47

图48

实例代码块 跟 静态代码块 的 初始化 规则是一模一样的,这里就不再去写。

总结

  1. 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
  2. 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。

匿名对象

  1. 1. 表示没有名字的对象.
  2. 2. 没有引用的对象称为匿名对象.
  3. 3. 匿名对象只能在创建对象时使用.
  4. 4. 如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象

代码实例

  1. class Person{
  2. public String name;
  3. public void eat(){
  4. System.out.println(name + "正在吃饭");
  5. }
  6. }
  7. public class ClassAndObject {
  8. public static void main(String[] args) {
  9. System.out.println(new Person().name);
  10. new Person().eat();
  11. System.out.println(new Person());
  12. System.out.println("================");
  13. // 正确写法
  14. Person person = new Person();
  15. System.out.println(person.name);
  16. person.eat();
  17. System.out.println(person);
  18. }
  19. }//图 49

图49

本文结束

相关文章

最新文章

更多

目录