1.面向过程(了解一下)
面向过程(Procedure-Oriented Programming,简称POP)是一种以过程为中心的编程思想。在面向过程的编程中,程序被设计为一系列的过程或函数的集合,通过按照特定顺序依次执行这些函数来完成任务。面向过程的编程思想强调问题解决过程中对步骤和操作的关注,注重算法和流程控制。
1.1简介:
结构化程序设计,也叫面向过程程序设计,首先采用结构化分析(Structured Analysis,SA)方法对系统进行需求分析,然后使用结构化设计(Structured Design,SD)方法对系统进行概要设计,详细设计,最后采用结构化编程(Structured Programming,SP)方法来实现系统。使用何种SA,SD,SP的方式可以较好的保证软件系统的开发进度和质量。
结构化程序设计,主张以功能(过程)/步骤为中心,将软件系统进行逐步细分。每个功能都负责对一些数据进行处理:每一个功能都会接收一些数据,然后也会输出一些数据。其设计思想可概括为:自顶而下,逐步精分,模块化。
1.2函数
结构化程序设计里最小的程序单元为函数,每个函数都负责一个功能。用于接受一些数据,进行处理,然后输出一些数据。整个软件系统都是由一个个函数组成。其中作为启动程序的函数,我们称之为主函数,主函数里会调用普通函数,普通函数之间依次调用,从而完成整个软件系统的功能。
由于采用自顶而下的设计方法,因此在设计阶段就需要考虑每个模块应该分解成哪些子模块,每个子模块又分解成哪些更小的模块…….依次类推,直到将模块细化成一个一个函数。
每个函数都是具有输入和输出的子系统。函数的输入数据包含函数的形参,全局变量,和常量等等;函数的输出数据包含函数的返回值及传出参数等。
1.3局限性
(1)设计不够直观,与人类的习惯思维不一致,开发者需要将客观世界模型分解成一个个功能,用以完成一定的数据处理
(2)适应性差,可扩展性不强。当客户的需求发生变化时,需要自顶而下的修改各个模块,维护成本高
我们简单了解了结构化程序设计,有了对比,我们就可以更好的理解下面的面向对象程序设计思想了~
2. 面向对象
2.1面向对象的介绍
面向对象(Object-Oriented,简称OO)是一种广泛应用的编程范式和软件设计方法。它通过将现实世界中的事物抽象为对象,以及将对象之间的关系和交互抽象为类、继承、多态等概念,来实现对复杂系统的建模和设计。面向对象编程(Object-Oriented Programming,简称OOP)是面向对象思想在编程实践中的具体实现。
面向对象(Object Oriented)是一种更加优秀的程序设计方法。它由面向对象分析(OOA),面向对象设计(OOD),面向对象编程(OOP)三部分组成。
它的基本思想是使用类,对象,继承,封装,消息等进行程序设计。它从现实世界中客观存在的事物出发来构造软件系统,在系统构造中尽可能的运用人类的自然思维方式,强调直接以现实世界中的事物为中心来思考问题,认识问题。并根据这些事物的本质特点,把他们抽象地表示为系统中的类,作为系统的基本构成单元,使得软件系统的组件可以直接映射现实世界,并保持客观世界事务及其相互关系的本来面貌。
面向对象的核心概念
- 对象:对象是面向对象编程中的基本单元,是数据和行为的封装体。每个对象都包含了自己的状态(即数据)和一系列可以执行的操作(即方法)。
- 类:类是对一组具有相同属性和方法的对象的抽象描述。它定义了对象的结构和行为,是创建对象的模板。
- 封装:封装是面向对象编程的一个重要特性,它指将对象的属性和方法结合在一起,并隐藏对象的内部实现细节,只对外提供有限的访问接口。这有助于保护对象的状态,防止外部代码直接访问和修改,从而提高代码的安全性和稳定性。
- 继承:继承是面向对象编程中实现代码复用的主要手段之一。它允许通过定义一个新的类来继承已有类的属性和方法,子类可以扩展或修改父类的功能,从而实现代码的复用和扩展。
- 多态:多态是面向对象编程的另一个重要特性,它指对象可以根据其实际类型的不同而表现出不同的行为。多态性允许程序员编写通用代码,通过不同类型的对象来实现不同的功能,这提高了代码的灵活性和扩展性。
2.2 面向对象的类
面向对象程序设计中,其最小的程序单元是类,类可以生成系统中的多个对象。而这些对象直接映射成客观世界的各种事物(各个实例)。
类的基本组成
属性(Attributes):也称为成员变量(Member Variables),用于描述对象的状态或特征。属性可以是基本数据类型(如int、float、char等)或引用数据类型(如其他类的实例)。
方法(Methods):定义了对象可以执行的操作或行为。方法包含了一组执行特定任务的代码块,可以接收输入参数并返回结果。方法的实现定义了对象如何响应外部刺激或请求。
构造函数(Constructor):是一种特殊类型的方法,用于在创建对象时初始化对象的属性。构造函数与类同名,没有返回类型,并且可以有参数来设置对象的初始状态。
面向对象的语言中除了使用类来封装一类事物的状态数据,而且还提供了操作这些状态数据的方法,为这类事物的行为特征提供实现,即方法Method,因此可以得到以下基本等式:
状态数据+方法 =类定义
从这个等式我们可以看出,面向对象比面向过程的编程粒度要大:面向对象的单位是类,面向过程的程序单位是函数,因此面向对象比面向过程要简单、易用。
我们就拿组装电脑这件事来说:用主板,cpu,内存条,硬盘这样的大件组装和用一堆二极管,三极管,集成电路等小件组装,哪个方便,一清二楚了吧。
2.3案例比较
1)把大象装进冰箱
面向过程:
1、打开冰箱门 我的行为
2、把大象装进去 我的行为
3、关上冰箱门 我的行为
面向对象:
1、打开冰箱门 冰箱的行为
2、把大象装进去 大象的行为
3、关上冰箱门 冰箱的行为
2)猪八戒吃西瓜
面向过程,因以函数为中心,因此我们可以表达成:
吃(猪八戒,西瓜)。
而面向对象,是以对象为中心,我们就可以表达成:
猪八戒.吃(西瓜)。
面向对象不是完全脱离面向过程的另外一种编程思想,面向对象中的Method逻辑设计其实体现的仍旧是面向过程的思想。可以说面向过程是面向对象的基础思想。
我们可以发现,面向过程和面向对象的本质都是为了解决问题。只不过,面向过程要关注的是解决问题的一系列步骤,而面向对象就显得方便些,只需要关注这个问题中涉及到的对象(类)和方法即可。当然,方法内部还是会实现“面向过程”的功能的,区别在于思维方式不同。
总结
面向过程: 关心的是每一个步骤,有前因,有后果。
面向对象: 秉着一切皆是对象的思想,关心的是这是哪个对象的行为,哪个对象的数据。
3.类与对象
3.1类的设计
在现实生活着,我们是根据具体的对象,发现有共同的特点,然后进行总结归纳成一类事物。
而在计算机编程中,正好反过来。参考现实生活,先将类定义出来,然后才能有该类的具体对象。
格式:
[访问权限修饰符] class 类名{
成员变量
成员方法
}
解析
1.访问权限修饰词:
- 在一个.java源文件里定义的普通类,修饰词只能是public或者默认的
2.类名:
- 大驼峰命名法
3.成员变量:
- 用来描述对象的共同特征,也就是状态数据
- 格式,即变量的声明
4. 成员方法:
- 用来描述对象的共同的行为, 或者用来操作状态数据
- 指的都是不带static修饰的方法
包的概念
1.关键字:package
2.作用:用来管理各种源文件,即.java文件。
如:所有的实体类,一般都放在com.公司名.项目名.模块名.实体
reg: com.yc.oop.mgr.pojo(vo,entity)
所有的工具类,一般都放在com.公司名.项目名.模块名.工具
reg: com.yc.oop.mgr.controller
所有的控制类:
reg: com.yc.oop.mgr.controller
所有的服务类:
reg:com.yc.oop.mgt.service
3.在一个类A中,如果想要使用别的类B,并且这个类B不和A同包,那么就需要
import. 导入操作:即将其引入到该类中,才能使用。
4. 类的名字:
-类名: 类的短名
-类全名: 从最外层的包开始写的名字。
比如Computer这个类
Computer就是短名,简称类名。
全名: com.oop.pojo.Computer
练习:
public class Person {
//成员变量:这类事物的共同特征。即状态数据,一堆不同类型的变量。也可以叫属性或者全局变量
String name;
int age;
char gender;
//成员变量: 这类事物的共同行为。在这里可以直接操作成员变量
public void eat(String food){
System.out.println("吃"+food);
}
public void play(String game){
System.out.println(name+"玩"+game);
}
public void work(){
System.out.println(name+"喜欢工作");
}
//int... a 可变长参数,数据类型...+变量名,注意,该变量是数组 可变长参数只能定义到最后面
public long calculate(int... a){
long sum = 0;
for(int i : a){
sum += i;
}
return sum;
}
}
3.2 对象和引用变量
对象是面向对象编程中的基本单元,它代表了现实世界中的一个实体或概念。对象由数据和操作这些数据的代码(即方法)组成。在内存中,对象被分配一块空间来存储其数据(属性)和代码(方法)。每个对象都是类的实例,类定义了对象的结构和行为。
类是对象的抽象(模版),对象是类的具体(实例)。
对象具有以下特点:
- 状态:对象的状态由其属性(也称为成员变量)表示,这些属性存储了对象的数据。
- 行为:对象的行为由其方法表示,方法是对象可以执行的操作或函数。
- 唯一性:每个对象在内存中都有一个唯一的地址(或称为标识符)。
类定义完成后,可以使用new关键字创建对象。 创建对象的过程通常称之为实例化。
语法:
new 类名(); (new +构造器)
如果想要访问实例化出来的对象,通常都会使用变量来接收一下该对象。即如下格式:
类名 变量名 = new 类名();
我们知道,java中除了8种基本数据类型之外,都是引用类型,包括我们自定义的类;而引用类型声明的变量名,有一个专业称呼,即引用变量,简称引用。
引用变量是存储对象内存地址的变量。在大多数现代编程语言中,当你创建一个对象时,实际上是在内存中为该对象分配了一块空间,并返回该空间的地址(或引用)。引用变量就是用来存储这个地址的。
为什么叫引用变量呢?
因为引用变量里存储的不是对象,而是对象在内存中的地址信息。 也就是引用通过地址信息指向对象。 取代了面向过程中的指针。
3.3成员访问
在面向对象编程(OOP)中,成员访问是指对象访问其类定义的属性(也称为成员变量)和方法的过程。成员访问权限是编程语言中一个重要的概念,它决定了哪些代码(例如,类的其他成员、子类或者外部代码)可以访问类的成员(属性和方法)。
基本成员访问:
成员访问,指的就是如何使用类里的成员变量(属性),方法。
引用变量.成员变量
引用变量.成员方法
案例代码:
package com.oop.pojo;
/*
成员的访问:
-通过引用变量和点来访问。如 p1.
*/
public class PersonTest {
public static void main(String[] args) {
//创建一个Person类型的一个对象
Person p1 = new Person();
//注意:成员变量都是有默认值的,在没赋值之前。
// String name = p1.name;
// System.out.println(name);
System.out.println(p1.name);// 引用类型的默认值 null
System.out.println(p1.age);// byte short int long 的默认值 0
System.out.println(p1.gender);// char的默认值 \u0000
p1.name = "jack";
p1.age = 18;
p1.gender='男';
System.out.println(p1.name);
System.out.println(p1.age);
System.out.println(p1.gender);
//上述是成员变量的访问,也可以使用该方式访问成员方法。
p1.eat("蛋糕");
p1.play("xxl");
p1.work();
long sum = p1.calculate(3,4,5,6,7,8,9);
System.out.println(sum);
}
}
静态和非静态
1. static修饰的属性,叫做静态属性;
2. static修饰的方法,叫做静态方法
3. 没有static修饰的属性,叫非静态属性,也叫成员变量
4. 没有static修饰的方法,叫非静态方法,也叫成员方法
static修饰的属性和方法,都是属于类的,需要使用类名调用。非static修饰的属性和方法,是属于对象的,通常使用引用变量来调用。
注意:
静态方法中,只能直接访问本类中的静态成员。不能访问非静态成员
非静态方法中,可以访问本类中的非静态成员和静态成员
3.4 this关键字
在面向对象编程中,this关键字用于指代当前对象的实例。它通常用于访问对象的属性(成员变量)和方法。this关键字不是由它在哪里定义来决定的,而是由谁来执行(或调用)它决定的。在一个类的非静态方法中(成员方法)中,使用this代表当前对象。
因为实例方法需要使用对象调用,哪个对象调用这个方法, this就是谁。(多用于形参跟实参相同时,用this.当前变量=形参的方式)
在方法里定义的变量都是局部变量。
案例代码:
public class Person {
String name;
int age;
char gender;
int height;
int weight;
void setInfo(String name, int age, char gender, int height, int weight) {
this.name = name;
this.age = age;
this.gender = gender;
this.height = height;
this.weight = weight;
}
}
3.5null和nullPointerException
null:
1.引用类型的默认值。表示引用变量里的地址被清空,即没有对象的地址信息
String str = null;
Person p =null;
nullPointerException:
1.空指针异常,运行时异常。
2.变量里没有地址信息,却使用变量来访问对象的成员,就会发生空指针异常。
代码测试:
public class PersonTest2 {
public static void main(String[] args) {
String str = "Hello World";
System.out.println(str.length());
str = null;
// System.out.println(str.length());//nullPointerException
Person person = new Person();
System.out.println(person.name);
person=null;
System.out.println(person.name);//nullPointerException
}
}
后面有注释的两句都会发生空指针异常,由于使用空地址调用,发生异常。
4.构造方法
4.1简介
构造方法,也是一个方法,构造方法也就是构造器。它和普通的方法有点区别:
-
语法不同。
-
构造方法没有返回值。这里的没有返回值并不是说返回值类型是void,而是返回值类型这个位置不存在。
-
构造方法的名字,必须和类名保持一致。
-
构造方法,不能使用static修饰
-
-
执行时机不同。
-
普通的方法,可以随时调用。构造方法是在实例化对象的时候调用执行的。 即new关键字调用的
-
-
构造方法可以重载
-
参数类型列表不同即可
-
如果程序员在定义类时,没有提供任何构造器,系统会自动提供一个无参数构造器
-
一旦程序员自己提供了构造器,系统不再自动提供无参构造器,如果想要,需要程序员自己定义无参构造器
-
4.2定义
public class Person2 { String name; int age; char gender; //无参构造器 public Person2() { System.out.println("--无参构造器--"); } //全参构造器 public Person2(String name, int age, char gender) { this.name = name; this.age = age; this.gender = gender; System.out.println("--全参构造器--"); }}
输出语句可以省略。不能有static修饰,构造方法的名字必须与类名相同。形参跟变量一样名字时,可以使用this.变量等于形参。
4.3 构造器
记住关键知识点。
构造器相关知识点:
1.类体中,程序员没有提供任何构造器时,系统已经提供了一个无参数的构造器。
public 类名(){}
2.程序员一旦提供构造器,系统就不再提供那个无参构造器了。
3.构造器也是一个方法,但是没有返回值类型这个位置,名字与类名一样。
4.构造器不能使用static修饰。
5.构造器可以重载。只需要参数列表不一样
6.构造器的作用就是给成员变量初始化的。
4.4调用构造器
构造器的调用:
1.使用new关键字,
new 构造方法(有参传参)
2.在构造器里可以使用this(有参传参)的方式调用本类中的其他构造器
this(有参传参)只能写在构造器的首行首句。在进行构造方法的调用的时候,不要出现循环调用。(不会出现编译错误,但是会发生死锁)
代码示例:
public class Person3 {
String name;
int age;
char gender;
public Person3() {
this.name = "";
this.age = -1;
this.gender = 'f';
}
public Person3(String name) {
this.name = name;
}
public Person3(String name, int age) {
//调用了本类中的一个参数构造器。
this(name);
this.age = age;
}
public Person3(String name, int age, char gender) {
//调用了两个参数的构造器
this(name,age);
this.gender = gender;
}
public void showInfo(){
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Gender: " + gender);
}
public static void main(String[] args) {
//调用两个参数构造器
Person3 p1 = new Person3("xiaba",19);
p1.showInfo();
Person3 p2 = new Person3("张三",29,'男');
p2.showInfo();
}
}
5. 代码块
5.1构造代码块(动态代码块)
听这名字就知道和构造方法离不开!没错,但是还是和构造方法有着本质区别。
构造方法比较“高冷”,你调用一次,就执行一次。而构造代码块就比较“舔狗”,只要构造器调用一次,构造代码块就执行一次,利用每次创建对象的时候都会提前调用一次构造代码块特性,可以利用其做统计创建对象的次数,也可以为成员变量初始化。
动态代码块(构造代码块)
语法:
{
代码片段
}
位置: 与构造器并列,都是在类体中。
特点:构造器执行一次,动态代码块就执行一次,并先于构造器执行
作用: 一般用于统计一个类型创建了多少个对象,或者提前给成员变量赋值
5.2 静态代码块(静态块、静态初始化块)
Java静态代码块中的代码会在类加载JVM时运行,且只被执行一次,也就是说这些代码不需要实例化类就能够被调用。一般情况下,如果有些代码必须在项目启动的时候就执行的时候,就需要使用静态代码块,所以静态块常用来执行类属性的初始化!
静态代码块(静态块)
语法:
static{
代码片段
}
位置: 与构造器并列,都是在类体中。
特点:只执行一次,在类加载器将该类的信息加载到内存时,执行的。
作用:一般用于加载静态资源到内存中,比如图片,音乐,视频等。
玩游戏时,第一次进游戏卡也是因为它在加载。
Static代码块总结:
Java静态代码块中的代码会在类加载JVM时运行,且只被执行一次
静态块常用来执行类属性的初始化
静态块优先于各种代码块以及构造函数,如果一个类中有多个静态代码块,会按照书写顺序依次执行
静态代码块可以定义在类的任何地方中除了方法体中【这里的方法体是任何方法体】
静态代码块不能访问普通变量
测试代码:
public class BlockDemo {
public static void main(String[] args) {
//调用无参构造器实例化对象
BlockDemo bd1 = new BlockDemo();
System.out.println(bd1.toString());
//调用有参构造器实例化对象
BlockDemo bd2 = new BlockDemo(10,20);
System.out.println(bd2);//bd2默认调用了toString方法。
}
int a;
int b;
static{
System.out.println("--静态块执行了--");
}
{
System.out.println("---动态块执行了---");
}
public BlockDemo(){
System.out.println("--无参构造器--");
}
public BlockDemo(int a, int b) {
this.a = a;
this.b = b;
System.out.println("--有参构造器--");
}
/**
* String toString():用于将对象成员信息变成一串字符并返回。
* 该方法,在将引用变量放在输出语句中时,会默认调用。
* @return
*/
public String toString(){
return "a=" + a + ", b=" + b;
}
}
6.JVM内存管理机制和GC(了解一下,知到部分)
Java语言本身是不能操作内存的,它的一切都是交给JVM来管理和控制的,因此Java内存区域的划分也就是JVM的区域划分,在说JVM的内存划分之前,我们先来看一下Java程序的执行过程,如下图:
上图主要知道,虚拟机栈、堆、方法区就好。
有图可以看出:Java代码被编译器编译成字节码之后,JVM开辟一片内存空间(也叫运行时数据区),通过类加载器加载到运行时数据区来存储程序执行期间需要用到的数据和相关信息,在这个数据区中,它由以下几部分组成:
虚拟机栈,堆,程序计数器,方法区,本地方法栈
6.1虚拟机栈 (先入后出FILO)
虚拟机栈是Java方法执行的内存模型,栈中存放着栈帧,每个栈帧分别对应一个被调用的方法,方法的调用过程对应栈帧在虚拟机中入栈到出栈的过程。
栈是线程私有的,也就是线程之间的栈是隔离的;当程序中某个线程开始执行一个方法时就会相应的创建一个栈帧并且入栈(位于栈顶),在方法结束后,栈帧出栈。
下图表示了一个Java栈的模型以及栈帧的组成:
栈帧:是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。
每个栈帧中包括:
局部变量表:用来存储方法中的局部变量(非静态变量、函数形参)。当变量为基本数据类型时,直接存储值,当变量为引用类型时,存储的是指向具体对象的引用。也就是地址。
操作数栈:Java虚拟机的解释执行引擎被称为"基于栈的执行引擎",其中所指的栈就是指操作数栈。
指向运行时常量池的引用:存储程序执行时可能用到常量的引用。
方法返回地址:存储方法执行完成后的返回地址。
栈的总结:
1.一个线程对应一个栈,main方法就是一个线程。 栈空间是私有的,不能被其他线程访问
2. 栈的数据结构的特点: first input last output : FILO
3. 每个方法在调用时,在栈里都会被分配一个栈帧空间. 栈帧用于存储该方法的所有局部变量。 方法执行完毕,栈帧消失。
6.2 堆
堆是用来存储对象本身和数组的,在JVM中只有一个堆,因此,堆是被所有线程共享的。
从jdk1.7开始,字符串常量池在堆中
6.3 方法区
方法区是一块所有线程共享的内存逻辑区域,在JVM中只有一个方法区,用来存储一些线程可共享的内容,它是线程安全的,多个线程同时访问方法区中同一个内容时,只能有一个线程装载该数据,其它线程只能等待。
方法区可存储的内容有:类的全路径名、类的直接超类的权全限定名、类的访问修饰符、类的类型(类或接口)、类的直接接口全限定名的有序列表、运行时常量池(字段,方法信息,静态变量,类型引用(class))等。
6.4 垃圾回收机制
java的垃圾回收机制,在程序运行时,就已经跟着启动了。 会主动去处理堆里的没有被任何引用指向的对象。这样的对象都会被认为是垃圾。 并不是程序员要调用System.gc()该功能就会立马处理。
如果这样的垃圾没有被处理,会出现堆内存溢出。但是,一般情况下gc还是会尽快处理的。不会出现内存溢出。
如果我们在编程时,对象用了几次后,不再使用了,那么应该尽快将引用变量赋值为null。即将对象视为垃圾,等待gc处理。
System.gc();
6.5值传递和址传递
在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。
值传递: 基本数据类型之间的赋值操作(本质是值的副本传递)。
改变形参的值,不会改变实际参数的值。
地址传递: 引用数据类型之间的变量的赋值操作(本质是地址的副本传递)
通过形参改变对象的数据,那么实际参数指向的对象被改变了。除非形参在改变对象前,指向了新对象。
如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
7.析构方法(finalize())
析构方法,是对象的生命周期中最后的一个方法。执行时机是当这个对象被销毁之前。执行了这个方法 之后,空间就会被销毁,这个对象也就不存在了。
@Override
protected void finalize() throws Throwable {
}
finalize()析构方法负责回收Java对象所占用的内存,该方法一般是在对象被垃圾收集器回收之前调用。通常我们会在finalize()方法中,指定对象销毁时要执行的操作,比如关闭对象打开的文件、IO流、释放内存资源等清理垃圾碎片的工作。
finalize()析构方法具有以下这些特点:
-
垃圾回收器是否会执行finalize方法,以及何时执行该方法,是不确定的;
-
finalize()方法有可能会使对象复活,恢复到可触及的状态;
-
垃圾回收器执行finalize()方法时,如果出现异常,垃圾回收器不会报告异常,程序会继续正常运行。
代码测试:
package com.oop.codeblock;
public class Counter{
private static int count = 0; //计数器变量
public Counter(){
this.count++;
}
public int getCount(){
return this.count;
}
@Override
protected void finalize(){
this.count--;
System.out.println("--对象已经被销毁---");
}
public static void main(String[] args) throws Exception{
Counter c1 = new Counter();
System.out.println(c1.getCount()); //1
Counter c2 = new Counter();
System.out.println(c2.getCount()); //2
//将c1指向的对象,视为垃圾
c1 = null;
System.gc();
Thread.sleep(2000);
System.out.println(c2.getCount()); //2
}
}
结果: