面向对象概述
面向对象编程(简称POP),其核心思想就是参照现实中的事物,将事物的属性特征、行为特征抽象出来,使用类来表示,当涉及到一个具体的实例时,就将类进行实例化,使用一个对象来表示。
简而言之,计算机是一个不能独立思考的物品。当使用者赋予某种权利的同时,计算机才能获得并使用该种权利。因此程序员想要计算机呈现某种事物时,就需要将此种事物的特征给抽象出来,而这些特征就会构造成一个整体,这个整体程序员使用类来形容。于是,当使用者进行使用时,直接去调用它的特征即可。此时需要考虑的一个问题就是每个人拥有相同的特征,但是特征的内容不尽相同,例如人们都拥有身份证,但是内容却不相同。对于此类问题,既然某种事物拥有相同的特征,那么就使用这种特征来表示这类事物,对于这类事物的个体,将这些特征实例化,每个具体的个体使用一个对象来表示即可。
举例:对与CSDN这个系统来说,其核心事物就是用户,因此可以将其抽象成类:用户类。对于用户类来说,其属性特征就是用户名、用户密码以及文章数等其他,其行为特征就是看文章、写文章以及修改文章等其他。对于我们使用者来说,都是用户,因此我们的属性特征和行为特征正是上述内容,但是其特征的内容对于每个人来说都不相同,因此一个用户使用CSDN时,都会使用上述事物来构建成一个对象,每个用户属于一个对象。所谓的类和对象,就是面向对象的核心概念。
类和对象
类和对象是面向对象的核心概念。对于Java来说,面向对象的程序设计思想贯穿始终,无论是现在学习的JavaSE,还是后续学习的JavaEE,都离不开类和对象。
什么是类?
类是具有相同特征事物的抽象描述,是抽象的、概念上的定义。
什么是对象?
对象是实际存在的该类事物的具体个体,是具体的。因此也被称为实例。
类的成员概述
面向对象程序设计的重点是类的设计;
类的设计,其实就是类的成员的设计。
- 现实世界中的生物体,大到鲸鱼、小到蚂蚁,都是由最基本的细胞构成的。同理,Java代码世界是由诸多个不同功能的类构成的。
- 现实生物世界中的细胞又是由什么构成的呢?细胞核、细胞质 ...
- Java中用类class来描述事物也是如此。类,是一组相关属性和行为的集合,这也是类两个最基本的成员之一。
a. 属性:该类事物的状态信息。对应类中的成员变量。
b. 行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的成员方法。
public class Person {// 属性,也被称为成员变量public String name; // 姓名public int age; // 年龄public char gender; // 性别// 行为,也被称为成员方法public void sayHi() {System.out.println("早上好!");}public void sing() {System.out.println("我非常喜欢唱歌!");}public void dance() {System.out.println("我非常喜欢跳舞!");}}
类的实例化
类表示的是一类事物的特征,而对象就是类实例化的产物。
格式:类型 对象名 = new 类型() ;
面向对象完成具体功能的三步骤
步骤一:创建类,并设计类的内部成员。
步骤二:创建类的对象。
步骤三:通过对象,调用其内部的属性或方法。
内存解析
JVM中使用到的内存区域
虚拟机栈:用于存储局部变量等。局部变量表存放了编译器可知长度的各种基本数据类型和对象引用(reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完,自动释放。
堆:此内存的唯一目的就是存放对象实例,几乎所有的对象实例都在这里进行分配内存。在Java虚拟机中的描述是,所有的对象实例以及数组都要在堆上分配。
方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
创建类的一个对象
创建类的多个对象
通过上述的内存解析,可以看出,对象和数组的内存位置大致相同。两者都是在虚拟机栈中存放数组名/类名的地址,然后地址再对应到堆内存中存放具体的数据。对于这种存放方式,导致的一个问题就是如果不new一个新的空间,那么可能存在几个变量对应的都是一个地址,即同一个对象。这使得若多个变量对应一个地址,那么进行修改时,其他变量的值也会同时被改变。
创建类的多个对象时,每个对象在堆空间中会有一个对象实体。每个对象实体中保存着一份类的属性,也就是成员变量、这里就包含着一个成员变量和局部变量的区别:成员变量会有一个默认初始值,初始值的大小和数组的一致;局部变量则没有默认初始值。
属性/成员变量
成员变量是类的成员之一,描述的是事物的状态信息。例如在用户类中的用户名、密码以及年龄等都属于成员变量。
变量的分类
按照数据类型来分:基本数据类型(8种),引用数据类型(6种)。
按照变量在类中声明的位置不同:成员变量,局部变量(方法内、方法形参、构造器内、构造器形参以及代码块内)。
成员变量 VS 局部变量
概念
成员变量:在方法体外、类体内声明的变量。
局部变量:在方法体内部等位置声明的变量。
public class User {// 成员变量private String username; // 姓名private String password; // 密码private int age; // 年龄private String gender; // 性别public void sleep(int hour) { // 形参,相当于是局部变量System.out.println(username + "的睡眠时间是" + hour + "小时!");}public void eat() {String food = "鱼香肉丝"; // 局部变量System.out.println("我最喜欢吃的菜是" + food + "!");}}
相同点
1. 变量声明的格式相同:数据类型 变量名 = 变量值;
2. 变量都有其有效的作用域,出了作用域就失效了;
3. 变量必须先声明,后赋值,再使用。
不同点
1. 声明的位置不同
成员变量:方法体外,类体内。
局部变量:声明在方法内、方法形参、构造器内、构造器形参、代码块内。
2. 内存位置不同
成员变量:随着对象的创建,存储在堆内存中。
局部变量:存储在栈空间中。
3. 生命周期不同
成员变量:随着对象的创建而创建,随着对象的消亡而消亡。
局部变量:随着方法对应的栈帧入栈,局部变量会在栈中进行分配,随着方法对应的栈帧出栈,局部变量消亡。
4. 作用域不同
成员变量:在整个类的内部都是有效的。
局部变量:仅限于声明此变量所在的方法(或构造器内、或代码块内)。
5. 权限修饰符不同
成员变量:可以使用权限修饰符。
局部变量:不可以使用权限修饰符。
6. 默认值不同
成员变量:存在初始默认值。
局部变量:没有初始默认值。
换句话说,如果没有给成员变量进行显式初始化赋值,则会有默认初始化值;而使用局部变量之前,必须要显式的赋值,否则报错。
方法
成员方法是类的成员之一,描述的是该类事物对应的操作。例如用户类可以修改自己的信息,可以进行吃饭的动作等行为。在Java中,方法是必定包含在某个类中的,即使程序的入口:main方法也是包含在Main类中,因此方法即是成员方法(个人理解)。
方法 / 函数的理解
方法是用来完成某个功能操作。在某些语言中也被称为函数或者过程。
通俗的讲:方法是对某个功能的封装。例如在某个游戏中,攻击是一个重复性极高的操作,如果每次攻击都写一段新的代码。显而易见,代码将会非常冗余。但是如果进行封装,将攻击的操作封装成一个方法,当使用攻击操作时,直接调用方法,此时代码就会变得简练许多。
将功能封装为方法的目的是,可以实现代码调用、减少冗杂、优化代码。
Java中方法不能独立存在,所有方法必须定义在类里。
方法声明的格式
修饰符 返回类型 方法名(形参列表) throws 异常列表 { // 方法头// 方法体}
1. 一个完整的方法 = 方法头 + 方法体
2. 修饰符不仅包括权限修饰符,还包括其他例如static、abstract、native、final、sychronized等。
方法调用的内存解析
public static void main(String[] args) {Person person = new Person();person.name = "王彬泽";person.age = 21;person.gender = '男';person.eat();person.sleep(6);person.interests("编程");
}
由于main方法是程序的入口,因此对于方法调用来说,一定是从main方法开始,最后从main方法结束。学过数据结构的对栈都有一定的了解——先后进出,虚拟机栈也是如此。程序开始时,main方法先进入虚拟机栈中,由于new了一个Person类,因此会在相应堆内存中分配空间。调用eat方法,那么eat方法就会进入虚拟机栈中,当eat方法调用完毕之后,出栈。后续操作仿照进行。sleep方法进栈,并且自身还带着hour局部变量,功能完成之后出栈。然后interests方法进栈出栈,最后main方法出栈,程序执行完毕。
方法重载(overload)
概念
在同一个类中,允许存在一个以上的同名方法,只要他们的参数列表不同即可。参数列表不同,意味着参数类型和参数个数的不同。
特点
只看参数列表,且参数列表必须不同(参数类型和参数个数)。调用时,根据方法参数列表的不同来区别。
如何判断两个方法是否相同?
方法名相同,且形参列表相同。
编译器是如何确定调用某个具体的方法?
先通过方法名确定了一波重载的方法,进而通过不同的形参列表,确定具体的方法。
形参个数可变的方法
JDK5.0
使用场景
在调用方法时,可能会出现形参的类型是确定的,但是参数的个数不确定。
格式
(参数类型 ... 参数名)
说明
1. 该方法在调用时,针对可变的形参赋的实参值可以是0个、1个 ... N个。
2. 该方法重载有其他方法时,先针对确定参数个数的方法进行调用,如果没有再调用形参个数不确定的方法。
3. 该方法不能重载数据类型相同的数组方法。因为从某一方面论证来说该方法的形参本质就是数组。在使用时,使用方法也和数组的使用方法一致。
4. 若某方法存在参数个数可变的形参,那么必须将此形参放在最后一位,并且参数个数可变的形参在一个方法中只能存在一个。
5. 如下图:若存在多个参数,那么多个参数类型一致的重载方法不能存在,编译器不知道调用哪个方法合适。例如方法一、三和四,参数类型都是int,那么调用print时,编译器认为方法一三四都可以调用,但没有一个最合适的方法,因而报错。
使用举例
方法的值传递机制
对于方法内声明的局部变量来说,如果出现赋值操作:
如果是基本数据类型的变量,则将此变量保存的数据值传递出去;
如果是引用数据类型的变量,则将此变量保存的地址值传递出去。
基本概念
形参:在定义方法时,方法名后面括号中声明的变量称为形式参数,简称形参。
实参:在调用方法时,方法名后面括号中使用的变量/值/表达式称为实际参数,简称实参。
方法参数的传递机制
值传递。
规则
实参给形参赋值的过程:
如果形参是基本数据类型的变量,则将实参保存的数据值赋给形参;
如果形参是引用数据类型的变量,则将实参保存的地址值赋给形参。
内存解析
形参是基本数据类型的变量:
如图所示,由于形参是基本数据类型的变量。因此只是将变量值赋给a与b,并非地址,这也导致最终m和n并不会进行交换。
形参是引用数据类型的变量:
public class Data {public int a;public int b;}
public class Test {public static void main(String[] args) {Data data = new Data();data.a = 10;data.b = 20;Test.swap(data);}public static void swap(Data data) {int temp = data.a;data.a = data.b;data.b = temp;}}
如图所示,由于形参是引用数据类型的变量。因此会在堆内存中分配空间,当给swap方法传递形参时,传递的是地址,从而使得操作的是同一块内存空间,最后的结果也使得值成功交换。
对于类和对象的叙述就到这里,下篇文章就正式步入面向对象三大特性的介绍,应该是分别用三篇文章进行介绍,这样即可以有较多的篇幅来对每一个特性进行解释,也可以文章使脉络清晰,读起来更加流程。