导图社区 JAVA基础思维导图
JAVA基础思维导图,包括SPI机制详解、反射机制详解、异常机制详解、泛型机制详解、面向对象等等。
编辑于2022-07-11 14:20:10JAVA基础
面向对象
三大特性
封装
把一个对象的属性,和行为放到同一个类中就是封装
继承
子类可以获得父类的属性和操作。 在java中常表示为 is-a的关系
多态
是指一个事物,多种状态 比如 一个动物 可以是 猫,狗。。。。 一个方法,可以有无参,1个参数,2个参数 也可使同一个方法,不同的表现
编译时多态
主要值方法的重载
运行时多态
继承
重写
向上转型
类图
泛化
实现
聚合
关联
依赖
知识点
数据类型
String
不可变的好处
1.可以缓存 hash 值
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。
2.String Pool 的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String, StringBuffer and StringBuilder
1. 可变性
String不可变
主要是因为string 受到了final的修饰
另外两个可变
基于动态数组, 基础长度16也就是需要扩容时,它会new 一个新数组,长度为原来的2倍
2. 线程安全
String,StringBuffer线程安全
StringBuilder线程不安全
String.intern()
运算
继承
Object通用方法
关键字
泛型机制详解
注解基础
java内置注解
元注解
注解与反射接口
自定义注解
深入理解注解
java8提供了那些新的注解
注解支持继承吗?
注解实现的原理
注解的应用场景
配置化到注解化-框架的演进
继承实现到注解实现-Junit3到junit4
自定义注解和AOP-通过切面实现解耦
异常机制详解
反射机制详解
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。Java反射机制在框架设计中极为广泛,需要深入理解。
反射基础
反射就是把java类中的各种成分映射成一个个java对象 例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
Class类
Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象)。数组同样也被映射为为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。 Class类也是类的一种,与class关键字是不一样的。 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件) 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。 Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载 Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)
类加载
类的加载机制流程
1.加载
链接
2.连接
3.初始化
4.使用
5.卸载
类的加载
1.类编译
2.类加载
类的结构放在栈区
类的实例放入堆区
反射的使用
基于此我们如何通过反射获取Class类对象以及类中的成员变量、方法、构造方法等
Class类对象的获取
在类加载的时候,jvm会创建一个class对象 class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种 根据类名:类名.class 根据对象:对象.getClass() 根据全限定类名:Class.forName(全限定类名) @Test public void classTest() throws Exception { // 获取Class对象的三种方式 logger.info("根据类名: \t" + User.class); logger.info("根据对象: \t" + new User().getClass()); logger.info("根据全限定类名:\t" + Class.forName("com.test.User")); // 常用的方法 logger.info("获取全限定类名:\t" + userClass.getName()); logger.info("获取类名:\t" + userClass.getSimpleName()); logger.info("实例化:\t" + userClass.newInstance()); }
Class类的方法
forName() (2)为了产生Class引用,forName()立即就进行了初始化。 Object-getClass() getName() getSimpleName() getCanonicalName() isInterface() getInterfaces() getSupercalss() newInstance() getFields() getDeclaredFields
getName、getCanonicalName与getSimpleName的区别
getSimpleName:只获取类名 getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。 getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
Constructor类及其用法
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。 同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下: 方法返回值 方法名称 方法说明 Field getDeclaredField(String name) 获取指定name名称的(包含private修饰的)字段, 不包括继承的字段 Field[] getDeclaredField() 获取Class对象所表示的类或接口的所有(包含private修饰的) 字段,不包括继承的字段 Field getField(String name) 获取指定name名称、具有public修饰的字段,包含继承字段 Field[] getField() 获取修饰符为public的字段,包含继承字段 void set(Object obj, Object value) 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 Object get(Object obj) 返回指定对象上此 Field 表示的字段的值 Class<?> getType() 返回一个 Class 对象,它标识了此Field 对象所表示 字段的声明类型。 boolean isEnumConstant() 如果此字段表示枚举类型的元素则返回 true;否则返回 false String toGenericString() 返回一个描述此 Field(包括其一般类型)的字符串 String getName() 返回此 Field 对象表示的字段的名称 Class<?> getDeclaringClass() 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 void setAccessible(boolean flag) 将此对象的 accessible 标志设置为指示的布尔值, 即设置其可访问性
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。 方法返回值 方法名称 方法说明 Method getDeclaredMethod 返回一个指定参数的Method对象, String name, Class<?>... parameterTypes) 该对象反映此 Class 对象所表示的类 或接口的指定已声明方法。 Method[] getDeclaredMethod() 返回 Method 对象的一个数组,这些对象反 映此 Class 对象表示的类或接口声明的 所有方法,包括公共、保护、默认(包)访 问和私有方法,但不包括继承的方法。 Method getMethod 返回一个 Method 对象,它反映此 Class 对 (String name, Class<?>... parameterTypes) 象所表示的类或接口的指定公共成员方法。 Method[] getMethods() 返回一个包含某些 Method 对象的数组, 这些对象反映此 Class 对象所表示的类或接 口(包括 那些由该类或接口声明的以及从超类 和超接口继承的那些的类或接口)的公共 member 方法。
反射机制的执行流程
反射获取类实例
反射获取方法
调用method.invoke()方法
反射调用流程小结
最后,用几句话总结反射的实现原理: 1.反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化; 2.每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上; 3.反射也是考虑了线程安全的,放心使用; 4.反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销; 5.反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器; 6.当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离; 7.调度反射方法,最终是由jvm执行invoke0()执行;
SPI机制详解
什么是SPI机制
SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制,可以用来启用框架扩展和替换组件,主要是被框架的开发人员使用,比如java.sql.Driver接口,其他不同厂商可以针对同一接口做出不同的实现,MySQL和PostgreSQL都有不同的实现提供给用户,而Java的SPI机制可以为某个接口寻找服务实现。Java中SPI机制主要思想是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要,其核心思想就是 解耦。 当服务的提供者提供了一种接口的实现之后,需要在classpath下的META-INF/services/目录里创建一个以服务接口命名的文件,这个文件里的内容就是这个接口的具体的实现类。当其他的程序需要这个服务的时候,就可以通过查找这个jar包(一般都是以jar包做依赖)的META-INF/services/中的配置文件,配置文件中有接口的具体实现类名,可以根据这个类名进行加载实例化,就可以使用该服务了。JDK中查找服务的实现的工具类是:java.util.ServiceLoader。
SPI机制的简单示例
SPI机制的广泛应用
JDBC DriverManager
在JDBC4.0之前,我们开发有连接数据库的时候,通常会用Class.forName("com.mysql.jdbc.Driver")这句先加载数据库相关的驱动,然后再进行获取连接等的操作。而JDBC4.0之后不需要用Class.forName("com.mysql.jdbc.Driver")来加载驱动,直接获取连接就可以了,现在这种方式就是使用了Java的SPI扩展机制来实现。  1.JDBC接口定义 首先在java中定义了接口java.sql.Driver,并没有具体的实现,具体的实现都是由不同厂商来提供的。 2.mysql实现||postgresql实现 在mysql的jar包mysql-connector-java-6.0.6.jar中,可以找到META-INF/services目录,该目录下会有一个名字为java.sql.Driver的文件,文件内容是com.mysql.cj.jdbc.Driver,这里面的内容就是针对Java中定义的接口的实现。 同样在postgresql的jar包postgresql-42.0.0.jar中,也可以找到同样的配置文件,文件内容是org.postgresql.Driver,这是postgresql对Java的java.sql.Driver的实现。 3.使用方法 上面说了,现在使用SPI扩展来加载具体的驱动,我们在Java中写连接数据库的代码的时候,不需要再使用Class.forName("com.mysql.jdbc.Driver")来加载驱动了,而是直接使用如下代码: String url = "jdbc:xxxx://xxxx:xxxx/xxxx"; Connection conn = DriverManager.getConnection(url,username,password); ..... 原理: 上面的使用方法,就是我们普通的连接数据库的代码,并没有涉及到SPI的东西,但是有一点我们可以确定的是,我们没有写有关具体驱动的硬编码Class.forName("com.mysql.jdbc.Driver")! 上面的代码可以直接获取数据库连接进行操作,但是跟SPI有啥关系呢?上面代码没有了加载驱动的代码,我们怎么去确定使用哪个数据库连接的驱动呢?这里就涉及到使用Java的SPI扩展机制来查找相关驱动的东西了,关于驱动的查找其实都在 DriverManager中,DriverManager是Java中的实现,用来获取数据库连接 其中loadInitialDrivers的作用是: 从系统变量中获取有关驱动的定义。 使用SPI来获取驱动的实现。 遍历使用SPI获取到的具体实现,实例化各个实现类。 根据第一步获取到的驱动列表来实例化具体实现类。 首先调用driversIterator.hasNext()方法,这里会搜索classpath下以及jar包中所有的META-INF/services目录下的java.sql.Driver文件,并找到文件中的实现类的名字,此时并没有实例化具体的实现类(ServiceLoader具体的源码实现在下面)。 然后是调用driversIterator.next();方法,此时就会根据驱动名字具体实例化各个实现类了。现在驱动就被找到并实例化了。
SPI机制-Common-Logging
SPI机制-插件体系
其实最具spi思想的应该属于插件开发,我们项目中也用到的这种思想,后面再说,这里具体说一下eclipse的插件思想。 Eclipse使用OSGi作为插件系统的基础,动态添加新插件和停止现有插件,以动态的方式管理组件生命周期。 一般来说,插件的文件结构必须在指定目录下包含以下三个文件: META-INF/MANIFEST.MF: 项目基本配置信息,版本、名称、启动器等 build.properties: 项目的编译配置信息,包括,源代码路径、输出路径 plugin.xml:插件的操作配置信息,包含弹出菜单及点击菜单后对应的操作执行类等 当eclipse启动时,会遍历plugins文件夹中的目录,扫描每个插件的清单文件MANIFEST.MF,并建立一个内部模型来记录它所找到的每个插件的信息,就实现了动态添加新的插件。 这也意味着是eclipse制定了一系列的规则,像是文件结构、类型、参数等。插件开发者遵循这些规则去开发自己的插件,eclipse并不需要知道插件具体是怎样开发的,只需要在启动的时候根据配置文件解析、加载到系统里就好了,是spi思想的一种体现。
SpI机制-Spring中的SPI机制
在springboot的自动装配过程中,最终会加载META-INF/spring.factories文件,而加载的过程是由SpringFactoriesLoader加载的。从CLASSPATH下的每个Jar包中搜寻所有META-INF/spring.factories配置文件,然后将解析properties文件,找到指定名称的配置后返回。需要注意的是,其实这里不仅仅是会去ClassPath路径下查找,会扫描所有路径下的Jar包,只不过这个文件只会在Classpath下的jar包中。
SPI机制深入理解
SPI机制通常怎么使用
1.定义标准 定义标准,就是定义接口。比如接口java.sql.Driver 2.具体厂商或者框架开发者实现 厂商或者框架开发者开发具体的实现: 在META-INF/services目录下定义一个名字为接口全限定名的文件,比如java.sql.Driver文件,文件内容是具体的实现名字,比如me.cxis.sql.MyDriver。 写具体的实现me.cxis.sql.MyDriver,都是对接口Driver的实现。 3.程序猿使用 我们会引用具体厂商的jar包来实现我们的功能: ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class); //获取迭代器 Iterator<Driver> driversIterator = loadedDrivers.iterator(); //遍历 while(driversIterator.hasNext()) { driversIterator.next(); //可以做具体的业务逻辑 }
SPI与API的区别
第一个SPI和API的区别? SPI - “接口”位于“调用方”所在的“包”中 概念上更依赖调用方。 组织上位于调用方所在的包中。 实现位于独立的包中。 常见的例子是:插件模式的插件。 API - “接口”位于“实现方”所在的“包”中 概念上更接近实现方。 组织上位于实现方所在的包中。 实现和接口在一个包中。 第二个什么时候用API,什么时候用SPI?
SPI机制实现原理
SPI机制的缺陷
1.不能按需加载,需要遍历所有的实现,并实例化,然后在循环中才能找到我们需要的实现。如果不想用某些实现类,或者某些类实例化很耗时,它也被载入并实例化了,这就造成了浪费。 2.获取某个实现类的方式不够灵活,只能通过 Iterator 形式获取,不能根据某个参数来获取对应的实现类。 3.多个并发多线程使用 ServiceLoader 类的实例是不安全的。