目录
第三章 面向对象
3.1 面向对象概述
3.1.1 面向过程和面向对象有什么区别?
从语言方面出发:
对于C语言来说,是完全面向过程的
对于C++语言来说,是一半面向过程,一般面向对象的,(C++是半面向对象的)
对于Java来说,是完全面向对象的
什么是面向过程的开发方式?
面向过程的开发方式主要特点是:
注重步骤:注重的是实现这个功能的步骤
第一步干什么
第二步干什么
……
另外面向过程也注重实现功能的因果关系
因为有A所以B
因为有B所以C
……
面向过程当中没有对象概念,只是实现这个功能的步骤以及因果关系
面向过程有什么缺点?
面向过程主要是每一步和每一步之间的因果关系,其中A步骤因果关系到B步骤,A和B联合起来形成一个子模块,子模块和子模块之间又因为因果关系结合在一起,假设其中任何一个因果关系出现问题(发生错误),此时整个系统的运转都会出现问题(因为代码和代码之间的耦合度太高了,扩展性太差了)
关于耦合度:
螺栓和螺母拧在一起:耦合度高吗?
这个耦合度低,因为螺栓和螺母可以分开(它们之间是有接口的)
螺栓和螺母拧在一起后,再用焊条焊接在一起,耦合度高吗?
这个耦合度很高,耦合度就是粘连程度
往往耦合度高,扩展性就差
面向过程有什么优点呢?
对于小型项目,采用面向过程的方式进行开发,效率很高,不需要前期进行对象的提取,模型的建立,采用面向过程方式可以直接开始干活,一上来直接写代码,编写因果关系,从而实现功能。
什么是面向对象的开发方式?
采用面向对象的开发方式,更符合人类的思维方式,人类就是以“对象”的方式去认识世界的
所以面向对象更容易让我们接受
面向对象就是将现实世界分割成不同的单元,然后每一个单元都实现成对象,然后去动一下,让各个对象之间协作起来形成一个系统
采用面向对象的开发方式:耦合度低,扩展力强
3.1.2 面向对象使用的术语(三大思想)
a. OOA:面向对象分析(Object-Oriented Analysis)
b. OOD:面向对象设计(Object-Oriented Design)
c. OOP:面向对象编程(Object-Oriented Programming)
3.1.3 面向对象包括3大特征
封装、继承、多态
3.1.4 类和对象的概念
什么是类:
“类”实际上在现实生活中是不存在的,是一个抽象的概念,是一个模板,是人类大脑进行思考总结抽象的一个结果。
类本质上是现实世界当中某些事物具有相同特征,将这些共同特征提取出来形成的概念就是一个“类”,“类”就是一个模板
什么是对象:(另一个名字叫“实例”)
对象是实际存在的个体
在java语言中,要想得到“对象”,就必须先定义一个“类”,“对象”是通过“类”这个模板创造出来的
类就是一个模板:类中描述的是所有对象的“共同特征信息”
对象就是通过类创建出的个体
通过类创建对象的过程叫做“实例化”
多个对象具有共同特征,进行思考总结抽取共同特征的过程叫做“抽象”
3.1.5 类 = 属性 + 方法
属性来源于:状态
方法来源于:动作
3.1.6 类的定义
a. 怎么定义一个类,语法格式是什么?
【修饰符列表】 class 类名{
//类体 = 属性 + 方法
//属性在代码上以“变量”的形式存在(描述状态)
//方法描述动作/行为
}
注意:修饰符列表可以省略
b. 为什么属性是以“变量”形式存在的?
因为属性对应的是“数据”,数据在程序中只能放到变量中,结论:属性其实就是变量
变量的分类:
1. 方法体中声明的变量:局部变量
2. 方法体外声明的变量:成员变量(这里的成员变量就是“属性”)
3.2 创建对象
3.2.1 创建对象内存分析
3.2.1.1 栈
栈的特点:先进后出的特点,存储速度比较快(比堆快)
栈存储速度快的原因:
栈内存通过栈指针来创建空间与释放空间
指针向下移动,会创建新的内存,向上移动会释放这些内存
这种方式速度很快,仅次于PC寄存器
(要明确移动的大小和范围)
明确了大小和范围是为了方便指针的移动,但是限制了数据结构的大小,影响了程序的灵活性
对于更大部分,大小不确定的数据(对象)存储到堆中
栈内存中存储的内容:
1. 基本数据类型
2. 引用数据类型数据的引用(对象、数组、字符串的引用)
3.2.1.2 堆
存放的是类对象
堆内存和栈内存的不同点:我们在创建对象的时候,不必关心堆内存里需要开辟多少的存储空间,不需要关注内存占用的时长(堆内存中内存的释放是由GC完成的)
垃圾回收的规则:
当栈内存中不存在此对象的引用的时候,就将其视为垃圾,等待垃圾回收器回收
3.3 构造方法
3.1.1 构造方法设计
建议每创建一个类,都手动写上去无参的构造方法。不依赖程序的自动生成。
3.4 方法重载
相同的方法名,不同的参数列表(参数类型和参数的数量)。
方法签名:方法名+参数列表(唯一的确定了一个方法),和返回值没有关系
方法重载属于静态多态
3.5 构造方法的重载
一个类中可以有多个构造方法,可以使在不同的创建对象的需求下,调用不同的构造方法来完成对象的初始化
一定要提供一个无参构造方法
3.6 匿名对象
没有对象名称的对象就是匿名对象
匿名对象只能使用一次,因为没有任何引用指向它,很快就会被垃圾回收掉
class Calculator{
int sum(int x, int y){
return x + y;
}
}
//匿名调用对象中的方法
int sum = new Calculator().sum(1,2); //可以直接在构造函数后面调用该类中的实例方法
3.7 封装
封装的意义在于:
- 保护或则和防止代码被自己无意中破坏
- 保护成员属性,不让类意外的程序直接访问和修改
- 可以通过对set方法的限制,约束输入的值,使其在合理范围内
封装的原则:隐藏对象的属性和实现细节,仅对外公开访问方法,并且控制访问级别
建议以后的开发中,对所有的属性进行封装,并为其设置setter和getter方法
3.8 this关键字
this可以完成的操作:
-
调用类中的属性
-
调用类中的方法或者构造方法(在一个构造方法中调用另一个构造方法)
规则:当在构造函数中调用另一个构造函数的时候,this() 必须放在第一行
-
表示当前对象
3.9 static 关键字
静态属性在内存中的分布:
static表示静态:可以用来修饰成员变量和成员方法,被修饰后就变成了静态变量和静态方法
static的主要作用:创建独立于具体对象的域变量和方法。静态变量存在于方法区中,随着类加载被初始化(成员变量在对象创建的过程中被初始化)
static变量的特点:static变量在内存中只有一处,存在方法区中,不会因为对象的多次创建而建立多个副本
static方法的特点:静态方法被调用的时候,有可能对象还没有被创建
static注意:静态上下文中不能有非静态(因为此时可能对象还没有创建),非静态上下文中可以有静态(此时确定对象已经创建)
3.10 包介绍
- 将功能相似或者相关的类或者接口组织在同一个包中,方便类的查找和使用
- 不同的包中的类的名字可以是相同的,调用两个包中的同名类时,应该加上包名以区分
- 包有访问权限,拥有包访问权限的类才能访问某个包中的类
import 关键字: import 包名.类名
普通的java代码中的方法和类不需要导包,因为它们都定义在java.lang包中,这个包中都是java最常用的方法
3.11 代码块
普通代码块
{
int a = 10;
System.out.println(a);
}
构造代码块:
public class demo {
public static void main(String[] args) {
Person person = new Person();
}
}
class Person{
private String name;
private int age;
// 以下就是构造代码块:随着对象的每次创建,执行一次,且执行在构造方法之前
//无论用户调用哪一个构造方法创建对象,构造代码块都必然执行
{
System.out.println("对象创建的时候执行");
}
//以下是静态代码块,在类加载的时候执行
static {
System.out.println("静态代码块在类加载时执行");
}
public Person(){
System.out.println("对象创建的时候执行");
}
}
面试题:
构造方法和构造代码块和静态代码块的执行顺序:
静态代码块---> 构造代码块 ---> 构造方法
3.11 子类继承
Java中只有单继承,多重继承,没有多继承
继承的注意事项:
- 父类的public,protected属性和方法可以被子类访问
3.11.1 super关键字
通过super关键字,可以访问父类的构造方法、属性、构造方法
当调用子类的构造方法时,子类在默认调用super(),如果此时父类没有无参构造方法,调用就会出错(super()必须在子类构造方法的第一行)
以下行为不合逻辑:
//子类构造方法:
public Son(){
this();
super();//this()和super()两个必须都在构造方法的第一行
}
只能是这样:
public Son(){//super()放在一定会执行的构造方法中
this(i);
}
public Son(int i){
super();
}
3.11.2 重写
重写的规则:
- 参数列表必须与被重写的方法相同
- 返回类型必须完全与被重写方法的返回类型相同
- 访问权限不能比被重写方法的访问权限更低
- 父类的成员方法只能被它的子类重写
- static方法和private方法不能被重写,但是可以被再次声明
3.11.3 final 关键字
final 修饰属性、变量、类、方法
final修饰属性和变量的时候:
1. 变量成为了常量,不能再次进行赋值
2. final修饰的局部变量,只能赋值一次(可以先声明后赋值)
3. final修饰的是成员变量,必须在声明时赋值
final用于修饰类的时候:
1。不可以被继承
final用于修饰方法:
1. 不能被子类重写
全局常量:(public static final)可以被任何位置直接使用,且不可改变
常量的命名规范:由1个或多个单词组成,单词和单词之间必须使用下划线隔开,单词中的所有字母大写
3.12 抽象类
3.13 接口
3.13.1 面向接口的编程思想
优点:
- 降低程序的耦合性
- 易于程序的扩展
- 有利于程序的维护
3.13.2 全局常量和抽象方法的简写;
-
全局常量编写:
public static final String INFO = "info"; | | v String INFO = "info";
-
抽象方法编写:
public abstract void print(); | V void print();
3.13.3 接口和抽象类的区别
- 抽象类要被子类继承,接口要被实现
- 接口只能写抽象方法,抽象类中可以有实现方法
- 接口定义的变量只能时公共的静态的常量,抽象类中的变量是普通变量
- 抽象类无法多继承,接口可以多实现
- 接口不能有构造方法,抽象类可以有
3.14 多态
3.14.1 多态的体现
方法的重载和重写都是多态的一种:
重载:一个类中方法的多态性体现,静态的实现多态
重写:子父类中方法的多态性体现
3.14.2 多态的使用:对象的类型转换
向上转型:将子类实例转换成父类对象
Person p = new Student();
向下转型:将父类实例转化成子类对象
Student s = (Student) p;
注意:向下转型的过程中,因为父类可能有多个不同的子类,需要提前判断这个父类是否可以被转为当前的具体子类:
if(p instanceof Student){
Student s = (Student) p;
}
3.15 Object类
3.15.1 toString方法
建议重写toString方法,可以返回对象的字符串表示形,更详细的描述类中的属性。如果不重写,就会直接使用Object的toString方法,返回对象的内存地址:
getClass().getName() + "@" + Integer.toHexString(hashCode());
3.15.2 equals方法
3.16 内部类
概念:广泛意义上内部类分为四种:
- 成员内部类
- 局部内部类
- 匿名内部类(局部内部类的一种)
- 静态内部类(成员内部类+static)
3.16.1 成员内部类
成员内部类的特点:
- 可以无条件访问外部类中的所有成员属性和成员方法(包括private成员和static成员)
- 如果外部类和内部类有同名成员,外部成员会隐藏(就近原则)
成员内部类的创建:
Outter o = new Outter();
Outter.Inner inner = o.new Inner();
3.16.2 局部内部类
特点:
- 局部内部类定义在一个方法或者一个作用域里面,其访问权限仅限于访问方法内或者该作用域内
- 就像方法中的局部变量一样,不能有public、protected、private和static修饰符
3.16.3 匿名内部类
特点:匿名内部类没有名字,创建的时候必须继承一个父类或者实现一个接口(只能二选一)
final int a = 0;
public static void main(String[] args) {
int a = 10;
something(new Person() {
@Override
public void say() { //重写了Person接口中的say方法
System.out.println(a);
}
});
}
public static void something(Person p){
p.say();
}
注意:
- 匿名内部类中不能有构造函数
- 匿名内部类中不能存在任何static成员
- 局部内部类的所有限制
- 匿名内部类不能是抽象的,因为必须要实现所有的继承过来的抽象方法
- 只能访问final修饰的局部变量
只能访问所在域的final类型的变量,final可以省略,但是定义的变量内容不能更改
为什么不能访问非final修饰的局部变量呢?
1. 在编译内部类(以下代码)的时候(内部类会被单独的编译成字节码文件):
int a = 10;
something(new Person() {
@Override
public void say() {
System.out.println(a);
}
});
内部类中会生成一个变量来copy 局部变量a中的10,
2. 编译成字节码之后,内部类会被单独存储(此时内部的备份就是10)
3. 如果外面的a发生了更改
4. 在使用内部类的时候就会出现内外不一致的情况(内部使用的是10,外部使用的不是10)
3.16.4 静态内部类
特点:
- 被static修饰
- 不需要依赖于外部类对象,和静态属性有些类似
- 不能使用外部类的非static成员(静态上下文中不能有非静态的内容,因为那些内容只有创建对象后才能使用)
- 可以做到在没有创建外部类的时候,就创建内部类
创建:
Book.Info bi = new Book.Info();
3.17 包装类
为了解决8中基本数据类型不是对象的问题,引入了八种基本数据类型的包装类
包装类的分类:
- Number:Integer, Short, Long, Double, Float, Byte 都是Number的子类,表示是一个数字
- Object:Character,Boolean都是Object的直接子类
JDK1.5引入了自动装箱和拆箱
3.18 方法的可变参数
JDK1.5之后引入
public static int sum(int ... nums){//此处可以输入任意数量的参数
int n = 0;
for (int i = 0; i < nums.length; i++) {
n+=nums[i];//参数在方法内部用数组接收
}
return n;
}
注意:可变参数只能出现在参数列表的最后
3.19 递归
3.20 异常
3.20.1 异常是如何产生的
1. 发生了异常,JVM根据异常的情况,创建了一个异常的对象,包含了异常的信息
2. main方法没有对这个异常进行处理,自动将异常抛给了main的调用者JVM
3. JVM对异常信息进行了相应(将异常信息显示到控制台,中断处理)
3.20.2 如果想要人工捕获异常,就需要使用try…catch代码块
try{
//有可能发生异常的代码段
}catch(异常类型1 对象名1){
//异常处理的操作
}catch(异常处理2 对象名2){
//异常处理操作
}finally{
//异常的统一出口
}
try catch处理流程
1. 一旦产生异常,JVM会自动产生一个异常类的实例化对象
2. 如果异常发生在try语句,会自动找到匹配的catch语句执行,如果没有在try语句中,就会抛出给调用者
3. 所有的catch根据catch方法的参数匹配异常类的实例化对象,如果匹配成功,才进入catch语句块进行处理
异常被捕获处理之后,代码不会在此中断,会继续向下运行
finally——异常的统一出口
try{
}catch(){
}finally{
//必然执行的异常统一处理出口
//无论是否发生异常,finally必然执行
//比如资源的释放
}
面试问题:
1. 如果是程序被关闭了(JVM停机,停电了),finally才不会执行,否则一定会执行
2. 如果try或者catch中有return,finally如何执行?
* 先计算返回值,将返回值存储起来,等待返回
* 执行finally代码块
* 将之前存储的返回值返回(即是finally对这个值造成了改变,返回值依旧不变)
3. 如果finally中有return,则程序会在finally中退出(finally中的return会覆盖try和catch中的finally)
3.20.3 throws
什么时候使用throws:
当方法产生的异常是由于传参导致的,而方法本身的逻辑是没有问题的,可以将异常抛出给调用者
如果最后抛给了JVM,程序就会中断
throw关键字:
在程序运行中人工抛出异常:
throw new RuntimeException("你的操作有误");
3.20.4 自定义异常类
如果是受检异常(Exception的直接子类),一定要抛出,不能用try catch,(不能自己抛出异常,又接受处理,没有意义)