面向对象编程
面向对象编程的概念
- 万物皆对象
- 对象有属性(特征),行为(方法)
- 先以面向对象的思想进行分析,然后使用面向对象的编程语言进行表达的过程。
- 面向对象 VS 面向过程
- 面向对象编程是软件产业化的结果
- 三大特点:封装、继承、多态
类和对象的概念
- 对象主要是客观存在的实体,在JAVA中指内存中的一块存储区域
- 类 是对具有相同特征和行为的对象的抽象, 在JAVA中体现为一种引用数据类型,里面包含属性和方法(行为)
- 类是用于构建对象的模版,对象的数据结构由定义她的类来决定
类的定义
class ClassName {
classBody;
…
}
注意:当类名由多个单词组成时,每个单词首字母大写
对象的创建
new ClassName();
注意: a. 使用new关键字创建该类的对象,这个过程叫做类的实例化
B. 创建对象的本质就是在内存空间的堆区申请一块空间,用于存放对象独有的特征信息
- 对象加载及使用时的内存分析
- 代码加载到内存之后,类相关的定义会存在方法区
- 开始执行某个方法,比如main的时候它会被存在栈区,在内存中申请一个栈帧
- 创建一个对象,对象的数据会被存在堆区,对象的应用存在栈区
- 可以通过object.property查找堆区的数据
-
成员变量的定义 class ClassName {
classBody;
…
type propertyName = initialValue;
}注意: 使用驼峰命名法,首字母小写
-
成员变量的初始值 定义成员的变量的时候可以直接赋默认值,不然的话系统会自动填充
填充规则
byte,short, int, long, float, double, char 默认值 0
boolean 默认值 false
引用类型 默认值 null
-
-
成员方法的定义 class ClassName {
classBody;
…
returnType methodName(type prams) {
method body;
…
return xxx;
}
}注意: 使用驼峰命名法,首字母小写
-
返回类型 void
int
String
…使用return 返回
-
形参类型 (int age, String name, ….)
-
方法的调用 调用方法的本质就是根据方法名跳转过去执行方法体后在跳转回这个位置
-
*可变长参数 返回值类型 方法名(类型… 参数名)
参数个数 0~n
注意:如果有多个参数,可变长度参数需放在最后
在方法体里边接收到的是一个一维数组
-
方法的传参过程 进入函数体的时候会在栈中开辟空间存储局部变量,等函数运行结束之后栈空间会被释放。
- 参数传递的注意事项🙃
- 基本数据类型是值传递
- 引用类型传递的是地址,方法内可以改变参数的实际值
- 内存结构之栈区
- 栈用于存放程序运行过程当中所有的局部变量。一个运行的Java程序从开 始到结束会有多次方法的调用。
- JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该 方法的栈帧。一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据。
- 当某一个方法调用完成后,其对应的栈帧将被清除。
-
- 构造方法
-
使用new关键字创建对象时会自动调用构造方法实现成员变量初始化工作。
-
构造方法名与类名完全相同并且没有返回值类型,连void都不许有。
-
语法
class ClassName {
ClassName(paramsList…) {
构造方法体;
}
}- 默认构造方法
- 当一个类没有定义构造方法时,编译器回自动添加一个无参的构造方法
- 若类中写了构造方法则会调用该构造方法
- 默认构造方法
-
-
重载(overload)
-
重载的意义 只需要记住一个方法名,就可以使用不同的方法
-
概念 方法名相同,参数列表不同,这样的方法之间构成重载的关系
-
体现形式 参数的个数不同、参数的类型不同、参数 的顺序不同,与返回值类型和形参变量名无关,但建议返回值类型最好 相同。
-
判断方法能否构成重载的核心:调用方法时能否加以区分
-
方法签名: 方法名+参数类型
-
-
this • 若在构造方法中出现了this关键字,则代表当前正在构造的对象。
• 若在成员方法中出现了this关键字,则代表当前正在调用的对象。
• this关键字本质上就是当前类类型的引用变量。-
工作原理 在构造方法中和成员方法中访问成员变量时,编译器会加上this.的前缀, 而this.相当于汉语中”我的”,当不同的对象调用同一个方法时,由于调用 方法的对象不同导致this关键字不同,从而this.方式访问的结果也就随之 不同。
-
注意点 当局部变量名与成员变量名相同时,在方法体中会优先使用局部变量(就 近原则),若希望使用成员变量,则需要在成员变量的前面加上this.的前 缀,明确要求该变量是成员变量
-
构建链式方法 可以把this 作为方法的返回值,构建链式方法
一顿操作之后 return this;
-
this() 的使用 在构造方法的第一行可以使用this()的方式来调用本类中的其它构造方法
- 注意递归的发生
-
引用的定义
使用引用数据类型定义的变量叫做引用型变量,指向堆区地址的一个变量
- 注意事项
-
引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不 指向任何对象。
-
当某个引用类型变量为null时无法对对象实施访问(因为它没有指向任何 对象)。此时,如果通过引用访问成员变量或调用方法,会产生 NullPointerException 异常。
这个跟c相关的语言不一样,OC,swift都是什么都不做,不会出现异常
-
static
使用static关键字修饰的成员变量,不再属于对象,它属于类,每个类共用一个static变量,多个对象都可以共享这份数据
static 修饰的是静态成员,也叫类成员
- static关键字修饰的静态成员可以使用引用.变量名 访问, 也可以使用类名.变量名 访问(推荐)
-
和swift的区别 对象可以直接访问静态变量
-
使用方式
- 在非静态成员方法中既能访问非静态的成员又能访问静态的成员。 (成员:成员变量 + 成员方法, 静态成员被所有对象共享)
- 在静态成员方法中只能访问静态成员不能访问非静态成员。 (成员:成员变量 + 成员方法, 因为此时可能还没有创建对象)
- 在以后的开发中只有隶属于类层级并被所有对象共享的内容才可以使用 static关键字修饰。(不能滥用static关键字)
构造块与静态代码块
-
构造块 在类体中直接使用{}括起来的代码块。
会在构造方法之前执行,如果需要在构造方法之前做一些准备工作,比如统一初始化成员变量
每次创建对象都会执行一次构造块
-
静态代码块 使用static关键字修饰的构造块。
静态代码块随着类加载时执行一次,先于构造块执行
随着类的加载执行一些操作,比如加载数据库的驱动包
-
顺序:静态代码块->构造块->构造方法
-
面试考点,子类和父类代码块的执行顺序
- 先执行父类的静态代码块,再执行子类的静态代码块。 (要初始化子类,必须先初始化父类)
- 执行父类的构造块,执行父类的构造方法体。
- 执行子类的构造块,执行子类的构造方法体。(父类完全生成之后再生成子类)
Supertest static block -----------SubSuperTest static block Supertest block Supertest constructor -----------SubSuperTest block -----------SubSuperTest constructor
单例设计模式
在某些特殊场合中,一个类对外提供且只提供一个对象时,这样的类叫 做单例类,而设计单例的流程和思想叫做单例设计模式。
创建单例
public class Singleton {
// 避免被外部更改
private static Singleton instance = new Singleton();
// 禁止使用构造方法
private Singleton() {
}
// 返回单例
public static Singleton getInstance() {
return instance;
} }
- 私有化构造方法,使用private关键字修饰。
- 声明本类类型的引用指向本类类型的对象,并使用private static关键字共
同修饰。 -
提供公有的get方法负责将对象返回出去,并使用public static关键字共同 修饰。
-
饿汉式
- 初始化类的时候直接创建实例
-
懒汉式
- 第一次使用到的时候再创建实例,(有多线程污染的风险)
封装
概念
- 通常情况下可以在测试类给成员变量赋值一些合法但不合理的数值,无 论是编译阶段还是运行阶段都不会报错或者给出提示,此时与现实生活 不符。
- 为了避免上述错误的发生,就需要对成员变量进行密封包装处理,来隐 藏成员变量的细节以及保证成员变量数值的合理性,该机制就叫做封装。
** 封装是为了避免外部直接修改对象的成员变量值
实现流程
- 私有化成员变量,加private
- 提供公有的get和set方法,并在方法体中进行合理值的判断。
- 在构造方法中调用set方法进行合理值的判断。
private, public,默认权限
private只能在类内部使用
public可以在外部使用
什么都不写是默认权限,在private和public之间
JavaBean
JavaBean是一种Java语言写成的可重用组件,其它Java 类可以通过反射机
制发现和操作这些JavaBean 的属性。
本质
JavaBean本质上就是符合以下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
继承
当多个类之间有相同的特征和行为时,可以将相同的内容提取出来组成 一个公共类,让多个类吸收公共类中已有特征和行为而在多个类型只需 要编写自己独有特征和行为的机制,叫做继承。
在Java语言中使用extends(扩展)关键字来表示继承关系。
class Children extends Father
使用继承提高了代码的复用性,可维护性及扩展性,是多态的前提条件。
final
-
final本意为”最终的、不可改变的”,可以修饰类、成员方法以及成员变量。
-
使用方式
- final关键字修饰类体现在该类不能被继承。
- 主要用于防止滥用继承,如:java.lang.String类等。
- final关键字修饰成员方法体现在该方法不能被重写但可以被继承。
- 主要用于防止不经意间造成重写,如:java.text.Dateformat类中format方法等。
- final关键字修饰成员变量体现在该变量必须初始化且不能改变。
- 主要用于防止不经意间造成改变,如:java.lang.Thread类中MAX_PRIORITY等。
单例可以使用final来修饰
- 初始化的三种方式
- 声明的时候初始化
- 在构建方法中初始化
- 构造块中初始化
- 常量
- 在以后的开发中很少单独使用final关键字来修饰成员变量,通常使用 public static final关键字共同修饰成员变量来表达常量的含义,常量的命 名规范要求是所有字母都要大写,不同的单词之间采用下划线连。
- public static final double PI = 3.14;
- final关键字修饰类体现在该类不能被继承。
访问权限
- public修饰的成员可以在任意位置使用。
- private修饰的成员只能在本类内部使用。
- 通常情况下,成员方法都使用public关键字修饰,成员变量都使用private 关键字修饰。
package语句的由来
定义类时需要指定类的名称,但如果仅仅将类名作为类的唯一标识,则 不可避免的出现命名冲突的问题。这会给组件复用以及团队间的合作造 成很大的麻烦!
-
在Java语言中,用包(package)的概念来解决命名冲突的问题。
- 定义规范
-
org.apache.commons.lang.StringUtil
-
其中StringUtils是类名而org.apache.commons.lang是多层包名,其含义 如下:org.apache表示公司或组织的信息(是这个公司(或组织)域名的 反写);common 表示项目的名称信息;lang 表示模块的名称信息。
-
- 包的导入
- 使用import关键字导入包。
- 使用import关键字导入静态成员,从Java5.0开始支持。
例如:
import static java.lang.System.out;out.println(“What ever?”);
方法递归调用
-
概念 递归本质就是指在方法体的内部直接或间接调用当前方法自身的形式。
-
注意事项
- 使用递归必须有递归的规律以及退出条件。
- 使用递归必须使得问题简单化而不是复杂化。
- 若递归影响到程序的执行性能,则使用递推取代之。(反例,费氏数列)
特点
-
子类不能继承父类的构造方法和私有方法,但私有成员变量可以被继承 只是不能直接访问。(private 都不被继承)
-
无论使用何种方式构造子类的对象时都会自动调用父类的无参构造方法, 来初始化从父类中继承的成员变量,相当于在构造方法的第一行增加代 码super()的效果。
-
使用继承必须满足逻辑关系:子类 is a 父类,也就是不能滥用继承。
-
Java语言中只支持单继承不支持多继承,也就是说一个子类只能有一个父 类,但一个父类可以有多个子类。
重写override
从父类中继承下来的方法不满足子类的需求时,就需要在子类中重新写 一个和父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做 方法的重写(Override)
-
@Override注解 提示编译器这个方法是重写,如果写了@Override,然而下面的方法不是重写,编译就会报错
- 原则
- 要求方法名相同、参数列表相同以及返回值类型相同,从Java5开始允许 返回子类类型。
- 要求方法的访问权限不能变小,可以相同或者变大。
- 要求方法不能抛出更大的异常(异常机制)。
- static 方法不需要重写
多态和特殊类
多态
同一种事物表现出来的多种形态
一个大类里边有多个子类,多种实现
比如
饮料:可乐,咖啡,水。。。
- 语法格式
ParentType parent = new Children(); // 比如: Shape sr = new Rect(); sr.show();
- 特点
Shape sr = new Rect();
- 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调 用父类独有的方法。
- 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接 调用子类独有的方法。
- 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段 调用子类重写的版本(动态绑定)。
Shape rect2 = new Rect(7, 8, 9, 10); // 编译阶段检测父类是否有show方法 rect2.show(); // 运行阶段实际上调用的是子类的show方法
- 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本。
多态是因为实例对象不同才有的多态,但是static方法都是直接跟类对象关联的,所以没有多态
- 应用类型之间的转换
** 引用数据类型之间的转换方式有两种:自动类型转换 和 强制类型转换。
- 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也
叫做向上转型。 - 强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也 叫做向下转型或显式类型转换。
⚠️ 引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
⚠️ 若强转的目标类型并不是该引用真正指向的数据类型时则编译通过,运 行阶段发生类型转换异常。
为了避免上述错误的发生,应该在强转之前进行判断,格式如下: if(引用变量 instanceof 数据类型) 判断引用变量指向的对象是否为后面的数据类型
- 自动类型转换主要指小类型向大类型的转换,也就是子类转为父类,也
- 实际意义 多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的效果。
抽象类
iOS 没有
抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是 不能创建对象。
是可以实现一部分功能,留一部分给子类实现
-
抽象方法 抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就 是没有方法体。
- public abstract void cry();
- 和抽象方法的关系
- 抽象类中可以有成员变量、构造方法、成员方法;
- 抽象类中可以没有抽象方法,也可以有抽象方法;
- 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有 抽象方法并且使用abstract关键字修饰的类。
⚠️ 没有抽象方法抽象类就没有意义,抽象类不能实例化
- 实际意义
-
用来被继承,不同的子类用不同的方式实现相同名字的方法
-
当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也 就是抽象类对子类具有强制性和规范性,因此叫做模板设计模式。
- 面向协议编程
-
- abstract vs private, final, static private 、final 、static 方法不能被继承,而abstract方法必须被重写,所以不能一起用
接口 interface
接口就是一种比抽象类还抽象的类,体现在所有方法都为抽象方法
interface MyInterface {
…
}
-
非抽象方法default JAVA8开始可以添加非抽象方法
方法需要添加 default 关键字
添加之后子类可以选择是否重写
不像以前牵一发动全身,所有实现interface的类都需要实现新增的方法
-
static 静态方法 可以实现并拥有static 静态方法
-
private 私有方法 JAVA9开始可以有private方法
特殊类
内部类
当一个类的定义出现在另外一个类的类体中时,那么这个类叫做内部类 (Inner),而这个内部类所在的类叫做外部类(Outer)。
-
实际作用 当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个 类定义为所服务类中的内部类,这样可以隐藏该类的实现细节并且可以 方便的访问外部类的私有成员而不再需要提供公有的get和set方法。
-
普通内部类 直接将一个类的定义放在另一个类的类体中
内部类可以直接访问外部类的private变量,private方法
- 创建语法
访问修饰符 class 外部类的类名 { 访问修饰符 class 内部类的类名 { 内部类的类体; } } NormalOuter outer = new NormalOuter(); // 想象一下再外部类内部调了一个new 方法 NormalOuter.NormalInner ni = outer.new NormalInner();
- 使用方式
- 普通内部类和普通类一样可以定义成员变量、成员方法以及构造方法等。
- 普通内部类和普通类一样可以使用final或者abstract关键字修饰。
- 普通内部类还可以使用private或protected关键字进行修饰。
- 普通内部类需要使用外部类对象来创建对象。
- 如果内部类访问外部类中与本类内部同名的成员变量或方法时,需要使 用this关键字。
调用同名变量的技巧
public void show1(int cnt) { System.out.println("the cnt "+ cnt); System.out.println("this cnt "+ this.cnt); System.out.println("outer cnt "+ NormalOuter.this.cnt); }
- 理解,记忆
NormalOuter$NormalInner
生成的编译文件如上,内部类就是关联到外部类上面的一个类,在外部类有一个指针指向内部类的定义
- 创建语法
-
静态内部类 使用static关键字修饰的内部类,隶属于类层级。
- 语法格式
// 声明 访问修饰符 class 外部类的类名 { 访问修饰符 static class 内部类的类名 { 内部类的类体; } } //创建,使用 StaticOuter.StaticInner si = new StaticOuter.StaticInner();
• 静态内部类不能直接访问外部类的非静态成员。
• 静态内部类可以直接创建对象。
• 如果静态内部类访问外部类中与本类内同名的成员变量或方法时,需要 使用类名.的方式访问。
- 语法格式
-
局部(方法)内部类 直接将一个类的定义放在方法体的内部时。
-
创建语法 访问修饰符 class 外部类的类名 {
访问修饰符 返回值类型 成员方法名(形参列表) {
class 内部类的类名 {
内部类的类体;
}
}
} -
使用方式
- 局部内部类只能在该方法的内部可以使用。
- 局部内部类可以在方法体内部直接创建对象。
- 局部内部类不能使用访问控制符和static关键字修饰符。(因为没有意义)
- 局部内部类可以使用外部方法的局部变量,但是必须是final的。由局部内 部类和局部变量的声明周期不同所致。
只要在局部类内部使用了外部方法的局部变量,自动变成final
-
-
匿名内部类 就是指没有名字的内部类
-
回调模式 传递一个接口约束的类给方法,然后执行到差不多的时候可以调用传进参数的方法
public static void test(AnonymousInterface ai) { ai.show(); }
- 当接口/类类型的引用作为方法的形参时,实参的传递方式有两种:
- 自定义类实现接口/继承类并重写方法,然后创建该类对象作为实参传递;
- 使用上述匿名内部类的语法格式得到接口/类类型的引用即可;
-
语法 接口/父类类型 引用变量名 = new 接口/父类类型() { 方法的重写 };
-
枚举
- 使用public static final表示的常量描述较为繁琐,使用enum关键字来定 义枚举类型取代常量,枚举类型是从Java5开始增加的一种引用数据类型。
- 枚举值就是当前类的类型,也就是指向本类的对象,默认使用public static final关键字共同修饰,因此采用枚举类型.的方式调用。
-
枚举类可以自定义构造方法,但是构造方法的修饰符必须是private,默 认也是私有的。(可以有对应的raw数据)
- 语法
public enum DirectionEnum { UP("up"), DOWN("down"), LEFT("left"), RIGHT("right"); private final String desc; private DirectionEnum(String desc) { this.desc = desc; } public String getDesc() { return desc; } }
- Enum
所以枚举都继承自java.lang.Enum类,常用方法
static T[] values() String toString() Int ordinal() Static T valueOf(String str) Int compareTo(E o)
- 枚举类实现接口的方式
- 重写一个,和正常的类一样
- 每个对象都重写(匿名类的写法)
注解
- 注解(annotation)又叫,一种特殊的接口,是一种引用数据类型
-
注解本质上就是代码中特殊标记,通过这些标记可以在编译、类加载、 以及运行时执行指定的处理。
- 语法格式
访问修饰符 @interface 注解名称 { 注解成员; }
-
自定义注解自动继承java.lang.annotation.Annotation接口
-
通过@注解名称的方式可以修饰包、类、 成员方法、成员变量、构造方 法、参数、局部变量的声明等。
-
- 使用方式
- 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方 法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该 成员变量的类型。
// 没有任何成员的注解,叫标记注解 public @interface MyAnnotation { //声明一个string类型的成员变量 public String value(); // 设置默认值 public String value2() default "default"; }
- 如果注解只有一个参数成员,建议使用参数名为value,而类型只能是八 种基本数据类型、String类型、Class类型、enum类型及Annotation类型
- 注解体中只有成员变量没有成员方法,而注解的成员变量以“无形参的方 法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该 成员变量的类型。
-
元注解 元注解是可以注解到注解上的注解,是一种基本注解,但是它能够应用到其他的注解上面
-
@Retention 应用到一个注解上用于说明该注解的的生命周期
-
RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时 它将被丢弃忽视。
-
RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加 载到 JVM 中,默认方式。
-
RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载 进入到 JVM 中,所以在程序运行时可以获取到它们。
-
- @Documented
- 使用javadoc工具可以从程序源代码中抽取类、方法、成员等注释形成一个和源代码配套的API帮助文档,而该工具抽取时默认不包括注解内容。
- @Documented用于指定被该注解将被javadoc工具提取成文档。
*( 定义为@Documented的注解必须设置Retention值为RUNTIME。) 在java11中不需要
-
@Target 用于指定被修饰的注解能用于哪些元素的修饰
-
ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
-
ElementType.CONSTRUCTOR 可以给构造方法进行注解
-
ElementType.METHOD 可以给方法进行注解
-
ElementType.FIELD 可以给属性进行注解
-
ElementType.PACKAGE 可以给一个包进行注解
-
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
-
ElementType.PARAMETER 可以给一个方法内的参数进行注解
-
ElementType.TYPE 可以给类型进行注解,比如类、接口、枚举
-
ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明 语句中,如:泛型(java8)
-
ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中(java8)
-
-
@Inherited 如果一个超类被该注解标 记过的注解进行注解时,如果子类没有被任何注解应用时,则子类就继 承超类的注解。
使用该注解标注的注解,注解的类,它的子类如果没有注解,就会继承该类的注解
- @Repeatable 表示自然可重复的含义,从Java8开始增加的新特性。
-
-
预制注解
-
@author 标明开发该类模块的作者,多个作者之间使用,分割
-
@version 标明该类模块的版本
-
@since 从哪个版本开始增加的
-
@see 参考转向,也就是相关主题
-
@param 对方法中某参数的说明,如果没有参数就不能写
-
@return 对方法返回值的说明,如果方法的返回值类型是void就 不能写
-
@exception 对方法可能抛出的异常进行说明
-
@Override 限定重写父类方法, 该注解只能用于方法
-
@Deprecated 用于表示所修饰的元素(类, 方法等)已过时
-
@SuppressWarnings 抑制编译器警告
-