面向对象编程(基础)
-
面向对象编程(OOP)是一种编程范式,它强调程序设计是围绕对象、类和方法构建的。在面向对象编程中,程序被组织为一组对象,这些对象可以互相传递消息。面向对象编程的核心概念包括封装、继承和多态。
-
相对应的,面向对象编程的对立面可以说是面向过程编程(Procedural Programming)。在面向过程编程中,程序主要由一系列的步骤或过程组成,更加侧重于顺序执行。与面向对象编程不同,面向过程编程没有对象的概念,而是专注于如何分解问题和编写独立的函数来解决它们。
两者对比:
特征 | 面向过程编程 | 面向对象编程 |
---|---|---|
组织方式 | 注重顺序执行,数据和功能分离 | 数据和功能被组织成对象 |
可维护性 | 较差,修改可能需要涉及多个部分 | 更好,通过封装和抽象降低耦合度 |
可重用性 | 较差,函数通常与特定问题耦合 | 更高,对象和类的概念提高了代码的重用性 |
可扩展性 | 较差,添加新功能可能需要对多处进行修改 | 更强,继承和多态特性使得修改和扩展更为容易 |
代码复杂度 | 高,数据和功能交织在一起,难以理解和维护 | 低,通过对象的封装和抽象,代码结构更清晰 |
适用场景 | 小型、直接问题的解决 | 大型、复杂问题的解决 |
面向对象编程比面向过程编程可以适用更加复杂的场景。
1、类与对象
类是一种数据类型,是抽象、泛指的概念,定义了对象的属性和行为,但是并不是一个具体的实体。一个类可以有多个对象。并不占用空间。
对象是一个类的具体的实例,拥有类描述的特征和行为。占据内存空间。
对象在内存中的存储方式:
- 栈: 一般存放的是基本的数据类型(局部变量)
- 堆:存放的是对象(数组、
Person
等) - 方法区:常量池(常量,比如字符串),类的加载信息。
(1)属性/成员变量/字段
属性是类的一个组成部分,一般是基本数据类型,也可是引用类型(对象,数组)。比如我们前面定义猫类的int age 就是属性
属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;
共有四种访问修饰符:public
, proctected
, 默认
, private
属性如果不赋值,有默认值,规则和数组一致。具体说: int 0
,short 0
, byte 0
, long 0
, float 0.0``,double 0.0
,char \u0000
,boolean false
,String null
举例:
class person{
//四个属性:性别,姓名,年龄,存款
String name;
int age;
double stroage;
boolean gender;
}
(2) 创建对象
-
先声明,再创建
//1、先声明再创建 Person xiaoming; xiaoming = new Person();
-
直接创建
//2、直接创建 Person xiaoming = new Person();
(3)访问对象的属性
基本语法
对象名.
属性名;
//访问属性
xiaoming.name;
xiaoming.age;
xiaoming.stroage;
2、成员方法
Java中的成员方法是指属于类或对象的函数,用于执行特定的操作或者返回特定的数值。它们可以访问类的属性和其他方法。
(1)方法调用机制
方法调用机制:执行方法或者函数时,会开辟一个独立的空间。
(2)成员方法的好处
- 可以提高代码的复用性
- 将实现的细节封装起来,可以供其他用户调用
(3)成员方法定义
访问修饰符 返回数据类型 方法名(形参列表){
语句:
return 返回值;
}
示例:
public void f3(String s, int n){
int m = n;
return; //没有返回值
}
}
(4)类定义完善
3、成员方法传参机制
-
基本数据类型:采用的事值传递(值拷贝),形参改变不影响实参。
-
引用数据类型:引用类型传递的是地址**(传递的也是值,但是是地址的值),可以通过形参影响实参。**
public static void main(String args[]){ //引用传参,传递的是地址,拷贝了一份地址的值 Person xiaoming = new Person(); xiaoming.name = "小明"; xiaoming.stroage = 1202.23; xiaoming.f3(xiaoming); //虽然穿进的f3将xiaoming置为空了,但是外面的xiaoming不影响,只是在新的栈空间拷贝了一份地址。 System.out.println("xiaoming"+xiaoming.stroage); } } class Person{ String name; double stroage; public void f3(Person xiaoming){ xiaoming = null; }
4、overload(方法重载)
(1)概念
java方法重载指的是在同一个类中可以有多个方法具有相同的名称,但参数列表不同的情况。参数列表可以包括参数的类型、数量或顺序。
(2)优点
- 代码可读性和易用性:相同方法名结合不同的参数类型或数量可以简化代码并提高易用性。
- 灵活性:允许处理不同类型或数量的输入。
- 避免命名冲突:避免为不同功能编写相似但不同的方法名。
- 代码复用:通过重载方法,可以避免为每个略有不同的情况编写新的方法,提高了代码的复用性。
(3)举例
public class MathHelper {
// 重载的方法1:计算两个整数之和
public int add(int a, int b) {
return a + b;
}
// 重载的方法2:计算三个整数之和
public int add(int a, int b, int c) {
return a + b + c;
}
// 重载的方法3:计算两个小数之和
public double add(double a, double b) {
return a + b;
}
}
(4)注意事项
- 方法名必须相同
- 形参列表,必须不同,(形参类型或者个数或者顺序,至少一样不同,参数名没有要求)
- 返回类型,没有要求
5、可变参数
(1)概念
可变参数允许方法接受不定数量的参数。在 Java
中,使用省略号 ...
来表示可变参数。这些参数被封装为数组,并且只能出现在方法参数列表的最后。
(2) 基本语法
基本的语法:
javaCopy codepublic void methodName(DataType... parameterName) {
// 方法体
}
其中:
methodName
是方法的名称。DataType
是可变参数的数据类型。它可以是任何数据类型,甚至可以是自定义的类。parameterName
是可变参数的名称。习惯上使用复数形式,如numbers
、values
等,但也可以使用其他合适的名称。...
表示可变参数的语法。
(3)举例
public class MyClass {
public void printValues(String... values) {
for (String value : values) {
System.out.println(value);
}
}
public static void main(String[] args) {
//可变参数
MyClass obj = new MyClass();
obj.printValues("Hello", "World");
obj.printValues("Java", "is", "awesome");
obj.printValues(); // 可以不传入任何参数
}
}
(4)注意事项
- 可变参数实参可以为0个或者多个
- 可变参数的实参可以为数组
- 可变参数的本质就是数组
- 可变参数可以和普通类型参数一起放在形参列表,但是必须保证可变参数在最后面
- 一个形参列表当中只可以出现一个可变参数
6、作用域
(1)基本概念
在java中,变量主要有两种,一种是属性(类里面的成员,也可以成为全局变量)变量一种是局部变量。一般的局部变量是指定义在成员方法中的变量,作用域是定义的代码块中,随着代码块的执行而创建,随着代码块的结束而销毁。。全局变量,一般是指属性,作用域为整个类。伴随着对象的创建而创建,对象的销毁而销毁。
(2)异同点
- 声明位置:
- 属性变量(成员变量):定义在类中,但在任何方法、构造函数或块之外。
- 局部变量:定义在方法、构造函数或块内部。
- 作用域:
- 属性变量(成员变量):整个类可见,或者其他类使用(通过对象调用)。
- 局部变量:仅在所属的方法、构造函数或块中可见。
- 默认值:
- 属性变量(成员变量):如果未显式初始化,会有默认值。
- 局部变量:必须在使用之前显式初始化,没有默认值,否则会产生编译错误。
- 生命周期:
- 属性变量(成员变量):对象创建至销毁。
- 局部变量:方法执行期间。
- 修饰符
(public,private,protected,默认)
:- 属性变量(成员变量):可以加修饰符。
- 局部变量:不可以加修饰符。
全局变量和局部变量可以重名,访问的原则是就近原则。
(3) 举例
public class Example {
// 属性变量(成员变量)
private int memberVariable;
public void someMethod() {
// 局部变量
int localVar = 10;
System.out.println(memberVariable); // 成员变量在类的任何方法中可见
System.out.println(localVar); // 局部变量仅在 someMethod() 方法中可见
}
public void anotherMethod() {
// 局部变量
int anotherLocalVar = 20;
// System.out.println(localVar); // 这里会产生编译错误,局部变量 localVar 不可见
System.out.println(anotherLocalVar);
}
}
7、构造器(构造方法)
构造类是类的一种特殊方法,主要作用就是完成对新对象的初始化。
(1)概念
构造器用于在创建对象时进行初始化。构造器的名称必须与类名完全相同,并且没有返回类型,甚至不是 void
类型。它们的主要作用是初始化新创建的对象,并且在使用 new
关键字实例化对象时被自动调用。
(2) 特点
- 与类同名:构造器的名称必须与类名完全相同。
- 没有返回类型:与普通方法不同,构造器没有返回类型。
- 自动调用:在使用
new
关键字创建对象时,构造器会自动被调用。 - 重载: 一个类可以定义多个不同的构造器
- 默认构造器: 如果没有定义构造器,系统会自动生成一个默认的无参的构造器
- 覆盖默认构造器:一旦自己定义了一个新的构造器,默认的构造器就被覆盖了,除非自己显示定义一下。即:
Dog(){}
(3)举例
public class Car {
private String make;
private String model;
private int year;
// 构造器1:接受三个参数
public Car(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
// 构造器2:接受两个参数(默认年份为0)
public Car(String make, String model) {
this.make = make;
this.model = model;
this.year = 0; // 默认年份为0
}
// 构造器3:接受一个参数(默认制造商为Unknown,型号为Unknown,年份为0),无参构造器,显示定义
public Car() {
this.make = "Unknown";
this.model = "Unknown";
this.year = 0;
}
// 其他方法
public void displayDetails() {
System.out.println("Make: " + make + ", Model: " + model + ", Year: " + year);
}
public static void main(String[] args) {
// 使用不同的构造器创建对象
Car car1 = new Car("Toyota", "Corolla", 2022);
Car car2 = new Car("Honda", "Civic");
Car car3 = new Car();
// 显示对象的属性信息
car1.displayDetails();
car2.displayDetails();
car3.displayDetails();
}
}
(4)对象创建的流程
代码如下:
//类person
class Person{
int age = 90;
String name;
//构造器
Person(String n, int a){
name = n; //给属性赋值
age = a;
}
}
Person p = new Person("小明", 20);
流程分析如下:
- 加载Person类信息(Person.class),只会加载一次
- 在堆中分配空间(地址)
- 完成对象的初始化
- 第一步:默认初始化,
age = 0, name = null
; - 第二步: 显式初始化,
age = 90, name = null
- 第三步:构造器初始化,
age = 20, name = 小明
- 第一步:默认初始化,
- 在对象在堆中的地址,返回给
p
(p是对象名,也可以理解成对象的引用)
到目前为止,类的定义如下:
8、this
(1)概念
java
虚拟机会给每个对象分配this
,this
可以在类的方法中引用当前对象的实例。例如,在类的方法内部使用 this
可以访问当前对象的属性和方法。
(2)内存理解
this
可以理解为一个当前对象,存在当前对象的堆内存当中,**这个对象指向的地址就是本身对象所处的地址。**如下图所示:
(3)举例
public class changeKind {
public static void main(String args[]){
Person p = new Person("小明", 20);
//hashcode可以近似表示对象的地址,可以输出this和当前对象的hashcode看看对象是否一样
System.out.println("person的地址:"+p.hashCode());
//输出:person的地址:1163157884
p.info();
//输出:小明当前对象的hashcode是1163157884,与前面的相同
//说明this指向当前对象,哪个对象调用,this就代表哪个对象
}
}
//类person
class Person{
int age = 90;
String name;
//构造器
Person(String n, int a){
name = n; //给属性赋值
age = a;
}
public void info(){
System.out.println(this.name+"当前对象的hashcode是"+this.hashCode());
}
}
(4)注意事项
-
this
关键字可以访问本类的属性,方法和构造器 -
this
可以用来区分当前类的属性和局部变量 -
访问成员方法的语法:
this.方法名(参数列表)
; -
访问构造器语法:this(参数列表);注意只能在构造器中使用 (在构造器中访问另外一个构造器,必须放在构造器的第一条语句)
class T{ public T(){ //必须放在构造器的第一条语句,在一个构造器中访问另外一个构造器 this("jack",100); System.out.println("T()构造器"); } public T(String name, int age){ System.out.println("T(String name, int age)构造器"); } }
-
this
不能在类定义的外部使用,只能在类定义的方法中使用。
9、递归
递归的重要规则:
- 执行一个方法时,就会创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响
- 如果方法中使用的是引用类型的变量(比如:数组,对象),就会共享该引用类型的数据
- 递归必须向退出条件逼近(递归出口),否则就是无限递归,出现栈溢出的情况
- 当一个方法执行完毕,或者遇到
return
,就会返回,遵守谁调用,就会将结果返回给谁。当方法执行完毕或者返回时,该方法也就执行完毕了。
举例: