Java进阶
java注解
java中注解(Annotation),又称为java标注,是一种特殊的注释,可以添加在包,类,成员变量,方法,参数等内容上面.注解会随同代码编译到字节码文件中,在运行时,可以通过反射机制获取到类中的注解,然后根据不同的注解进行相应的解析.
内置注解
Java 语言中已经定义好的注解
@Override- 检查该方法是否是重写方法。如果发现其父类,或者是引用的接 口中并没有该方法时,会报编译错误。
@Deprecated- 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings- 指示编译器去忽略注解中声明的警告。
@FunctionalInterface 用于指示被修饰的接口是函数式接口。
元注解:注解的注解
元注解是javaAPI提供的,是用于修饰注解的注解,通常用在注解的定义上:
@Retention- 标识这个注解怎么保存,是只在代码中,还是编入class文件中, 或者是在运行时可以通过反射访问。
@Documented- 标记这些注解是否包含在用户文档中。 @Target- 标记这个注解应该是哪种 Java 成员。
@Inherited- 标记这个注解是继承于哪个注解类(默认注解并没有继承于任何 子类)
@Repeatable- 标识某注解可以在同一个声明上使用多次。 重点掌握
@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:在运行时有效(即运行时保留)
自定义注解
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message() default "";
int minlength() default 0;
String lengthmessage() default "";
}
java
public class User {
private int num=0;
@NotNull(message="姓名不能为空",minlength=3,lengthmessage="长度不能小于3")
private String name=null;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
java
//解析注解
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, Exception {
User user = new User();
//user.setName("jim");
//通过反射解析User类中注解
Field[] fields = user.getClass().getDeclaredFields();//拿到类中所有的成员变量 连同私有的也可以获取
//循环所有的属性
for (Field field : fields) {
NotNull notNull = field.getAnnotation(NotNull.class);//获取属性上面 名字为NotNull注解
if (notNull != null) {
//通过属性,生成对应的get方法
Method m = user.getClass().getMethod("get" + getMethodName(field.getName()));
//调用方法 obj就是get方法的返回值
Object obj=m.invoke(user);
if (obj==null) {
System.err.println(field.getName() +notNull.message());
throw new NullPointerException(notNull.message());
}else{
if(String.valueOf(obj).length()<(notNull.minlength())){
System.err.println(field.getName() +notNull.lengthmessage());
throw new NullPointerException(notNull.lengthmessage());
}
}
}
}
}
/**
* 把一个字符串的第一个字母大写
*/
private static String getMethodName(String fildeName) throws Exception {
byte[] items = fildeName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}
}
对象克隆
为什么要克隆?直接new一个对象不行吗?
在已有的对象基础,克隆出一个新的对象,并将已有对象中的属性值,复制到新克隆出来的对象中.
new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来 保存当前对象的"状态"就靠clone方法了。
误区: 我们常见的Studentstu1 = newStudent (); Student stu2 = stu1 ; 这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了 同一个对象。这种只能称为引用复制,两个引用指向的还是同一个对象
如何实现克隆
先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆 (DeepClone)。
在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类 型包括int、double、byte、boolean、char 等简单数据类型,引用类型包括 类、接口、数组等复杂类型。基本类型的值可以直接复制,引用类型只能复制引 用地址。
所以浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制.
java
public class Person implements Cloneable{
int num;
String name;
@Override
protected Person clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
return person;
}
}
测试
Person p1 = new Person(100,"jim");
Person p2 = p1.clone(); //克隆一个新的对象
System.out.println(p1==p2);//false 说明是两个不同的对象
浅克隆和深克隆
浅克隆:对于基本类型,在克隆对象时,可以将值直接复制到新的对象中.
浅克隆主要是在于对象中关联的另一个对象是否能被克隆出一个新的对象,如果克隆时,只是将关联对象的地址进行复制,那么就属于浅克隆.如果克隆时,也一同克隆除了一个新对象,那么就属于深克隆.
如何实现深克隆?
方式1:在克隆对象时,将对象中关联的对象也一同进行克隆.(虽然可以实现,层级较多时比较麻烦)
方式2:使用对象序列化,反序列化 (需要我们自己定义一个方法,现将对象序列化,然后进行反序列化,自动将多级关联的对象也一并创建,使用起来比较方便,注意需要序列化的类必须要实现Serializable接口)
对象序列化: 将java中的对象输出到一个文件中 ObjectOutputStream
反序列化:将文件中的信息输入到程序,创建一个新的对象
ObjectInputStream
创建对象的几种方式:1.new关键字 2.反序列化3.反射机制4.对象克隆
Java 设计模式(java design patterns)
背景:设计模式概念首先起源于建筑领域 在1990年软件领域也诞生设计模式概念
直到1995 年,艾瑞克·伽马(ErichGamma)、理査德·海尔姆 (Richard Helm)、拉尔夫·约翰森(RalphJohnson)、约翰·威利斯迪斯(John Vlissides)等 4 位作者合作出版了《设计模式:可复用面向对象软件的基础》 一书,在此书中收录了 23 个设计模式,这是设计模式领域里程碑的事件,导 致了软件设计模式的突破。这 4 位作者在软件开发领域里也以他们的"四人 组"(GangofFour,GoF)著称。
什么是设计模式?
软件设计模式(SoftwareDesign Pattern),又称设计模式,是一套被反 复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它描述了在软 件设计过程中的一些不断重复发生的问题,以及该问题的解决方案。也就是说, 它是解决特定问题的一系列套路,是前辈们的代码设计经验的总结,具有一定的 普遍性,可以反复使用。
在长期编程过程中,针对某一类问题经过反复的优化,最终总结出一个固定的解决方案,这些方案经过反复使用,具有普遍性.
为什么要学习设计模式?
学习设计模式就是学习好的编程思想.
1.可以提高程序员的思维能力、编程能力和设计能力。
2.使程序设计更加标准化、使软件开发效率大大提高。
3.使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强。
4.能够更好的去理解源码架构
建模语言
统一建模语言(Unified Modeling Language,UML)是一种用于软件系 统分析和设计的语言工具,用于帮助软件开发人员进行思考和记录思路的结果. UML图:通过不同的图形和符号,来描述软件模型以及各个元素之间的关系.
类图(Class diagram)是显示了模型的静态结构,特别是模型中存在的类、类 的内部结构以及它们与其他类的关系等。类图不显示暂时性的信息。类图是面向 对象建模的主要组成部分。
在软件工程中,类图是一种静态的结构图,描述了系统的类的集合,类的属 性和类之间的关系,可以简化了人们对系统的理解;类图是系统分析和设计阶段 的重要产物,是系统编码和测试的重要模型。
统一建模语言(Unified Modeling Language,UML)是一套软件设计和分析的语言工具,用图形化的方式,记录表示类与类,类与接口,接口与接口之间的关系,一般把图形化方式也成为UML类图.
类图中两个基本的要素:1.类2.接口
1.类
类是指具有相同属性、方法和关系的对象的抽象,它封装了数据和行为,是 面向对象程序设计(OOP)的基础,具有封装性、继承性和多态性等三大特性。 在 UML 中,类使用包含类名、属性和操作且带有分隔线的矩形来表示。
(1) 类名(Name)是一个字符串,例如,Student。
(2) 属性(Attribute)是指类的特性,即类的成员变量。UML 按以下格式表示:
[可见性]属性名:类型[=默认值]
例如:-name:String
注意:"可见性"表示该属性对类外的元素是否可见,包括公有(Public)、私 有(Private)、受保护(Protected)和朋友(Friendly)4 种,在类图中分别 用符号+、-、#、~表示。 (3) 操作(Operations)是类的任意一个实例对象都可以使用的行为,是类的成 员方法。UML 按以下格式表示:
[可见性]名称(参数列表)[:返回类型]
例如:+display():void
2.接口
接口(Interface)是一种特殊的类,它具有类的结构但不可被实例化,只可以 被子类实现。它包含抽象操作,但不包含属性。它描述了类或组件对外可见的动 作。在 UML 中,接口使用一个带有名称的小圆圈来进行表示。
图形类接口的 UML 表示。
类之间的关系
1.依赖关系
在一个类中的方法,把另一个类作为参数进行使用,具有临时性,方法执行结束后,依赖关系就不存在了.
一般把xxx类用到了xxx类 这种关系成为依赖关系,也称为use-a关系
2.关联关系
在一个类中把另一个类当做自己的成员.
单向关联,双向关联,自关联,一对一关联,一对多关联.
关联关系根据强弱分为聚合 和 组合
是一种 has-a的关系
3.聚合关系
聚合关系也是一种关联关系,是一种整体和部分的关系
例如 : 学校包含老师,若学校不存在 ,老师依然存在
4.组合关系
组合关系也是一种关联关系,是一种整体和部分的关系,是一种更强烈的关联关系
例如:头和嘴的关系,头如果不存在 ,嘴也没有存在的意义
5.继承关系
继承关系是对象之间耦合度最大的一种关系,表示一般与特殊的关系,是父 类与子类之间的关系,是一种继承关系,是 is-a 的关系。
在 UML 类图中,继承关系用带空心三角箭头的实线来表示,箭头从子类指 向父类。在代码实现时,使用面向对象的继承机制来实现继承关系。例如, Student 类和 Teacher 类都是 Person 类的子类,其类图下图所示
6.实现关系
实现关系是接口与实现类之间的关系。在这种关系中,类实现了接口,类中 的操作实现了接口中所声明的所有的抽象操作。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实 现类指向接口。例如,汽车和船实现了交通工具,其类图如下图所示。
面向对象设计原则
单一职责原则
单一职责原则是最简单的面向对象设计原则,它用于控制类的粒度大小。
一个类只负责某一个具体功能,细化类的颗粒度
开闭原则
对修改关闭,对扩展开放,尽可能的在扩展功能时,不要修改已有的代码,而是扩展一个类来实现新功能.
里氏替换原则
继承优势:
1.提高代码的复用性(子类继承父类的功能)
2.提高代码的扩展性(子类可以扩展自己的功能,不影响其他类)
3.重写父类方法
继承的劣势:
继承是侵入性的(只要继承,就拥有父类的属性和方法,体系结构复杂)
继承使得类的体系结构变得复杂了
里氏替换:首先是有里斯科父女士提出,其次是关于继承使用的
当子类继承父类后,在使用时,用子类替换父类之后,要确保父类中的功能不受影响.
**主要思想:**保证了程序的稳定性.它是功能正确性的保证。加强程序的健壮性,提高程序的维护性降低需求变更时引入的风险。
组合/聚合复用原则
优先使用组合/聚合,使系统更灵话,其次才考虑继承,达到复用的目的。
继承使得类的体系变得复杂,如果我们只是想使用某个类中的方法时,也可以优先选择关联关系/依赖关系,降低类与类之间的耦合度.
依赖倒置
上层模块不应该依赖底层模块,它们都应该依赖于抽象,抽象不应该依赖于 细节,细节应该依赖于抽象.
其核心思想是:要面向接口编程,不要面向实现编程。 这里的抽象指的是接口或者抽象类,而细节是指具体的实现类。 使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操 作,把展现细节的任务交给它们的实现类去完成。 依赖倒置原则是实现开闭原则的重要途径之一,它降低了类与类模块之间的 耦合。由于在软件设计中,细节具有多变性,而抽象层则相对稳定,因此以抽象 为基础搭建起来的架构要比以细节为基础搭建起来的架构要稳定得多。
面向抽象编程,不要面向具体的实现编程,具体实现应该依赖抽象层( 多态 抽象层表示 具体的子实现类)
接口隔离原则
使用多个接口,而不使用单一的总接口,不强迫新功能实现不需要的方法
不要把所有功能都定义到一个总的接口中,应该把不同的种类功能,定义在不同的接口中.,让实现类根据自己的需要去灵活的选择.
迪米特原则
迪米特原则是1987年秋天在美国东北大学一个叫做迪米特的项目设计提出 只跟朋友联系,不跟"陌生人"说话
如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以 通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性
在程序间相互调用时,如果两个类之间没有直接联系,但是想相互调用,可以通过第三方进行转发调用.降低了模块之间的耦合度.
总结
设计原则的核心思想
找出应用中可能需要变化之处,独立出来,不要和不需要变化的代码混在一 起
针对抽象编程,而不是针对实现编程
为了交互对象的松耦合设计而努力 遵循设计原则:就是为了让程序高内聚,低耦合
java 设计模式类型
https://www.runoob.com/design-pattern/design-pattern-tutorial.html
设计模式分类
分为三大类:创建型模式,结构性模式,行为性模式.
常用模式
1.单例模式:在一个项目中,如何确保一个类始终只有一个对象
单例模式有 3 个特点:
- 单例类只有一个实例对象;
- 该单例对象必须由单例类自行创建;
- 单例类对外提供一个访问该单例的全局访问点;
java
*
* /*懒汉式单例 在类加载时没有创建对象,在第一次获取单例对象时 才创建对象
* 优点:类加载时不创建,当使用的时候再创建
* 缺点:会出现线程安全*/
public class Mywindow {
private static volatile Mywindow window;
//构造方法私有化,不让外界访问
private Mywindow() {
}
//写法一: 会出现线程安全问题,多个线程同时进入,会返回多个对象
/* public Mywindow getmywindow(){
if(window==null){
window=new Mywindow();
}
return window;
}*/
//写法二: 为方法加锁 效率低,一次只有一个线程进入到该方法
/*public static synchronized Mywindow getmywindow(){
if(window==null){
window=new Mywindow();
}
return window;
}*/
//方法三:双重检索
/* public static Mywindow getmywindow(){
if(window==null){
synchronized (Mywindow.class){
if(window==null){
window=new Mywindow();
}
}
}
return window;
}*/
//方法四:双重检索+volatile(可见性,避免重排序)
/* 创建对象这一条语句编译成指令时,可以分为三个指令 1.new 申请空间 2.调用构造方法初始化对象 3.把对象地址赋值给引用变量
* 如果按正常的顺序执行,没有问题的,但是在执行时 如果2,3两条指令顺序发生变化,导致把没有初始化完成的对象地址返回,这样会导致问题出现
* 因为这个对象没有初始化完成
* 所以需要volatile关键字修饰单例成员变量,确保对其赋值时,确保执行命令按顺序进行*/
public static Mywindow getmywindow(){
if(window==null){
synchronized (Mywindow.class){
if(window==null){
window=new Mywindow();
}
}
}
return window;
}
}
java
public class MyWindow {
/*
* 饿汉模式(单例模式中的急切式) 在创建类的时候 提前将唯一的对象创建好 优点:不要需要考虑线程安全问题
* */
private static MyWindow myWindow=new MyWindow();
private MyWindow() {
}
public static MyWindow getMyWindow() {
return myWindow;
}
}
工厂模式(Factory Pattern)
解决的就是在项目创建对象和适用对象分离的问题,如何更好的管理类与类之间的关系.
1.简单工厂模式:简单工厂模式并不是一种设计模式,违背了开闭原则.主要是引出工厂方法和抽象工厂模式.
涉及的角色:
工厂角色:根据我们的需求创建对应的对象
抽象产品:具体产品的抽象,具体产品实现/继承抽象产品.可以使用长层的抽象父类表示任意的子类对象
具体产品:具体的对象
优点:客户端不负责对象的创建,而是由专门的工厂类完成;客户端只负责对象的 调用,实现了创建和调用的分离,降低了客户端代码的难度.
缺点:只能创建实现了同一个父类/接口的子类对象,扩展新的类型,需要修改工厂,违反了开闭原则,不利于,不利于后期维护...
工厂方法模式
由于简单工厂中,一个工厂可以造同一类型的所有具体产品,导致简单工厂比较复杂,扩展一个新类型时,需要修改工厂代码.
工厂方法模式,为工厂也进行抽象,并且为同类型每个具体的产品都创建了一个具体的工厂.
优点:每一个工厂负责创建一个具体的产品(类型)对象,这样扩展一个新的类型,与之对应一个工厂,就不需要修改工厂,遵守了开闭原则,单一职责原则.
不足:类的数量怎么多
抽象工厂
工厂方法模式,按照产品类型进行分类,一类产品对应一类工厂.不同类型产品之间相互隔离.例如 小米 既要造汽车,又要造手机,属于同一家公司的产品.但是工厂方法这种设计,同一个公司的产品与产品之间没有联系
抽象工厂对工厂重新进行分类,以公司为单位,进行工厂的抽象(提取),一个工厂内,可以创建不同的产品,这样我们就可以创建出像小米工厂,这样具体的工厂可以创建不同公司的各种产品.
原型模式
在某些场景下,为了避免自己手动的创建对象,可以使用对象克隆方式,创建并返回一个新的对象,这种克隆新对象的效率要比自己创建对象的效率高.
对象克隆实现方式有两种:
1.实现Cloneable接口,重写clone()方法
2.使用对象序列化,反序列化进行生成对象.
注意:深克隆和浅克隆问题.
java
public class Test {
public static void main(String[] args) {
CarFactory aodicarFactory = new AodiFactory();
Car aodi = aodicarFactory.createCar();
aodi.run();
CarFactory bmwcarFactory = new BmwFactory();
Car bmw = bmwcarFactory.createCar();
bmw.run();
}
}
java
/*
Cloneable 为抽象对象
Resume为原型对象
*/
public class Resume implements Cloneable {
public Resume() {
System.out.println("原型对象Resume构造方法");
}
@Override
protected Resume clone() throws CloneNotSupportedException {
System.out.println("克隆对象");
return (Resume)super.clone();
}
}
代理模式
在spring aop思想中,已经用到了代理思想.在不修改原来代码的前提下,为我们方法添加额外功能,通过代理对象帮助我们进行调用.
有些时候,目标对象不想或不能直接与客户打交道,通过代理对象进行访问.
优点:1.代理对象可以保护目标对象2.对目标对象的功能进行扩展,降低了模块之间的耦合度.
涉及到三个主体:
1.抽象主题(抽取的功能,让目标对象进行实现,以及代理对象进行实现)
2.具体主题:真正要实现功能的类
3.代理对象
代理模式的实现方式有两种:
1.静态代理:创建一个代理类,代理实现与具体对象相同的接口/抽象类,重写抽象方法,还有一个成员变量可以用来接收具体主题.在代理对象中重写的抽象方法中 调用真实主题方法,这样就可以在调用之前和之后添加额外的功能.
缺点:一个代理对象智能代理一个接口类型的对象,不灵活
2.动态代理
动态代理分为jdk动态代理和cglib动态代理
1.jdk代理:通过反射机制实现,目标类必须要实现一个接口,通过接口动态获得目标类中的信息
2.cglib代理:是spring中提供的一种代理技术,目标类可以不识闲任何接口,采用字节码生成子类的方式,对方法进行拦截,实现机制不同.cglib不能代理final和static修饰的方法.
目前spring中两种动态代理都支持,如果目标类没有实现接口,那么默认使用cglib代理.如果目标类中有接口,则采用jdk代理
策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算 法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法 进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这 些算法进行管理。
结构:
策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。 此角色给出所有的具体策略类所需的接口。
具体策略(ConcreteStrategy)类:实现了抽象策略定义的接口,提供具体的 算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
优点:
策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以 自由切换。 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改 变原有的代码,符合"开闭原则", 避免使用多重条件选择语句(ifelse),充 分体现面向对象设计思想。
使用场景
中提供的一种代理技术,目标类可以不识闲任何接口,采用字节码生成子类的方式,对方法进行拦截,实现机制不同.cglib不能代理final和static修饰的方法.
目前spring中两种动态代理都支持,如果目标类没有实现接口,那么默认使用cglib代理.如果目标类中有接口,则采用jdk代理
策略模式
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算 法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法 进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这 些算法进行管理。
结构:
策略模式的主要角色如下:
抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。 此角色给出所有的具体策略类所需的接口。
具体策略(ConcreteStrategy)类:实现了抽象策略定义的接口,提供具体的 算法实现或行为。
环境(Context)类:持有一个策略类的引用,最终给客户端调用。
优点:
策略类之间可以自由切换,由于策略类都实现同一个接口,所以使它们之间可以 自由切换。 易于扩展,增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改 变原有的代码,符合"开闭原则", 避免使用多重条件选择语句(ifelse),充 分体现面向对象设计思想。
使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式 出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执 行的行为