Java面向对象
面向过程
- 是站在过程的角度思考问题,强调的就是功能行为,功能的执行过程。
- 即先干啥,后干啥。而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现,使用的时候依次调用函数就可以了。
- 按顺序一个一个来去做。
🐤面向过程的设计
- 最小的程序单元是
函数
,每个函数负责完成某一个功能,用以接受输入数据,函数对输入数据进行处理,然后输出结果数据。- 整个软件系统由一个个的函数组成,其中作为程序入口的函数称之为
主函数
。- 主函数依次调用其他函数,普通函数之间可以相互调用,从而实现整个系统功能。
🐸面向过程的缺陷
- 面向过程的设计,是采用置顶而下的设计方式。
- 在设计阶段就需要考虑每一个模块应该分解成哪些子模块,每一个子模块又细分为更小的子模块。
- 如此类推,直到将模块细化为一个个函数。
public class Demo {
static void buy(){
System.out.println("拿钱买菜");
}
static void doFood(){
System.out.println("做饭");
}
static void eat(){
System.out.println("吃饭");
}
static void wash(){
System.out.println("洗碗");
}
public static void main(String[] args) {
buy();
doFood();
eat();
wash();
}
}
面向对象基本理论
🐤什么是对象
- 万物皆对象。
- 对象是具体的物体。
- 拥有属性。
- 拥有行为。
- 把很多零散的东西,封装成为了一个整体。
例如
- 王二小
- 属性
- 姓名
- 年龄
- 身高
- 体重
- ...
- 行为
- 走路
- 吃饭
- 放牛
- ...
🦄面向过程和面向对象
- 都是一种解决问题的思路(思想)
面向过程
- 在解决问题的时候,关注的是解决问题的每一个的过程(步骤)
面向对象
- 在解决问题的时候,关注的是解决问题所需要的对象。
🐥举个例子:做好饭之后洗碗
面向过程
- 你
- 洗菜
- 点火倒油
- 放菜,材料
- 翻炒
- 盛出
- 放水
- 放碗
- 倒洗洁精
- 开始刷
- 擦干水
- 摆放好
面向对象
- 你:对象
- 技能1:做饭
- 洗菜
- 点火倒油
- 放菜,材料
- 翻炒
- 盛出
- 技能2:洗碗
- 放水
- 放碗
- 倒洗洁精
- 开始刷
- 擦干水
- 摆放好
🐬对比
- 面向对象和面向过程都是解决问题的一种方式(思想),面向对象本身是对面向过程的封装。
面向过程编程最重要的是什么
- 按照步骤划分。
- 把一个任务,分解成具体的每一个步骤。
面向对象编程最重要的是什么
- 按照功能对象进行划分。
- 找到对象,确定对象的属性和行为。
如何从面向过程编程的思想,过渡到面向对象编程
- 一样的,列举出一个任务的具体实现步骤。
- 试图分离这些实现步骤中的功能代码块。
- 将这些功能代码块,划分到某一个对象中。
- 根据这个对象以及对应的行为,抽象出对应的类(设计类)
类
- 某一个具体对象特征的抽象。
例如
- 张三这个具体的人。
- 属性
- 年龄:23
- 身高:180
- 体重:250
- ...
- 行为
- 吃:吧唧嘴
- 喝:放白糖
- 赌:没赢过
- ...
抽象出来的类
- 不良青年类。
- 属性
- 年龄
- 身高
- 体重
- ...
- 行为
- 吃
- 喝
- 打架
- ...
类的作用
- 根据抽象的类,生产出具体的对象。
例如
- 类
- 不良青年
- 属性
- 年龄
- 身高
- 体重
- ...
- 行为
- 吃
- 喝
- 打架
- ...
- 对象
- 张三
- 不同的身高,体重等属性。
- 不同的行为。
- 李四
- 不同的身高,体重等属性。
- 不同的行为。
- 王五
- 不同的身高,体重等属性。
- 不同的行为。
🐤列举生活中的类
类:钱
对象:具体的1毛的,5毛的,1块的,5块的,...
类:熊类
对象:熊大,熊二,...
类:汽车
对象:奥拓, 奥迪, 马自达,...
🦄对象和类的关系
对象 → 抽象 → 类 → 实例化 → 对象。
面向对象
- 一种基于面向过程的新的编程思想,顾名思义该思想是站在对象的角度来思考问题。
- 我们把多个功能合理的放到不同的对象里,强调的是具备某些功能的对象。
- 具备某种功能的实体,称为对象。
- 面向对象的最小程序单元是
类
。
面向对象三大特征
- 封装(Encapsulation):封装是指将对象的实现细节隐藏起来,然后通过公共的方法来向外暴露该对象的功能。
- 继承(Inheritance):继承是面向对象实现软件复用的重要手段,当子类继承父类后,子类是一种特殊的父类,能直接或间接获得父类里的成员。
- 多态(Polymorphism):多态是可以直接把子类对象赋给父类变量,但是运行时依然表现出子类的行为特征,这意味着同一类型的对象在运行时可能表现出不同的行为特征。
对象与类的关系
类的定义
🐱🐉类的定义格式
[修饰符] class 类名
{
0~N个成员变量(字段 / Field)
0~N个方法
}
🐤注意点
- 如果类使用了
public
修饰符,必须保证当前文件名称和当前的类名相同。- 类名使用名称表示,类表示某一类事物,首字母大写,如果是多个单词组成使用驼峰表示法来进行命名。
对象的创建与对象的操作
public class StackTest {
public static void sum(int a, int b) {
System.out.println(a + b);
}
public static void main(String[] args) {
int x = 10;
sum(10, 20);
}
}
JVM 的内存分析
JVM 将内存主要划分为了:方法区
、栈
、本地方法栈
、堆
、程序计数器
,这些区域。
🤳虚拟机栈
- 执行引擎每调用一个函数时,就为这个函数创建一个栈帧,并加入虚拟机栈。
- 换个角度理解,每个函数从调用到执行结束,其实是对应一个栈帧的入栈和出栈。
- 声明的变量都是存到栈当中的。
🐱🏍堆
- 被所有线程共享的一块区域,在虚拟机启动时创建,所有的对象实例及数组都在堆上分配。
- 使用
new
关键字,表示在堆中开辟一块新的存储空间。
✨方法区
- 又叫静态区,存放所有的
class字节码
和static变量
;方法区中包含的都是在程序中永远唯一的元素。
🐱👤程序计数器
- 每个线程都有个计数器记录当前执行到那个指令。可以把它看成是当前线程所执行的字节码的行号指示器。
🐱👓本地方法区
- 本地方法栈与虚拟机栈所发挥的作用很相似,他们的区别在于虚拟机栈为执行 Java 代码方法而服务,而本地方法栈是为 Native(本地的 Java 代码提供的)方法服务。
🐱💻GC
- 后面在介绍。
数据类型
基本数据类型:值传递
- 只有一块存储空间, 在栈中,存放的是具体的值。
引入数据类型:内存地址传递
- 引用数据类型有两块存储空间。
- 一个在栈(Stack)中,一个在堆(heap)中。栈中存放的是堆中的地址。
- 栈中的存取速度要快于存储在堆中的对应包装类的实例对象。
匿名对象与构造器
🐤匿名对象
- 匿名对象就是没有名字的对象。
- 匿名对象只能使用一次。
public class PersonTest {
public static void main(String[] args) {
// 这个zs,就是一个对象,名字就是zs
Person zs = new Person();
// 这个也是一个对象,但是没有名字
new Person();
}
}
构造器
方法名称与类名相同
这种特殊的方法我们称之为构造器。- 某一个类,至少存在一个构造器。
🦄构造器的作用
- 创建对象,凡是必须和
new
一起使用。- 完成对象的初始化操作。
🐬构造器的特点
- 构造器的名称和当前所在类的名称相同。
- 禁止定义返回类型,千万不要使用 void 作为返回类型,例如:void Person(){} 这就成了普通方法了,不是构造器了。
- 在构造器中,不需要使用 return 语句。
- 构造器其实是有返回值,它返回的是当前创建对象的地址。
🐥默认构造器的特点
- 符合构造器的特点。
- 无参数的。
- 无方法体。
- 如果类 A 没有使用 public 修饰,则编译器创建的构造器也没有 public 修饰,使用了 public 修饰,则编译器创建的构造器也使用 public 修饰。
- 如果需要证明这一点,可以自己创建一个类首先使用 public 修饰和不修饰,然后查看一下编译的字节码即可证明。
🐸自定义构造器
我们可以自己来去定义构造器,自定义的构造器也要符合构造器的特点。
- 我们自己写的构造器,就称为自定义构造器。
- 如果我们自己定义了构造器,则编译器不再创建默认构造器。
- 一个类至少存在一个构造器。
- 创建对象其实是在调用构造器。
public class User {
String name;
User(String userName) {
System.out.println("---执行了自己的构造器-");
//在构造器当中,对字段做了初始化
name = userName;
}
public static void main(String[] args) {
User user = new User("BNTang");
System.out.println(user.name);
}
}
🐪构造器的重载
- 方法重载:方法名相同 ,参数不一样。
- 构造器重载:构造器的名相同 ,参数不一样。
- 参数不一样,在创建对象时,根据参数不同,调用不同的构造器。
public class User {
String userName = null;
int userAge;
User(String name) {
userName = name;
}
User(String name, int age) {
userName = name;
userAge = age;
}
public static void main(String[] args) {
User user = new User("BNTang");
System.out.println("用户名 = " + user.userName);
System.out.println("用户年龄 = " + user.userAge);
}
}
🐪自定义构造器参数传递注意事项
- 变量赋值的顺序,先找自己方法当中的变量,没有找到就会到类中来去找对应的变量。
- 如果想要直接赋值给类当中的变量,就必须在前面加上一个
this
关键字。
public class User {
String userName = null;
int userAge;
User(String name) {
this.userName = name;
}
User(String name, int age) {
this.userName = name;
this.userAge = age;
}
public static void main(String[] args) {
User user = new User("BNTang");
System.out.println("用户名 = " + user.userName);
System.out.println("用户年龄 = " + user.userAge);
}
}
static 关键字
- 在我们的生活当中,有些事物不属于某一个对象,而是属于整个事物的类型。
- 比如:全世界人口的总数。
- 人的毁灭行为:毁灭的行为应该属于人类,不应该属于某一个人。
- 状态和行为应该有
对象
和类
之分。- 有的状态和行为,应该属于某个对象。
- 有的状态和行为,应该属于类。
static 的作用
- 通过 static 修饰符就能解决这个问题,它修饰的成员就不属于对象,它属于类本身。
- 它可以修饰字段,方法,内部类。
- 作用:确定修饰的内容是属于类还是属于对象。
static 的特点
- static 修饰的内容,随着类的加载而加载。
- 优先于对象的存在。
- static 修饰的成员被该类的所有对象共享。
- 可以直接使用当前类的类名来访问 static 成员。
DemoOne
public class Person {
// 人口
static int totalNums = 0;
// 毁灭
//静态方法,只能访问静态成员
static void destroy() {
System.out.println("人类毁灭啦");
}
// 状态:名词
String name;
int age;
// 行为:动词
void die() {
System.out.println(name + "死了");
Person.totalNums--;
}
//---------以上属于对象---------
// 构造器的作用,创建对象,一创建对象时,就给内部的字段做初始化
Person(String n, int a) {
name = n;
age = a;
Person.totalNums++;
}
public static void main(String[] args) {
System.out.println(Person.totalNums);
Person per = new Person("zs", 1);
System.out.println(per.name);
System.out.println(per.age);
per.die();
System.out.println(Person.totalNums);
Person per2 = new Person("ls", 2);
System.out.println(per2.name);
System.out.println(per2.age);
Person.destroy();
// 通过类名调用
System.out.println(Person.totalNums);
// 可以通过,对象来去调用静态成员
// 本质还是通过类名来调用
System.out.println(per2.totalNums);
}
}
DemoTwo
public class Person {
static int totalNums = 0;
String name;
int age;
void eat(String food) {
System.out.println(name + "吃了" + food);
}
Person(String name, int age) {
this.name = name;
this.age = age;
Person.totalNums++;
}
Person(int age) {
this.age = age;
Person.totalNums++;
}
void die() {
System.out.println(name + "从地球上消失");
Person.totalNums--;
}
static void destroy() {
Person.totalNums = 0;
}
}
class test {
public static void main(String[] args) {
Person person = new Person("张三", 1);
person.name = "张四";
person.eat("奶粉");
System.out.println(Person.totalNums);
Person person1 = new Person(1);
person1.name = "李四";
person1.eat("奶粉");
Person ww = new Person("王五", 1);
ww.eat("奶粉");
person.die();
Person.destroy();
System.out.println("总人口:" + Person.totalNums);
// 当使用对象调用静态的内容(方法,还是字段 )在运行时, 都会去掉
// 还是使用类名来调用
System.out.println(ww.totalNums);
}
}
成员变量与局部变量
所谓变量就是:数据类型 变量名 = 值;
成员变量
直接定义在类中,方法的外面,也称全局变量或字段,不要叫属性。
- 类成员变量:使用 static 修饰的字段。
- 实例成员变量:没有使用 static 修饰的字段。
- 在方法中不能定义 static 变量,static 修饰的属于类。
- 方法属于对象。
局部变量
变量除了成员变量,其它都是局部变量。
- 方法内部的变量就是局部变量。
- 方法的参数也是。
- 代码块中的变量也是的。
🐤成员变量与局部变量的区别
变量的初始值
- 成员变量:默认是有初始值的。
- 局部变量:没有初始值,所以必须先初始化才能使用。
变量的使用域
- 成员变量:在整个类中都有效。
- 局部变量:只在它定义的大括号内生效。
- 成员变量,可以先使用后定义,局部变量必须先定义之后才能使用。
🐸变量的作用域及生命周期
变量的生命周期指的是一个变量被创建并分配内存空间开始,到该变量被销毁并清除所占内存空间的过程,作用域不同,生命周期就不一样。
🐥变量使用时的注意点
- 局部变量定义后,必须显示初始化后才能使用,因为系统不会为局部变量执行初始化操作。
- 这就意味着,定义局部变量后,系统还没有给这个变量分配内存空间。
- 直到程序为这个变量赋值时,系统才会为局部变量分配内存,并将初始值保存到该内存中。
- 局部变量不属于任何类或实例,因此它总是保存在其所在方法的栈内存中。
- 基本数据类型的局部变量,直接把这个变量的值保存到该变量数据类型所对应的内存中(栈)
- 引用数据类型的局部变量,这个变量在内存中存的是地址,通过该地址引用到该变量实际内存中堆里的的对象的值。
- 栈内存中的变量无需系统来做垃圾回收,其往往随,方法或代码块的运行结束而结束。
public class Person {
// 成员变量
static int totalNums;
// 实例成员变量
String name;
int age;
int times;
void eat(String food) {
// 局部变量
int a = 10;
if (times >= 3) {
System.out.println("不能再吃了");
} else {
System.out.println("吃了" + food);
times++;
}
}
Person(String n, int a) {
name = n;
age = a;
Person.totalNums++;
}
public static void main(String[] args) {
Person per = new Person("zs", 18);
System.out.println(Person.totalNums);
per.eat("米饭");
}
}