知识框架
面向过程和面向对象的区别
面向过程:“面向过程”(Procedure Oriented)是一种以过程为中心的编程思想,简称 OP。“面向过程”也可称之为“面向记录”编程思想,就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。所以面向过程的编程方式关注点不在“事物”上,而是做这件事分几步,先做什么,后做什么。例如:早晨起来:起床、穿衣、洗漱、上班,只要按照这个步骤来,就能实现“一天”的功能,整个这个过程中关注的是一步一步怎么做,并没有关注“人”这个事物。
面向对象:“面向对象”(Object Oriented)是一种以对象为中心的编程思想,简称 OO。随着计算机技术的不断提高,计算机被用于解决越来越复杂的问题。一切事物皆对象,通过面向对象的方式,将现实世界的事物抽象成对象。通过面向对象的方法,更利于用人理解的方式对复杂系统进行分析、设计与编程。同时,面向对象能有效提高编程的效率,通过封装技术,可以像搭积木的一样快速开发出一个全新的系统。面向对象将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
使用面向对象编程思想开发系统,在现代开发中会将面向对象贯穿整个过程,
- OOA:面向对象分析(Object-Oriented Analysis)
- OOD:面向对象设计(Object-Oriented Design)
- OOP:面向对象编程(Object-Oriented Programming)
- 面向过程其实是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际地实现。一般的面向过程是从上往下步步求精。面向对象主要是把事物给对象化,对象包括属性与行为。当程序规模不是很大时,面向过程的方法还会体现出一种优势。因为程序的流程很清楚,按着模块与函数的方法可以很好的组织。但对于复杂而庞大的系统来说,面向过程显得就很无力了。
面向对象分析方法分析问题的思路和步骤:
- 根据问题需要,选择问题所针对的现实世界中的实体。
- 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
- 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序 语言,把类构造成计算机能够识别和处理的数据结构。
- 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
面向对象的三大特征
- 封装 (Encapsulation)
- 继承 (Inheritance)
- 多态 (Polymorphism)
类和对象的概念
什么是类
- 现实中的属性:对应类中的成员变量
- 现实中的行为:对应类中的成员方法
什么是对象
- 对象的属性以变量形式存在,并且这里所说的变量是成员变量当中的实例变量。实例变量就是对象级别的变量,这样的变量要求必须先存在对象,通过对象才能访问。例如:“中国人”这个类,有一个属性是“身份证号”,每一个中国人的“身份证号”都是不一样的,所以身份证号必须使用一个真实存在的“中国人对象”来访问。不能使用“中国人”这个类去访问身份证号。一个类可以实例化 N 多个对象,假设通过“中国人”这个类创建了 100 个“中国人对象”,那么“身份证号”必然会有 100 个实例变量空间去存储。
- 对象的行为以方法形式表示,并且这里所说的方法是成员方法。对象调用方法时,根据对象中方法标记(地址值),去类中寻找方法信息。成员方法多个对象共用的一份。当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
类与对象的关系
- 类是对一类事物的描述,是抽象的。
- 对象是一类事物的实例,是具体的。
- 类是对象的模板,对象是类的实体。
类的定义
类的语法格式
类的成员之一:属性
示例:
public class Person { private int age; //声明private变量 age public String name = "Lila"; //声明public变量 name }
变量的分类:成员变量与局部变量
变量根据定义位置的不同,我们给变量起了不同的名字。在方法体外,类体内声明的变量称为成员变量。 在方法体内部声明的变量称为局部变量。
如下图所示:
成员变量(属性)和局部变量的区别?
创建Java自定义类
步骤:
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
package demo01; /* 手机类: 类名: 手机(Phone) 成员变量: 品牌(brand) 价格(price) 成员方法: 打电话(call) 发短信(sendMessage) */ public class Phone { //成员变量 String brand; int price; //成员方法 public void call() { System.out.println("打电话"); } public void sendMessage() { System.out.println("发短信"); } }
知识框架
对象的创建和使用
常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。
1.导包:也就是指出需要使用的类,在什么位置。
- 格式:import 包名称.类名称;
对于和当前类属于同一个包的情况,可以省略导包语句不写。
2. 创建对象
- 格式:类名称 对象名 = new 类名称();
3. 使用,分为两种情况:(也就是,想用谁,就用对象名点儿谁)
- 使用成员变量:对象名.成员变量名
- 使用成员方法:对象名.成员方法名(参数)
我们要创建一个对象首先必须创建一个类。我们现在创建对象使用上面定义的类
package demo01; /* 创建对象 格式:类名 对象名 = new 类名(); 范例:Phone p = new Phone(); 使用对象 1:使用成员变量 格式:对象名.变量名 范例:p.brand 2:使用成员方法 格式:对象名.方法名() 范例:p.call() */ public class PhoneTest { public static void main(String[] args) { //创建对象 Phone p = new Phone(); //使用成员变量 System.out.println(p.brand);//null System.out.println(p.price);//0 p.brand = "小米"; p.price = 2999; System.out.println(p.brand);//小米 System.out.println(p.price);//2999 //使用成员方法 p.call();//打电话 p.sendMessage();//发短信 } }
注意事项:
- 如果成员变量没有进行赋值,那么将会有一个默认值。如下图所示:
对象创建和使用的深层次解密
- 概念:可以看做当前线程所执行的字节码的行号指示器。
- 特点:线程私有的内存
- 概念:描述的是 java 方法执行的内存模型。(每个方法在执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至完成的过程,就对应一个栈帧从入栈到出栈的过程。)
- 特 点 : 线 程 私 有, 生 命 周期 和 线 程 相同 。 这 个 区域 会 出 现 两种 异 常 :StackOverflowError 异常: 若线 程请求 的深 度大于 虚拟 机所允 许的 深度 。OutOfMemoryError 异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。
- 概念:它与虚拟机栈所发挥的作用是相似的,区别是 java 虚拟机栈为执行 java 方法服务,而本地方法栈是为本地方法服务。
- 特点:线程私有,也会抛出两类异常:StackOverflowError 和 OutOfMemoryError。
- 概念:是被所有线程共享的一块区域,在虚拟机启动时创建。
- 特点:线程共享,存放的是对象实例(所有的对象实例和数组),GC 管理的主要区域。可以处于物理上不连续的内存空间。
- 概念:存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。
- 特点:线程共享的区域,抛出异常 OutOfMemory 异常:当方法区无法满足内存分配需求的时候。
以上所描述内容,有看得懂的,也有看不懂的,例如:线程、本地方法等,这个需要大家在学习后面内容之后,返回来再看一看,那个时候你就全部明白了。针对于目前来说,大家必须要知道 java 虚拟机有三块主要的内存空间,分别是“虚拟机栈(后面简称栈)”、“方法区”、“堆区”,方法区存储类的信息,栈中存储方法执行时的栈帧以及局部变量,堆区中主要存储 new 出来的对象,以及对象内部的实例变量。其中垃圾回收器主要针对的是堆内存,方法区中最先有数据,因为程序执行之前会先进行类加载。栈内存活动最频繁,因为方法不断的执行并结束,不断的进行压栈弹栈操作。将目前阶段需要掌握的内存空间使用一张简单的图表示出来,这个图是大家需要掌握的:
public class StudentTest { public static void main(String[] args) { int i = 10; Student s1 = new Student(); } }
内存发生的变化:
总结一下
- 上图中 i 变量和 s1 变量都是局部变量,都在栈内存当中,只不过 i 变量是基本数据类型 int,而 s1 变量是引用数据类型 Student。
- 上图中堆区当中的称为“对象”,“该对象”内部 no、name、age、sex 都是实例变量/属性,这些变量在 new对象的时候初始化,如果没有手动赋值,系统会赋默认值。上图堆区中“对象”创建完成之后,该对象在堆区当中的内存地址是:0x1111,程序中的“=”将 0x1111 这个堆内存地址赋值给 s1 变量,也就是说 s1 变量保存了堆内存对象的内存地址,我们对于这种变量有一种特殊的称呼,叫做“引用”。也就是说对于 Student s1=new Student()代码来说,s1 不是对象,是一个引用,对象实际上是在堆区当中,s1 变量持有这个对象的内存地址。
- 如果创建了一个类的多个对象,对于类中定义的属性,每个对象都拥有各自的一套副本,且互不干扰。
空指针异常
java.lang.NullPointerException 被称为空指针异常,在 java 编程当中属于很常见的异常,接下来研究一下以上程序执行过程的内存图是如何变化的。请看下图:
以上代码的 ball.color。但是程序在运行阶段会通过 ball 引用查找堆内存当中的对象,因为 color 是实例变量,该变量存储在 java 对象内部,当 ball=null 执行之后表示“引用 ball”不再保存 java对象的内存地址,换句话说通过 ball 引用已经无法找到堆内存当中的 java 对象了,对于程序来说这个时候就没有办法正常访问了,这种情况下就会发生空指针异常。总之,当一个“空的引用”去访问“对象相关/实例相关”数据的时候,此时一定会发生空指针异常。
方法调用时参数的传递问题
方法参数传递基本类型
- 基本数据类型的参数,形式参数的改变,不影响实际参数
依据:
- 每个方法在栈内存中,都会有独立的栈空间,方法运行结束后就会弹栈消失
代码举例
public class Demo { public static void main(String[] args) { int number = 100; System.out.println("调用change方法前:" + number);//调用change方法前:100 change(number); System.out.println("调用change方法后:" + number);//调用change方法后:100 } public static void change(int number) { number = 200; } }
方法参数传递引用类型
结论:
- 对于引用类型的参数,形式参数的改变,影响实际参数的值
依据:
- 引用数据类型的传参,传入的是地址值,内存中会造成两个引用指向同一个内存的效果,所以即使方法 弹栈,堆内存中的数据也已经是改变后的结果。当多个对象的引用指向同一个内存空间(变量所记录的地址值是一样的)只要有任何一个对象修改了内存中的数据,随后,无论使用哪一个对象进行数据获取,都是修改后的数据。
代码示例
public class Demo { public static void main(String[] args) { int[] arr = {10, 20, 30}; System.out.println("调用change方法前:" + arr[1]);//调用change方法前:20 change(arr); System.out.println("调用change方法后:" + arr[1]);//调用change方法后:200 } public static void change(int[] arr) { arr[1] = 200; } }
当实例变量是一个引用
构造方法 Constructor
构造方法的作用:
- 当一个对象被创建时候,构造方法用来初始化该对象,给对象的成员变量赋初始值。
构造方法怎么定义,请看以下的语法格式:
根据参数不同,构造器可以分为如下两类:
- 隐式无参构造器(系统默认提供)
- 显式定义一个或多个构造器(无参、有参)
注 意:
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器,具体调用哪个构造方法,那要看调用的时候传递的实际参数列表符合哪个构造方法了
- 父类的构造器不可被子类继承
-
构造方法名和类名一致。
-
构造方法返回值类型不需要写,写上就报错,包括 void 也不能写
-
无参数构造方法又叫做缺省构造器,或者默认构造方法。
-
一般在开发中为了方便编程,建议程序员手动的将无参数构造方法写上,因为不写无参数构造方法的时候,这个默认的构造方法很有可能就不存在了,另外也是因为无参数构造方法使用的频率较高。
代码演示
package com.wrg; /* * 自定义的Student类.成员变量,name age * 要求在 new Person的同时,就指定好name,age的值 * 实现功能,利用方法去实现, 构造方法,构造器 Constructor * 作用: 在new 的同时对成员变量赋值, 给对象的属性初始化赋值 new Person 对属性 name,age赋值 * * 构造方法的定义格式 * 权限 方法名(参数列表){ * } * 方法的名字,必须和类的名字完全一致 * 构造方法不允许写返回值类型 , void 也不能写 * * 构造方法在什么时候,运行呢, 在new 的时候,自动执行 * 只运行一次,仅此而已 * * 每个class必须拥有构造方法,构造方法不写也有 * 编译的时候,javac, 会自动检查类中是否有构造方法 * 如果有,就这样的 * 如果没有,编译器就会自动添加一个构造方法 * 编译器自动添加的无参构造方法: * 自己手写了构造方法,编译的时候,不会自动添加构造方法! */ public class Student { private String name; private int age; //定义出Student类的构造方法 public Student(String name, int age) { this.name = name; this.age = age; //System.out.println("我是一个空参数构造方法"); } //定义出Student类的无参构造方法 public Student() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
构造方法和一般方法区别
- 构造方法在对象创建时就执行了,而且只执行一次。
- 一般方法是在对象创建后,需要使用时才被对象调用,并可以被多次调用。
注意:
- 可以在类的构造器中使用"this(形参列表)"的方式,调用本类中重载的其他的构造器!
- 明确:构造器中不能通过"this(形参列表)"的方式调用自身构造器
- 如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了 "this(形参列表)"
- "this(形参列表)"必须声明在类的构造器的首行!
- 在类的一个构造器中,最多只能声明一个"this(形参列表)"
总结:属性赋值过程
截止到目前,我们讲到了很多位置都可以对类的属性赋值。现总结这几个位 置,并指明赋值的先后顺序。
赋值的位置:
- ① 默认初始化,实例变量没有手动赋值的时候,实际上系统会默认赋值,实例变量是在构造方法执行的过程中完成初始化的,完成赋值的。
- ② 显式初始化
- ③ 构造器中初始化
- ④ 通过“对象.属性“或“对象.方法”的方式赋值
赋值的先后顺序: ① - ② - ③ - ④