当前位置: 首页 > news >正文

移动医护网站建设利弊登封网络推广公司

移动医护网站建设利弊,登封网络推广公司,网页设计构建的基本流程,wordpress 在线 主题1、#x1f4aa; 目录1、#x1f4aa;1.0、什么是面向对象1.1、JDK、JRE、JVM之间的区别1.2、什么是字节码1.3、hashCode()与equals()之间的联系1.4、String、StringBuffer、StringBuilder的区别1.5、和equals方法的区别1.6、重载和重写的区别1.7、List和Set的区别1.8、Array…1、 目录1、1.0、什么是面向对象1.1、JDK、JRE、JVM之间的区别1.2、什么是字节码1.3、hashCode()与equals()之间的联系1.4、String、StringBuffer、StringBuilder的区别1.5、和equals方法的区别1.6、重载和重写的区别1.7、List和Set的区别1.8、ArrayList和LinkedList的区别1.9、ArrayList的优缺点1.10、Array和ArrayList有何区别什么时候更适合用Array1.11、遍历一个List有哪些不同的方式1.12、ArrayList的扩容机制1.13、ConcurrentHashMap的实现原理是什么1.14、JDK1.8 中为什么使用内置锁 synchronized替换 可重入锁 ReentrantLock1.15、ConcurrentHashMap的扩容机制1.16、HashMap 与 ConcurrentHashMap 的区别是什么1.17、JDK1.7和JDK1.8的ConcurrentHashMap实现有什么不同1.18、ConcurrentHashMap的put方法执行逻辑是什么1.19、ConcurrentHashMap的get方法执行逻辑是什么1.20、ConcurrentHashMap 的 get 方法是否要加锁为什么1.21、get 方法不需要加锁与 volatile 修饰的哈希桶数组有关吗1.22、ConcurrentHashMap 不支持 key 或者 value 为 null 的原因1.23、ConcurrentHashMap 和 Hashtable 的效率哪个更高为什么1.24、具体说一下Hashtable的锁机制1.25、jdk1.7到jdk1.8HashMap发生了什么变化1.26、解决hash冲突的办法有哪些HashMap用的哪种1.27、为什么在解决hash冲突的时候不直接用红黑树而选择先用链表再转为红黑树1.28、HashMap默认加载因子是多少为什么是0.751.29、HashMap的Put方法1.30、HashMap为什么线程不安全1.31、深拷贝和浅拷贝1.32、HashMap的扩容机制1.33、Java中的异常体系是怎样的1.34、什么时候应该抛出异常什么时候捕获异常1.35、了解ReentrantLock吗1.36、ReentrantLock中tryLock()和lock()方法的区别1.37、ReentrantLock中的公平锁和非公平锁的底层实现1.38、ReadWriteLock是什么1.39、Sychronized的偏向锁、轻量级锁、重量级锁1.40、synchronized为什么是非公平锁非公平体现在哪些地方1.41、Sychronized和ReentrantLock的区别1.42、谈谈你对AQS的理解AQS如何实现可重入锁1.43、什么是泛型有什么作用1.44、泛型的使用方式有哪几种1.45、Java泛型的原理是什么什么是类型擦除1.46、什么是泛型中的限定通配符和非限定通配符1.47、Array中可以用泛型吗1.48、项目中哪里用到了泛型1.49、反射1.50、如何获取反射中的Class对象1.51、Java反射API有几类1.52、反射机制的应用有哪些1.53、什么是注解1.54、注解的解析方法有哪几种1.55、序列化和反序列化1.56、如果有些字段不想进行序列化怎么办1.57、为什么不推荐使用JDK自带的序列化1.58、静态变量会被序列化吗1.56、如果有些字段不想进行序列化怎么办1.57、为什么不推荐使用JDK自带的序列化1.58、静态变量会被序列化吗 1.0、什么是面向对象 对比面向过程面向对象是不同的处理问题的角度。面向过程更注重事情的每一个步骤及顺序面向对象更注重事情有哪些参与者及各自需要做什么。 比如洗衣机洗衣服。面向过程就会将任务拆解成一系列的函数1.打开洗衣机 2.放衣服 3.放洗衣粉 4.清洗 面向对象会拆出人和洗衣机两个对象人打开洗衣机、放衣服、放洗衣粉洗衣机只需要清洗即可。 面向过程比较直接高效面向对象更易于维护、扩展和复用。 面向对象有三大特性封装、继承和多态 封装的意义在于明确标识出允许外部使用的所有成员函数和数据项内部细节对外部调用透明外部调用无需关心内部的实现Java中有两个比较经典的场景 Javabean 的属性私有对外提供 get、set 方法访问 private String name; public void setName(String name){this.name name; }orm 框架操作数据库我们不需要关心链接是如何建立的、sql是如何执行的只需要引入 mybatis调相应的方法即可 继承继承父类的方法并作出自己的改变和扩展 子类拥有父类对象所有的属性和方法包括私有属性和私有方法但是父类中的私有属性和方法子类是无法访问只是拥有。子类可以拥有自己属性和方法即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。 多态比如都是动物类型的对象执行 run 方法cat 猫类和 dog 狗类会表现出不同的行为特征。 多态的使用前提必须存在继承或者实现关系、必须存在父类类型的变量引用(指向)子类类型的对象、需要存在方法重写多态本质上分为两种编译时多态又称静态多态、运行时多态又称动态多态。**我们通常所说的多态指的都是运行时多态也就是编译时不确定究竟调用哪个具体方法一直延迟到运行时才能确定。**这也是为什么有时候多态方法又被称为延迟方法的原因。 1.1、JDK、JRE、JVM之间的区别 JDKJava SE Development KitJava标准开发包它拥有 JRE 所拥有的一切还有编译器javac和工具如 javadoc 和 jdb。它能够创建和编译程序。JREJava Runtime Environment它是运行已编译 Java 程序所需的所有内容的集合包括 Java 虚拟机JVMJava 类库java 命令和其他的一些基础构件。但是它不能用于创建新程序JVMJava Virtual MechinalJava 虚拟机它是整个 Java 实现跨平台的最核心的部分负责运行字节码文件。 我们写出来的 Java 代码想要运行需要先编译成字节码那就需要编译器而 JDK 中就包含了编译器 javac编译之后的字节码想要运行就需要一个可以执行字节码的程序这个程序就是 JVM Java虚拟机专门用来执行 Java 字节码的。 JDK包含JREJRE包含JVM。 1.2、什么是字节码 Java之所以可以“一次编译到处运行”一是因为JVM针对各种操作系统、平台都进行了定制二是因为无论在什么平台都可以编译生成固定格式的字节码.class文件供JVM使用。因此也可以看出字节码对于Java生态的重要性。 Java语言通过字节码的方式在一定程度上解决了传统解释型语言执行效率低的问题同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效而且由于字节码并不专对一种特定的机器因此Java程序无须重新编译便可在多种不同的计算机上运行。 1.3、hashCode()与equals()之间的联系 在Java中每个对象都可以调用自己的hashCode()方法得到自己的哈希值(hashCode)我们可以利用hashCode来做一些提前的判断比如 如果两个对象的hashCode不相同那么这两个对象肯定不同的两个对象如果两个对象的hashCode相同不代表这两个对象一定是同一个对象也可能是两个对象如果两个对象相等那么他们的hashCode就一定相同 在Java的集合类的实现中在比较两个对象是否相等时会先调用对象的hashCode()方法得到哈希值进行比较如果hashCode不相同就可以直接认为这两个对象不相同。如果hashCode相同那么就会进一步调用equals()方法进行比较可以用来最终确定两个对象是不是相等的。所以如果我们重写了equals()方法一般也要重写 hashCode() 方法。 1.4、String、StringBuffer、StringBuilder的区别 String是不可变的如果尝试去修改会新生成一个字符串对象StringBuffer和StringBuilder是可变的【补充String 不是基本数据类型是引用类型底层用 char 数组实现的】String类利用了 final 修饰的 char 类型数组存储字符它里面的对象是不可变的也就可以理解为常量显然线程安全。 private final char value[];StringBuffer是线程安全的StringBuffer 属于可变类对方法加了同步锁线程安全【说明StringBuffer对方法加了同步锁或者对调用的方法加了同步锁所以是线程安全的】 StringBuilder是线程不安全的 执行效率StringBuilder StringBuffer String 线程安全当多个线程访问某一个类对象或方法时对象对应的公共数据区始终都能表现正确那么这个类对象或方法就是线程安全的 对于三者使用的总结 操作少量的数据: 适用 String单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer String 为什么要设计为不可变类不可变对象的好处是什么 主要的原因主要有以下三点 字符串常量池的需要字符串常量池是 Java 堆内存中一个特殊的存储区域, 当创建一个 String 对象时假如此字符串值已经存在于常量池中则不会创建一个新的对象而是引用已经存在的对象允许 String 对象缓存 HashCodeJava 中 String 对象的哈希码被频繁地使用, 比如在 HashMap 等容器中。字符串不变性保证了 hash 码的唯一性因此可以放心地进行缓存。这也是一种性能优化手段意味着不必每次都去计算新的哈希码String 被许多的 Java 类(库)用来当做参数例如网络连接地址 URL、文件路径 path、还有反射机制所需要的 String 参数等, 假若 String 不是固定不变的将会引起各种安全隐患 不可变对象指对象一旦被创建,状态就不能再改变,任何修改都会创建一个新的对象,如 String、Integer及其它包装类.不可变对象最大的好处是线程安全. String 字符串修改实现的原理 当用 String 类型来对字符串进行修改时其实现方法是首先创建一个 StringBuilder其次调用 StringBuilder 的 append() 方法最后调用 StringBuilder 的 toString() 方法把结果返回。 String str “i” 和 String str new String(“i”) 一样吗 不一样因为内存的分配方式不一样。String str “i” 的方式Java 虚拟机会将其分配到常量池中。 String str new String(“i”) 则会被分到堆内存中。 public class StringTest { public static void main(String[] args) {String str1 abc;String str2 abc;String str3 new String(abc);String str4 new String(abc);System.out.println(str1 str2); // trueSystem.out.println(str1 str3); // falseSystem.out.println(str3 str4); // falseSystem.out.println(str3.equals(str4)); // true} } 在执行 String str1 “abc” 的时候JVM 会首先检查字符串常量池中是否已经存在该字符串对象如果已经存在那么就不会再创建了直接返回该字符串在字符串常量池中的内存地址如果该字符串还不存在字符串常量池中那么就会在字符串常量池中创建该字符串对象然后再返回。所以在执行 String str2 “abc” 的时候因为字符串常量池中已经存在“abc”字符串对象了就不会在字符串常量池中再次创建了所以栈内存中 str1 和 str2 的内存地址都是指向 “abc” 在字符串常量池中的位置所以 str1 str2 的运行结果为 true 而在执行 String str3 new String(“abc”) 的时候JVM 会首先检查字符串常量池中是否已经存在“abc”字符串如果已经存在则不会在字符串常量池中再创建了如果不存在则就会在字符串常量池中创建 “abc” 字符串对象然后再到堆内存中再创建一份字符串对象 String类的常用方法都有哪些 indexOf()返回指定字符的索引charAt()返回指定索引处的字符replace()字符串替换trim()去除字符串两端空白split()分割字符串返回一个分割后的字符串数组length()返回字符串长度substring()截取字符串equals()字符串比较 String 有哪些特性 不变性常量池优化String 对象创建之后会在字符串常量池中进行缓存如果下次创建同样的对象时会直接返回缓存的引用final使用 final 来定义 String 类表示 String 类不能被继承提高了系统的安全性 在使用HashMap的时候用 String 做 key 有什么好处 HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置因为字符串是不可变的所以当创建字符串时它的 hashcode 被缓存下来不需要再次计算所以相比于其他对象更快。 1.5、和equals方法的区别 如果是基本数据类型比较是值如果是引用类型比较的是引用地址equals具体看各个类重写equals方法的比较逻辑比如String类虽然是引用类型但是String类中重写了equals方法方法内部比较的是字符串中的各个字符是否全部相等。 1.6、重载和重写的区别 重载方法重载发生在同一个类中方法名称必须相同参数类型不同、个数不同、顺序不同也是方法重载方法返回值和访问修饰符可以不同如果只是方法返回值不同就不是重载在编译时就会报错。重写方法重写发生在父子类中子类重写父类的方法方法名称、参数列表必须相同 返回值范围 ≤ 父类返回值范围抛出异常范围 ≤ 父类抛出异常范围访问修饰符范围 ≥ 父类访问修饰符范围如果父类方法访问修饰符为 private 则子类就不能重写该方法子类不能重写父类的私有方法 public int add(int a,String b) public String add(int a,String b) // 上方不是重载,重载与返回值和访问修饰符无关,只看方法名称和参数构造器是否可被重写 构造器不能被继承因此不能被重写但可以被重载。每一个类必须有自己的构造函数负责构造自己这部分的构造。子类不会覆盖父类的构造函数相反必须一开始调用父类的构造函数。 1.7、List和Set的区别 List有序可重复按对象插入的顺序保存对象允许多个Null元素对象。可以使用迭代器 Iterator 取出所有元素再逐一遍历各个元素。或者使用 get(int index) 方法获取指定下标的元素。Set无序不可重复。最多只允许一个 NULL 元素对象取元素只能用迭代器 Iterator 取得所有元素再逐一遍历各个元素。 1.8、ArrayList和LinkedList的区别 首先他们的底层数据结构不同ArrayList底层是基于数组实现的LinkedList底层是基于链表实现的ArrayList更适合随机查找LinkedList更适合删除和添加(不要下意识地认为 LinkedList 作为链表就最适合元素增删的场景LinkedList 仅仅在头尾插入或者删除元素的时候时间复杂度近似 O(1)其他情况增删元素的时间复杂度都是 O(n) )ArrayList和LinkedList都实现了List接口但是LinkedList还额外实现了Deque接口所以LinkedList还可以当做队列来使用两个都不保证线程安全。我们在项目中一般是不会使用到 LinkedList 的需要用到 LinkedList 的场景几乎都可以使用 ArrayList 来代替并且性能通常会更好就连 LinkedList 的作者都说他自己几乎从来不会使用 LinkedList 1.9、ArrayList的优缺点 ArrayList的优点如下 ArrayList 底层以数组实现ArrayList 实现了 RandomAccess 接口根据索引进行随机访问的时候速度非常快。ArrayList 在尾部添加一个元素的时候非常方便。 ArrayList 的缺点如下 在非尾部的增加和删除操作影响数组内的其他数据的下标需要进行数据搬移比较消耗性能 ArrayList 比较适合顺序添加、随机访问的场景。 1.10、Array和ArrayList有何区别什么时候更适合用Array Array 可以包含基本类型和对象类型ArrayList 只能包含对象类型Array 大小是固定的ArrayList 的大小是动态变化的ArrayList 提供了更多的方法和特性比如addAll()removeAll()iterator() 等等 1.11、遍历一个List有哪些不同的方式 遍历方式有以下几种 for 循环遍历基于计数器。在集合外部维护一个计数器然后依次读取每一个位置的元素当读取到最后一个元素后停止迭代器遍历Iterator。Iterator 是面向对象的一个设计模式目的是屏蔽不同数据集合的差异提供统一遍历集合的接口。Java 在 Collections 集合都支持了 Iterator 遍历foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现使用时不需要显式声明 Iterator 或计数器。优点是代码简洁不易出错缺点是只能做简单的遍历不能在遍历过程中不能对集合进行增删操作 Java Collections 框架中提供了一个 RandomAccess 接口用来标记 List 实现是否支持 Random Access。 如果一个数据集合实现了该接口最好使用for 循环遍历如果没有实现该接口则推荐使用 Iterator 或 foreach 遍历。 1.12、ArrayList的扩容机制 ArrayList扩容的本质就是计算出新的扩容数组的size后实例化并将原有数组内容复制到新数组中去。默认情况下新的容量会是原容量的1.5倍。 1.13、ConcurrentHashMap的实现原理是什么 先来看下JDK1.7 JDK1.7 中的 ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成即 ConcurrentHashMap 把哈希桶数组切分成小数组Segment 每个小数组有 n 个 HashEntry 组成。 如下图所示首先将数据分为一段一段的存储然后给每一段数据配一把锁当一个线程占用锁访问其中一段数据时其他段的数据也能被其他线程访问实现了真正的并发访问。 Segment 继承了 ReentrantLock所以 Segment 是一种可重入锁扮演锁的角色。Segment 默认为 16也就是并发度为 16。 再来看下JDK1.8 在数据结构上 JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的Node数组链表红黑树结构在锁的实现上抛弃了原有的 Segment 分段锁采用CAS synchronized实现更加细粒度的锁。 将锁的级别控制在了更细粒度的哈希桶数组元素级别也就是说只需要锁住这个链表头节点红黑树的根节点就不会影响其他的哈希桶数组元素的读写大大提高了并发度。 1.14、JDK1.8 中为什么使用内置锁 synchronized替换 可重入锁 ReentrantLock 在 JDK1.6 中对 synchronized 锁的实现引入了大量的优化并且 synchronized 有多种锁状态会从无锁 - 偏向锁 - 轻量级锁 - 重量级锁一步步转换减少内存开销 。假设使用可重入锁来获得同步支持那么每个节点都需要通过继承 AQS 来获得同步支持。但并不是每个节点都需要获得同步支持的只有链表的头节点红黑树的根节点需要同步这无疑带来了巨大内存浪费。 1.15、ConcurrentHashMap的扩容机制 1.7版本 1.7版本的ConcurrentHashMap是基于Segment分段实现的每个Segment相对于一个小型的HashMap每个Segment内部会进行扩容和HashMap的扩容逻辑类似先生成新的数组然后转移元素到新数组中扩容的判断也是每个Segment内部单独判断的判断是否超过阈值 1.8版本 1.8版本的ConcurrentHashMap不再基于Segment实现当某个线程进行put时如果发现ConcurrentHashMap正在进行扩容那么该线程一起进行扩容如果某个线程put时发现没有正在进行扩容则将key-value添加到ConcurrentHashMap中然后判断是否超过阈值超过了则进行扩容ConcurrentHashMap是支持多个线程同时扩容的扩容之前也先生成一个新的数组在转移元素时先将原数组分组将每组分给不同的线程来进行元素的转移每个线程负责一组或多组的元素转移工作 1.16、HashMap 与 ConcurrentHashMap 的区别是什么 HashMap 不是线程安全的而 ConcurrentHashMap 是线程安全的。ConcurrentHashMap 采用锁分段技术将整个Hash桶进行了分段segment也就是将这个大的数组分成了几个小的片段 segment而且每个小的片段 segment 上面都有锁存在那么在插入元素的时候就需要先找到应该插入到哪一个片段 segment然后再在这个片段上面进行插入而且这里还需要获取 segment 锁这样做明显减小了锁的粒度。 1.17、JDK1.7和JDK1.8的ConcurrentHashMap实现有什么不同 线程安全实现方式 JDK 1.7 采用 Segment 分段锁来保证安全 Segment 是继承自 ReentrantLock。JDK1.8 放弃了 Segment 分段锁的设计采用 Node CAS synchronized 保证线程安全锁粒度更细synchronized 只锁定当前链表或红黑二叉树的首节点Hash 碰撞解决方法 : JDK 1.7 采用拉链法JDK1.8 采用拉链法结合红黑树链表长度超过一定阈值时将链表转换为红黑树并发度 JDK 1.7 最大并发度是 Segment 的个数默认是 16。JDK 1.8 最大并发度是 Node 数组的大小并发度更大。 1.18、ConcurrentHashMap的put方法执行逻辑是什么 先来看JDK1.7 首先会尝试获取锁如果获取失败利用自旋获取锁如果自旋重试的次数超过 64 次则改为阻塞获取锁 获取到锁后 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。遍历该 HashEntry如果不为空则判断传入的 key 和当前遍历的 key 是否相等相等则覆盖旧的 value。不为空则需要新建一个 HashEntry 并加入到 Segment 中同时会先判断是否需要扩容。释放 Segment 的锁。 再来看JDK1.8 根据 key 计算出 hash值。判断是否需要进行初始化。定位到 Node拿到首节点 f判断首节点 f 如果为 null 则通过cas的方式尝试添加。如果为 f.hash MOVED -1 说明其他线程在扩容参与一起扩容。如果都不满足 synchronized 锁住 f 节点判断是链表还是红黑树遍历插入。 当在链表长度达到8的时候数组扩容或者将链表转换为红黑树。 1.19、ConcurrentHashMap的get方法执行逻辑是什么 先来看JDK1.7 首先根据 key 计算出 hash 值定位到具体的 Segment 再根据 hash 值获取定位 HashEntry 对象并对 HashEntry 对象进行链表遍历找到对应元素。 由于 HashEntry 涉及到的共享变量都使用 volatile 修饰volatile 可以保证内存可见性所以每次获取时都是最新值。 再来看JDK1.8 根据 key 计算出 hash 值判断数组是否为空如果是首节点就直接返回如果是红黑树结构就从红黑树里面查询如果是链表结构循环遍历判断。 1.20、ConcurrentHashMap 的 get 方法是否要加锁为什么 get 方法不需要加锁。因为 Node 的元素 value 和指针 next 是用 volatile 修饰的在多线程环境下线程A修改节点的 value 或者新增节点的时候是对线程B可见的。这也是它比其他并发集合比如 Hashtable、HashMap效率高的原因。 1.21、get 方法不需要加锁与 volatile 修饰的哈希桶数组有关吗 没有关系。哈希桶数组table用 volatile 修饰主要是保证在数组扩容的时候保证可见性 1.22、ConcurrentHashMap 不支持 key 或者 value 为 null 的原因 我们先来说value 为什么不能为 null。因为 ConcurrentHashMap 是用于多线程的 如果ConcurrentHashMap.get(key)得到了 null 这就无法判断是映射的value是 null 还是没有找到对应的key而为 null 就有了二义性。 而用于单线程状态的 HashMap 却可以用containsKey(key) 去判断到底是否包含了这个 null 。 1.23、ConcurrentHashMap 和 Hashtable 的效率哪个更高为什么 ConcurrentHashMap 的效率要高于 Hashtable因为 Hashtable 给整个哈希表加了一把大锁从而实现线程安全。而ConcurrentHashMap 的锁粒度更低在 JDK1.7 中采用分段锁实现线程安全在 JDK1.8 中采用CASsynchronized实现线程安全。 1.24、具体说一下Hashtable的锁机制 Hashtable 是使用 synchronized来实现线程安全的给整个哈希表加了一把大锁多线程访问时候只要有一个线程访问或操作该对象那其他线程只能阻塞等待需要的锁被释放在竞争激烈的多线程场景中性能就会非常差 1.25、jdk1.7到jdk1.8HashMap发生了什么变化 jdk1.7中底层是数组链表jdk1.8中底层是数组链表红黑树加红黑树的目的是提高HashMap插入和查询整体效率jdk1.7链表插入使用的是头插法jdk1.8中链表插入使用的是尾插法因为jdk1.8中插入 key 和 value 时需要判断链表元素个数当链表个数达到8个需要将链表转化为红黑树所以每次插入都需要遍历统计链表中元素个数所以正好直接使用尾插法jdk1.7中哈希算法比较复杂存在各种右移与异或运算jdk1.8中进行了简化jdk1.8中新增了红黑树所以可以适当的简化哈希算法节省CPU资源 1.26、解决hash冲突的办法有哪些HashMap用的哪种 解决Hash冲突方法有:开放定址法、再哈希法、链地址法拉链法。HashMap中采用的是 链地址法 将哈希值相同的元素构成一个同义词的单链表,并将单链表的头指针存放在哈希表的第i个单元中查找、插入和删除主要在同义词链表中进行。链表法适用于经常进行插入和删除的情况。 1.27、为什么在解决hash冲突的时候不直接用红黑树而选择先用链表再转为红黑树 因为红黑树需要进行左旋右旋变色这些操作来保持平衡而单链表不需要。当元素小于 8 个的时候此时做查询操作链表结构已经能保证查询性能。当元素大于 8 个的时候 红黑树搜索时间复杂度是 O(logn)而链表是 O(n)此时需要红黑树来加快查询速度但是新增节点的效率变慢了。 因此如果一开始就用红黑树结构元素太少新增效率又比较慢无疑这是浪费性能的。 不用红黑树用二叉查找树可以吗 可以。但是二叉查找树在特殊情况下会变成一条线性结构这就跟原来使用链表结构一样了造成很深的问题遍历查找会非常慢。 1.28、HashMap默认加载因子是多少为什么是0.75 0.75是对空间和时间效率的一个平衡选择一般不要修改除非在时间和空间比较特殊的情况下 如果内存空间很多而又对时间效率要求很高可以降低负载因子Load factor的值相反如果内存空间紧张而对时间效率要求不高可以增加负载因子loadFactor的值这个值可以大于1 1.29、HashMap的Put方法 HashMap的Put方法的流程 根据 put 进来的 key 通过哈希算法得出数组下标如果数组下标位置元素为空则直接将 key 和 value 封装为 Entry 对象jdk1.7中是 Entry 对象jdk1.8中是 Node 对象并将对象放入该位置如果数组下标位置元素不为空则要分情况讨论 如果是jdk1.7则先判断是否需要扩容 HashMap如果要扩容就扩容如果不用扩容就生成 Entry 对象并使用头插法将对象插入到当前位置的链表中如果是jdk1.8则会先判断当前位置上的 Node 类型看是红黑树的 Node还是链表的 Node 如果是红黑树的 Node则将 key 和 value 封装为一个红黑树结点并添加到红黑树中去如果是链表的 Node则将 key 和 value 封装为一个链表 Node 并通过 尾插法插入到链表的最后位置因为是尾插法所以需要遍历链表当发现链表结点个数大于等于8那么就会将该链表转成红黑树将 key 和 value 封装为 Node 插入到链表或者红黑树中后再判断是否需要进行扩容如果需要就扩容如果不需要就结束 put 方法。 jdk1.7是先判断需要需要扩容再进行插入。jdk1.8是先插入然后再判断是否需要扩容 1.30、HashMap为什么线程不安全 多线程下扩容死循环。JDK1.7中的 HashMap 使用头插法插入元素在多线程的环境下扩容的时候有可能导致环形链表的出现形成死循环。因此JDK1.8使用尾插法插入元素在扩容时会保持链表元素原本的顺序不会出现环形链表的问题。多线程的put可能导致元素的丢失。多线程同时执行 put 操作如果计算出来的索引位置是相同的那会造成前一个 key 被后一个 key 覆盖从而导致元素的丢失。此问题在JDK 1.7和 JDK 1.8 中都存在。put和get并发时可能导致get为null。线程1执行put时因为元素个数超出threshold而导致rehash线程2此时执行get有可能导致这个问题。此问题在JDK 1.7和 JDK 1.8 中都存在。 1.31、深拷贝和浅拷贝 java当中的克隆跟生物上所说的克隆类似就是复制出一个一模一样的个体当然迁移到java当中那就是复制出一个一模一样对象。 克隆的作用对象克隆主要是为了解决引用类型在进行等号赋值时使得两个引用同时指向同一个对象实例从而导致通过两个引用去操作对象时会直接更改实例中的属性破坏对象的相互独立性 //例如一下代码段 public class Test {public static void main(String[] args) {// TODO Student s1 new Student(Tom, 12);Student s2 s1;//s2的引用指向s1System.out.println(s1); // name Tom,age 12System.out.println(s2); // name Tom,age 12s2.setName(Jerry);//修改s2的值时s1的属性System.out.println(s1); // name Jerry,age 12System.out.println(s2); // name Jerry,age 12}由上述运行结果可知在引用类型当中由于都是指向同一个对象实例当我们用引用类型去修改对象实例的值时原来对象的属性也会跟着改变从而导致了数据的不一致性。对象的克隆就能解决上述问题防止发生此类情况。 浅克隆创建一个新对象新对象的属性和原来对象完全相同对于非基本类型属性仍指向原有属性所指向的对象的内存地址也就是在进行修改的时候同样会修改原来的属性深克隆创建一个新对象属性中引用的其他对象也会被克隆不再指向原有对象地址。深度克隆就是把对象的所有属性都统统复制一份新的到目标对象里面去使他成为一个独立的对象当修改新的对象实例的属性时原来对象中的属性任然不变。基本数据类型就是 int、double等实例对象就是 User 类、 Student类等 如何实现对象的克隆 浅克隆实现 Cloneable 接口并重写 clone() 方法浅克隆 //实现Cloneable接口 public class Product implements Cloneable{private String name;private Integer price;public String getName() {return name;} public void setName(String name) {this.name name;}public Integer getPrice() {return price;}public void setPrice(Integer price) {this.price price;}public Product(String name, Integer price) {super();this.name name;this.price price;}Overridepublic String toString() {return Product [name name , price price ];}//重写clone的方法Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();} }测试 public class Test {public static void main(String[] args) {// TODO 对象的克隆Product product1 new Product(篮球, 189);// 1.在Product实现CoCloneable接口// 2.重写clone方法Product product2 product1.clone();System.out.println(product2);product2.setPrice(200);//篮球涨价了System.out.println(product1);//此时修改product2不会影响product1的值System.out.println(product2);} }深克隆实现 Serializable 接口通过对象的序列化和反序列化实现克隆可以实现真正的深克隆 1.32、HashMap的扩容机制 HashMap 在容量超过负载因子所定义的容量之后就会扩容。Java 里的数组是无法自动扩容的方法是将 HashMap 的大小扩大为原来数组的两倍并将原来的对象放入新的数组中。 jdk1.7版本 先生成新数组遍历老数组中的每个位置上的链表上的每个元素取每个元素的key计算出每个元素在新数组中的下标将元素添加到新数组中去所有元素转移完了之后将新数组赋值给HashMap对象的table属性 jdk1.8版本 先生成新数组遍历老数组中的每个位置上的链表或红黑树如果是链表则直接将链表中的每个元素重新计算下标并添加到新数组中去如果是红黑树则先遍历红黑树先计算出红黑树中每个元素对应在新数组中的下标位置 统计每个下标位置的元素个数如果该位置下的元素个数超过了8则生成一个新的红黑树并将根节点添加到新数组的对应位置如果该位置下的元素个数没有超过8则生成一个链表并将链表的头节点添加到新数组的对应位置 所有元素转移完了之后将新数组赋值给HashMap对象的table属性 1.33、Java中的异常体系是怎样的 Java中的所有异常都来自顶级父类Throwable。Throwable下有两个子类Exception和Error。 Error表示非常严重的错误比如java.lang.StackOverFlowError 栈溢出和Java.lang.OutOfMemoryError 内存溢出通常这些错误出现时仅仅想靠程序自己是解决不了的可能是虚拟机、磁盘、操作系统层面出现的问题了所以通常也不建议在代码中去捕获这些Error因为捕获的意义不大因为程序可能已经根本运行不了了。 Exception表示异常表示程序出现Exception时是可以靠程序自己来解决的比如NullPointerException空指针异常IllegalAccessException违法访问异常等我们可以捕获这些异常来做特殊处理。 Exception的子类通常又可以分为RuntimeException运行时异常和非RuntimeException非运行时异常两类 RunTimeException表示运行期异常表示这个异常是在代码运行过程中抛出的这些异常在写代码时可能发现不了。比如NullPointerException空指针异常、IndexOutOfBoundsException数组下标越界异常等。非RuntimeException表示非运行期异常比如 IO异常、SQL异常是编译器认为这块读文件可能会读不到二者区别是否强制要求调用者必须处理此异常如果强制要求调用者必须进行处理那么就使用非运行时异常否则就选择运行异常。 throw 和 throws 的区别 throw在方法体内部表示抛出异常由方法体内部的语句处理throw 是具体向外抛出异常的动作所以它抛出的是一个异常实例throws在方法声明后面表示如果抛出异常由该方法的调用者来进行异常的处理表示出现异常的可能性并不一定会发生这种异常。 1.34、什么时候应该抛出异常什么时候捕获异常 异常相当于一种提示如果我们抛出异常就相当于告诉上层方法我抛了一个异常我处理不了这个异常交给你来处理而对于上层方法来说它也需要决定自己能不能处理这个异常是否也需要交给它的上层。 所以我们在写一个方法时我们需要考虑的就是本方法能否合理的处理该异常如果处理不了就继续向上抛出异常包括本方法中在调用另外一个方法时发现出现了异常如果这个异常应该由自己来处理那就捕获该异常并进行处理。 本方法能处理异常则捕获异常不能处理异常则向上抛出。 常见的异常类有哪些 NullPointerException当应用程序试图访问空对象时则抛出该异常。SQLException提供关于数据库访问错误或其他错误信息的异常IndexOutOfBoundsException指示某排序索引例如对数组、字符串或向量的排序超出范围时抛出FileNotFoundException当试图打开指定路径名表示的文件失败时抛出此异常。OException当发生某种 I/O 异常时抛出此异常。此类是失败或中断的 I/O 操作生成的异常的通用类。ClassCastException当试图将对象强制转换为不是实例的子类时抛出该异常IllegalArgumentException抛出的异常表明向方法传递了一个不合法或不正确的参数 主线程可以捕获到子线程的异常吗 线程设计的理念“线程的问题应该线程自己本身来解决而不要委托到外部”。 正常情况下如果不做特殊的处理在主线程中是不能够捕获到子线程中的异常的。如果想要在主线程中捕获子线程的异常我们可以用如下的方式进行处理使用 Thread 的静态方法。 Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandle());1.35、了解ReentrantLock吗 ReetrantLock是一个可重入的独占锁主要有两个特性一个是支持公平锁和非公平锁一个是可重入。 ReetrantLock实现依赖于AQSReetrantLock主要依靠AQS维护一个阻塞队列多个线程对加锁时失败则会进入阻塞队列。等待唤醒重新尝试加锁。 1.36、ReentrantLock中tryLock()和lock()方法的区别 lock() 方法是阻塞加锁没有返回值如果有线程在调用这个方法如果线程没有加到锁那么线程就会停在 reentrantLock.lock() 这行代码后续代码不会执行。直到这个线程获得锁之后才会解阻塞tryLock() 是尝试加锁不一定能加到是非阻塞加锁返回值是 Boolean还有一种锁是很多框架都会用到的是自旋锁比较灵活性能会很高但是比较消耗CPU public class Hello {static ReentrantLock reentrantLock new ReentrantLock();public static void main(String[] args) {reentrantLock.lock(); // 阻塞加锁// 若没有加锁后续代码不会执行boolean result reentrantLock.tryLock(); //尝试加锁,非阻塞加锁// 自旋锁while(!reentrantLock.tryLock()){// 其他事情}} }1.37、ReentrantLock中的公平锁和非公平锁的底层实现 首先不管是公平锁还是非公平锁它们的底层实现都会使用 AQS 来进行排队它们的区别在于线程在使用 lock() 方法加锁时 如果是公平锁会先检查 AQS 队列中是否存在线程在排队如果有线程在排队则当前线程也进行排队如果是非公丕锁则不会去检查是否有线程在排队而是直接竞争锁 不管是公平锁还是非公平锁一旦没竞争到锁都会进行排队当锁释放时都是唤醒排在最前面的线程所以非公平锁只是体现在了线程加锁阶段而没有体现在线程被唤醒阶段。 另外 ReentrantLock 是可重入锁(同一个线程不停的加锁都可以)不管是公平锁还是非公平锁都是可重入的。默认情况下我们 new ReentrantLock() 底层是非公平锁因为非公平性能会更高。但是解锁时候也得不停解锁加锁两次解锁也得两次。 public class Hello {static ReentrantLock reentrantLock new ReentrantLock();public static void main(String[] args) {reentrantLock.lock(); // 阻塞加锁reentrantLock.lock(); // 阻塞加锁reentrantLock.unlock();reentrantLock.unlock();} }1.38、ReadWriteLock是什么 首先ReentrantLock某些时候有局限如果使用ReentrantLock可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致但这样如果线程C在读数据、线程D也在读数据读数据是不会改变数据的没有必要加锁但是还是加锁了降低了程序的性能。 因为这个才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现实现了读写的分离读锁是共享的写锁是独占的读和读之间不会互斥读和写、写和读、写和写之间才会互斥提升了读写的性能 1.39、Sychronized的偏向锁、轻量级锁、重量级锁 偏向锁在锁对象的对象头中记录一下当前获取到该锁的线程ID该线程下次如果又来获取该锁就可以直接获取到了。也就是这把锁偏向于这个线程所以称为 偏向锁 轻量级锁由偏向锁升级而来当一个线程获取到锁后此时这把锁是偏向锁此时如果有第二个线程来竞争这把锁偏向锁就会升级为轻量级锁之所以叫轻量级锁是为了和重量级锁区分轻量级锁底层是通过自旋来实现的并不会阻塞线程。如果自旋次数过多仍然没有获取到锁则会升级为重量级锁重量级锁会导致线程阻塞。 自旋锁自旋锁就是线程在获取锁的过程中不会去阻塞线程也就无所谓唤醒线程阻塞和唤醒这两个步骤都需要操作系统去进行的比较消耗时间自旋锁就是一直循环获取锁。 说一说自己对于 synchronized 关键字的了解 synchronized关键字解决的是多个线程之间访问资源的同步性synchronized 关键字可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。另外在 Java 早期版本中synchronized 属于重量级锁效率低下JDK6 对锁的实现引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。 说说 jdk1.6 之后的 synchronized 关键字底层做了哪些优化可以详细介绍一下这些优化吗 说明这道题答案有点长但是回答的详细面试会很加分。 JDK1.6 对锁的实现引入了大量的优化如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减少锁操作的开销。锁主要存在四种状态依次是无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态它们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级这种策略是为了提高获得锁和释放锁的效率。 重量级锁 我们知道我们要进入一个同步、线程安全的方法时是需要先获得这个方法的锁的退出这个方法时则会释放锁。如果获取不到这个锁的话意味着有别的线程在执行这个方法这时我们就会马上进入阻塞的状态等待那个持有锁的线程释放锁然后再把我们从阻塞的状态唤醒我们再去获取这个方法的锁。这种获取不到锁就马上进入阻塞状态的锁我们称之为重量级锁。 自旋锁和自适应自旋锁 我们知道线程从运行态进入阻塞态这个过程是非常耗时的因为不仅需要保存线程此时的执行状态上下文等数据还涉及到用户态到内核态的转换。当然把线程从阻塞态唤醒也是一样也是非常消耗时间的。 刚才我说线程拿不到锁就会马上进入阻塞状态然而现实是它虽然这一刻拿不到锁可能在下 0.0001 秒就有其他线程把这个锁释放了。如果它慢0.0001秒来拿这个锁的话可能就可以顺利拿到了不需要经历阻塞/唤醒这个花时间的过程了。 然而重量级锁就是这么坑它就是不肯等待一下一拿不到就是要马上进入阻塞状态。为了解决这个问题我们引入了另外一种愿意等待一段时间的锁 — 自旋锁。 自旋锁就是如果此时拿不到锁它不马上进入阻塞状态而是等待一段时间看看这段时间有没其他人把这锁给释放了。怎么等呢这个就类似于线程在那里做空循环如果循环一定的次数还拿不到锁那么它才会进入阻塞的状态 至于是循环等待几次这个是可以人为指定一个数字的。 上面我们说的自旋锁每个线程循环等待的次数都是一样的例如我设置为 100次的话那么线程在空循环 100 次之后还没拿到锁就会进入阻塞状态了。而自适应自旋锁就牛逼了它不需要我们人为指定循环几次它自己本身会进行判断要循环几次而且每个线程可能循环的次数也是不一样的。而之所以这样做主要是我们觉得如果一个线程在不久前拿到过这个锁或者它之前经常拿到过这个锁那么我们认为它再次拿到锁的几率非常大所以循环的次数会多一些。 而如果有些线程从来就没有拿到过这个锁或者说平时很少拿到那么我们认为它再次拿到的概率是比较小的所以我们就让它循环的次数少一些。因为你在那里做空循环是很消耗 CPU 的。 所以这种能够根据线程最近获得锁的状态来调整循环次数的自旋锁我们称之为自适应自旋锁。 轻量级锁 上面我们介绍的三种锁重量级、自旋锁和自适应自旋锁他们都有一个特点就是进入一个方法的时候就会加上锁退出一个方法的时候也就释放对应的锁。 轻量级锁认为当你在方法里面执行的时候其实是很少刚好有人也来执行这个方法的所以当我们进入一个方法的时候根本就不用加锁我们只需要做一个标记就可以了也就是说我们可以用一个变量来记录此时该方法是否有人在执行。也就是说如果这个方法没人在执行当我们进入这个方法的时候采用CAS机制把这个方法的状态标记为已经有人在执行退出这个方法时在把这个状态改为了没有人在执行了。 倘若偏向锁失败虚拟机并不会立即升级为重量级锁它还会尝试使用一种称为轻量级锁的优化手段(JDK1.6 之后加入的)。轻量级锁不是为了代替重量级锁它的本意是在没有多线程竞争的前提下减少传统的重量级锁使用操作系统互斥量产生的性能消耗因为使用轻量级锁时不需要申请互斥量。另外轻量级锁的加锁和解锁都用到了 CAS 操作。 如果没有竞争轻量级锁使用 CAS 操作避免了使用互斥操作的开销。但如果存在锁竞争除了互斥量开销外还会额外发生 CAS 操作因此在有锁竞争的情况下轻量级锁比传统的重量级锁更慢如果锁竞争激烈那么轻量级将很快膨胀为重量级锁所以轻量级锁适合用在那种很少出现多个线程竞争一个锁的情况也就是说适合那种多个线程总是错开时间来获取锁的情况。 偏向锁 偏向锁就更加牛逼了我们已经觉得轻量级锁已经够轻然而偏向锁更加省事偏向锁认为你轻量级锁每次进入一个方法都需要用CAS来改变状态退出也需要改变多麻烦。 偏向锁进入一个方法的时候是这样处理的如果这个方法没有人进来过那么一个线程首次进入这个方法的时候会采用CAS机制把这个方法标记为有人在执行了和轻量级锁加锁有点类似并且也会把该线程的 ID 也记录进去相当于记录了哪个线程在执行。 然后但这个线程退出这个方法的时候它不会改变这个方法的状态而是直接退出来懒的去改因为它认为除了自己这个线程之外其他线程并不会来执行这个方法。 然后当这个线程想要再次进入这个方法的时候会判断一下这个方法的状态如果这个方法已经被标记为有人在执行了并且线程的ID是自己那么它就直接进入这个方法执行啥也不用做 你看多方便第一次进入需要CAS机制来设置以后进出就啥也不用干了直接进入退出。 然而现实总是残酷的毕竟实际情况还是多线程所以万一有其他线程来进入这个方法呢如果真的出现这种情况其他线程一看这个方法的ID不是自己这个时候说明至少有两个线程要来执行这个方法论这意味着偏向锁已经不适用了这个时候就会从偏向锁升级为轻量级锁。所以呢偏向锁适用于那种始终只有一个线程在执行一个方法的情况哦。 但是对于锁竞争比较激烈的场合偏向锁就失效了因为这样场合极有可能每次申请锁的线程都是不相同的因此这种场合下不应该使用偏向锁否则会得不偿失需要注意的是偏向锁失败后并不会立即膨胀为重量级锁而是先升级为轻量级锁。 好文推荐https://mp.weixin.qq.com/s/9zRmjH5Bgzo-EDIzZ5C7Hg 最开始我们说的三种锁重量级锁、自旋锁和自适应自旋锁进入方法之前就一定要先加一个锁这种我们为称之为悲观锁。悲观锁总认为如果不事先加锁的话就会出事这种想法确实悲观了点这估计就是悲观锁的来源了。 而乐观锁却相反认为不加锁也没事我们可以先不加锁如果出现了冲突我们在想办法解决例如 CAS 机制上面说的轻量级锁就是乐观锁的。不会马上加锁而是等待真的出现了冲突在想办法解决。 1.40、synchronized为什么是非公平锁非公平体现在哪些地方 synchronized 的非公平其实在源码中应该有不少地方因为设计者就没按公平锁来设计核心有以下几个点 当持有锁的线程释放锁时该线程会执行以下两个重要操作 先将锁的持有者 owner 属性赋值为 null唤醒等待链表中的一个线程 当线程尝试获取锁失败进入阻塞时放入链表的顺序和最终被唤醒的顺序是不一致的也就是说你先进入链表不代表你就会先被唤醒 1.41、Sychronized和ReentrantLock的区别 sychronized是一个关键字ReentrantLock是一个类。这是二者的本质区别。synchronized 依赖于 JVM 而 ReenTrantLock 依赖于 API。sychronized会自动的加锁与释放锁ReentrantLock需要程序员手动加锁与释放锁也就是 API 层面需要 lock() 和 unlock 方法配合 try/finally 语句块来完成ReentrantLock 比 synchronized 增加了一些高级功能 等待可中断也就是说正在等待的线程可以选择放弃等待改为处理其他事情ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的 1.42、谈谈你对AQS的理解AQS如何实现可重入锁 AQS是一个JAVA线程同步的框架。是JDK中很多锁工具的核心实现框架。AQS是同步队列在AQS中维护了一个信号量state和一个线程组成的双向链表队列。其中这个线程队列就是用来给线程排队的而state就像是一个红绿灯用来控制线程排队或者放行的。 在不同的场景下有不用的意义。在可重入锁这个场景下state就用来表示加锁的次数。0标识没有线程加锁每加一次锁state就加1。释放锁state就减1。 1.43、什么是泛型有什么作用 **泛型就是将类型参数化其在编译时才确定具体的参数。**这种参数类型可以用在类、接口和方法的创建中分别称为泛型类、泛型接口、泛型方法。使用泛型参数可以增强代码的可读性以及稳定性。 ArrayListPersion persons new ArrayListPersion(); // 这行代码就指明了该 ArrayList对象只能传入 Persion 对象使用泛型的好处有以下几点 类型安全译时期就可以检查出因 Java 类型不正确导致的异常符合越早出错代价越小原则消除强制类型转换使用时直接得到目标类型消除许多强制类型转换性能收益所有工作都在编译器中完成支持泛型几乎不需要 JVM 或类文件更改 1.44、泛型的使用方式有哪几种 泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。 泛型类 //此处T可以随便写为任意标识常见的如T、E、K、V等形式的参数常用于表示泛型 //在实例化泛型类时必须指定T的具体类型 public class GenericT{private T key;public Generic(T key) {this.key key;}public T getKey(){return key;} } 如何实例化泛型类 GenericInteger genericInteger new GenericInteger(123456);泛型接口 public interface GeneratorT {public T method(); }实现泛型接口不指定类型 class GeneratorImplT implements GeneratorT{Overridepublic T method() {return null;} }实现泛型接口指定类型 class GeneratorImplT implements GeneratorString{Overridepublic String method() {return hello;} }泛型方法 public static E void printArray( E[] inputArray ) {for ( E element : inputArray ){System.out.printf( %s , element );}System.out.println(); }使用 // 创建不同类型数组 Integer, Double 和 Character Integer[] intArray { 1, 2, 3 }; String[] stringArray { Hello, World }; printArray(intArray); printArray(stringArray);注意: public static E void printArray( E[] inputArray ) 一般被称为静态泛型方法在 java 中泛型只是一个占位符必须在传递类型后才能使用。 类在实例化时才能真正的传递类型参数由于静态方法的加载先于类的实例化就是说类中的泛型还没有传递真正的类型参数静态的方法的加载就已经完成了所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的 E 1.45、Java泛型的原理是什么什么是类型擦除 泛型是一种语法糖泛型这种语法糖的基本原理是类型擦除。也就是说**泛型只存在于编译阶段而不存在于运行阶段。**在编译后的 class 文件中是没有泛型这个概念的。 类型擦除使用泛型的时候加上的类型参数编译器在编译的时候去掉类型参数。 public class CaculateT {private T num; }// 反编译一下上面的 Caculate 类 public class Caculate{public Caculate(){}private Object num; }发现编译器擦除 Caculate 类后面的两个尖括号并且将 num 的类型定义为 Object 类型。大部分情况下泛型类型都会以 Object 进行替换而有一种情况则不是。那就是使用到了extends和super语法的有界类型 public class CaculateT extends String {private T num; }这种情况的泛型类型num 会被替换为 String 而不再是 Object。它限定 T 是 String 或者 String 的子类 1.46、什么是泛型中的限定通配符和非限定通配符 有两种限定通配符 ? extends T 表示包括T在内的任何T的子类来设定类型的上界 ? super T 表示包括T在内的任何T的父类来设定类型的下界(很少用) public class PeopleE extends Number {}表示非限定通配符因为 可以用任意类型来替代 例如 List ? extends Number 可以接受 List Integer 或 List Float 。 1.47、Array中可以用泛型吗 不可以。因为 List 可以提供编译期的类型安全保证而 Array 却不能。 1.48、项目中哪里用到了泛型 自定义接口通用返回结果 CommonResultT 通过参数 T 可根据具体的返回类型动态指定结果的数据类型构建集合工具类 1.49、反射 通过反射你可以获取任意一个类的所有属性和方法你还可以调用这些方法和属性。反射的核心思想和关键就是得到编译后的class文件对象。 像是使用 Spring 的时候一个Component注解就声明了一个类为 Spring Bean 通过一个 Value注解就读取到配置文件中的值这些都是因为你可以基于反射分析类然后获取到类/属性/方法/方法的参数上的注解。你获取到注解之后就可以做进一步的处理。 Field 可以使用 get() 和 set() 方法读取和修改 Field 对象关联的字段Method 可以使用 invoke() 方法调用与 Method 对象关联的方法Constructor 可以用 Constructor 创建新的对象 应用举例工厂模式使用反射机制根据全限定类名获得某个类的 Class 实例 反射的优缺点 优点运行期类型的判断class.forName() 动态加载类提高代码的灵活度缺点反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。由于反射允许代码执行一些在正常情况下不被允许的操作比如访问私有的属性和方法所以使用反射可能会导致意料之外的副作用这可能导致代码功能失调并破坏可移植性。 1.50、如何获取反射中的Class对象 Class.forName(“类的路径”)当你知道该类的全路径名时你可以使用该方法获取 Class 类对象 Class clz Class.forName(java.lang.String);类名.class。这种方法只适合在编译前就知道操作的 Class。 Class clz String.class;对象名.getClass() String str new String(Hello); Class clz str.getClass();如果是基本类型的包装类可以调用包装类的Type属性来获得该包装类的Class对象 1.51、Java反射API有几类 反射 API 用来生成 JVM 中的类、接口或则对象的信息 Class 类反射的核心类可以获取类的属性方法等信息Field 类表示类的成员变量可以用来获取和设置类之中的属性值Method 类表示类的方法它可以用来获取类中的方法信息或者执行方法Constructor 类表示类的构造方法 反射的使用步骤 获取想要操作的类的Class对象这是反射的核心通过Class对象我们可以任意调用类的方法调用 Class 类中的方法即就是反射的使用阶段使用反射 API 来操作这些信息 1.52、反射机制的应用有哪些 Oracle 希望开发者将反射作为一个工具用来帮助程序员实现本不可能实现的功能。举两个最常见使用反射的例子来说明反射机制的强大之处 第一种JDBC 的数据库的连接在JDBC 的操作中如果要想进行数据库的连接则必须按照以下的几步完成 通过Class.forName()加载数据库的驱动程序通过 DriverManager 类进行数据库的连接连接的时候要输入数据库的连接地址、用户名、密码通过Connection 接口接收连接 第二种Spring 框架的使用最经典的就是xml的配置模式 Spring 通过 XML 配置模式装载 Bean 的过程 将程序内所有 XML 或 Properties 配置文件加载入内存中Java类里面解析xml或properties里面的内容得到对应实体类的字节码字符串以及相关的属性信息使用反射机制根据这个字符串获得某个类的Class实例动态配置实例的属性 Spring这样做的好处是 不用每一次都要在代码里面去new或者做其他的事情以后要改的话直接改配置文件代码维护起来就很方便了有时为了适应某些需求Java类里面不一定能直接调用另外的方法可以通过反射机制来实现。 1.53、什么是注解 注解 Annotation 可以看作是一种特殊的注释主要用于修饰类、方法或者变量提供某些信息供程序在编译或者运行时使用。注解本质是一个继承了Annotation 的特殊接口JDK 提供了很多内置的注解比如 Override 、Deprecated同时我们还可以自定义注解。 1.54、注解的解析方法有哪几种 注解只有被解析之后才会生效常见的解析方法有两种 编译期直接扫描 编译器在编译 Java 代码的时候扫描对应的注解并处理比如某个方法使用Override 注解编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。运行期通过反射处理 像框架中自带的注解(比如 Spring 框架的 Value 、Component)都是通过反射来进行处理的 1.55、序列化和反序列化 如果我们需要持久化 Java 对象比如将 Java 对象保存在文件中或者在网络传输 Java 对象这些场景都需要用到序列化。 序列化 将数据结构或对象转换成二进制字节流的过程反序列化将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程 对于 Java 这种面向对象编程语言来说我们序列化的都是对象Object也就是实例化后的类(Class)但是在 C这种半面向对象的语言中struct(结构体)定义的是数据结构类型而 class 对应的是对象类型。 下面是序列化和反序列化常见应用场景 对象在进行网络传输之前需要先被序列化接收到序列化的对象之后需要再进行反序列化 将对象存储到文件之前需要进行序列化将对象从文件中读取出来需要进行反序列化 将对象存储到数据库如 Redis之前需要用到序列化将对象从缓存数据库中读取出来需要反序列化 将对象存储到内存之前需要进行序列化从内存中读取出来之后需要进行反序列化 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。 OSI 七层协议模型中表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话就是将二进制流转换成应用层的用户数据。所以序列化协议对应于 TCP/IP 模型的表示层。 序列化的实现需要被序列化的类实现 Serializable 接口用于标注该对象是可被序列化的然后使用一个输出流如FileOutputStream来构造一个 ObjectOutputStream 对象接着使用 ObjectOutputStream 对象的 writeObject(Object obj) 方法可以将参数为 obj 的对象写出要恢复的话则使用输入流构造一个 ObjectInputStream 对象接着使用 ObjectInputStream 对象的 readObject () 方法读取对象。 1.56、如果有些字段不想进行序列化怎么办 对于不想进行序列化的变量使用 transient 关键字修饰。 transient 关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化当对象被反序列化时被 transient 修饰的变量值不会被持久化和恢复。 注意 transient 只能修饰变量不能修饰类和方法。transient 修饰的变量在反序列化后变量值将会被置成类型的默认值。例如如果是修饰 int 类型那么反序列后结果就是 0static 变量因为不属于任何对象(Object)所以无论有没有 transient 关键字修饰均不会被序列化 1.57、为什么不推荐使用JDK自带的序列化 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了性能差 相比于其他序列化框架性能更低主要原因是序列化之后的字节数组体积较大导致传输成本加大存在安全问题 序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制那么攻击者即可通过构造恶意输入让反序列化产生非预期的对象在此过程中执行构造的任意代码。 1.58、静态变量会被序列化吗 不会。因为序列化是针对对象而言的, 而静态变量优先于对象存在, 随着类的加载而加载, 所以不会被序列化. 的对象之后需要再进行反序列化 将对象存储到文件之前需要进行序列化将对象从文件中读取出来需要进行反序列化 将对象存储到数据库如 Redis之前需要用到序列化将对象从缓存数据库中读取出来需要反序列化 将对象存储到内存之前需要进行序列化从内存中读取出来之后需要进行反序列化 序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。 OSI 七层协议模型中表示层做的事情主要就是对应用层的用户数据进行处理转换为二进制流。反过来的话就是将二进制流转换成应用层的用户数据。所以序列化协议对应于 TCP/IP 模型的表示层。 序列化的实现需要被序列化的类实现 Serializable 接口用于标注该对象是可被序列化的然后使用一个输出流如FileOutputStream来构造一个 ObjectOutputStream 对象接着使用 ObjectOutputStream 对象的 writeObject(Object obj) 方法可以将参数为 obj 的对象写出要恢复的话则使用输入流构造一个 ObjectInputStream 对象接着使用 ObjectInputStream 对象的 readObject () 方法读取对象。 1.56、如果有些字段不想进行序列化怎么办 对于不想进行序列化的变量使用 transient 关键字修饰。 transient 关键字的作用是阻止实例中那些用此关键字修饰的的变量序列化当对象被反序列化时被 transient 修饰的变量值不会被持久化和恢复。 注意 transient 只能修饰变量不能修饰类和方法。transient 修饰的变量在反序列化后变量值将会被置成类型的默认值。例如如果是修饰 int 类型那么反序列后结果就是 0static 变量因为不属于任何对象(Object)所以无论有没有 transient 关键字修饰均不会被序列化 1.57、为什么不推荐使用JDK自带的序列化 不支持跨语言调用 : 如果调用的是其他语言开发的服务的时候就不支持了性能差 相比于其他序列化框架性能更低主要原因是序列化之后的字节数组体积较大导致传输成本加大存在安全问题 序列化和反序列化本身并不存在问题。但当输入的反序列化的数据可被用户控制那么攻击者即可通过构造恶意输入让反序列化产生非预期的对象在此过程中执行构造的任意代码。 1.58、静态变量会被序列化吗 不会。因为序列化是针对对象而言的, 而静态变量优先于对象存在, 随着类的加载而加载, 所以不会被序列化.
http://www.dnsts.com.cn/news/40745.html

相关文章:

  • 教师在哪些网站可以做兼职出库入库管理软件app
  • 弄一个网站要多少钱宠物网站建设规划书
  • 医疗行业网站策划成都微信网站建设公司
  • 清华紫光做网站项目管理咨询公司
  • 网站突然打不开工地施工模板尺寸要求
  • 江阳建设集团网站培训机构退费法律规定
  • 小工作室做网站可以接单做网站的软件
  • 建立网站 数据分析戴尔公司网站开发的经营目标
  • 郑州网站设计制作哪家好重庆网站建设 快速建站
  • 公司网站域名注册wordpress RSS怎么用
  • 淘宝指数网站wordpress 显示空白
  • 查排名网站如花建站
  • 单页网站上传教程wordpress驾校模版
  • 如何构建自己的网站网站转化率
  • 自己做网站的选修课网站建设方案书是什么意思
  • 上海营销型网站建站门户网站做等保需要备案哪些
  • django做视频网站风铃上做的网站发布时号码填写
  • 网站做宣传专业seo站长工具全面查询网站
  • 制作网站需要的服务器重庆招工招聘信息查询
  • 专门做销售招聘网站重装电脑后下载wordpress
  • 建设对公银行网站打不开有个专门做简历的网站叫
  • 做白酒的网站中企动力做网站收费标准
  • 如何注册公司网站域名怎么自己做导航网站
  • 合肥制作手机网站平台搭建不
  • 普洱市住房和城乡建设局网站郑州网站建设 服务创业
  • 手机网站建设cz35百度文库个人登录
  • 金融投资网站开发公司网站域名备案对网站名称有要求或界定吗
  • 上海网站建设报价方案常州网站专业制作
  • 东莞石龙网站建设定制tk域名网站多少
  • 互展科技网站建设北京优化推广