前言:
学习面向对象的三条主线:
- java类以及类的成员:1.(重点)属性、方法、构造器;(熟悉)代码块、内容块。
- 面向对象的特征:封装、继承、多态、(抽象)
- 其他关键字的使用:this、super、package、import、static、final、interface、abstract等等。
面向对象编程的两个核心概念:类、对象。
理解:
- 类:具有相同特征的事物的抽象描述,是“抽象的”、概念上的定义。
- 对象:实际存在的该类事物的“每个个体”,是“具体的”,也因而称为“实例”。
类的内部成员: 1.属性、成员变量、field(字段、域);2.方法、函数、method
1.类的实例化与对象内存的解析
1.1 什么是类?
- 在Java中,类是对象的蓝图或模板。类定义了对象的属性(字段)和行为(方法)。
实例化的步骤(三步流程)
- 创建类,并设计类的内部成员(属性、方法)
- 创建类的对象,如 Dog myDog = new Dog();
- 通过对象,调用其内部声明的属性或方法,完成相关功能。
格式:类类型 对象名 = 通过new创建的对象实体
代码示例:
class Dog {
String name;
int age;
// 构造函数
Dog(String name, int age) {
this.name = name;
this.age = age;
}
void bark() {
System.out.println(name + "在对着邻居汪汪汪!");
}
}
public class Person {
public static void main(String[] args) {
// 实例化对象
Dog myDog = new Dog("斑迪", 3);
// 调用方法
myDog.bark();
}
}
1.2 对象的内存解析
1. 堆内存与栈内存
- 堆内存:用于存放对象实例和数组。所有通过
new
创建的对象都存储在堆内存中。 - 栈内存:用于存储局部变量和方法调用的状态,包括对象的引用。每个线程都有自己的栈。
举例:
创建类的一个对象:
创建类的多个对象:
说明:创建类的多个对象时,每个对象在堆空间中有一个对象实体,每个对象实体中保存着一份类的属性。如果修改某一个对象的某属性值时,不会影响其他对象此属性的值。
补充:
Dog p1 = new Dog();
Dog p3 = p1;
此时p1和p3两个变量指向了堆空间中的同一个对象实体(或者p1,p3保存的地址值相同),如果通过其中的某一个变量修改对象的属性时,会影响另一个对象变量此属性的值。
2.成员变量与局部变量
说明:
- 在方法体外,类体内声明的变量称为成员变量。
- 在方法体内等位置声明的变量称为局部变量。
成员变量与局部变量对比:
特性 | 成员变量 | 局部变量 |
---|---|---|
定义位置 | 类中,但在任何方法外 | 方法、构造函数或代码块内 |
作用域 | 该类的所有方法和构造函数 | 仅在定义它们的方法或代码块内 |
生命周期 | 从对象创建到对象被垃圾回收 | 仅在定义它们的方法或代码块执行期间 |
默认值 | 如果未初始化,JVM会提供默认值(如0 、null 等) | 无默认值,使用前必须显式初始化 |
访问修饰符 | 可以使用public 、private 等修饰符 | 无法使用修饰符 |
存储位置 | 存储在堆内存中 | 存储在栈内存中 |
代码演示:
class Per {
// 成员变量
String name; // 默认值为null
int age; // 默认值为0
// 构造函数
Per(String name, int age) {
this.name = name; // 使用成员变量
this.age = age; // 使用成员变量
}
// 方法
void displayInfo() {
// 局部变量
String info = name + " is " + age + " years old."; // info是局部变量
System.out.println(info);
}
}
public class Person {
public static void main(String[] args) {
// 创建对象
Person person1 = new Person("Alice", 30);
person1.displayInfo();
// 尝试访问局部变量(info)会出错
// System.out.println(info); // 这行代码会报错,info是局部变量
}
}
结果:
3.方法的作用与方法的声明
方法的作用
理解:“方法”是类或对象行为特征的抽象,用来完成某个功能操作。
-
代码重用:方法可以在不同的地方被多次调用,从而避免重复代码,提高代码的复用性。
-
提高可读性:使用方法可以将复杂的逻辑分解为简单的步骤,使代码更加清晰易懂。
-
模块化:方法将代码分割成小的、独立的部分,使得每个方法只负责完成一项特定任务,便于维护和修改。
-
降低复杂性
使用方法的好处
-
便于维护:当方法的实现需要更改时,只需修改该方法的代码,不影响调用该方法的其他部分。
-
测试与调试:可以单独测试每个方法,便于查找和修复问题,提高了调试的效率。
-
提高开发效率:可以将常用的代码逻辑封装在方法中,提高代码编写的效率和一致性。
-
参数化功能:方法可以接收参数,使得同一方法可以用于不同的数据,增加了灵活性。
使用举例:
- Math.random()的random()方法
- Math.sqrt(x)的sqrt(x)方法
- System.out.println(x)的println(x)方法
- new Scanner(System.in).nextlnt()的nextlnt()方法
声明举例:
- 无返回值的方法 public void printMessage()
- 返回整型值的方法 public int add()
- 返回布尔值的方法 public boolean isEven()
- 返回数组的方法 public int[] getNumbers()
方法声明的格式
//[]内的不是必须的
权限修饰符 [其他修饰符] 返回类型 方法名(形参列表) [throws 异常类型]{
方法体
// 返回值; // 如果返回类型不是 void,则需要返回相应类型的值
}
具体的方法声明的细节
1.权限修饰符: private \ 缺省 \ protected \ pubilc
2.返回值类型,当调用完方法后,是否需要返回一个值,分两类:
- 无返回值类型:使用void表示,如System.out.println()的println()方法
- 有具体返回值:需要指明返回的数据的类型,可为基本数据类型或引用数据类型,要求在方法内部使用“return + 返回值类型的变量或常量”。
3.方法名,属于标识符,要求做到“见名知意”。
4.形参列表:局部变量,可声明多个。
- 格式:(形参类型1 形参1, 形参类型2 形参2)
图解:
5.方法调用的内存解析
- 形参,方法在声明时,一对()内声明的一个或多个
简单练习:
package test;
public class exer2 {
public static void main(String[] args) {
exer2 e1 = new exer2();
e1.methon1();
exer2 e2 = new exer2();
// int area = e2.methon2();
// System.out.println("面积为:" + area);
int area1 = e2.methon3(4,5);
System.out.println("面积为:" + area1);
}
public void methon1(){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 8; j++) {
System.out.print("*");
}
System.out.println();
}
System.out.println("\n\n");
}
public int methon2(){
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 8; j++) {
System.out.print("*");
}
System.out.println();
}
return 10*8;
}
public int methon3(int m,int n){
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
System.out.print("*");
}
System.out.println();
}
return m*n;
}
}
分开两个类:
//在一个包中创建了两个类
//Person中的代码
public class Person{
String name;
int age;
char gender;
public void study(){
System.out.println("studying");
}
public int showAge(){
return age;
}
public void addAge(int addAge){
age = age + addAge;
}
}
//PersonTest中的代码
import java.sql.SQLOutput;
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.name = "jerry";
p1.age = 13;
p1.gender = '女';
p1.study();
p1.addAge(2);
int age = p1.showAge();
System.out.println("age:" +age);
Person p2 = new Person();
p2.addAge(10);
System.out.println(p2.showAge());
}
}
4.对象数组
说明:对象数组是一个存储对象引用的数组。它与普通的基本数据类型数组类似,但数组中的每个元素存储的都是对象的引用而不是直接存储对象本身。
4.1 对象数组的声明和使用
对象数组的声明和基本数据类型数组类似,只不过数组的元素类型是类,而不是基本类型(如 int
、char
等)。
举例:
class Student {
String name;
int age;
// 构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 打印学生信息的方法
public void displayInfo() {
System.out.println("姓名: " + name + ", 年龄: " + age);
}
}
public class Person{
public static void main(String[] args) {
// 创建一个可以存储5个Student对象的数组
Student[] students = new Student[5];
// 为每个数组元素分配对象
students[0] = new Student("甲一", 20);
students[1] = new Student("乙二", 21);
students[2] = new Student("丙三", 22);
students[3] = new Student("丁四", 23);
students[4] = new Student("王五", 24);
// 访问数组中的对象并调用其方法
for (Student student : students) {
student.displayInfo();
}
}
}
结果:
4.2 对象数组的内存解析
java的内存主要分为以下几个区域:
- 栈内存:用于存储局部变量和方法调用的相关信息。
- 堆内存:用于存储对象实例,所有对象和数组都存放在堆内存中。
- 方法区:用于存储类信息、常量、静态变量等。
4.1举例代码的对象数组内存模型
-
当声明
Student[] students = new Student[5];
时,在栈内存中创建了一个students
变量,引用了一个数组对象。这个数组对象存储在堆内存中,但此时数组中的每个元素都是null
,因为还没有分配具体的对象。 -
当执行
students[0] = new Student("Alice", 20);
时,会在堆内存中分配一个Student
对象,同时将该对象的引用存储在students[0]
中。同样地,students[1]
到students[4]
也分别引用堆内存中的其他Student
对象。
解释:
-
栈内存:students -> 堆中的数组对象
-
堆内存:数组对象[5]: [students[0]] -> Student(姓名="甲一", 年龄=20) [students[1]] -> Student(姓名="乙二", 年龄=21) [students[2]] -> Student(姓名="丙三", 年龄=22) [students[3]] -> Student(姓名="丁四", 年龄=23) [students[4]] -> Student(姓名="王五", 年龄=24)
图解:
注意:
-
数组元素是对象引用:在对象数组中,每个元素存储的是对象的引用而非对象本身。如果数组元素未被初始化,它们的默认值是
null
。 -
对象的内存分配:每个数组中的对象都需要单独分配内存。声明数组时仅分配了存储对象引用的内存,只有在使用
new
创建对象时,才会在堆内存中为这些对象分配空间。 -
数组的大小固定:一旦创建对象数组,其大小是固定的,不能动态调整。
4.3 小结
- 对象数组是一种数组形式,其中每个元素存储的是对象的引用,而不是对象本身。
- 内存分配涉及栈内存和堆内存的协作:数组本身存储在堆内存中,数组的引用存储在栈内存中,数组元素是指向对象的引用。
5.package与import关键字
5.1 package
关键字
定义:
package
关键字用于定义一个 Java 类或接口所属的包。包是一个命名空间,用于组织类和接口,从而避免名称冲突,并提供访问控制。
用途:
- 组织类:通过将类放入不同的包中,可以对类进行逻辑分组,方便管理和查找。
- 避免名称冲突:在不同的包中可以有同名的类,避免了类名冲突的问题。
- 访问控制:包可以用来控制类的可见性,只有同一包中的类可以访问默认访问级别的类。
声明格式
package packageName;
示例
package com.example.myapp;
public class MyClass {
public void display() {
System.out.println("Hello from MyClass!");
}
}
5.1.1 JDK中主要的包
包名 | 功能描述 | 主要类 |
---|---|---|
java.lang | Java 语言核心类 | Object , String , Math , System |
java.util | 提供集合框架、日期时间处理等功能 | ArrayList , LinkedList , HashMap , Date |
java.io | 输入和输出操作,包括文件操作 | File , InputStream , OutputStream |
java.nio | 非阻塞 I/O 操作和缓冲区支持 | ByteBuffer , FileChannel |
java.net | 网络操作的支持,包括 URL、套接字 | URL , Socket |
java.awt | 创建图形用户界面(GUI)的类 | Frame , Button , Label , TextField |
javax.swing | 提供更高级的 GUI 组件 | JFrame , JButton , JLabel , JTextField |
java.sql | 与数据库的连接和操作 | Connection , Statement , PreparedStatement |
java.security | 提供安全相关功能,包括加密和身份验证 | MessageDigest , KeyStore |
java.time | 新的日期和时间 API | LocalDate , LocalTime , LocalDateTime |
5.2 import
关键字
定义
import
关键字用于引入其他包中的类和接口,使得在当前类中可以直接使用这些类和接口,而无需使用全限定名。
用途
- 简化代码:避免在使用类时写全限定名,使代码更简洁。
- 提高可读性:明确指出当前类依赖于哪些外部类。
声明格式
import packageName.ClassName; // 导入单个类
import packageName.*; // 导入包中的所有类
示例
假设我们有两个包,com.example.myapp
和 com.example.utils
,在 MyClass
中我们想使用 Utils
类:
Utils.java
package com.example.utils;
public class Utils {
public static void printMessage(String message) {
System.out.println(message);
}
}
MyClass.java
package com.example.myapp;
// 导入 Utils 类
import com.example.utils.Utils;
public class MyClass {
public static void main(String[] args) {
Utils.printMessage("Hello from MyClass!"); // 使用 Utils 类中的方法
}
}
5.3 小结
package:
用于定义类和接口的包,帮助组织代码和避免命名冲突。import:
用于引入其他包中的类,使得在当前类中可以直接使用这些类,简化代码和提高可读性。
6.封装性
面对对象的开发原则要遵循“高内聚,低耦合”
- 高内聚、低耦合是软件I程中的概念,也是UNIX 操作系统设计的经典原则。
- 内聚,指一个模块内各个元素彼此结合的紧密程度;耦合指一个软件结构内不同模块之间互连程度的度量。内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。
而“高内聚,低耦合”的体现之一:
- 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
- 低耦合:仅暴露少量的方法给外部使用,尽量仿便外部调用。
什么叫封装性?
- java规定了4种权限修饰符,分别为private、缺省、protected、pubilc
- 使用4种权限修饰符来修饰类及类的内部成员,当成员被调用,体现可见性的大小
4种权限具体使用
- 类:只能使用pubilc,缺省修饰
- 类的内部成员,可以使用4种权限修饰进行修饰。
使用频率
- 较高:pubilc、private
- 较低:缺省、protected
封装性的体现
- 私有化(private)类的属性,提供公共(public)的get和set方法, 对此属性进行获取或修改
- 将类中不需要对外暴露的方法,设置为private.
- 单例模式中构造器private的了,避免在类的外部创建实例。
举例:
Student.java中代码
public class Studnt {
// 学生类
public static class Student {
// 私有属性
private String name;
private int age;
// 公共构造函数
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// 公共方法 - 获取名字
public String getName() {
return name;
}
// 公共方法 - 设置名字
public void setName(String name) {
this.name = name;
}
// 公共方法 - 获取年龄
public int getAge() {
return age;
}
// 公共方法 - 设置年龄
public void setAge(int age) {
if (age > 0) { // 进行简单的验证
this.age = age;
} else {
System.out.println("年龄必须为正数");
}
}
// 打印学生信息
public void displayInfo() {
System.out.println("学生姓名: " + name + ", 年龄: " + age);
}
}
}
Main.java中的代码
// 测试封装性
public class Main {
public static void main(String[] args) {
// 创建学生对象
Studnt.Student student = new Studnt.Student("张三", 20);
// 显示学生信息
student.displayInfo();
// 修改学生信息
student.setName("李四");
student.setAge(22);
// 显示修改后的学生信息
student.displayInfo();
// 尝试设置一个无效的年龄
student.setAge(-5); // 这将触发验证失败的消息
}
}