面向对象的理解
- 面向对象和面向过程的区别
- 都是编程思想
- 面向过程强调的是“怎么做”,面向过程的程序,是以函数为基本单位
- 面向对象强调的是“谁来做”,面向对象是关注的是对象的个体,以类为基本单位,重点在于类的设计,某个数据、行为是在哪个类中描述更合适
- 好处
- 解决中大型的问题,使用面向对象代码简化了
- 以人类的思维方式来思考问题,比较好理解
- 思考步骤
- 分析里面涉及到几个类,分别是什么
- 抽取每个类中的属性和方法,再加以定义
- 面向过程正常调用和执行
类与对象
- 类是对象的设计模板,对象是类的实例
- 区别:类是抽象的,对象是具体的、实际存在的
- 关系:类是由对象总结或抽取出来的一个概念,是对象的所属类型,对象是通过类创建出来的具体的实体
类
- 对现实世界中,具有共同特性的某一类事物的抽象的描述,在Java中使用一个类来描述一类事物
组成
- 现实世界的事物
- 属性:事物的信息描述,对应类中的成员变量
- 行为:事物的功能,对应类中的成员方法
修饰符 class 类名 {
属性列表
构造器列表
方法列表
}
定义类的步骤
- 定义类(考虑修饰符、类名)
- 编写类的属性(考虑修饰符、属性类型、属性名、初始化值)
- 编写类的方法(考虑修饰符、返回值类型、方法名、形参等)
对象
- 对象是类的一个具体的个体,是类的一个实例(instance)
创建
类名 对象名 = new 类名();
- 匿名对象
匿名对象只能使用一次
new Person().shout();
- 使用
**对象名.对象成员**
的方式访问对象成员(包括属性和方法) - 类的访问机制
- 一个类中访问:类中的方法可以直接访问类中的成员变量(例外:static方法访问非static编译不通过)
- 不同类中访问:先创建要访问类的对象,再用对象访问类中定义的成员
- 对象的生命周期
- 从创建对象到被垃圾回收器回收
对象的内存结构
虚拟机栈:将局部变量存储在栈结构中
- 存放方法中的基本类型变量
- 存放方法中的引用类型变量的引用(地址号)
- 栈描述的是方法执行的内存模型。每个方法被调用都会创建一个栈帧(存储局部变量、操作数、方法出口等)
- JVM为每个线程创建一个栈,用于存放该线程执行方法的信息(实际参数、局部变量等)
- 栈是一个连续的内存空间,由系统自动分配,速度快
- 栈属于线程私有,不能实现线程间的共享
- 存储特性是“先进后出,后进先出”
堆:存放对象本身,对象的属性(非static的)加载在堆空间中
- 堆用于存储创建好的对象和数组
- 堆是一个不连续的内存空间,分配灵活,速度慢
- JVM只有一个堆,被所有线程共享
方法区
- JVM只有一个方法区,被所有线程共享
- 方法区实际也是堆,只是用于存储类、常量相关的信息!
- 用来存放程序中永远不变或唯一的内容。(类信息(Class对象)、静态变量、字符串常量等)
创建一个对象占多大堆空间?
markword 包含对象信息 8字节,其中包含所信息
类型指针 表明所属类,既指向其class对象
实例数据 即成员属性
对齐 对象长度必须是8字节的整数倍,对齐用于补齐
抽象类
抽象类
在父类中明确子类应该包含某些方法,但是在父类中又不能给出具体的实现,父类中只能把这个方法声明为抽象方法,包含抽象方法的类必须是抽象类
修饰符 abstract class 抽象类的名称 {
类的成员列表
}
abstract class 几何图形类 {
public abstract double getArea();
}
class 圆 extends 几何图形类 {
private double 半径;
public double getArea() {
return Math.PI * 半径 * 半径;
}
}
class 矩形 extends 几何图形类 {
private double 长;
private double 宽;
public double getArea() {
return 长 * 宽;
}
}
原因
- 从逻辑角度:几何图形类中,应该包含所有几何图形共同的特征。那么所有几何图形,就应该包含 “获取面积”的功能
- 语法角度:通过“几何图形”类对圆对象,矩形对象等的多态引用,应该是可以调用“获取面积”的方法,如果父类中没有声明该方法,就无法调用,无法实现多态引用
注意
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
- 包含抽象方法的类就是抽象类。通过abstract方法定义规范,然后要求子类必须定义具体实现
- 通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用
-
抽象方法
修饰符 abstract 返回值类型 方法名(形参列表);
抽象方法只有方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的
- 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
- 若子类没有重写父类中的所有的抽象方法,则此子类得是一个抽象类,需要使用abstract修饰
注意
- 不能用abstract修饰变量、代码块、构造器
- 不能用abstract修饰私有方法、静态方法、final的方法、final的类
接口
- 接口的本质是契约、标准、规范
为什么要声明接口
- 解决Java的单继承
例如:多功能的交通工具
既具有飞机的飞行功能,
又具有船的水上航行功能
又具有车的路上行驶功能
- 多个不相关的类可能具有共同的行为特征,这样的行为特征,就可以提取成一个接口
继承只能表示is-a的关系
类与类之间的关系还有has-a,like-a关系
例如:
飞机有飞的能力
小鸟有飞的能力
…
飞机与小鸟之间没有is-a的关系
例如:
学生对象可比较
商品对象可比较
。。。
学生与商品没有is-a的关系,可比较行为可以抽取成接口
设计理念区别
- 抽象类,被继承体现的是:“is a”的关系,抽象类中定义的是该继承体系的共性功能
- 接口,被实现体现的是:“like a”的关系,接口中定义的是该继承体系的扩展功能
定义
修饰符 interface 接口名 {
}
- 访问修饰符只能是public或缺省
- 接口使用interface来定义
- 接口名和类名采用相同命名机制
- extends:接口可以多继承
- 常量:接口中的属性只能是全局常量,由
**public static final**
修饰,不写也是 方法:接口中的方法只能是:
**public abstract**
,不写也是修饰符 class 子类 extends 父类 implements 接口名1, 接口名2... {
}
要点
Java中,接口和类是并列的两个结构
- 如何定义接口:定义接口中的成员
- JDK7及以前:只能定义全局常量和抽象方法
- JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、default方法
- 接口中不能定义构造器的!意味着接口不可以实例化
- Java开发中,接口通过让类去实现implements的方式来使用
- 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
- 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
- Java类可以实现多个接口 —->弥补了Java单继承性的局限性
- 接口与接口之间可以多继承
接口的使用
- 接口使用上也满足多态性
- 实际上就是定义了一种规范
- 开发中,体会面向接口编程
- 面向接口编程是面向对象编程的一部分。 为什么需要面向接口编程? 软件设计中最难处理的就是需求的复杂变化,需求的变化更多的体现在具体实现上。 我们的编程如果围绕具体实现来展开就会陷入”复杂变化”的汪洋大海中,软件也就不能最终实现。我们必须围绕某种稳定的东西开展,才能以静制动,实现规范的高质量的项目。 接口就是规范,就是项目中最稳定的东东! 面向接口编程让我们把握住真正核心的东西,使实现复杂多变的需求成为可能。通过面向接口编程,而不是面向实现类编程,可以大大降低程序模块间的耦合性,提高整个系统的可扩展性和可维护性。面向接口编程的概念比接口本身的概念要大得多。 设计阶段相对比较困难,在你没有写实现时就要想好接口,接口一变就乱套了,所以设计要比实现难! 接口语法本身非常简单,但是如何真正使用?这才是大学问。我们需要后面在项目中反复使用,大家才能体会到
接口Java8新特性
- 接口中定义的静态方法,只能通过“接口.方法”来调用
- 通过实现类的对象,可以调用接口中的默认方法
- 如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
- 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法, 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 —->类优先原则
- 如果实现类实现的多个接口中定义了同名同参数的默认方法, 那么在实现类没有重写此方法的情况下,报错
—->接口冲突。这就需要我们必须在实现类中重写此方法
- 子类(或实现类)的方法中调用父类、接口中被重写的方法
**super.默认方法();**
**接口名.super.默认方法();**
面试题
interface A {
int x = 0;
}
class B {
int x = 1;
}
class C extends B implements A {
public void pX() {
System.out.println(x); // 🔴编译不通过,因为x不明确,方法有类优先原则,属性没有
System.out.println(super.x);// 1
System.out.println(A.x); // 0
}
}