文章目录
- 注解
- 内置注解
- 元注解
- 对象克隆
- 为什么要克隆?
- 如何克隆
- 浅克隆
- 深克隆
- Java设计模式
- 什么是设计模式?
- 为什么要学习设计模式?
- 建模语言
- 类
- 接口
- 类之间的关系
- 依赖关系
- 关联关系
- 聚合关系
- 组合关系
- 继承关系
- 实现关系
- 面向对象设计原则
- 单一职责
- 开闭原则
- 里氏替换原则
- 依赖倒置
- 接口隔离
- 迪米特原则
- 组合/聚合服用原则
- 原则总结:
- 设计原则的核心思想
- 23种设计模式
- 常用的设计模式
- 单例模式
- 饿汉式单例
- 懒汉式单例
- Runtime类
注解
注解:也叫标注,用于包、类、变量、方法、参数上。可以通过反射获取标注。可以在编译期间使用,也可以被编译到字节码文件中,运行时生效。
内置注解
内置注解:Java语言已经定义好的注解。
@Overread:用于方法重写。
@Deprecated:标记过时方法。
@SuppressWarnings:指示编译器去忽略注解中声明的警告。
@FunctionalInterface:用于指标被修饰的接口是函数式接口。
元注解
元注解:修饰注解的注解。
@Target:用于描述注解作用于那些元素上。
CONSTRUCTOR//用于描述构造器
FIELD//用于描述域
LOCAL_VARIABLE//用于描述局部变量
METHOD//用于描述方法
PACKAGE//用于描述包
PARAMETER//用于描述参数
TYPE//用于描述类、接口(包括注解类型) 或enum声明
@Retention:表示注解什么时候生效。
SOURCE//在源文件中有效(即源文件保留)
CLASS//在class文件中有效(即class保留)
RUNTIME//在运行时有效(即运行时保留)
对象克隆
克隆:在一个现有的对象基础上克隆一个新的对象。
为什么要克隆?
通过new出来的对象,所有属性都是空值或者默认值,希望将原来对象的属性也一并赋值给一个新的对象。
//一下情况并不是克隆,只是将对象地址赋值给了另一个对象.
Student stu1 = new Student();
Student stu2 = stu1;
如何克隆
- 类实现Cloneable接口,重写Object类中的clone方法。
- 通过序列化来实现。
java语言中数据类型分为基本数据类型和引用数据类型,基本数据类型的值可以直接进行复制,但是引用数据类型只能复制引用地址。所以浅克隆和深克隆的区别就在于是否支持引用类型的成员变量的复制。
浅克隆
不支持对象中的引用类型复制值,仅仅复制地址。
简单来说就是,当对象被克隆时只复制它本身,和其他基本数据类型的成员变量,而引用类型的对象并没有复制。
深克隆
支持对象中的引用类型成员变量复制值,引用类型成员变量也会克隆一个新的对象。
简单来说,就是除了对象本身被复制外,对象包含的所有成员变量也将复制。
Java设计模式
设计模式本来是在建筑上使用的,后来运用于程序设计。
什么是设计模式?
设计模式是指一套被反复使用,代码设计经验的总结。针对一些问题的解决方式,经过很长时间的修改打磨最终成为一种固定的模式。
为什么要学习设计模式?
在面向对象的基础上(继承、封装、多态),更好的理解和运用。
使用设计模式的优点:
可以提高程序员的思维、编程、设计能力;
使程序更加标准化,提高软件开发效率;
使得代码的可重用性高,可读性强,灵活性好,可维护性强;
更好的理解源代码。
建模语言
统一建模语言(Unified Modeling Language,UML)是一种用于软件系统分析和设计的语言工具,用于帮助开发人员进行思考和记录思路的结果。
UML图:通过不同的图形和符号,描述软件模型以及各个元素之间的联系。
类图:是显示了模型的静态结构,特别是模型中存在的类、类的内部结构以及他们与其它类之间的关系等。类图是面向对象建模的主要组成部分。
类图是一种静态的结构图,描述了系统的类的集合,类的属性和类之间的关系,可以简化人们对系统的理解;类图是系统分析与设计的产物。
类
类是指具有相同属性、方法和关系的对象的抽象,它封装了数据的行为,是面向对象程序设计的基础,具有封装、继承和多态三种特性。
在UML中,类的UML:
—no:long 表示不可见参数no的数据类型为long;
+display():void表示方法display为public,且返回值为void;
protected(#)
接口
接口是一种特殊的类,它具有类的结构但是不能实例化,只可以被子类实现。它包含抽象操作,但是不包含属性。它描述了类或组件对外可见的动作。
在UML中,接口使用一个带有名称的圆圈表示。
类之间的关系
在系统中,类并不是孤立存在的,类与类之间存在一定关系的。根据类之间的耦合度从弱到强依次为:依赖关系、关联关系、聚合关系、组合关系、泛化关系和实现关系。
依赖关系
依赖关系是一种临时关系。代码中,某个类的方法通过局部变量、方法的参数或者对静态方法的调用来访问另一个类(被依赖类)中的某些方法来完成某些功能。
在UML类图中,依赖关系使用带箭头的虚线表示,从使用类指向被依赖类。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLETOFmh-1692757184340)(
)]
关联关系
关联关系是对象之间的一种引用关系,用于表示一类对象与另一类对象之间的联系,如老师与学生等。关联关系是类与类之间最常用的一种关系,分为关联关系,聚合关系和组合关系。
关联关系一般分为单向关联、双向关联、自关联。
单向关联:
在 UML 类图中单向关联用一个带箭头的实线表示。上图表示每个顾客都有一个地址,这通过让 Customer 类持有一个类型为 Address 的成员变量类实现。
双向关联:
从上图中我们很容易看出,所谓的双向关联就是双方各自持有对方类型的成员变量。
在 UML 类图中,双向关联用一个不带箭头的直线表示。
自关联:
自关联在 UML 类图中用一个带有箭头且指向自身的线表示。上图的意思就是Node 类包含类型为 Node 的成员变量,也就是“自己包含自己”。
聚合关系
聚合关系是关联关系的一种,是整体与部分之间的关系。
聚合关系也是通过成员对象来实现的,其中成员对象是整体对象的一部分,但是成员对象可以脱离整体对象而独立存在。例如,学校与老师的关系,学校包含老师,但如果学校停办了,老师依然存在。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。
组合关系
组合表示类之间的整体与部分的关系,但它是一种更强烈的聚合关系。
在组合关系中,整体对象可以控制部分对象的生命周期,一旦整体对象不存在,部分对象也将不存在,部分对象不能脱离整体对象而存在。例如,头和嘴的关系,没有了头,嘴也就不存在了。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。
继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父类与子类之间的关系,是一种继承关系。
在 UML 类图中,继承关系用带空心三角箭头的实线来表示,箭头从子类指向父类。在代码实现时,使用面向对象的继承机制来实现继承关系。例如上图, Student 类和 Teacher 类都是 Person 类的子类。
实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。例如上图,汽车和船实现了交通工具。
面向对象设计原则
如何同时提高系统的可维护性和可服用性。就需要遵从面向对象语言设计原则,可以在进行设计方案时减少设计错误,提高软件的实际水平。
单一职责
单一职责可以理解为一个类只负责一个功能领域中的相应职责。
优点:低耦合、高内聚。
开闭原则
开闭原则:扩展开放,修改关闭。
系统的需求是一直在变化的,因此在设计时需要考虑怎样设计才能在需求改变时,系统依旧可以稳定。
需要系统进行抽象化设计,抽象化是开闭原则的关键。在进行软件设计时提前将一些可能会改变的类设计为抽象类来隔离变化。在发生变化时,无需对抽象类进行改动,只需要添加具体类来实现业务功能即可。在不修改自己原有的代码的基础上扩展功能。
/*
开闭原则案例
*/
public class CarDemo {
public static void main(String[] args) {
new CarFactory().createCar(1);
new CarFactory().createCar(2);
new CarFactory().createCar(3);
new CarFactory().createCar(4);
new CarFactory().createCar(5);
}
}
/*
汽车工程类,专门负责造汽车
*/
class CarFactory{
/*
违反了开闭原则,后期如果添加新的汽车类,则需要修改代码
*/
public void createCar(int type){
if(type==1){
System.out.println("造宝马汽车"+new Car("宝马汽车"));
}else if(type==2){
System.out.println("造奥迪汽车"+new Car("奥迪汽车"));
}else{
System.out.println("造大众汽车"+new Car("大众汽车"));
}
}
}
class Car{
String name;
public Car(String name) {
this.name = name;
}
}
class CarDemo{
public static void main(String[] args) {
new CarFactory().carfactory(new BMW());
new CarFactory().carfactory(new Aodi());
new CarFactory().carfactory(new DaZhong());
}
}
class CarFactory{
void carfactory(Car car){
car.createCar();
}
}
//扩展业务时只需要继承Car类无需修改原代码
abstract class Car{
public abstract void createCar();
}
class BMW extends Car{
@Override
public void createCar() {
System.out.println("造宝马汽车");
}
}
class Aodi extends Car{
@Override
public void createCar() {
System.out.println("造奥迪汽车");
}
}
class DaZhong extends Car{
@Override
public void createCar() {
System.out.println("造大众汽车");
}
}
class BC extends Car{
@Override
public void createCar() {
System.out.println("造奔驰汽车");
}
}
优点:适应性强、灵活、稳定、可扩展性强、可复用。
里氏替换原则
回顾继承:
优势:提高了代码的复用性,提高了代码的扩展性。
弊端:继承是侵入式(只要继承,就必须拥有父类的属性和方法);继承机制很大的增加了耦合性(父类被子类继承,父类功能修改会影响子类)。
里氏替换原则:继承必须确保父类所拥有的性质子类中必然成立。
里氏替换原则的定义:通俗的讲就是,子类继承父类时除添加新的方法完成新增功能外尽量不要重写父类的方法。
public class CalculatorDemo{
public static void main(String[] args) {
System.out.println(new SuperCalculator().sum(5,5,5));
}
}
//计算器 基类
class Calculator {
//加法
public int add(int a,int b){
return a+b;
}
//减法
public int sub(int a,int b){
return a-b;
}
}
/*
超级计算器子类
*/
class SuperCalculator extends Calculator{
//重写了父类加法
@Override
public int add(int a, int b) {
return a+b+5;
}
//求和方法 子类新增的功能
public int sum(int a,int b,int c){
//调用add(),但是子类重写了父类方法,此处调用的子类方法发生了变化,这里相当于调用了重写的add方法,
int result = add(a,b);//重写add后结果为20,不重写结果为15
return result+c;
}
}
里氏替换原则的作用:功能正确性得到保障,实现开闭原则的重要方式之一,降低了需求变更时引入的风险。
依赖倒置
上层模块不应该依赖底层模块,它们都应该依赖于抽象
简单讲就是:要求对抽象进行编程,不要对实现进行编程,这样就降低了客户与实现模块间的耦合。就是针对抽象层编程,面向接口编程。
接口隔离
使用多个接口,而不使用单一的总接口,不强迫新功能实现不需要的方法。
迪米特原则
一个对象应该对其他对象有最少的了解
就是只对直接朋友进行交流
public class Demeter {
public static void main(String[] args) {
new SchoolManger().printAllEmployee(new CollegeManger());
}
}
/*
学校员工类
*/
class SchoolEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
/*
学院员工类
*/
class CollegeEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//学院员工管理管理类
class CollegeManger{
//生成学院所有的员工
public List<CollegeEmployee> getCollegeEmployee(){
ArrayList<CollegeEmployee> collegeEmployeeArrayList = new ArrayList<CollegeEmployee>();
for (int i = 0; i <10 ; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("学院员工的id="+i); //添加学院员工
collegeEmployeeArrayList.add(collegeEmployee);
}
return collegeEmployeeArrayList;
}
}
//学校员工管理类
class SchoolManger {
//生成学校的员工
public List<SchoolEmployee> getSchoolEmployee() {
ArrayList<SchoolEmployee> employeeArrayList = new ArrayList<SchoolEmployee>();
for (int i = 0; i < 5; i++) {
SchoolEmployee employee = new SchoolEmployee();
employee.setId("学校的员工id=" + i);
employeeArrayList.add(employee);
}
return employeeArrayList;
}
//输出学校员工和学院员工信息
public void printAllEmployee(CollegeManger collegeManger) {
//获取到学校员工
List<SchoolEmployee> employeeArrayList = this.getSchoolEmployee();
System.out.println("--------学校员工--------");
for (SchoolEmployee employee1 : employeeArrayList) {
System.out.println(employee1.getId());
}
System.out.println("--------学院员工--------");
List<CollegeEmployee> collegeEmployees = collegeManger.getCollegeEmployee();
//此处学校管理类中出现CollegeEmployee,此类与SchoolManger并非直接朋友,不合理
for (CollegeEmployee collegeEmployee : collegeEmployees) {
System.out.println(collegeEmployee.getId());
}
}
}
public class Demeter {
public static void main(String[] args) {
new SchoolManger().printAllEmployee(new CollegeManger());
}
}
/*
学校员工类
*/
class SchoolEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
/*
学员员工类
*/
class CollegeEmployee{
private String id;
public void setId(String id){
this.id = id;
}
public String getId(){
return id;
}
}
//学院员工管理管理类
class CollegeManger{
//生成学员所有的员工
public List<CollegeEmployee> getCollegeEmployee(){
ArrayList<CollegeEmployee> collegeEmployeeArrayList = new ArrayList<CollegeEmployee>();
for (int i = 0; i <10 ; i++) {
CollegeEmployee collegeEmployee = new CollegeEmployee();
collegeEmployee.setId("学院员工的id="+i); //添加学院员工
collegeEmployeeArrayList.add(collegeEmployee);
}
return collegeEmployeeArrayList;
}
public void printCollegeEmployee(){
List<CollegeEmployee> collegeEmployee = getCollegeEmployee();
for (CollegeEmployee employee : collegeEmployee) {
System.out.println("学员员工id="+employee.getId());
}
}
}
//学校员工管理类
class SchoolManger {
//生成学校的员工
public List<SchoolEmployee> getSchoolEmployee() {
ArrayList<SchoolEmployee> employeeArrayList = new ArrayList<SchoolEmployee>();
for (int i = 0; i < 5; i++) {
SchoolEmployee employee = new SchoolEmployee();
employee.setId("学校的员工id=" + i);
employeeArrayList.add(employee);
}
return employeeArrayList;
}
//输出学校员工和学院员工信息
public void printAllEmployee(CollegeManger collegeManger) {
//获取到学校员工
List<SchoolEmployee> employeeArrayList = this.getSchoolEmployee();
System.out.println("--------学校员工--------");
for (SchoolEmployee employee1 : employeeArrayList) {
System.out.println(employee1.getId());
}
//CollegeManger与SchoolManger是直接朋友,相互之间访问
System.out.println("--------学员员工-----------");
collegeManger.printCollegeEmployee();
}
}
组合/聚合服用原则
优先使用组合,使系统更灵话,其次才考虑继承,达到复用的目的。
案例:现在假设有一个 A 类,里面有两个方法,有一个类 B,想要复用这两个方法,请问有几种方案?
方案一:让B继承A;
方案二:B中引用A;
方案三:方法传参;
原则总结:
开闭原则:要求对扩展开放,对修改关闭
里氏替换原则:不要破坏继承体系
依赖倒置原则:要求面向接口编程
单一职责原则:实现类职责要单一
接口隔离原则:在设计接口的时候要精简单一
迪米特法则:只与直接的朋友的通信
合成复用原则:尽量使用聚合和组合的方式,而不是使用继承
设计原则的核心思想
找出应用中可能需要变化之处,独立出来,不要和不需要变化的代码混在一起
针对接口编程,而坏是针对实现编程
为了交互对象的松耦合设计而努力
遵循设计原则:就是为了让程序高内聚,低耦合
23种设计模式
https://www.runoob.com/design-pattern/design-pattern-tutorial.html
- 单例(Singleton)模式:某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其拓展是有限多例模式。
- 原型(Prototype)模式:将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。
- 工厂方法(Factory Method)模式:定义一个用于创建产品的接口,由子类决定生产什么产品。
- 抽象工厂(AbstractFactory)模式:提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。
- 建造者(Builder)模式:将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。
- 代理(Proxy)模式:为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。
- 适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
- 桥接(Bridge)模式:将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现,从而降低了抽象和实现这两个可变维度的耦合度。
- 装饰(Decorator)模式:动态的给对象增加一些职责,即增加其额外的功能。
- 外观(Facade)模式:为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。
- 享元(Flyweight)模式:运用共享技术来有效地支持大量细粒度对象的复 用。
- 组合(Composite)模式:将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。
- 模板方法(TemplateMethod)模式:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
- 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
- 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
- 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
- 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能 力。
- 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
- 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
- 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数 据,而不暴露聚合对象的内部表示。
- 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
- 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
- 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。
常用的设计模式
单例模式
有些系统或者项目中,为了节省资源、保证数据内容的一致性,对某些类要求只能创建一个对象实例。如Windows中的只能打开一个任务管理器,这样可以避免因为打开多个任务管理器窗口而造成内存资源浪费,或者出现各个窗口显示的内容不一致等错误。
特点:
只能有一个实例对象
单例对象必须由单例类自行创建(关联关系中的自关联)
单例类需要对外提供一个访问单例的全局访问点
单例模式实现形式有两种:饿汉式、懒汉式。
饿汉式单例
在类被加载时创建对象,不存在线程安全问题。
public class Window {
//创建静态对象,随着类的加载一起加载
private static Window window = new Window();
private Window(){}//构造方法私有化,防止外部直接构造
//提供给外部获取实例对象的方法
public static Window getWindow() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return window;
}
}
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Window.getWindow());
}).start();
}
}
}
/*
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
com.single.demo1.Window@6bfcab86
*/
//由结果可以看出创建的十个对象是同一个对象
懒汉式单例
在类加载的时候不创建对象,在使用的时候创建。这时生成的对象需要我们自己进行控制,存在线程安全问题。
public class Window {
private static Window window=null;
private Window(){}
/*
懒汉式单例会出现线程安全问题:
在多线程访问时,可能会有多个线程同时进行到if,就会创建出多个对象
*/
public static Window getWindow(){
if(window==null){
window = new Window();
}
return window;
}
}
//解决方法1.给方法加锁, 可以解决,但是效率低,一次只能有一个线程进入获取
public static synchronized Window getWindow() {
if (window == null) {
window = new Window();
}
return window;
}
//解决方法2.给代码块加锁,双重检索+synchronized
public static Window getWindow(){
if(window==null){
synchronized(Window.class){
if(window == null){
window = new Window3();
}
}
}
return window;
}
经过上面两次改进任然存在问题,就是指令重排问题,new一个对象的时候一般步骤为:申请空间——调用构造方法——将对象地址赋值给引用变量。
问题:在一个线程先将半成品对象地址赋值给引用变量时暂停了,另一线程来了引用变量不为空,指向半成品对象。
解决方法:在解决办法2的基础上加volatile
private static volatile Window window=null;
Runtime类
Jdk 中的源码 Runtime 类就是一个单例类,利用 Runtime 类可以启动新的进程或进行相关运行时环境的操作。比如,取得内存空间以及释放垃圾空间。