重学Java泛型
一丶从字节码层面看范型擦除
public class Type1<T> {
private T t;
}
使用jclasslib插件查看其字节码:
可以看到 t属性的类型是List<Obeject>
可以知道Java泛型确实通过类型擦除来实现,所以字节码中没有类型信息。
二丶泛型信息存储于常量池
public class Type2 {
List mylist; //mylist 字段的GenericType是Class 而不是ParameterizeType
}
使用idea查看字节码信息
可以看到mylist字段的签名是一个List类型
public class Type3 {
List<String> mylist;
}
对于Type3我们可以看到类型是一个List<String>
签名索引指向常量池中,说明范型信息存储在常量池
三丶复杂类型如何使用Json序列化与其原理
1.使用TypeReference(alibaba fastJson包,其他api有对应的方法)
//复杂类型 List嵌套List
List<List<String>> lists= Arrays.asList(Collections.singletonList("1")
,Collections.singletonList("2")
,Collections.singletonList("3"));
String s = JSON.toJSONString(lists);
List<List<String>> list = (List<List<String>>)JSON.parseObject(s, List.class);//强转并不报错 但是实际类型是List<JsonArray>
TypeReference<List<List<String>>> type = new TypeReference<List<List<String>>>() {}; //注意这里是匿名内部类
List<List<String>> lists1 = JSON.parseObject(s, type);
System.out.println(lists1);//类型就是List<List<String>>
2.原理
new TypeReference<List<List<String>>>() {}
其实是new 了一个对象 这个对象继承自TypeReference,相当于new了一个确切类型(TypeReference<List<List<String>>>
)类的子类,已经明确知道类型了,jvm就不会进行类型擦除,类型信息会被保存下来
如下面这个类 继承子LinkedList<String>
public class Type4 extends LinkedList<String> {
}
对应的字节码内容
在签名信息里确切的知道了父类类型是LinkedList<String>
3.TypeReference源码
protected TypeReference(){
//拿父类的通用类型
Type superClass = getClass().getGenericSuperclass();
//拿父类真实类型的第一个 也就是第一个类型 就是 List<List<String>>
Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
//缓存
Type cachedType = classTypeCache.get(type);
if (cachedType == null) {
classTypeCache.putIfAbsent(type, type);
cachedType = classTypeCache.get(type);
}
this.type = cachedType;
}
四丶泛型擦除
1.定义
Java 泛型擦除(类型擦除)是指在编译器处理带泛型定义的类、接口或方法时,会在字节码指令集里抹去全部泛型类型信息,泛型被擦除后在字节码里只保留泛型的原始类型(raw type)。
原始类型是指抹去泛型信息后的类型,在 Java 语言中,它必须是一个引用类型(非基本数据类型),一般而言,它对应的是泛型的定义上界。
示例:<T> 中的 T 对应的原始泛型是 Object,<T extends A> 对应的原始类型就是 A。
2.泛型擦除验证
如下代码,我们定义了一些类,然后反射获取这些类当中create方法的出参,并打印分析
static class A {
}
//返回值上界是A
static abstract class B<T extends A> {
abstract T create();
}
//返回值没有上界,或者可以视为T extends Object
static abstract class C<T> {
abstract T create();
}
// T 要求同时是A的子类且实现Comparable
static abstract class D<T extends A & Comparable<?>> {
abstract T create();
}
//泛型多限制时 类必须在接口前
// static abstract class E<T extends Comparable<A> & A> {
// abstract T create();
// }
//T 要求实现Serializable 和 Comparable
static abstract class E<T extends Serializable & Comparable<?>> {
abstract T create();
}
//T 要求实现 Comparable 和 Serializable
static abstract class F<T extends Comparable<?> & Serializable> {
abstract T create();
}
//反射获取方法返回值 并且大于
static void reflectCreateMethodInfoPrint(Class<?> clazz, String methodName, Class<?>... paramClass) throws Exception {
Method createMethod = clazz.getDeclaredMethod(methodName, paramClass);
System.out.println("===============");
System.out.println("当前class:" + clazz.getSimpleName());
System.out.println(methodName + "方法返回值:" + createMethod.getReturnType().getSimpleName());
}
public static void main(String[] args) throws Exception {
//泛型擦除反射获取方法返回值
reflectCreateMethodInfoPrint(B.class, "create");
reflectCreateMethodInfoPrint(C.class, "create");
reflectCreateMethodInfoPrint(D.class, "create");
reflectCreateMethodInfoPrint(E.class, "create");
reflectCreateMethodInfoPrint(F.class, "create");
}
1)对于B类和D类
打印结果是
我们可以理解为编译器直接将方法的返回值设置为T类型的上界也就是A
2)对于C类
打印结果是
我们可以理解为编译器直接将方法的返回值设置为T类型的上界,泛型没有指定上界 那么就是Object
3)对E和F类
打印结果是
在要求泛型实现多个接口时,编译器默认将返回值设置成最左接口的类型
3.泛型擦除导致的问题
泛型类型变量不能是基本数据类型
泛型类型变量只能是引用类型,不能是 Java 中的 8 种基本类型(
char
、byte
、short
、int
、long
、boolean
、float
、double
)。
以 List 为例,只能使用List<Integer>
,但不能使用List<int>
,因为在进行类型擦除后,List 的原始类型会变为 Object,而 Object 类型不能存储 int 类型的值,只能存储引用类型 Integer 的值。类型的丢失
对于泛型对象使用 instanceof 进行类型判断的时候就不能使用具体的类型,而只能使用通配符?
,示例如下所示:
ArrayList<String> list = new ArrayList<>();
System.out.println(list instanceof ArrayList);//true
System.out.println(list instanceof ArrayList<?>);//true
//无法编译
// System.out.println(list instanceof ArrayList<String>);
catch中不能使用泛型异常类
假设有一个泛型异常类的定义 MyException<T>,
try{
}catch (MyException<String> e1)
catch ( MyException<Integer>e2){…}
//MyException<String> 和 MyException<Integer> 都会被擦除为 MyException<Object>,因此,两个 catch 的条件就相同了,所以这种写法是不允许的。
//也不允许在 catch 子句中使用泛型变量
public <T extends Throwable> void test(T t) {
try{
...
}catch(T e) { //编译错误
...
} catch(IOException e){
}
}
//假设上述代码能通过编译,由于擦除的存在,T 会被擦除为 Throwable。由于异常捕获的原则为:先捕获子类类型的异常,再捕获父类类型的异常。
即使T擦除之后是下一个catch的子类,也是不行的
泛型类的静态方法与属性不能使用类上面的泛型
由于泛型类中的泛型参数的实例化是在实例化对象的时候指定的,而静态变量和静态方法的使用是不需要实例化对象的,显然这二者是矛盾的。
如果没有实例化对象,而直接使用泛型类型的静态变量,那么此时是无法确定其类型的。
静态方法使用泛型
五丶桥接方法
1.定义:
桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。
2.如何判断是否是桥接方法
可以通过 Method.isBridge()
来判断一个方法是不是桥接方法。
3.代码分析帮助理解桥接方法
public static void main(String[] args) {
//桥接方法
printMethodByName("getData", BridgeMethodT.class);
printMethodByName("setData", BridgeMethodT.class);
printMethodByName("getData", BridgeMethodSubT.class);
printMethodByName("setData", BridgeMethodSubT.class);
printMethodByName("getData", BridgeMethodString.class);
printMethodByName("setData", BridgeMethodString.class);
}
//反射分析clazz 中的methodName方法
static void printMethodByName(String methodName, Class<?> clazz) {
//所有的放啊
Method[] methods = clazz.getMethods();
System.out.println("=============");
System.out.println("当前分析类:" + clazz.getSimpleName());
System.out.println("当前方法名称:" + methodName);
for (Method method : methods) {
//如果方法名字相同
if (method.getName().equals(methodName)) {
//是否是桥接方法
System.out.println("是否是桥接方法:" + method.isBridge());
//返回值类型
System.out.println("当前方法返回值:" + method.getReturnType());
//所有的方法入参类型
Parameter[] allParams = method.getParameters();
//打印第一个入参 应为setData具备第一个入参
if (allParams.length > 0) {
System.out.println("当前方法入参:" + allParams[0].getType().getSimpleName());
} else {
System.out.println("无入参");
}
//方法分割线
System.out.println("----");
}
}
}
static class BridgeMethodT<T> {
T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
static class BridgeMethodSubT<T> extends BridgeMethodT<T> {
@Override
public T getData() {
return super.getData();
}
@Override
public void setData(T data) {
super.setData(data);
}
}
static class BridgeMethodString extends BridgeMethodT<String> {
@Override
public String getData() {
return super.getData();
}
@Override
public void setData(String data) {
super.setData(data);
}
}
输出
=============
当前分析类:BridgeMethodT
当前方法名称:getData
是否是桥接方法:false
当前方法返回值:class java.lang.Object
无入参
----
=============
当前分析类:BridgeMethodT
当前方法名称:setData
是否是桥接方法:false
当前方法返回值:void
当前方法入参:Object //BridgeMethodT的方法入参被擦除成Object
----
=============
当前分析类:BridgeMethodSubT
当前方法名称:getData
是否是桥接方法:false
当前方法返回值:class java.lang.Object
无入参
----
=============
当前分析类:BridgeMethodSubT
当前方法名称:setData
是否是桥接方法:false
当前方法返回值:void
当前方法入参:Object //方法入参被擦除成Object
----
=============
当前分析类:BridgeMethodString
当前方法名称:getData
是否是桥接方法:true
当前方法返回值:class java.lang.Object //这个是编译器自动生成的桥接方法 实际是调用父类的getData方法
无入参
----
是否是桥接方法:false
当前方法返回值:class java.lang.String //我们重写的方法
无入参
----
=============
当前分析类:BridgeMethodString
当前方法名称:setData
是否是桥接方法:true
当前方法返回值:void
当前方法入参:Object //这个是编译器自动生成的桥接方法 实际是调用父类的setData方法
----
是否是桥接方法:false
当前方法返回值:void
当前方法入参:String //我们重写的方法
----
可以得出结论 对于getData子类BridgeMethodString存在两个方法
Object getData()
String getData()
这似乎违背了方法签名(方法名+参数列表确定为一个一个方法),这里getData只是出参不同怎么可以存在昵——程序员不能写违背方法签名的方法,但是jvm允许
JVM会用参数类型和返回类型来确定一个方法。 一旦编译器通某种方式自己编译出方法签名一样的两个方法 (只能编译器自己来创造这种奇迹,我们程序员却不能人为的编写这种代码)。JVM还是能够分清楚这些方法的,前提是需要返回类型不一样。
看到这里在理解下——桥接方法是 JDK 1.5 引入泛型后,为了使Java的泛型方法生成的字节码和 1.5 版本前的字节码相兼容,由编译器自动生成的方法。
setData的调用可以理解成
BridgeMethodString 存在两个setData 一个接受object类型, 一个接受string 类型
BridgeMethodString.setData("1")
其实是先掉头setData(Object)然后强转成String 后执行setData(String)
4.桥方法带来的问题
1.不小心调用到桥方法
BridgeMethodT b = new BridgeMethodString();
invoke(b,"setData",new Object());
//抛出异常java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.String
//这里其实就是调用到了父类的setData 然后将Object 强转成String
//反射调用方法
public static void invoke(Object obj,String methodName,Object param1) throws InvocationTargetException, IllegalAccessException {
Method[] methods = obj.getClass().getMethods();
for (Method method : methods) {
if (method.getName().equals(methodName)){
//检验第一个参数类型和入参相同 这个时候上面调用传入的是new Object 这个时候就会掉调用到桥方法 将object强转成String
if (method.getParameters()
[0].getType().equals(param1.getClass())
method.invoke(obj,param1);
break;
}
}
}
看一下hutool是怎么解决的:
public static Method getMethodByName(Class<?> clazz, boolean ignoreCase, String methodName) throws SecurityException {
if (null == clazz || StrUtil.isBlank(methodName)) {
return null;
}
final Method[] methods = getMethods(clazz);
if (ArrayUtil.isNotEmpty(methods)) {
for (Method method : methods) {
if (StrUtil.equals(methodName, method.getName(), ignoreCase)
// 排除桥接方法 false == method.isBridge()
//可以通过method.isBridge() 来判断是否是桥接方法
&& false == method.isBridge()) {
return method;
}
}
}
return null;
}
2.桥方法导致重写方法冲突
六丶如何构造泛型数组
public static <T> T[] newTArray(Class<T> clazz, int len) {
//底层是一个native 方法 malloc开辟 单个clazz对象大小*长度+数据额外的空间
return (T[]) Array.newInstance(clazz, len);
}
public static <T> T[] newTArray2(Class<T> clazz, int len) {
ArrayList<T> ts = new ArrayList<>(len);
//调用 Arrays.copyOf
//底层调用Array.newInstance然后 System.arraycopy
return (T[]) ts.toArray();
}
七丶协变 逆变,通配符
1.定义
逆变与协变用来描述类型转换(type transformation)后的继承关系,其定义:如果A、B表示类型,f(⋅)表示类型转换操作
f(⋅)是逆变(contravariant)的,当A是B的子类的时有f(B)是f(A)的子类成立;
f(⋅)是协变(covariant)的,当A是B的子类时有f(A)是f(B)的子类成立;
f(⋅)是不变(invariant)的,当A是B的子类时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
2.背景
//工人
abstract static class Worker {
abstract void work();
}
//清洁工
static class Cleaner extends Worker {
void clean() {
}
@Override
void work() {
}
}
//后端
static class BackEnd extends Worker {
void backEnd() {
}
@Override
void work() {
System.out.println("backEnd");
}
}
//java 后端
static class JavaCoder extends BackEnd {
void java() {
System.out.println("java");
}
}
//前端
static class FondEnd extends Worker {
void fondEnd() {
}
@Override
void work() {
System.out.println("fondEnd");
}
}
2.数组是协变的
f(⋅)是协变(covariant)的,当A是B的子类时有f(A)是f(B)的子类成立;
也就是说A是B的子类===》A【】是B【】的子类,如下
//数组支持协变
//A是B的子类 A[] 也是B[]的子类
BackEnd[] backEndArray = new BackEnd[10];
Worker[] workerArray = backEndArray;//说明BackEnd[] 是 Worker[]的子类 数组支持协变
System.out.println(workerArray.getClass().getComponentType());//BackEnd
System.out.println(backEndArray.getClass().isAssignableFrom(workerArray.getClass()));//true
workerArray[0] = new Cleaner();//java.lang.ArrayStoreException
以上代码可以通过编译,但是运行抛出java.lang.ArrayStoreException
3.泛型是不变的
f(⋅)是不变(invariant)的,当A是B的子类时上述两个式子均不成立,即f(A)与f(B)相互之间没有继承关系。
也就是说A是B的子类,List《A》 和List《B》没有任何关系
List<Worker> workerList = new ArrayList<>();
List<BackEnd> backEndList = new ArrayList<>();
workerList = backEndList;//无法编译
backEndList = workerList;//无法编译
//因为泛型擦除的时候 都是List<object> 如果支持这种操作 那么将导致
backEndList.get(1) 可能是一个清洁工 强转成BackEnd 失败
4.泛型上界extends 是协变的
f(⋅)是协变(covariant)的,当A是B的子类时有f(A)是f(B)的子类成立;
//?extends worker的子类 是BackEnd的子类
ArrayList<BackEnd> backEnds = new ArrayList<>();
List<? extends Worker> workers = backEnds;//可以
//但是协变之后 不可使用任何入参是泛型的方法
workers.add(new BackEnd());//编译错误
workers.add(new JavaCoder());//编译错误
//编译器无法确定所持有的具体类型是什么
//所以一旦执行这种类型的向上转型,你就将丢失掉向其中传递任何对象的能力。
5.泛型下界super是逆变的
f(⋅)是逆变(contravariant)的,当A是B的子类的时有f(B)是f(A)的子类成立;
// worker 是 ? super Backend 的子类
List<Worker> workers1 = new ArrayList<>();
//==>List<Worker> 是 List<? super BackEnd>的子类
List<? super BackEnd> backEndArrayList = workers1;
//可以加入任何BackEnd的父类
backEndArrayList.add(new JavaCoder());
//但是遍历的时候 得到的是object 因为Object 也是BackEnd的父类
//无法保证集合里面的元素到底是具体什么类型
for (Object o : backEndArrayList) {
}
6.producer-extends, consumer-super
从数据流来看,extends是限制数据来源的(生产者),而super是限制数据流入的(消费者)
作为生产者的时候使用extends
//越界返回 Optional.empty() 反之返回对应位置的optional保证
public static <T> Optional<T> outBoundsReturnNull(List<? extends T> collection, int index) {
if (collection == null || index < 0 || index >= collection.size()) {
return Optional.empty();
}
return Optional.ofNullable(collection.get(index));
}
作为消费者使用super
//尝试添加到指定位置
public static <T> boolean tryAddAtAppointIndex(List<? super T> list, int index, T value) {
if (list == null) {
return false;
}
if (index < 0 || index > list.size()) {
return false;
}
list.add(index,value);
return true;
}
八丶反射与泛型
1.Type类
Type是Java当前所有类型的通用的顶层接口,包括
原始类型(Type)
不仅仅包含我们平常所指的类,还包括枚举、数组、注解等
参数化类型(ParameterizedType)
Map<K,V>
,Set<T>
,`这里的参数化指这些泛型可以像参数一样去指定
数组类型(GenericArrayType)
带有泛型的数组,即T[]
类型变量(TypeVariable)
比如 T a
基本类型(Class)
public interface Type {
//1.8后默认实现
default String getTypeName() {
return toString();
}
}
2.ParameterizedType
ParameterizedType 表示参数化类型,参数化类型即我们通常所说的泛型类型,例如 Collection<String>
。参数化类型在反射方法第一次需要时创建。创建参数化类型 p 时,解析 p 实例化的泛型类型声明,并递归创建 p 的所有类
2.1.getActualTypeArguments():
该方法返回参数化类型<>中的实际参数类型, 如 Map<String,Person> map 这个 ParameterizedType 返回的是 String 类,Person 类的全限定类名的 Type Array。注意: 该方法只返回最外层的<>中的类型,无论该<>内有多少个<>。
2.2.getOwnerType()
返回ParameterizedType类型所在的类的Type。如Map.Entry<String, Object>这个参数化类型返回的事Map(因为Map.Entry这个类型所在的类是Map)的类型。
2.3.getRawType()
返回的是当前这个 ParameterizedType 的类型。 如 Map<String,Person> map 这个 ParameterizedType 返回的是 Map 类的全限定类名的 Type 数组。
3.TypeVariable
类型变量,即泛型中的变量;例如:T、K、V等变量,可以表示任何类,TypeVariable代表着泛型中的变量,而ParameterizedType则代表整个泛型
3.1.getBounds
获取泛型的上限,如果没有指定那么默认是Object
3.2.getGenericDeclaration
获取声明该类型变量实体(即获取类,方法或构造器名)(java 只可以在这三个位置声明泛型)
3.3.getAnnotatedBouds
返回一个AnnotatedType对象的数组,表示使用类型来表示此TypeVariable表示的类型参数的上限。 数组中的对象的顺序对应于type参数的声明中的边界的顺序。 如果type参数声明没有边界,则返回长度为0的数组。
4.GenericArrayType
泛型数组类型,用来描述ParameterizedType、TypeVariable类型的数组;即List[] 、T[]等
4.1.getGenericComponentType
返回表示此数组的组件类型的Type
对象。
如果数组内部的元素既不是ParameterizedType也不是TypeVariable,那么该数组代表的Type则是一个数组Class,而不是GenericArrayType。
5.WildcardType
WildcardType表示通配符类型表达式,例如?
、? extends Number
、? super Integer
。
5.1.getUpperBounds
获取通配符的所有上边界(使用extends关键字),为了保持扩展返回数组
5.2.getLowerBounds
获取通配符的所有下边界(使用super关键字),为了保持扩展返回数组
重学Java泛型的更多相关文章
- 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...
- 重学Java(一):与《Java编程思想》的不解之缘
说起来非常惭愧,我在 2008 年的时候就接触了 Java,但一直到现在(2018 年 10 月 10 日),基础知识依然非常薄弱.用一句话自嘲就是:十年 IT 老兵,Java 菜鸡一枚. 于是,我想 ...
- 重学 Java 设计模式:实战抽象工厂模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!
- 重学 Java 设计模式:实战单例模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 5个创建型模式的最后一个 在设计模式中按照不同的处理方式共包含三大类:创建型模式.结 ...
- 重学 Java 设计模式:实战适配器模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 擦屁屁纸80%的面积都是保护手的! 工作到3年左右很大一部分程序员都想提升自己的技术 ...
- 重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)
作者:小傅哥 博客:https://bugstack.cn - 编写系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么你的代码那么多ifelse 同类的业务.同样的功能, ...
- 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...
- 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你感受到的容易,一定有人为你承担不容易 这句话更像是描述生活的,许许多多的磕磕绊绊总 ...
- 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 程序员的上下文是什么? 很多时候一大部分编程开发的人员都只是关注于功能的实现,只 ...
随机推荐
- js基础学习-数组
let arr1 = [ {name: 1} ] let arr2 = [ {age: 23} ] let ages = [11, 22, 23] let newArr = arr1.concat(a ...
- 【黄啊码】MySQL入门—3、我用select *,老板直接赶我坐火车回家去,买的还是站票
大家好!我是黄啊码,学会了DDL语句了吗?那我们今天就来学习一下基本的查询语法,我见过很多外包机构的程序员都是万物皆可select *,然后项目跑了一段时间就基本跑不动了,问就回答:服务器配置不够,加 ...
- java的访问权限protected和default
protected和default的区别 第一点:在同一个包中,protected和default表现一致,即,当main方法所在的类和使用了protected与default修饰属性.方法的类在同一 ...
- static关键字和代码块
static关键字 static修饰的变量称为静态变量/共享变量/类变量 用于修饰类的成员,如成员变量.成员方法以及代码块等,内static修饰的成员具备一些特殊性 1.静态变量 在java类中使用s ...
- 在centos7.6上部署前后端分离项目Nginx反向代理vue.js2.6+Tornado5.1.1,使用supervisor统一管理服务
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_102 这一次使用vue.js+tornado的组合来部署前后端分离的web项目,vue.js不用说了,前端当红炸子鸡,泛用性非常广 ...
- js中数组去重的方法
在实际工作或面试中,我们经常会遇到"数组去重"问题,接下来就是使用js实现的数组去重的多种方法: 1.借助ES6提供的Set结构 var arr = [1,1,2,2,3,3,4, ...
- 使用idea remote 开发体验
本地使用idea开发最不好的一个体验就是打开稍大的工程就非常的卡,怎么调参数都没用,现在idea推出了idea remote就赶紧来体验下. 使用方式 除了idea不需要额外下载什么包,但是因为rem ...
- 熔断器-Hystrix。。。之降级方法
与Feign的Fallback降级方法不同,这个hystrix降级方法是写在被调用方的 需要依赖: <dependency> <groupId>org.springframew ...
- 【Matlab】学习记录1-简单的函数介绍
sind(30) %正弦函数,以角度为单位 ans =0.5000 exp(2) %以e为底的指数函数,即e^x ans =7.3891 log10(10) ans =1log(exp(1)) ...
- DispatcherServlet 分发流程
0 太长不看版 HTTPServlet 的 Service 方法将请求按类进行分解 主要是根据HTTP方法的类型调用 doXXX 方法 GET 和 HEAD 方法需要对 if-modified-sin ...