JavaPro
一、注解
1.1注解简单介绍
注解是java中的一种标注,可以用在类、方法、变量以及形参上。会在编译时编译到字节码文件中,最后会通过反射机制来读取注解内容对其进行解析。
1.2注解的分类
1.1.1内置注解
内置注解是java中已经定义好的注解
1.1.2元注解
元注解是注解的注解。
@Target({TYPE, FIELD, METHOD}) Target定义注解应该作用在哪些成员目标上
@Retention(RUNTIME) 定义被修饰的注解在什么时候生效
//表示该注解可以作用在类、属性、方法等位置
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)//表示该注解会从源码中开始生效
1.3自定义注解
二、对象克隆
2.1对象克隆的分类
浅克隆:浅克隆在进行对象的克隆时会对原本对象中的基本类型数据进行复制,对引用类型数据并不能复制,而是会将新对象中的该属性指向原对象中的该数据。
person中有引用类型的name变量,对p1进行克隆后比较发现p1和p2的name是同一个引用,验证了浅克隆。
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(100,"jim");
Person p2 = p1.clone(); //克隆一个新的对象
System.out.println(p1.name==p2.name);//true
}
深克隆:深克隆和浅克隆的区别在于,深克隆会将被克隆对象中的引用类型对象也进行克隆。
preson中有address属性,为引用类型,在对其进行深克隆后对p1执行的引用进行修改,发现p2的地址不会受影响,因此他们的引用类型属性不是一个引用,完成了克隆。
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address();
address.setAddress("汉中");
Person p1 = new Person(100,"jim");
p1.setAddress(address);
Person p2 =p1.clone();
p2.setName("tom");
address.setAddress("西安");
System.out.println(p1);
//Person{num=100, name='jim', address=Address{address='西安'}}
System.out.println(p2);
//Person{num=100, name='tom', address=Address{address='汉中'}}
}
2.2实现克隆
实现克隆,需要被克隆对象所在的类实现cloneable接口,并重写clone()方法。
浅克隆:
对People类的实例进行克隆
@Override
protected Person clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
return person;
}
深克隆:
方式一:想要对People类的实例进行深克隆,类中含有引用类型数据,address,需要在Address类中也对clone()方法进行重写。
//Person类中
@Override
protected Person clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
person.address = (Address)person.address.clone();
//深度复制,联同person中关联的对象也一同克隆.
return person;
}
//Address类中
@Override
protected Address clone() throws CloneNotSupportedException {
return (Address)super.clone();
}
方式二:利用反序列化实现深克隆,反序列化可以将字节流文件恢复成一个对象。
public Person myclone() {
Person person = null;
try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。
// 所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流反序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
person = (Person) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return person;
}
三、设计模式
3.1什么是设计模式
计算机领域中设计模式是广大的开发前辈们,在实际的开发中把重复出现的问题的解决方案进行优化,最终得到一套最优解决方案,值得被拿来反复使用.
为什么要学习设计模式
设计模式是好的经验,学习好的经验
可以提高我们的编程能力和设计能力
可以提高软件设计的标准化,提高开发效率
可以提高代码的复用性,可扩展性
可以更好的理解阅读源码(看懂底层复杂的源码结构)
3.2建模语言
统一建模语言(Unified Modeling Language,UML),是一种软件设计阶段,用于表达软件设计思路的语言,使用类图的方式,将类与类之间的关系进行描述.
类图: 类和接口关系
3.3类与类的关系
依赖关系
该种关系最弱,是一种使用关系,如在类中的方法中使用到了另一个类作为参数,或者使用局部变量或静态方法来访问另一个类,方法执行完后,就关系就解除了。
例如人类中有一个打电话方法,在打电话方法中要用到手机类,只是在打电话时,临时使用.
关联关系
关联关系可以根据强弱关系分为: 一般关联 -> 聚合 -> 组合
一般关联:在一个类中把另一个类当做自己的成员,可以是单向,双向,自关联(即本类中将本类对象作为成员变量,如Student类中有个 Student student作为属性)
聚合: 学校和老师关系, 学校不存在了,老师可以独立的存在
组合: 头和嘴关系 头如果不存在了,那么嘴也就没有存在意义了
继承关系
同一类之间相互继承
实现关系
类和接口关系
3.4面向对象设计原则
1.单一职责原则
一个类只负责一件事情,不要让一个类做过多的事情,否则类内部的功能耦合度太高,修改一些功能时,相互会有影响。
2.开闭原则
在程序扩展新功能时,尽量不要修改原有的代码,尽可能通过扩展新的类来实现功能,核心思想是对扩展开放,对修改封闭。
3.依赖倒置原则
上层不应该依赖于细节(上层应当是抽象的),只需要定义好功能即可,具体实现由底层在抽象的指导下完成细节的具体实现。总之,应当是底层依赖与上层的抽象。
4.接口隔离原则
不要将所有的功能都定义在同一个接口中,应使用多个接口,将不同的功能定义在不同的接口中。
5.组合复用原则
优先使用关联,实现代码的复用,其次才考虑继承。在类B中,想使用类A中的某些功能, 可以使用继承,但是会使得类体系的耦合度提高,因此优先使用关联关系,替代继承关系, 也可以使用更弱的依赖关系实现。
6.里氏替换原则
该原则主张子类重写父类的非抽象方法时,不要影响到原方法的功能。因此我们尽量对父类中的方法定义成抽象的,抽象的方法没有方法体,因此一定不会背离里氏替换原则。
7.迪米特原则
只和朋友交谈,不和陌生人说话,
两个类之间如果没有直接联系,那么就不要相互之间调用,可以通过一个第三方调用
例如:明星和粉丝,公司之间的交流 可以通过经纪人来完成
3.5Java设计模式
1.单例模式
单例模式分为饿汉式和懒汉式,单例模式下构造器被私有化,外界无法调用。
1.饿汉式
饿汉式单例实在类加载时就急切地创建一个静态实例,构造器私有化后外部无法创建新的对象,只能通过getInstance方法来获得静态实例。这种单例模式因为可以保证只创建一个对象,因此是线程安全的。但是也存在问题,如类加载之初还用不到单例,但是已经创建出来占据内存了。
public class Window {
/*
在类加载时,只创建一个对象
*/
private static Window window = new Window();
/*
构造方法私有化,在类之外就访问不到构造方法
*/
private Window(){
}
/*
向外界提供一个方法,获得new出来的唯一的对象
*/
public static Window getInstance(){
return window;
}
public static void test(){
}
}
2.懒汉式
懒汉式单例不着急创建对象,在第一次调用时才会创建唯一的对象。但是这种写法存在线程安全问题,如果多个线程同时调用,且还没有单例存在,就可能导致创建了多个对象,破坏了单例性。
解决方案:双重检索+volatile
1.多线程中,一开始window是null,但是如果window变量被初始化后,因为不可见性,其他线程还以为window是null,就会再次创建对象。因此使用volatile来解决此问题。
2.使用synchronized锁保证每次只有一个线程进去创建对象。
public class Window {
private static volatile 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 void test(){
}
}
2.工厂模式
1.简单工厂
简单工厂并不属于java的设计模式,因为其违背的开闭原则。将产品进行抽象,而工厂可以创造所有产品,如果有新的产品需要在大工厂里添加,这便是修改了原本的代码。
2.工厂方法
将工厂和产品都进行抽象一个工厂生产一个产品,为每个产品都要定义一个工厂。这样添加新的产品时为其添加个新的工厂即可,遵守了开闭原则。但是这种模式如果产品多了代码量会大幅增加。
3.抽象工厂
在抽象工厂中,一个工厂可以生产一个公司的多个产品,不需要再一个产品一个工厂了,工厂就不会太过冗余了。
3.原型模式
如果我们需要创建多个对象时,每次new+构造方法执行速度慢(原因:每次都要执行构造方法中的逻辑),那么我们就可以先创建一个对象,在已有对象的基础上进行对象克隆(拷贝),提高创建对象的效率.
例如: 要手写5份简历, 太浪费时间了,我们可以写好一份后,复印4次,就可以得到多个对象,效率高.
2.结构型模式
3.行为模式
4.代理模式
代理模式主张客户和服务者不直接接触,而是通过第三方代理者来进行沟通。
该模式有以下优点:
1.代理对象可以为目标对象提供保护
2.代理对象可以为目标对象拓展功能
3.代理对象可以降低目标对象和客户之间的耦合度
代理模式的结构
1. 抽象主题类:使用接口或抽象来声名目标对象存在的业务。
2. 真实主题类:实现了抽象主题中的具体业务,是代理对象所真是用到的类
3. 代理类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理
静态代理可以在保证开闭原则的前提下提供代理,但是有个明显的缺点就是静态代理只适合在情况简单的时候使用。
因为一个代理类,只能为那些实现了某个接口的目标类实现代理,如果要为其他接口的目标类实现代理,就必须重新创建新的代理的,在复杂场景下,就不太适合了
动态代理
1.jdk动态代理
jdk动态代理是采用反射机制来进行动态代理
2.cglib动态代理
cglib代理是spring中的一种实现,使用cglib代理,目标类可以不用实现接口.采用底层的字节码生成技术,为我们的目标类生成子类对象,采用方法拦截技术,在调用调用方法时,会进入到拦截中,获得所调用的方法即可.目标类不能是final修饰的. 目标类中的方法如果为final,static也不能进行增强.
spring中两种动态代理方式都进行了实现,可以根据不同的情况进行自动的选择
1.单例对象,没有实现接口的类,可以使用cglib代理
2.原型对象(创建多个对象),实现接口的类,可以使用jdk代理
5.模板方法模式
模板方法模式讲究将程序中固定流程中的相同步骤进行提取(如去银行存款:1.抽号 2.排队 3.具体操作 4.为本次服务打分)
在抽象父类中提供一个模版方法,在模版方法中,按顺序把固定步骤的方法进行调用,其中有些方法时固定的,如抽号、牌堆、打分等方法,而具体操作因人而异,在模板方法模式中就用不同的子类来重写该方法。
此种方法模式符合开闭原则,想要添加新的功能只需要添加新的类即可。
6.策略模式
策略模式主张不同的情况采取不同的策略。
策略模式结构如下:
1.抽象策略类:这是一个抽象角色,通常由一个接口或抽象类实现。 此角色给出所有的具体策略类所需的接口。
2.具体策略类:实现了抽象策略定义的接口,提供具体的 算法实现或行为。
3.环境类:持有一个策略类的引用,最终给客户端调用