目录
思考: 对于上面代码中的getName方法和getAge方法,去掉后不影响结果的输出,那么他们的存在是否多余?
一、面向对象的三大特性——封装、继承和多态
Java 面向对象的三大特性是 封装、继承 和 多态。下面用通俗易懂的语言来解释它们:
1. 封装
封装 就像把复杂的东西藏起来,只暴露必要的部分。就好比你用遥控器开电视,你只需要按下按钮,电视就会开,但你不需要知道遥控器内部是怎么工作的。
通俗解释:
就像一个汉堡包,你只需要知道它是汉堡,可以吃,但你不需要知道里面的肉饼是怎么做的。
在 Java 中,封装是把数据(变量)和操作数据的方法(函数)打包在一起,隐藏内部细节,只提供简单的接口供外界使用。
2. 继承
继承 就像“复制粘贴”,但更高级。它允许一个类(子类)继承另一个类(父类)的特性,就像儿子继承了父亲的财产,同时可以有自己的新东西。
通俗解释:
就像一个家族,父亲是
Animal
,儿子是Dog
。Dog
继承了Animal
的特性(比如会叫、会跑),但Dog
还有自己的特性(比如会摇尾巴)。在 Java 中,子类可以继承父类的属性和方法,减少重复代码。
3. 多态
多态 是“一个接口,多种实现”。就像一个按钮,按下去可能有不同的效果,具体取决于它在什么设备上。比如,手机上的按钮可能打开应用,而电视上的按钮可能调高音量。
通俗解释:
就像一个遥控器,上面有一个“播放”按钮。你按这个按钮时,不同的设备(电视、音响、DVD)会有不同的反应,但它们都实现了“播放”这个功能。
在 Java 中,多态允许一个方法在不同的对象上有不同的表现形式。
总结一下:
封装:隐藏复杂性,只暴露简单的接口。
继承:复制父类的特性,同时可以扩展自己的功能。
多态:一个接口,多种实现,让代码更灵活。
二、类与对象
1、对象的创建与使用
示例代码
class Student {
String name;
void read(){
System.out.println("大家好,我是"+name+"我在看书!");
}
}
public class InnerClassDemoTest {
public static void main(String[] args){
Student stu = new Student();//创建一个Student对象
stu.name="zhangsan";//为stu对象的name属性赋值
stu.read();//调用对象的方法
}
}
输出
2、对象的引用传递
类属于引用数据类型,引用数据类型的内存空间可以同时被多个栈内存引用。
代码示例
class Student {
String name; // 声明 name 属性
int age; // 声明 age 属性
void read() {
System.out.println("大家好,我是" + name + ",年龄" + age);
}
}
class Example02 {
public static void main(String[] args) {
Student stu1 = new Student(); // 创建 stu1 对象并实例化
Student stu2 = null; // 创建 stu2 对象,但不对其进行实例化
stu2 = stu1; // stu1 给 stu2 分配空间使用权
stu1.name = "小明"; // 为 stu1 对象的 name 属性赋值
stu1.age = 20;
stu2.age = 50;
stu1.read(); // 调用对象的方法
stu2.read();
}
}
输出
stu1对象给stu2对象分配了空间使用权,二者指向同一个堆内存,所以stu2修改属性的值也会影响到stu1。
所谓的引用传递,就是将一个堆内存的空间使用权分配给多个栈内存使用,每个栈内存空间都可以修改堆内存空间中的内容。
注意:一个栈内存空间只能指向一个堆内存空间。如果想要再指向其他堆内存空间,就必须先断开已有的指向,才能分配新的指向。
3、访问控制权限
public class Test {
public int aa; // aa 可以被所有的类访问
protected boolean bb; // bb 可以被所有子类以及本包的类访问
void cc() { // 默认访问权限,能在本包内访问
System.out.println("包访问权限");
}
// private 权限的内部类,即私有的类,只能在本类中访问
private class InnerClass {
}
}
需要注意的是,外部类的访问权限只能是public或default,所以Test类只能使用public修饰或者不写修饰符 。 局部变量是没有访问权限的(即不可以设置访问权限),因为局部成员只在其所在的作用域内起作用,不可能被其它类访问。
局部变量:定义在方法中的变量
小提示:Java程序的文件名
如果一个源文件中定义的所有类都没有使用 public 修饰,那么这个源文件的文件名可以是一切合法的文件名;如果一个源文件中定义了一个使用 public 修饰的类,那么这个源文件的文件名必须与 public 修饰的类名相同。
4、封装性
示例代码
// 定义一个名为 Student 的类,用于表示学生的信息
class Student {
// 定义一个私有字符串变量 name,用于存储学生的名字
private String name;
// 定义一个私有整型变量 age,用于存储学生的年龄
private int age;
// 定义一个公共方法 getName(),用于获取学生的姓名
// 这是一个 getter 方法,返回 name 的值
public String getName() {
return name;
}
// 定义一个公共方法 setName(String name),用于设置学生的姓名
// 这是一个 setter 方法,将传入的参数 name 赋值给类中的 name 变量
public void setName(String name) {
this.name = name; // this 关键字表示当前对象的 name 属性
}
// 定义一个公共方法 getAge(),用于获取学生的年龄
// 这是一个 getter 方法,返回 age 的值
public int getAge() {
return age;
}
// 定义一个公共方法 setAge(int age),用于设置学生的年龄
// 这是一个 setter 方法,但加入了逻辑判断,确保年龄是合法的
public void setAge(int age) {
// 如果传入的 age 小于 0,则输出错误提示
if (age < 0) {
System.out.println("您输入的年龄有误!");
} else {
// 如果年龄合法,则将传入的 age 赋值给类中的 age 变量
this.age = age;
}
}
// 定义一个公共方法 read(),用于输出学生的信息
// 这个方法没有参数,也没有返回值
public void read() {
// 输出学生的姓名和年龄
System.out.println("大家好,我是" + name + ", 年龄" + age);
}
}
// 定义一个公共类 InnerClassDemoTest,作为程序的入口
public class InnerClassDemoTest {
// main 方法是程序的入口点
public static void main(String[] args) {
// 创建一个 Student 类的对象,命名为 stu
Student stu = new Student();
// 调用 setName 方法,将学生的名字设置为 "zhangsan"
stu.setName("zhangsan");
// 调用 setAge 方法,尝试将学生的年龄设置为 -18
// 由于 -18 小于 0,setAge 方法会输出错误提示
stu.setAge(-18);
// 调用 read 方法,输出学生的信息
// 由于年龄设置失败,age 的值仍然是默认值 0
stu.read();
}
}
输出
思考: 对于上面代码中的getName方法和getAge方法,去掉后不影响结果的输出,那么他们的存在是否多余?
回答: 不多余。理由如下:
去掉
getName()
方法确实可以在当前代码中运行,但它的存在是为了遵循面向对象编程的 封装原则,并且为未来的扩展和维护提供便利。即使当前代码中没有用到getName()
,它的存在仍然是有意义的。以下是一些具体的理由:
1. 封装原则
封装是面向对象编程的核心思想之一,它的目的是将类的内部实现细节隐藏起来,只暴露必要的接口给外部。通过
getName()
方法,我们控制了对name
属性的访问,而不是直接暴露name
。即使现在看起来它只是简单地返回值,但这种设计为未来可能的修改提供了灵活性。2. 未来的扩展性
即使现在
getName()
看起来是多余的,但如果未来需要对name
的访问进行额外的逻辑处理(比如验证、格式化等),直接修改getName()
就可以实现,而不需要修改调用它的代码。例如:public String getName() { return name.toUpperCase(); // 返回大写的姓名 }
如果直接访问
name
,这种逻辑就无法实现。3. 代码的可维护性
在大型项目中,代码可能会被多个模块或开发者使用。如果直接访问
name
,那么任何对name
的修改都会影响到所有直接访问它的代码。而通过getName()
,你可以集中管理对name
的访问逻辑,减少维护成本。4. 统一的访问方式
在 Java 中,通常会为每个属性提供 getter 和 setter 方法,这是一种通用的编程规范。即使现在
getName()
没有实际作用,但它的存在让代码更符合规范,便于其他开发者理解和维护。5. 代码的可读性
使用
getName()
方法可以让代码更清晰,尤其是当类中有多个属性时。例如:System.out.println("大家好,我是" + getName() + ", 年龄" + getAge());
这种方式比直接访问
name
和age
更符合面向对象编程的风格。总结
虽然在当前的代码中去掉
getName()
不会影响运行,但它的存在是为了遵循封装原则、提高代码的可维护性、扩展性和可读性。如果你现在去掉getName()
,虽然代码可以运行,但会失去这些潜在的好处。
5、构造方法
5-1、定义构造方法
无参构造方法示例
class Student {
public Student() { // 构造方法
System.out.println("调用了无参构造方法");
}
}
class Example05 {
public static void main(String[] args) {
System.out.println("声明对象...");
Student stu = null; // 声明对象
System.out.println("实例化对象...");
stu = new Student(); // 实例化对象
}
}
输出
在实例化对象时,调用了无参构造方法
有参构造方法
定义有参构造方法,可实现对属性的赋值
// 定义一个名为 Student 的类,用于表示学生的信息
class Student {
// 定义一个私有字符串变量 name,用于存储学生的名字
private String name;
// 定义一个私有整型变量 age,用于存储学生的年龄
private int age;
// 定义一个有参构造方法,用于初始化 Student 对象的属性
public Student(String n, int a) {
// 将传入的参数 n 赋值给类中的 name 属性
name = n;
// 将传入的参数 a 赋值给类中的 age 属性
age = a;
// 输出一条消息,表示有参构造方法被调用
System.out.println("调用了有参构造方法");
}
// 定义一个公共方法 read(),用于输出学生的信息
public void read() {
// 输出学生的姓名和年龄
System.out.println("我是:" + name + ", 年龄:" + age);
}
}
// 定义一个名为 Example05 的类,作为程序的入口
class Example05 {
// main 方法是程序的入口点
public static void main(String[] args) {
// 创建一个 Student 类的对象,命名为 stu
// 调用有参构造方法,传入 "张三" 和 18 作为参数
Student stu = new Student("张三", 18);
// 调用 stu 对象的 read() 方法,输出学生的信息
stu.read();
}
}
输出
5-2、构造方法的重载
示例代码
class Student {
private String name; // 声明私有属性 name
private int age; // 声明私有属性 age
// 无参构造方法
public Student() { }
// 一个参数的构造方法
public Student(String n) {
name = n;
System.out.println("调用了一个参数的构造方法");
}
// 两个参数的构造方法
public Student(String n, int a) {
name = n;
age = a;
System.out.println("调用了两个参数的构造方法");
}
// 读取学生信息的方法
public void read() {
System.out.println("我是:" + name + ",年龄:" + age);
}
}
class Example07 {
public static void main(String[] args) {
Student stu = new Student("张三"); // 实例化对象,调用一个参数的构造方法
Student stu2 = new Student("张三", 18); // 实例化对象,调用两个参数的构造方法
stu.read(); // 调用对象的 read 方法
stu2.read(); // 调用对象的 read 方法
}
}
输出
注意:一旦为类定义了构造方法,系统就不再提供默认的构造方法了。
在一个类中如果定义了有参构造方法,最好在构建一个无参构造方法。
另外,构造方法通常使用public 进行修饰
6、this关键字 (标识成员变量)
当成员变量与局部变量发生重名问题时,需要使用this这个关键字来分辨。
6-1、调用本类中的属性
代码示例
class Student {
private String name;
private int age;
// 定义构造方法
public Student(String name, int age) {
name = name;
age = age;
}
public String read() {
return "我是:" + name + ",年龄:" + age;
}
}
public class Example09 {
public static void main(String[] args) {
Student stu = new Student("张三", 18);
System.out.println(stu.read());
}
}
输出
从输出中可以看到,成员变量并没有被赋值成功 ,因为成员变量与局部变量重名,编译器无法识别。
将构造方法中的代码改为 this.name = name;
this.age = age;
输出
当局部变量与成员变量相同时,需要使用this关键字标识成员变量,否则编译器无法确定那个名称是当前对象的成员。
6-2、调用成员方法
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
// 成员方法 1:sayHello()
public void sayHello() {
System.out.println("Hello, my name is " + name);
}
// 成员方法 2:greet()
public void greet() {
// 使用 this 调用 sayHello() 方法
this.sayHello();
System.out.println("Nice to meet you!");
}
public static void main(String[] args) {
Person person = new Person("Alice");
person.greet(); // 输出:Hello, my name is Alice 和 Nice to meet you!
}
}
输出
6-3、调用构造方法
代码示例
public class Person {
private String name;
private int age;
// 无参构造方法
public Person() {
this("Unknown", 0); // 调用带参数的构造方法
System.out.println("无参构造方法被调用__");
}
// 带参数的构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("带参数构造方法被调用");
}
public static void main(String[] args) {
Person p1 = new Person(); // 调用无参构造方法
Person p2 = new Person("Alice", 25); // 调用带参数的构造方法
}
}
输出
7、代码块
代码块,简单来讲就是{}括起来的一段代码
7-1、普通代码块
上述代码中,每一对“{}”括起来的代码都称为一个代码块
7-2、构造块
构造块,是指在类中定义的代码块
class Student {
String name; // 成员属性
{
System.out.println("我是构造块"); // 与构造方法同级
}
// 构造方法
public Student() {
System.out.println("我是 Student 类的构造方法");
}
}
public class Example12 {
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student();
}
}
输出