一.注解
最先接触到的注解:@Override 表示对父类方法的重写。
注解是java中的标注方式,可以用在类、方法、参数成员上。
在编译期间,会被编译到字节码文件中,运行时通过反射机制获得注解内容,进行解析。
内置注解
java中定义好的注解。
eg:@Override
@Deprecated - 标记过时方法。如果使用该方法,就会报编译警告。
@SuppressWarnings - 指示编译器去忽视注解中声明的警告。
@Functionallnterface - 用于指示被修饰的接口是函数式接口。
元注解
注解的注解,用于定义注解时使用的注解。
@Target({TYPE, FIELD, METHOD}) 定义此注解应该作用在哪些成员目标上
@Retention(RUNTIME) 定义注解在什么时候生效
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
@Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方.)
ElementType.TYPE 可以应用于类的任何元素。
ElementType.CONSTRUCTOR 可以应用于构造函数。
ElementType.FIELD 可以应用于字段或属性。
ElementType.LOCAL_VARIABLE 可以应用于局部变量。
ElementType.METHOD 可以应用于方法级注释。
ElementType.PACKAGE 可以应用于包声明。
ElementType.PARAMETER 可以应用于方法的参数。
@Retention:@Retention 定义了该注解被保留的时间长短:某些注解仅出现
在源代码中,而被编译器丢弃;而另一些却被编译在 class 文件中;编译在 class
文件中的注解可能会被虚拟机忽略,而另一些在 class 被装载时将被读取(请注
意并不影响 class 的执行,因为注解与 class 在使用上是被分离的)。用于描述
注解的生命周期(即:被描述的注解在什么范围内有效)取值有:
1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在 class 文件中有效(即 class 保留)
3.RUNTIME:在运行时有效(即运行时保留)
eg:
自定义一个注解
定义注解
使用注解
解析注解
二.对象克隆
为什么要克隆?
new出来的对象的属性都是默认值,如果需要一个新的对象来保存当前对象的属性值时,就需要用到clone()方法了。
误区:
Student stu1 = new Student ();
Student stu2 = stu1 ;这样不是对象克隆,只是两个引用指向了同一个对象,这种只能称为引用复制,两个引用指向的还是同一个对象。
深克隆和浅克隆的区别?
在于是否支持引用类型的成员变量的复制。
浅克隆:克隆一个对象,只把关联对象的地址克隆了一下,并没有创建新的关联对象。
深克隆:克隆一个对象,把关联的对象也进行了克隆,克隆出了一个新的关联对象。
克隆实现
浅克隆:实现Cloneable接口,重写clone()方法,调用Object中的clone()方法,并进行向下转型。
public class Person implements Cloneable{
int num;
String name;
/*
重写Object类中clone()方法
*/
@Override
protected Person clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
return person;
}
}
//测试
package org.example.cloneDemo.demo1;
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person p1 = new Person(100,"jim");
Person p2 = p1.clone(); //克隆一个新的对象
System.out.println(p1==p2);//false
}
}
结果返回:false;
深克隆:当Preson的成员变量有其他的引用型的变量的话(其它类对象等),这个时候想要实现深克隆有两种方法:
1.关联的对象也要实现Cloneable接口,重写clone()方法,不管有多少级,只要想要实现深克隆,那么就需要多级克隆。
2.对象序列化,反序列化
对象序列化---将对象信息输出
对象反序列化---将输出的对象信息,重新输入,创建新的对象。
eg:
public class Address implements Serializable {
//000000000000000000000000
String address;
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
import java.io.*;
public class Person implements Serializable {
int num;
String name;
Address address;
public Person() {
}
public Person(int num, String name) {
this.num = num;
this.name = name;
}
/**
* 自定义克隆方法
* @return
*/
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;
}
}
三.Java设计模式
设计模式的概念
最早的设计模式式建筑领域的,后来才发展到软件开发领域。
就是广大的前辈们,在实际的开发中,把重复出现的问题的解决方案进行优化,最终得到一套最优解决方案。
可以拿出来重复使用。
为什么要学习设计模式?
设计模式是好的经验,学习好的经验。
可以提高我们的编程能力和设计能力。
可以提高我们软件设计的规范化,提高开发效率
可以提高代码的复用性和扩展性。
可以更好的理解阅读源码(看懂底层复杂的源码结构)。
建模语言
统一建模语言(Unified Modeling Language,UML),是一种软件设计阶段,用于表达设计思路的语言,使用**类图**的方式,将类与类之间的关系进行描述。
类图:类与接口之间的关系。
类之间的关系
1.依赖关系
在一个类的方法中使用到了另一个类,(例如,方法中使用到了另一个类作为参数),具有临时性,关系较弱,方法执行完之后关系就解除了。
一般也称为:use-a
eg:人类中有一个打电话的方法,打电话的方法中有一个手机的参数,只是打电话的时候才会使用手机这个类
2.关联关系
在一个类中把另一个类当作是自己的成员。
例如:一个类中有另一个类的对象作为成员变量。
关联关系根据强弱又可以分为三类:
一般关联:一般关联就是上面说的一个类中引用另一个类。
聚合关联:是一种强关联关系,是一种整体与部分的关系,整体不存在部分依旧可以单独存在。
例如学校和老师的关系,学校不存在,老师还是可以存在的。
组合关联:是一种更为强烈的聚合关系,也是整体和部分的关系,整体不存在部分也就不存在了。
例如头和嘴之间的关系,头不存在,嘴也就不存在了。
3.继承关系
继承关系就是父类子类之间的关系,是一种继承关系,是is-a的关系。
4.实现关系
实现关系就是类与接口之间的关系,类实现接口。
面向对象设计原则(7种)
在面向对象的设计过程中,首先需要考虑的是如何同时提高一个软件系统的可维护性和可复用性。这时,遵从面向对象的设计原则,可以在进行设计方案时减少错误设计的产生,从不同的角度提升一个软件结构的设计水平。
1.单一职责原则
一个类只负责一个一件事情,不要让一个类做过多的事情,否则类内部的功能耦合度太高,修改一些功能时,相互会有影响,应该尽可能低的降低类的粒度。
举例:用户信息类
2.开闭原则
开闭原则即扩展开放,对修改封闭。就是说你可以扩展其他的功能,但是不能修改本身的代码。
在程序扩展新功能时,尽量不要修改原有的代码,尽可能通过扩展新的类来实现功能.
3.依赖倒置原则
上层模块不应该依赖底层模块,它们都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象.
上层应该是抽象的,底层是具体实现,底层应该依赖于上层的抽象。
也就是说:抽象类不应该局限于实现类,抽象类可以继承抽象类,实现类实现抽象类。
面向抽象编程,不要面向实现编程。
4.接口隔离原则
使用多个接口,而不是使用单一的总接口,不强迫新功能实现不需要的方法。
5.组合/聚合复用原则
优先使用组合/聚合(关联),实现代码的复用性,其次再考虑继承。
在类B中要想使用类A的功能,可以使用继承使类的体系耦合度变高,
所以也可以使用关联关系代替继承关系,还可以使用更弱的依赖关系实现。
eg:在类B的成员中关联一个类A的变量,在类B的方法中就可以使用类A中的方法。
6.里氏替换原则
在使用继承时,如果父类中的非抽象方法被子类重写时需要小心,在使用父类时,如果换成子类对象,调用这个非抽象方法时,方法就会调用子类中已经被重写过的方法,就可能会导致实现不同,导致结果不对。
编译看左边,运行看右边。
在实际的开发中,抽象类中的方法都应该尽可能设置为抽象的。
7.迪米特原则
之和朋友交谈,不和陌生人说话。
两个类之间如果没有直接关系,那么就不要相互之间调用,可以通过一个第三方来调用。
例如:
明星和粉丝,公司之间的交流都通过经纪人来完成。
面向对象设计模式(23种)
分为三大类:1.创建型模式 2.结构型模式 3.行为型模式
常用的设计模式:
单例模式(创建型模式)
解决一个类在一个项目当中只能创建一个对象的问题。
单例模式分为两种写法:
1.饿汉式单例模式
直接在类加载的时候就把对象给new出来,不存在线程安全的问题,但是对象的存在周期太长。
/**
* 饿汉式单例模式
*/
public class Window {
private static Window window=new Window();
private Window(){
}
public static Window getInstance(){
return window;
}
}
2.懒汉式单例模式
在类加载时先不new处对象,而是在第一次使用对象(调用拿到对象的方法)时才将对象给new出来并返回。
这种模式可能会存在线程安全的问题。
为什么会出现线程安全问题?
因为懒汉式单例模式是在第一次使用对象才new出来这个唯一的对象,假如说,两个线程同时进入到getInstance()中,并且同时进行判断该唯一对象不为空,
那么这两个线程就可能同时进入同时new对象,那么这样就会出现问题了。
如何解决线程安全问题?
首先在方法上面加锁也是可以的,但是效率太低,一次只能一个线程进来。
使用(volatile+双重检索)的解决方案
/**
* 懒汉式单例模式
*/
public class Window {
private volatile static Window window=null;
private Window(){
}
//双重检索+volatile 为了消除 window=new Window(); 乱序性的问题
public static Window getInstance(){
if(window==null){
synchronized (Window.class){
if(window==null){
window=new Window();
}
}
}
return window;
}
}
这里的volatile关键字的作用?问什么要使用它?
因为new对象这个操作是可以分为三条指令的:
1.申请内存空间
2.初始化对象
3.把对象地址赋给左边的引用变量
使用volatile关键字就是为了防止这三条指令的乱序性问题,万一先执行了3这条指令,那么肯定也是有问题的。
Runtime类
Jdk 中的源码 Runtime 类就是一个单例类,利用 Runtime 类可以启动新的进程或进行相关运行时环境的操作。比如,取得内存空间以及释放垃圾空间。
Runtime 类属于典型的单例设计,并且使用的是饿汉式单例模式。
工厂模式(创建型模式)
把对象的创建和使用分离,提供一个工厂类专门负责对象的创建,
一个工厂可以创建一类产品。
简单工厂模式
简单工厂并不是一种设计模式,因为他违背了开闭原则。
简单工厂只是对工厂模式的引入。
该模式中的角色:
工厂角色 全能的工厂
抽象产品(接口/抽象类), 用来组织关系
具体产品,需要继承/实现抽象产品
客户端--->工厂--->对象
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.序列化,反序列化
代理模式(重点)(结构型模式)
案例: 汽车厂只管造汽车,不直接把汽车卖给个人客户.
把卖汽车交给代理商(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代理
模板方法模式(行为模式)
将程序中一些固定流程的步骤进行提取(银行取款,存款,转账 抽号,排队,操作,评分),
在抽象父类中提供一个模版方法,在模版方法中,按顺序把固定步骤的方法进行调用,
其中有一些方法是公共的都一样,在父类中进行实现即可,
其中还有一些方法实现不同,定义为抽象的,就需要创建不同的子类来继承重写,
使用时,子类对象只要调用模版方法, 相同的功能调用抽象父类中的方法,不同的调用子类中自己重写的即可.
复合开闭原则,增加功能时,只需要扩展新的类即可。
//模版方法
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)类:持有一个策略类的引用,最终给客户端调用。
public interface Strategy {
void show();
}
//环境角色
public class SalesMan {
//持有抽象策略角色的引用
private Strategy strategy;
public SalesMan(Strategy strategy) {
this.strategy = strategy;
}
//向客户展示促销活动
public void salesManShow() {
strategy.show();
}
}
/*
为春节准备的促销活动A
*/
public class StrategyA implements Strategy {
public void show() {
System.out.println("春节活动: 买一送一");
}
}
/*
为中秋准备的促销活动B
*/
public class StrategyB implements Strategy {
public void show() {
System.out.println("中秋活动: 满200元减50元");
}
}
//测试类
public class Test {
public static void main(String[] args) {
SalesMan salesManA = new SalesMan(new StrategyA());
salesManA.salesManShow();
SalesMan salesManB = new SalesMan(new StrategyB());
salesManB.salesManShow();
}
}
这样new出来时同一个类型的对象,但是其中执行的方法是各自对象中的方法。