Java进阶 面试速记
注解
注解
@Override 类似一个标签, 作用在方法上,表示此方法是从父类中重写而来
注解是java中的标注方式,可以最用在类,方法,变量,参数成员上
在编译期间,会被编译到字节码文件中,运行时通过反射机制获得注解内容,进行解析.
内置注解
java中内定好的注解
例如@Override
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。
@FunctionalInterface 用于指示被修饰的接口是函数式接口。
元注解
注解的注解 ,用于定义注解的注解
@Target({TYPE, FIELD, METHOD}) 定义此注解应该作用在哪些成员目标上 @Retention(RUNTIME) 定义注解在什么时候生效 @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE)
自定义一个注解
定义注解
使用注解
解析注解
对象克隆
对象克隆: 从一个已有的对象克隆出来一个新的对象, 新对象可以拥有已有对象中的数据.
克隆时基本数据类,直接可以把值克隆到新对象中,深浅克隆判断依据主要是关联的其他对象是否被新克隆出一个对象.
浅克隆: 克隆一个对象,只把关联对象的地址克隆了一下,并没有创建新的关联对象.
深克隆: 克隆一个对象,把关联的对象也进行克隆,克隆出一个新的关联对象.
如何实现克隆:
public class Person implements Cloneable{ /* 重写Object类中clone()方法 */ @Override protected Person clone() throws CloneNotSupportedException { Person person = (Person)super.clone();//调用父类中的clone方法,克隆对象 return person; } } 测试 Person p1 = new Person(100,"jim"); Person p2 = p1.clone(); //克隆一个新的对象 System.out.println(p1==p2);//false
如何实现深克隆:
方式1: 关联的对象也要实现Cloneable接口,重写clone(),不管有多少级,只要需要深克隆,那么就需要多级克隆
方式2:对象序列化,反序列化
对象序列化--将对象信息输出
对象反序列化--将输出的对象信息,重新输入,创建新的对象
总结Object类中方法:
equals() == 一般的类都重写了equals(),用来比较对象中的内容是否相等
toString()
hashCode(); 获得对象内存地址, 一般的类的也进行重写,获得用对象内容计算出来的哈希值
wait() 线程等待
notify() 唤醒等待的线程
notifyAll()
getClass() 获得类的Class对象
finalize() 对象被回收前调用,只调用一次
clone() 对象克隆
设计模式
什么是设计模式:
最早的设计模式是建筑领域的,后来发展到软件开发领域.
就是广大的开发前辈们,在实际的开发中把重复出现的问题的解决方案进行优化,最终得到一套最优解决方案.
可以被拿来反复使用.
为什么要学习设计模式
设计模式是好的经验,学习好的经验
可以提高我们的编程能力和设计能力
可以提高软件设计的标准化,提高开发效率
可以提高代码的复用性,可扩展性
可以更好的理解阅读源码(看懂底层复杂的源码结构)
建模语言
统一建模语言(Unified Modeling Language,UML),是一种软件设计阶段,用于表达软件设计思路的语言,
使用类图的方式,将类与类之间的关系进行描述.
类图: 类和接口关系
类与类的关系
依赖关系
在一个类中的方法中使用到了另一个类,例如把另一个类当做参数传入,
具有临时性,关系较弱. 方法执行完后,就关系就解除了.
一般也称为: use-a
例如人类中有一个打电话方法,在打电话方法中要用到手机类,只是在打电话时,临时使用.
关联关系
在一个类中把另一个类当做自己的成员.
例如人类中关联一个地址类
关联关系可以根据强弱关系分为: 一般关联,聚合,组合
聚合: 学校和老师关系, 学校不存在了,老师可以独立的存在
组合: 头和嘴关系 头如果不存在了,那么嘴也就没有存在意义了
继承关系
同一类之间相互继承
实现关系
类和接口关系
面向对象设计原则
单一职责原则
一个类只负责一件事情,不要让一个类做过多的事情,否则类内部的功能耦合度太高,修改一些功能时,相互会有影响.
尽可能的降低类的粒度.
举例: 用户信息类
开闭原则
在程序扩展新功能时,尽量不要修改原有的代码,尽可能通过扩展新的类来实现功能.
对扩展开放,对修改关闭
依赖倒置原则
上层不应该依赖于细节(实现),上层应该是抽象的,进行功能的定义即可,变动小
底层应该依赖于抽象,底层在抽象的指导下进行细节上的具体功能实现.
上层应该是抽象,底层是具体实现,底层依赖于上层的抽象.
接口隔离原则
不要把所有的功能都定义到一个接口中,应该使用多个接口,把不同的功能定义在不同的接口中.
组合/聚合复用原则
优先使用组合/聚合(关联),实现代码的复用,其次才考虑继承.
在类B中,想使用类A中的某些功能, 可以使用继承,使得类的体系耦合性高,
所以也可以使用关联关系,替代继承关系, 还可以使用更弱的依赖关系实现.
里氏替换原则
在使用继承时,如果父类中方法是非抽象的,那么子类继承父类后,对父类中的非抽象方法进行重写时,需要注意小心, 在使用父类的时候,如果换成子类对象,由于子类对象重写的父类方法,从而导致有可能实现不相同,导致结果不对.
实际开发,父类中的功能尽可能定义为抽象的.
迪米特原则
只和朋友交谈,不和陌生人说话,
两个类之间如果没有直接联系,那么就不要相互之间调用,可以通过一个第三方调用
例如:
明星和粉丝,公司之间的交流 可以通过经纪人来完成.
面向对象语言设计模式总共有23种
分为3大类:
根据模式是用来完成什么工作来划分,这种方式可分为创建型模式、结构型模式和行为型模式 3 种。
创建型模式:用于描述“怎样创建对象”,它的主要特点是“将对象的创建与使用分离”。提供了单例、原型、工厂方法、抽象工厂、建造者5 种创建型模式。
结构型模式:用于描述如何将类或对象按某种布局组成更大的结构,提供了代理、 适配器、桥接、装饰、外观、享元、组合 7 种结构型模式。
行为型模式:用于描述类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,以及怎样分配职责。提供了模板方法、策略、命令、职责链、状态、观察者、中介者、迭代器、访问者、备忘录、解释器 11 种行为型模式
单例模式
解决一个类在一个项目中,只能创建一个对象.
单例模式有两种写法:
1.饿汉式(急切式)单例
在类加载时,直接把唯一的对象创建好,调用方法时,直接返回即可,不存在线程安全问题,
只是在类加载之初可能还用不到,生命周期长.
2.懒汉式单例
在类加载时,不着急创建对象,在第一次调用方法时才会创建唯一的对象,
这种写法存在线程安全问题. 需要解决线程安全问题
双重检索+volatile
public class Window { /* volatile修饰的变量,不同线程中修改了立即可见,禁止指令的重排序 Window window = new Window(); 上面这行代码,从指令的角度上来讲,可以有三条指令 1.new 申请内存空间 2.初始化对象 3.把对象地址赋给左边的引用变量 如果正常按这个顺序执行没有问题的. 但是在执行的过程中,有可能在执行第2步执行,同时把第3步提前执行了,此时对象还可能没有初始化完成, 其他线程可能会拿到一个没有初始化完成的对象. 所以单例成员需要使用volatile修饰,禁止指令的重排序 */ private volatile static Window window=null; private Window(){ } /* 双重检索,效率提高 还可以有其他的写法 */ public static Window getInstance(){ if(window==null){ synchronized (Window.class){ if(window==null){ window = new Window(); } } } return window; }
工厂模式
把对象的创建和使用分离,提供一个工厂类专门负责对象的创建,
一个工厂可以创建一类产品
简单工厂
简单工厂并不是一种设计模式,这只是对工厂模式的引入
工厂角色 全能的工厂
抽象产品(接口/抽象类), 用来组织关系
具体产品,需要继承/实现抽象产品
客户端--->工厂--->对象
public static Car createCar(String name){ if(name.equals("aodi")){ return new Aodi(); } if(name.equals("bmw")){ return new Bmw(); } if(name.equals("dazhong")){ return new DaZhong(); } return null; }
简单工厂,违背了开闭原则,在增加一个新的具体产品时,需要修改工厂代码
工厂方法
首先将工厂也进行抽象,一个抽象工厂负责生产一类产品,定义规则,
为每一种具体的产品都提供一个专门的工厂负责生产.
这样后来添加新产品时,不需要修改原来的工厂,直接扩展一个新的工厂即可.
遵守了开闭原则
工厂方法中,一个工厂只负责一类产品生产,但是产品多了以后,会增加代码量(弊端)
抽象工厂
抽象工厂模式中,工厂不再只负责单一产品生产,
而是工厂可以生产同一家公司,在一个工厂中可以生产同一家的多个产品,这样就不需要太过冗余的工厂类
原型模式
如果我们需要创建多个对象时,每次new+构造方法执行速度慢(原因:每次都要执行构造方法中的逻辑),
那么我们就可以先创建一个对象,在已有对象的基础上进行对象克隆(拷贝),提高创建对象的效率.
例如: 要手写5分简历, 太浪费时间了
写好一份后,复印4次,就可以得到多个对象,效率高.
对象克隆
如何实现对象克隆:
1.类实现Cloneable接口 重写Object类中的clone()
2.序列化,反序列化
注意深克隆和浅克隆问题
2.结构型模式
代理模式
案例: 汽车厂只管造汽车,不直接把汽车卖给个人客户.
把卖汽车交给代理商(4s店)卖给普通个人客户.
代理商就可以在中间向客户介绍汽车信息, 买完汽车后,还可以帮助客户办理手续.
代理商在中间增加了一些额外的功能.
汽车厂自卖车, 汽车厂不仅要卖车,还要给客户介绍汽车,还要帮助办理手续,都要自己完成 sell(){ 介绍汽车 汽车厂卖车 办理手续 } sell(){ 汽车厂卖车 } //代理 sell(){ 介绍汽车 sell(); 办理手续 }
代理模式: 为目标对象(汽车厂)提供一个代理对象,不让目标对象直接与客户进行交互.
而是通过代理对象,
好处: 代理对象可以为目标对象提供保护
代理对象可以为目标对象扩展功能
代理模式可以降低目标对象与客户之间的耦合度
代码结构:
抽象主题: 汽车厂和代理做同一件事,所以进行抽象 都是买汽车
真实主题(目标对象 汽车厂)
代理对象
代理模式又分为静态代理和动态代理
静态代理:
静态代理在一些简单的场景下可以使用,
因为一个代理类,只能为那些实现了某个接口的目标类实现代理,
如果要为其他接口的目标类实现代理,就必须重新创建新的代理的.
在复杂场景下,就不太适合了
动态代理:
动态代理可以在运行时,根据不同的类生成代理对象,可以为所有的任意的类提供代理.
动态代理分为:
jdk代理:
jdk代理是通过反射机制实现的,在运行时,可以动态的获得目标类的接口,获得接口中的方法信息,从而为目标类生成代理对象,
只要写一个代理对象生成器,就可以为所有的类生成代理对象,
但是jdk代理方式实现时,目标类必须要实现一个接口, 不实现接口,就不能使用jdk代理
public Object getProxy(){ return Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),this); }
cglib代理:
cglib代理是spring中的一种实现,使用cglib代理,目标类可以不用实现接口.
采用底层的字节码生成技术,为我们的目标类生成子类对象,采用方法拦截技术,在调用调用方法时,
会进入到拦截中,获得所调用的方法即可.
目标类不能是final修饰的. 目标类中的方法如果为final,static也不能进行增强.
spring中两种动态代理方式都进行了实现,可以根据不同的情况进行自动的选择
1.单例对象,没有实现接口的类,可以使用cglib代理
2.原型对象(创建多个对象),实现接口的类,可以使用jdk代理
3.行为模式
模版方法模式
模版方法模式:
将程序中一些固定流程的步骤进行提取(银行取款,存款,转账 抽号,排队,操作,评分),
在抽象父类中提供一个模版方法,在模版方法中,按顺序把固定步骤的方法进行调用,
其中有一些方法是公共的都一样,在父类中进行实现即可,
其中还有一些方法实现不同,定义为抽象的,就需要创建不同的子类来继承重写,
使用时,子类对象只要调用模版方法, 相同的功能调用抽象父类中的方法,不同的调用子类中自己重写的即可.
复合开闭原则,增加功能时,只需要扩展新的类即可
//模版方法 public void handle(){ this.offerNumber();//抽号 this.lineup();//排队 this.business();//具体的操作 抽象方法,在子类中实现 this.score();//平分 } //抽号 public void offerNumber(){ System.out.println("抽号"); } //排队 public void lineup(){ System.out.println("排队"); } //办理具体业务--抽象方法,由具体子类实现 public abstract void business(); //评分 public void score(){ System.out.println("评分"); }
策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构 策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。
具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
例如
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋节、国庆节)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
优点: 策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以自由切换。易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“, 避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
使用场景 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为。