[JavaSE] 类和对象(二)
目录
🔴 封装
🔵 private实现封装
🔵 getter和setter方法
🔴 构造方法
🔵 基本语法
🔵 this关键字
🔴 认识代码块
🔵 什么是代码块
🔵 普通代码块
🔵 构造代码块
🔵 静态代码块
🔴 补充说明
🔵 toString方法
🔵 匿名对象
疫情当前,大家要做好防护哦。
带好口罩了嘛?
那么大家跟着Nick来学习今天的知识!
🔴 封装
什么叫封装?
🔸 《代码大全》开篇就在讨论一个问题: 软件开发的本质就是对程序复杂程度的管理. 如果一个软件代码复杂程 度太高, 那么就无法继续维护. 如何管理复杂程度? 封装就是最基本的方法.
🔸 在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
🔸 封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.
🔸 这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度
🔵 private实现封装
🔹 private/ public 这两个关键字表示 "访问权限控制"
- 被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用.
- 被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用.
🔹 换句话说, 类的使用者根本不需要知道, 也不需要关注一个类都有哪些 private 的成员. 从而让类调用者以更低的成本来使用类
📝 直接使用 public
class Person { public String name = "Nick"; public int age = 21;}class Test { public static void main(String[] args) { Person person = new Person(); System.out.println("我叫" + person.name + ", 今年" + person.age + "岁"); }}// 执行结果我叫Nick, 今年21岁
- 这样的代码导致类的使用者(main方法的代码)必须要了解 Person 类内部的实现, 才能够使用这个类. 学习成本较高
- 一旦类的实现者修改了代码(例如把 name 改成 myName), 那么类的使用者就需要大规模的修改自己的代码, 维护成本较高.
📝 范例:
使用 private 封装属性, 并提供 public 方法供类的调用者使用
class Person{ private String name="Nick"; private int old =21; public void show(){ System.out.println("大家好!我是"+name+"今年"+old+"啦!"); }}public class Test { public static void main(String[] args) { Person person = new Person(); person.show(); }}//执行结果大家好!我是Nick今年21啦!
- 此时字段已经使用 private 来修饰. 类的调用者(main方法中)不能直接使用. 而需要借助 show 方法. 此时类的使 用者就不必了解 Person 类的实现细节.
- 同时如果类的实现者修改了字段的名字, 类的调用者不需要做出任何修改(类的调用者根本访问不到 name, age 这样的字段).
💬 那么问题来了~~ 类的实现者万一修改了 public 方法 show 的名字, 岂不是类的调用者仍然需要大量修改代码嘛?
🔘 这件事情确实如此, 但是一般很少会发生. 一般类的设计都要求类提供的 public 方法能比较稳定, 不应该频繁发生大的改变. 尤其是对于一些基础库中的类, 更是如此. 每次接口的变动都要仔细考虑兼容性问题
🔻 注意事项
- private 不光能修饰字段, 也能修饰方法 通常情况下我们会把字段设为 private 属性, 但是方法是否需要设为 public, 就需要视具体情形而定.
- 一般我们希望一个类只提供 "必要的" public 方法, 而不应该是把所有的方法都无脑设为 public.
🔵 getter和setter方法
🔹 当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了.
📝 代码示例
class Person{ private String name="Nick"; private int old =21; public void show(){ System.out.println("大家好!我是"+name+"今年"+old+"啦!"); }}public class Test { public static void main(String[] args) { Person person = new Person(); person.name="小米"; person.show(); }}//编译报错:Error:(14, 15) java: name可以在封装.Person中访问private
🔹 此时如果需要获取或者修改这个 private 属性, 就需要使用 getter / setter 方法.
class Person{ //这里未赋值就为默认值 private String name; private int old; public String getName() { return name; } public void setName(String name) { //name = name;//不能这样写 this.name = name;//this引用,表示调用该方法的对象 } public int getOld() { return old; } public void setOld(int old) { this.old = old; } public void show(){ System.out.println("大家好!我是"+name+"今年"+old+"啦!"); }}public class Test { public static void main(String[] args1) { Person person = new Person(); person.setName("小米"); person.setOld(18); person.getName(); person.getOld(); person.show(); }}
🔻 注意事项
- getName 即为 getter 方法, 表示获取这个成员的值.
- setName 即为 setter 方法, 表示设置这个成员的值
- 当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例的引用.
- 不是所有的字段都一定要提供 setter / getter 方法, 而是要根据实际情况决定提供哪种方法.
- 在 IDEA 中可以使用 alt + insert (或者 alt + F12,有的电脑要加上fn) 快速生成 setter / getter 方法. 在 VSCode 中可以使用鼠标右键 菜单 -> 源代码操作 中自动生成 setter / getter 方法.
🔴 构造方法
🔵 基本语法
🔸 构造方法是一种特殊方法, 使用关键字new实例化新对象时会被自动调用, 用于完成初始化操作
new 执行过程
- 为对象分配内存空间
- 调用对象的构造方法
语法规则
- 方法名称必须与类名称相同
- 构造方法没有返回值类型声明
- 每一个类中一定至少存在一个构造方法(没有明确定义,则系统自动生成一个无参构造)
🔻 注意事项
- 如果类中没有提供任何的构造函数,那么编译器会默认生成一个不带有参数的构造函数
- 若类中定义了构造方法,则默认的无参构造将不再生成.
- 构造方法支持重载. 规则和普通方法的重载一致
📝 代码示例
class Person { private String name;//实例成员变量 private int age; private String sex; //默认构造函数 构造对象 public Person() { this.name = "Nick"; this.age = 20; this.sex = "男"; } //带有3个参数的构造函数 public Person(String name,int age,String sex) { this.name = name; this.age = age; this.sex = sex; } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); }}class Main{ public static void main(String[] args) { //调用不带参数的构造函数 如果程序没有提供会调用不带参数的构造函数 Person p1 = new Person(); p1.show(); //调用带有3个参数的构造函数 Person p2 = new Person("Nick02",21,"男"); p2.show(); }}
🔵 this关键字
🔸 this表示当前对象引用(注意不是当前对象). 可以借助 this 来访问对象的字段和方法
class Person { private String name;//实例成员变量 private int age; private String sex; //默认构造函数 构造对象 public Person() { //this调用构造函数 this("Nick", 12, "man");//必须放在第一行进行显示 } //这两个构造函数之间的关系为重载。 public Person(String name,int age,String sex) { this.name = name; this.age = age; this.sex = sex; } public void show() { System.out.println("name: "+name+" age: "+age+" sex: "+sex); }}class Main { public static void main(String[] args) { Person person = new Person();//调用不带参数的构造函数 person.show(); }}
🔸 我们会发现在构造函数的内部,我们可以使用this关键字,构造函数是用来构造对象的,对象还没有构造好,我们就使用了this,那this还代表当前对象吗?当然不是,this代表的是当前对象的引用。
🔴 认识代码块
字段的初始化方式有:
- 就地初始化
- 使用构造方法初始化
- 使用代码块初始化
🔸 前两种方式前面已经学习过了, 接下来Nick介绍第三种方式, 使用代码块初始化
🔵 什么是代码块
使用 {} 定义的一段代码. 根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造块
- 静态块
- 同步代码块(后续分享多线程部分再谈)
🔵 普通代码块
🔹 普通代码块:定义在方法中的代码块
public class 普通代码块 { public static void main(String[] args) { {//直接使用{}定义,普通方法块 int x = 10 ; System.out.println("x1 = " +x); } int x = 100 ; System.out.println("x2 = " +x); }}//执行结果x1 = 10x2 = 100
🔹 这种用法较少见
🔵 构造代码块
🔹 构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
package 代码块.构造代码块;class Person{ private String name;//实例成员变量 private int age; private String sex; public Person() { System.out.println("I am Person init()!"); } //实例代码块 { this.name = "Nick"; this.age = 21; this.sex = "man"; System.out.println("I am a boy"); } public void show(){ System.out.println("name: "+name+" age: "+age+" sex: "+sex); }}public class Test { public static void main(String[] args) { Person p1 = new Person(); p1.show(); }}
🔻 注意事项
- 实例代码块优先于构造函数执行
🔵 静态代码块
🔹 使用static定义的代码块。一般用于初始化静态成员属性。
class Person{ private String name;//实例成员变量 private int age; private String sex; private static int count = 0;//静态成员变量 由类共享数据 方法区 public Person(){ System.out.println("I am Person init()!"); } //实例代码块 { this.name = "Nick"; this.age = 21; this.sex = "man"; System.out.println("I am instance init()!"); } //静态代码块 static { count = 10;//只能访问静态数据成员 System.out.println("I am static init()!"); }}public class Test { public static void main(String[] args) { Person p1 = new Person(); Person p2 = new Person();//静态代码块是否还会被执行? }}
🔻 注意事项
- 静态代码块不管生成多少个对象,其只会执行一次,且是最先执行的。
- 静态代码块执行完毕后, 实例代码块(构造块)执行,再然后是构造函数执行。
🔴 补充说明
🔵 toString方法
🔹 我们刚刚注意到,我们在把对象的属性进行打印的时候,都自己实现了show函数,其实,我们大可 不必。接下来我们看一些示例代码:
📝 代码示例
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); }}public class Test { public static void main(String[] args) { Person person = new Person("Nick",21); person.show(); //我们发现这里打印的是一个地址的哈希值 原因:调用的是Object的toString方法 System.out.println(person); }}// 执行结果name:Nick age:21Person@1c168e5
🔹 可以使用 toString 这样的方法来将对象自动转成字符串
📝 代码示例
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); } //重写Object的toString方法 @Override public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; }}public class Test { public static void main(String[] args) { Person person = new Person("Nick",21); person.show(); //我们发现这里打印的是一个地址的哈希值 原因:调用的是Object的toString方法 System.out.println(person); }}
🔻 注意事项
💠 toString 方法会在 println 的时候被自动调用.
💠 将对象转成字符串这样的操作我们称为 序列化.
💠 toString 是 Object 类提供的方法, 我们自己创建的 Person 类默认继承自 Object 类, 可以重写 toString 方法实 现我们自己版本的转换字符串方法. (关于继承和重写这样的概念, Nick后面会重点介绍).
💠 @Override 在 Java 中称为 "注解", 此处的 @Override 表示下面实现的 toString 方法是重写了父类的方法. 关于 注解后面的笔记后面会详细介绍.
💠 IDEA快速生成Object的toString方法快捷键:alt+f12(insert),有的电脑还要+Fn键。
🔵 匿名对象
匿名只是表示没有名字的对象.
- 没有引用的对象称为匿名对象.
- 匿名对象只能在创建对象时使用.
- 如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象.
class Person { private String name; private int age; public Person(String name,int age) { this.age = age; this.name = name; } public void show() { System.out.println("name:"+name+" " + "age:"+age); }}public class Test { public static void main(String[] args) { new Person("Nick",21).show();//通过匿名对象调用方法 }}
🔻 内容重点总结
💠 一个类可以产生无数的对象,类就是模板,对象就是具体的实例。
💠 类中定义的属性,大概分为几类:类属性,对象属性。其中被static所修饰的数据属性称为类属性,
💠 static修饰的 方法称为类方法,特点是不依赖于对象,我们只需要通过类名就可以调用其属性或者方法。
💠 静态代码块优先实例代码块执行,实例代码块优先构造函数执行。
💠 this关键字代表的是当前对象的引用。并不是当前对象。