请像对待每一行代码一样对待每篇文章 —- 鹰嘴豆
学习目标
反射 —- spring框架基本技术应用
内部类 —- 框架封装经常使用
泛型
多线程 — 所有锁的使用
集合
开始学习
- 对继承和依赖的理解
I、继承是java面向对象的一个特性而依赖是类的一种关系。如果按照一个家庭来比喻这两种的关系,继承的父类和子类可以比喻成父亲和孩子,父亲有多个孩子并且父亲拥有的一些基因是不可变的完完全全的遗传给孩子,也就是说子类继承父类的功能,那些功能不管是在子类A还是子类B都是一模一样的并且不管以后代码如何变他们都不会变,如果做不到这一点,那么就不应该从父类中继承这种功能,用is-a has-a 去描述就是A根本就不是B的孩子,A是B领养的,A is not a child of B,A has a 养父。 依赖在语义上可以用has-a去描述,A有一个女朋友,那么女朋友就是A的一个依赖,女朋友是可以随时更换的,所以依赖的对象可以是一个接口,在这个接口就是你的一个择偶标准,如果满足了这个择偶标准的女孩就可以随时成为你的女朋友,只要注入是将具体实体对象注入进去就行了,从而开启全新的生活,虽然不能改变你的生活习惯和轨迹但至少注入了新鲜元素吧
II、继承和依赖都可以用类图去表示,如果看包含复杂关系的源码时先从继承中梳理整个图谱,在单独梳理图谱节点中的具体的依赖关系。
- 对面向对象的理解
I、通过接口、继承的多态性,我们可以通过给定一个子类对象强转成父类祖父类,将属于父类和祖父类的功能分离出来单独应用
II、每个类都具有一种特殊功能且满足单一原则。类似于堆积木,类似于前端的组件,拿来就可以用。我们想要拼出一个高塔,我们选取合适大小、合适风格的堆积木块,并通过有条理的组织管理,有条不紊、各司其职的搭建出一座个性化的高塔
至于如何有效管理,我想设计模式应该能提供一些灵感
III、上面第二点讲的是类功能上的,那么这些功能产生的数据又是如何管理的呢? 我们可以设计存放数据的数据结构来符合当前存放的需求,在复杂情况上对数据有效管理。在java中这些数据结构就是常见的POJO类
关于字符串的replace
有下面三个方法,需要注意的是replace第一个参数是普通字符串,replaceAll和replaceFirst都是正则表达式
上面使用replaceAll和replaceFirst时需要特别注意,比如window下的资源是D:\resouce\text.txt ,那么把这个字符串传递给replaceAll还是replaceFirst都会出错,Uncaught SyntaxError: Invalid Unicode escape sequence异常处理, 无效的转移字符,也就是说把上面路径的\r \t都当成转移字符,具体怎么解决? 先用最简单的一种办法使用replace来将资源路径作为普通字符串而不是正则
读写锁
jdk提供的了synchronized、ReentrantLock(可重入锁) 、ReentrantReadWriteLock(读写锁),synchronized说是重量级锁但是由于jvm虚拟机不断优化效率提高了很多,且可以不断的升级降级来提供并发效率; ReentrantLock和synchronized类似都是重量级锁,但它更加细粒度的对锁操作;ReentrantReadWriteLock读写锁比其他两种锁效率更高,其他两种锁所不管请求是否只是读,并发下涉及到共享资源竞争,那么就会锁住等待当前竞争到锁的线程执行完。读写锁分读锁和写锁,当前可能会触发共享资源竞争的代码是读操作那么就加读锁,反之如果是写操作,那么就加写锁
上面的可重入锁和synchronized锁在读读上互斥,读写锁可以解决这个问题
线程进入读锁的条件
没有其他线程的写锁
没有写请求或者存在写请求但是待进入线程和持有锁的线程是同一个
线程进入写锁的条件
没有其他线程的读锁
没有其他线程的写锁
读写锁降级,可重入读写锁在获取一个读锁的时候可以在读锁释放之前获取一个写锁。根据这个特性我们可以将锁从写锁降级到读锁,达到减少线程阻塞的效果。锁降级只要在上面获取到写锁的时候去释放这个读锁,这就把写锁变成了读锁,之前阻塞的其他线程写操作依然阻塞,而之前阻塞的读操作这时候就可以执行了
读锁升级写锁,会造成死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock**是不支持**的
多线程的双重校验
下图代码红框内的判断是一模一样的,这种就是双重校验,在高并发下多线程竞争methodResolverCache资源时,代码存在对这个资源的修改,这时使用synchronized增加一个锁,所有的并发线程阻塞在这个锁前面等待锁的释放,因此第一个 resolver == null的条件判断可能会被多个线程满足,在等待锁的过程中获得锁的线程更改了methodResolverCache(增加了一个handleClass对应的方法解析处理器)并锁释放,下一个线程获取到锁并进入的时候如果不再做一次resolver,那么可能这个resolver会重复put相同的解析器
那么为什么要这么 写两层判断呢,而不是将sychronized移动到第一个红框的上面只做一层的校验? 因为大部分线程这个resolver是不为空的,这时候根本不需要通过methodResolverCache.put增加一个方法解析处理器,也就是说不会出现并发问题,这时候如果sychronized移动到最上面的红框前,那么也会锁住资源造成等待时间
枚举
jdk提供java.lang.Enum类支持枚举
枚举和类差不多,有自己的构造函数、静态方法、静态块、方法
枚举的equals 是 this == other,也就是内存地址的比较来判断是否同一个枚举对象
枚举提供一个默认的name表示枚举常量的名字,可以通过name()/ toString()两个方法获取name
枚举提供ordinal属性表示枚举的顺序,从0开始
定义枚举时简单精炼最好,每个枚举属性使用/* / 注释,不需要在枚举属性后面定义一些说明这个枚举的中文描述,取好英文名字即可
枚举可以定义一个value表示该枚举具体的值,在外部可以指定这个值来获取具体的枚举对象,比如在枚举类中实现一个方法检索values()的所有枚举对象哪个枚举包含这个值,从而得到这个枚举对象
枚举代码赏析 ```java public enum HttpMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE;
private static final Map<String, HttpMethod> mappings = new HashMap<String, HttpMethod>(8);
static {
for (HttpMethod httpMethod : values()) {
mappings.put(httpMethod.name(), httpMethod);
}
}
/**
* Resolve the given method value to an {@code HttpMethod}.
* @param method the method value as a String
* @return the corresponding {@code HttpMethod}, or {@code null} if not found
* @since 4.2.4
*/
public static HttpMethod resolve(String method) {
return (method != null ? mappings.get(method) : null);
}
/**
* Determine whether this {@code HttpMethod} matches the given
* method value.
* @param method the method value as a String
* @return {@code true} if it matches, {@code false} otherwise
* @since 4.2.4
*/
public boolean matches(String method) {
return (this == resolve(method));
}
}
7. 内部类
1. 内部类可以实现多继承效果,在一个类里面定义多个私有类,继承不同功能,在最外层类中通过属性关联的形式关联这些私有内部类实例,客户端只要知道最外层类实例就能访问这些私有方法功能
8. 泛型
1. 方法上的泛型
9. 异常
1. 在一个业务代码中,如果没有满足某种条件抛出异常,并必须要上一层去处理,而不是抛到最外层直接捕捉记录日志。这时候就可以使用检查时异常
2. 如果异常不需要处理,直接抛到最外层记日志并提示前端,这时候就可以用运行时异常
3. 检查时异常和运行时异常区别在于是否要强调对异常的处理并让程序继续运行下去,或者直接在某一层处理,不需要告知最上层日志曾经出现过这个异常。 检查时异常是可控的,能解决的;运行时异常是没有解决方案的,只能抛最外层去记日志的
4. 检查时异常赏析,在某一层就处理了,没有继续往上抛,表示这个异常我是有能力处理的<br />
10. 反射
1. 判断方法是否可重写
```java
/**
* Determine whether the given method is overridable in the given target class.
* @param method the method to check
* @param targetClass the target class to check against
*/
private static boolean isOverridable(Method method, Class<?> targetClass) {
if (Modifier.isPrivate(method.getModifiers())) {
return false;
}
if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) {
return true;
}
return getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass));
}
修饰符用位运算(值没具体含义,位置有具体含义)计算
public static final int NATIVE = 0x00000100; public static final int INTERFACE = 0x00000200; public static final int ABSTRACT = 0x00000400; /** 通过位置上的计算(16进制转成二进制,错开用&计算),如果相同的修饰符值不为0,不相同 使用&计算就是为0 */ public static boolean isAbstract(int mod) { return (mod & ABSTRACT) != 0; }
- 比较两个方法是否是同一个方法,比较方法名,比较参数类型数组是否相等
public static Method findMethod(Class<?> clazz, String name, Class<?>... paramTypes) { Assert.notNull(clazz, "Class must not be null"); Assert.notNull(name, "Method name must not be null"); Class<?> searchType = clazz; while (searchType != null) { Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); for (Method method : methods) { if (name.equals(method.getName()) && (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { return method; } } searchType = searchType.getSuperclass(); } return null; }
- 接口类也可以反射获得方法名
// Hello就是一个Interface public static void main(String[] args) { Method[] methods = Hello.class.getMethods(); for (Method method : methods) { System.out.println(method.getName()); } }
- 通过接口方法获取接口类名字
public static void main(String[] args) { Method[] methods = Hello.class.getMethods(); for (Method method : methods) { Class clazz = method.getDeclaringClass(); System.out.println(clazz.getName()); } }
- 根据接口方法获取实现类方法
public static void main(String[] args) { // 接口方法 Method[] methods = Hello.class.getMethods(); // 接口实现类 Class<HelloOne> helloOne = HelloOne.class; for (Method method : methods) { try { // 根据接口方法获取实现类方法 method = helloOne.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { System.out.println("没有这个方法"); continue; } System.out.println(method.getName()); } }
通过反射获取注解
public static void main(String[] args) { // 接口方法 Method[] methods = Hello.class.getMethods(); // 接口实现类 Class<HelloOne> helloOne = HelloOne.class; for (Method method : methods) { try { // 根据接口方法获取实现类方法 method = helloOne.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { System.out.println("没有这个方法"); continue; } // 获取方法上的注解 Annotation annotation = method.getAnnotation(HeHe.class); if (Objects.nonNull(annotation)) { System.out.println(((HeHe) annotation).value()); } } }
泛型
- 方法中的泛型
public static final <T> List<T> emptyList() { return (List<T>) EMPTY_LIST; } // 调用方式 Collections.<RequestMethod>emptyList()
- 方法中的泛型
…符号使用,它其实是一个数组
public class Main { private static void test(String... names) { System.out.println(Arrays.asList(names)); } public static void main(String[] args) { Main.test("ying", "zui", "dou"); } }
getDeclaringClass的使用
getDeclaringClass定义在Class对象中,也就是说class对象才能调用这个getDeclaringClass方法
它返回的是当前调用getDeclaringClass方法的class对象所在的那个类的class,如果它没有在其他类里面定义,那么返回null
Class
和Double.class 不是同一个对象,但是它们表示的都是class对象,Double.class是double的包装类型
java迭代器Iterator
迭代器是一种设计模式,采用接口统一规范,不同的对象都能使用同一种规范完成遍历。例如使用for(,,)遍历时,我们需要清楚所遍历集合内部的方法,比如获取集合的需要知道集合的个数,可能有些集合是提供size(),有些集合是length(),这样会造成代码差异性
在java中使用forEach就可以完成Iterator迭代
自定义迭代器实现 ```java /**
- 类功能描述
- 自定义类型实现Iterable规范迭代 *
- @author 鹰嘴豆
- @date 2019/2/2
- 时间 作者 版本 描述
- ====================================================
*/
public class Main
implements Iterable{
Object[] objs = new Object[10];
int size;
public void add(E element) {
if (size >= 10) { Object[] exAry = new Object[size+10]; System.arraycopy(objs, 0, exAry, 0, size); objs = exAry; } objs[size++] = element;
}
@Override public Iterator iterator() {
return new Itr();
}
class Itr
implements Iterator { int count; @Override public boolean hasNext() { if (count >= size) { return false; } return true; } @Override public Object next() { return objs[count++]; }
}
public static void main(String[] args) {
Main main = new Main(); for (int index = 0; index < 20; index++) { main.add("good"); } for (Object item : main) { System.out.println(item); }
} } ```
- 反射生成数组
Object array = Array.newInstance(expectedType.getComponentType(), 1); Array.set(array, 0, value); value = array;
将java源文件编译成class文件并查看class文件结构
通过javac 编译成class
使用notepad++打开class并在notepad++中找到插件菜单-Plugins-admin, 安装HEX-Editor, 安装好后在插件菜单中再选择hex-editor打开class文件
学习总结
贡献者列表
编辑人 | 编辑时间 | 编辑内容 |
---|---|---|
鹰嘴豆 | 2018/12/15 | 初稿 |