![](https://i-blog.csdnimg.cn/blog_migrate/ab7f29008ab3dd7175370d2869ab0dca.png)
Java | Java 面向对象(一)
一、概述
Java
面向对象学习的三条主线Java
类及类的成员:- 常用:属性、方法、构造器
- 熟悉:代码块、内部类
- 面向对象的三大特性:封装性、继承性、多态性
面向对象的基本特性:抽象性、封装性、继承性、多态性 - 其他关键字:
this
、super
、static
、final
、abstract
、interface
、package
、import
等
二、面向过程与面向对象
2.1 面向过程编程(Procedure Oriented Programming)
概念
- 面向过程编程是一种以过程为中心的编程思想,它首先分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,在使用时依次调用,是一种基础的顺序的思维方式。
- 面向过程编程主要关注 “怎么做”,强调的是功能行为,即完成任务的具体细节,以函数为最小单位。
2.2 面向对象编程(Object Oriented Programming)
2.2.1 概念
- 面向对象编程是按照人们认识客观世界的思维方式,采用基于对象(实体)的概念,以对象为中心,以类和继承为构造机制,来认识、理解、刻画客观世界并设计、构建相应的软件系统。
- 面向对象编程主要关注“谁来做”,强调的是具备了功能的对象,即完成任务的对象,以类/对象为最小单位。
- 面向对象编程方法直接把所有事物都当作独立的对象,处理问题过程中所思考的不再主要是怎样用数据结构来描述问题,而是直接考虑重现问题中各个对象之间的关系。
- 面向对象编程中的类和继承是适应人们一般思维方式的描述范式,方法是允许作用于该类对象上的各种操作。这种对象、类、消息和方法的程序设计范式的基本点在于对象的封装性和类的继承性。通过封装能将对象的定义和对象的实现分开,通过继承能体现类与类之间的关系,以及由此带来的动态绑定和实体的多态性。
2.2.2 面向对象的三大特征
- 封装(
Encapsulation
) - 继承(
Inheritance
) - 多态(
Polymorphism
)
2.2.3 面向对象分析方法分析问题的思路和步骤
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中的类的定义 。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象 。对象是计算机世界中解决问题的最终工具。
三、类和对象
3.1 概述
- 类(
Class
)和对象(Object
)是面向对象的核心概念。- 类是对一类事物的描述 ,是抽象的、概念上的定义。
- 对象是实际存在的该类事物的独立个体,因而也称为实例(
instance
) 。
- “ 万事万物皆对象!”
- 在
Java
语言范畴中,我们都将功能、结构等封装到类中,通过类的实例化,来调用具体的功能结构。例:Scanner
、String
、文件File
、网络资源URL
等。 - 涉及到
Java
语言与前端html
、后端的数据库交互时,前后端的结构在Java
层面交互时,都体现为类和对象。
- 在
3.2 类
- 面向对象程序设计的重点是类的设计,因为个体所具备的功能都取决于对类的设计,类的设计其实就是类的成员的设计。
- 类是一个模板,它描述一类对象的行为和属性。
- 类的属性:对应类中的成员变量。
Field
= 属性 = 成员变量 = 域 = 字段
- 类的行为:对应类中的成员方法。
Method
=(成员)方法 = 函数
- 生活中描述事物无非就是描述事物的属性和行为。如:人有身高、体重等属性,有说话、打球等行为。
- 类的语法格式:
修饰符 class 类名 { 属性声明; 方法声明; }
- 创建
Java
自定义类步骤:- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
- 举例:
public class Student { private int id;// 学号 public void setId(int num) {// 声明方法 setId id = num; } public int getId() {// 声明方法 getId return id; } }
3.3 对象
- 如何使用
Java
类?通过Java
类的实例化,即创建类的对象。- 创建类的对象 = 类的实例化 = 实例化类
- 创建对象语法:类名 对象名 =
new
类名;
- 创建对象的步骤:在
Java
中,使用关键字new
来创建一个新的对象。创建对象需要以下三步:- 声明:声明一个对象,包括对象名称和对象类型。例:声明学生类
Student
的对象stu
,Student stu
。 - 实例化:使用关键字
new
来创建一个对象。 - 初始化:使用
new
创建对象时,会调用构造方法初始化对象(默认初始化类的属性)。
- 声明:声明一个对象,包括对象名称和对象类型。例:声明学生类
- 使用 “对象名
.
对象成员” 的方式访问对象成员(包括属性和方法)。 - 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非
static
的)- 如果我们修改了一个对象的属性
a
,则不影响另外一个对象属性a
的值。
- 如果我们修改了一个对象的属性
- 对象的内存解析:
- 堆(
Heap
):堆内存用于存放由new
创建的对象和数组,几乎所有的对象实例都在这里分配内存。在堆中分配的内存,由java
虚拟机自动垃圾回收器来管理。- 在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址,在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。
- 栈(
Stack
):通常所说的栈(Stack
)是指虚拟机栈。 虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean
、byte
、char
、short
、int
、float
、long
、double
)、对象引用(reference
类型,它不等同于对象本身,是对象在堆内存的首地址)。当在一段代码块中定义一个变量时,java
就在栈中为这个变量分配内存空间,当超过变量的作用域后,java
会自动释放掉为该变量分配的内存空间。- 数组和对象本身在堆中分配,即使程序运行到使用
new
产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放,数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被垃圾回收器释放掉。这个也是java
比较占内存的主要原因。
- 数组和对象本身在堆中分配,即使程序运行到使用
- 方法区(
Method Area
):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 。
- 堆(
- 举例:
public class Person { public static void main(String[] args) { Student stu = new Student();// 创建对象 stu.name = "dong";// 访问属性 stu.setId(88888888);// 访问方法 System.out.println(stu.getId());// 打印 88888888 } }
3.4 类和对象的使用(面向对象思想落地)
- 创建类,设计类的成员
- 创建类的对象
- 通过 “对象
.
属性” 或 “对象.
方法()” 调用对象的结构。
3.4.1 类的变量
(1)语法格式
- 修饰符 数据类型 属性名
=
初始化值;
- 修饰符:
- 常用的权限修饰符有:
private
、缺省
、protected
、public
。 - 其他修饰符:
static
、final
、abstract
。
- 常用的权限修饰符有:
- 数据类型:基本数据类型、引用数据类型。
- 注:引用数据类型的变量只可以存储两种类型值:
null
值 或 地址值(包含变量的类型)。
- 注:引用数据类型的变量只可以存储两种类型值:
- 属性名:标识符。
- 修饰符:
(2)变量的分类
- 属性(成员变量):在方法体外、类体内部声明的变量称为属性(成员变量)。
- 实例变量(不以
static
修饰) - 类变量(以
static
修饰)
- 实例变量(不以
- 局部变量:在方法体内部声明的变量称为局部变量。
- 形参(方法、构造器中定义的变量)。
- 方法局部变量(在方法内定义)。
- 代码块局部变量(在代码块内定义)。
(3)属性(成员变量)VS 局部变量
- 相同点:
- 定义变量的格式:数据类型 变量名
=
变量值;
- 先声明,后使用。
- 变量都有其对应的作用域(生命周期)。
- 定义变量的格式:数据类型 变量名
- 不同点:
- 在类中声明的位置不同:
\quad 属性:直接定义在类的一对大括号{}
内。
\quad 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。 - 关于权限修饰符的不同:
\quad 属性:可以在声明属性时,指明其权限,使用权限修饰符。
\qquad 常用的权限修饰符:private
、缺省
、protected
、public
\quad 局部变量:不可以使用权限修饰符。 - 默认初始化值的情况:
\quad 属性:具有默认初始化值。
\qquad 整数类型(byte
、short
、int
和long
)的值是0
。
\qquad 浮点类型(float
、double
)的值是0.0
。
\qquad 字符类型(char
)的值是'\u0000'
。
\qquad 布尔类型(boolean
)的值是false
。
\qquad 引用类型(类
、接口
和数组
)的值是null
。
\quad 局部变量:没有默认初始化值。意味着,我们在调用局部变量之前,一定要显示赋值。
\qquad 特别的,形参在调用时,我们赋值即可。 - 在内存中加载的位置:
\quad 属性:加载到堆空间或静态域内。
\quad 局部变量:加载到栈空间。
- 在类中声明的位置不同:
3.4.2 类的方法
(1)方法是什么?
Java
方法是语句的集合,它们在一起描述了一个类所具备的功能。Java
方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。Java
方法不能独立存在,所有的方法必须定义在类中。Java
将功能封装为方法的目的:实现代码重用,简化代码。Java
方法必须由其所在类或对象调用才有意义。方法的参数分类如下:形参
:方法声明时的参数。实参
:方法调用时实际传给形参的参数值。
(2)方法的声明格式
- 声明格式:
修饰符 返回值类型 方法名(形参数据类型 形参名1, 形参数据类型 形参名2,...) { 方法体程序代码 return 返回值; }
- 修饰符:
private
、缺省
、protected
、public
。 - 返回值类型:
- 有返回值:在方法声明时指定返回值的类型。与方法体中 “
return 返回值
” 搭配使用。 - 没有返回值:
void
。通常在没有返回值的方法中无需使用return
关键字,如果使用只能return;
表示方法结束。 return
关键字:- 范围:使用在方法体中。
- 作用:1. 结束方法。2. 针对有返回值类型的方法,使用 “
return 返回值
” 返回所需的数据。 - 注意:方法中在
return
语句之后的代码都不会被执行,这一点与break
相同。
- 有返回值:在方法声明时指定返回值的类型。与方法体中 “
- 方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”。
- 形参列表:可以包含
0
个,1
个或多个参数。多个参数时,中间用 “,
” 隔开。 - 返回值:方法在执行完毕后返还给调用它的程序的数据。
- 方法体:方法功能的实现。
- 修饰符:
(3)方法的分类
(4)注意事项
- 方法被调用一次,就会执行一次。
- 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
- 方法中只能调用方法(方法调用自己就是递归调用)或属性,不可以在方法内部定义方法。
3.4.3 对象数组
- 对象数组与
Java
的数组类似,只不过数组元素是对象。 - 创建对象数组的步骤:
- 声明以类为数据类型的数组变量,并用
new
分配内存空间给数组。例:Person[] person = new Person[3];
。 - 用
new
产生新的对象,并分配内存空间给它。例:person[0] = new Person();
。 - 为对象赋值即可。
- 声明以类为数据类型的数组变量,并用
- 定义对象数组的方式:
- 方式
1
:静态方式。Person[] p1 = { new Person(), new Person(), new Person() };
- 方式
2
:动态初始化。Person[] p2; p2 = new Person[3]; // Person[] p2 = new Person[3]; for (int i = 0; i < p2.length; i++) { p2[i] = new Person(); }
- 方式
3
:数组元素指向所定义的对象。Person[] p3; p3 = new Person[3]; p3[0] = new Person(); p3[1] = new Person(); p3[2] = new Person();
- 方式
4
:创建对象数组,并分配对象空间。Person[] p4 = new Person[3]; p3[0] = new Person(); p3[1] = new Person(); p3[2] = new Person();
- 方式
char
类型数组可以直接用数组名打印,其它类型数组用数组名打印是地址值。char
类型的数组就相当于一个字符串。- 输出流
System.out
是PrintStream
对象,PrintStream
有多个重载的println
方法,其中一个就是public void println(char[] x)
。这个方法直接打印数组内容,而不像其他重载方法打印数组首地址。
- 输出流
3.4.4 匿名对象
- 匿名对象就是没有明确的给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次,而且匿名对象只在堆内存中开辟空间,而不存在栈内存的引用。例:
new Student().setId(id);
- 匿名对象在实际开发中基本都是作为其他类实例化对象或方法调用的参数传递的,在后面的
Java
应用部分的很多地方都可以发现其用法。例:PhoneMall mall = new PhoneMall(); mall.show(new Phone());
- 匿名对象实际上就是个堆内存空间,对象不管是匿名的还是非匿名的,都必须在开辟堆空间之后才可以使用。
四、再谈方法
4.1 方法的重载(Overload)
- 概念:重载是指在同一个类里面,允许存在一个以上的同名方法,只要它们的参数列表不同即可。方法的返回类型可以相同也可以不同。
- 特点:
- 方法重载与方法的权限修饰符、返回值类型和方法体无关,只看参数列表,且参数列表必须不同(参数个数、参数类型或参数顺序不同,与参数名无关)。
- 被重载的方法可以改变返回类型。
- 被重载的方法可以改变权限修饰符。
- 调用重载的方法时,根据参数列表的不同来判断调用的方法。
- 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
- 方法的重载最常用的地方就是构造器的重载。
- 方法重载与方法的权限修饰符、返回值类型和方法体无关,只看参数列表,且参数列表必须不同(参数个数、参数类型或参数顺序不同,与参数名无关)。
- 注:在通过对象调用方法时,需要通过方法名和参数列表确定的调用一个指定的方法。
4.2 可变长参数
JavaSE 5.0
中提供了Varargs
(variable number of arguments
) 机制,也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用。因此,可以用一种更简单的方式来传递个数可变的实参。JDK 5.0
之前:采用数组形参来定义方法。例:public static void test(int a ,String[] books);
JDK5.0
及之后:采用可变个数形参来定义方法。例:public static void test(int a ,String…books);
- 可变长参数的声明:在定义方法时,在最后一个形参的数据类型后加上三点
...
,就表示该形参可以接受多个参数值,多个参数值被当成数组传入。例:int ... args
。 - 可变长参数的使用:可以像使用数组一样的方式使用可变长参数。示例如下:
public double max(double x, double y, double ... z) { double max = x > y ? x : y; for (int i = 0; i < z.length; i++) { if (z[i] > max) { max = z[i]; } } return max; }
- 注意事项:
- 可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数。
- 由于可变参数必须是最后一个参数,所以一个函数最多只能有一个可变参数。
- 调用可变参数方法,可以给出
0
到任意
多个参数,编译器会将可变参数转化为一个数组。也可以直接传递一个数组。test();//0个参数 test("a");//1个参数 test("a","b");//多个参数 test(new String[] {"a", "b", "c"});//直接传递数组
- 在调用方法的时候,如果既能与固定参数的方法匹配,也能与可变长参数的方法匹配,则选择固定参数的方法。
- 如果要调用的方法可以与两个可变参数的方法匹配,则出现错误。
- 含有可变参数的方法与同一个类中的同名方法(参数列表不同)可以构成重载。
- 变长参数在编译为字节码后,在方法签名中就是以数组形态出现的。即变长参数与数组形参的两个方法的签名是一致的,不能作为方法的重载。如果同时出现,是不能编译通过的。可变参数可以兼容数组,反之则不成立。
- 例:
public int min(int...args) {...}
和public int min(int[] args) {...}
不是方法重载,编译不过。
- 例:
4.3 值传递机制
- 对于程序设计语言来说,一般方法的参数传递有两种:按值传递和按引用传递。
- 按值传递表示方法接收的是调用者提供的值。方法不可以修改按值传递的参数对应的变量值
- 按引用传递表示方法接收的是调用者提供的变量地址。方法可以修改按引用传递的参数对应的变量值。
- 对于
Java
来说,并不存在引用传递,而是采用按值传递,方法得到的是参数的拷贝,不能修改传递给它的参数变量的内容。- 形参是基本数据类型:将实参基本数据类型变量的 “数据值” 传递给形参。也即是说实参把它的值拷贝了一份传递给形参,对拷贝的修改不会影响实参的值。
- 形参是引用数据类型:将实参引用数据类型变量的 “地址值” 传递给形参。也即是说实参把内存中的地址拷贝了一份传给形参,将拷贝的地址值重新指向不会影响实参的值,但对拷贝所指向的内存的值进行修改会影响实参的值。示例如下:
public class TransferTest { public static void main(String[] args) { TransferTest test = new TransferTest(); test.first();// 调用 first 方法 } public void first() { int i = 5; Value v = new Value(); v.i = 25; second(v, i);// 将 first 方法的变量 i 和对象 v 的内存地址拷贝传递给 second 方法 System.out.println(v.i);// 20 } public void second(Value v, int i) { i = 0; v.i = 20;// 对内存地址拷贝的属性进行修改会影响到实际参数 Value val = new Value(); v = val;// 将内存地址拷贝的指向进行修改,不会影响到实际参数 System.out.println(v.i + " " + i);// 15 0 } }
内存分析:public class Value { int i = 15; }
4.4 递归(recursion)方法
- 在
Java
中,调用自身的方法称为递归方法。并且,此过程称为递归。 - 递归方法包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
- 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
- 递归的两个条件:
- 可以通过递归调用来缩小问题规模,且新问题与原问题有着相同的形式。(自身调用)
- 存在一种简单情境,可以使递归在简单情境下退出。(递归出口)
- 递归三要素:
- 一定有一种可以退出程序的情况。
- 总是在尝试将一个问题化简到更小的规模。
- 父问题与子问题不能有重叠的部分。
- 示例:输入一个数据
n
,计算斐波那契数列 (Fibonacci
) 的第n
个值,并将整个数列打印出来。- 斐波那契数列:一个数等于前两个数之和。
public int fibonacci(int num) { if (num == 1) { return 1; } else if (num == 2) { return 1; } else { return fibonacci(num - 1) + fibonacci(num - 2); } }
- 斐波那契数列:一个数等于前两个数之和。