【干货】面试总结——Java基础知识
2023-03-31 19:22:34 0 举报
AI智能生成
【干货】面试总结——Java基础知识
作者其他创作
大纲/内容
如何将数组转换为ArrayList?
自定义方法实现
结合Arrays.asList()实现(推荐)
使用Java 8 的Stream(推荐)
使用Java 9 的List.of()方法
不要在 foreach 循环里进行元素的 remove / add 操作?
<b>fail-fast(快速失败) 机制</b>:java集合(Collection)中的一种错误机制。
当多个线程对同一个集合内容进行操作时,就可能会产生fail-fast事件。
单线程下,在foreach循环里调用集合类的remove方法,将抛出ConcurrentModificationException异常。
解决方案一:Java 8 开始可以使用Collection#removeIf()方法删除满足条件的元素。
解决方案二:使用Iterator方式。(如果并发,需要对Iterator对象加锁)
解决方案三:使用CopyOnWriteArrayList替换ArrayList;
<b>fail-safe(安全失败)</b>机制:采用安全失败机制的容器,在遍历时不是直接在集合内容上访问的,而是<b>先复制原有集合内容,在拷贝的集合上进行遍历</b>。所以,在遍历过程中对原集合所做的修改并不能被迭代器检测到,故不会抛ConcurrentModificationException异常。
创建对象的方式有哪几种?
new 方法。
clone():使用Object的clone方法。
反射
调用public无参构造函数,若没有,则会报异常。
调用带有参数的构造函数,先获取到其构造对象,再通过构造方法类获取实例。
发序列化创建对象。(被创建实例的类需实现Serializable接口)
如何实现静态代理?优缺点?
实现方式
1. 为现有的每一个类都编写一个对应的代理类,并且让它实现和目标类相同的接口。
2. 在创建对象时,通过构造器塞入一个目标对象,然后在代理对象的方法内部调用目标对象同名方法。
优点
在客户端和目标对象之间充当中介的作用,保护目标对象;
可以扩展目标对象的功能;(在调用目标对象方法的前后增加其他一些方法。)比如:打印日志
缺点
需要为每一个目标类编写对应的代理类,产生的类太多,工作量大。
相比直接调用目标对象的方法,效率低一些。
了解动态代理?在哪些地方用到?
作用
为其它对象提供一种代理以控制对这个对象的访问。
JDK动态代理的实现
在运行时,通过反射机制动态生成代理对象;
调用程序必须实现InvocationHandler接口;
使用Proxy类中的newProxyInstance方法动态的创建代理类。
在哪些地方应用到?
AOP、RPC 框架中都有用到。
JDK的动态代理与CGLIB的区别
JDK动态代理只能代理实现了接口的类;而CGLIB可以代理未实现任何接口的类。
JDK动态代理是通过反射的方式创建代理类;而CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能将代理类声明为final类型的类和方法;
JDK动态代理的效率更优。
对Java注解的理解,解决声明问题?
Java语言中的类、方法、变量、参数和包等都可以注解标记,在程序运行期间可以获取到相应的注解以及注解中定义的内容。
注解极大地简化了代码,通过注解可以帮助我们简单地完成一些事情;比如:Spring中如果检测到你的类被 <span class="tag">@Component</span> 注解标记的话,Spring容器在启动的时候就会把这个类进行管理,我们可以通过 <span class="tag">@Autowired</span> 注解注入类的实例。
内存泄漏和内存溢出?
内存泄漏
定义:是指不再使用的对象持续占用内存或者它们占用的内存得不到及时释放,从而造成内存空间的浪费。
根本原因:长生命周期的对象持有短生命周期对象的引用;
内存泄漏场景
静态集合类引起:静态成员的生命周期是整个程序运行期间。
当集合里的对象属性被修改后,再调用remove()方法是不起作用的。
各种连接对象(IO流对象、数据库连接对象、网络连接对象)使用后未关闭。
监听器的使用。
不正确使用单例模式。
解决措施
尽量减少使用静态变量,类的静态变量的生命周期是和类同步的。
声明对象引用之前,明确内存对象的有效作用域,尽量减小对象的作用域,将类的成员变量改写为方法内 的局部变量。
减少长生命周期的对象持有短生命周期的引用。
使用StringBuilder和StringBuffer替换String进行字符串连接。避免产生大量临时字符串。
对于不需要使用的对象,手动设置null值,不管GC何时会开始清理,我们都应该及时的将无用的对象标记为可被清理的对象。
各种连接(数据库连接、网络连接、IO连接)操作,操作结束都务必显式调用close关闭。
内存溢出
指程序运行过程中无法申请到足够的内存而导致的一种错误。
通常发生在OLD段或perm段垃圾回收后,仍然无内存空间容纳新的对象的情况。
内存溢出场景
JVM Heap(堆)溢出:(java.lang.OutOfMemoryError:java heap space)
解决方法
手动设置JVM Heap的大小;
检查程序,看是否有死循环或不必要地创建大量对象;
Metaspace溢出:(java.lang.OutOfMemoryError:Metaspace)
解决方法
通过 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize 设置永久代大小即可。
栈溢出:(java.lang.OutOfMemoryError:Thread Stack space)
解决方法
修改程序;
通过 -Xss:来设置每个线程的Stack 大小;
BIO、NIO、AIO
BIO (Blocking I/O)
NIO (Non-Blocking/New I/O)
NIO中所有I/O操作都是从Channel(通道)开始的。
从通道进行数据读取,创建一个缓存区,然后请求通道读取数据。
从通道进行数据写入,创建一个缓冲区,填充数据,并请求通道写入数据。
AIO (Asynchronous I/O)
Java中finalize()方法的使用?
finalize() 是Object的protected方法,子类可以覆盖该方法以实现资源清理工作,GC在回收对象之前调用该方法。
finalize() 方法中一般用于释放非Java资源(如:打开的文件资源、数据库连接等),或是调用非Java方法(native方法)时分配的内存。
避免使用的原因
finalize()方法的调用时机具有不确定性,从一个对象变得不可到达开始,到finalize()方法被执行,所花费的时间是任意长的。我们不能依赖finalize()方法能及时的回收占用的资源,可能在资源耗尽之前,gc仍为触发。因此通常的做法是提供显式的close()方法供客户端手动调用。
finalize() 方法意味着回收对象时需要进行更多的操作,从而延长了对象回收的时间。
Java中Class.forName 和 ClassLoader的区别?
Class.forName() 和 ClassLoader 都可以对类进行加载;
ClassLoader 遵循双亲委派模型,实现的功能是通过一个类的全限定名来获取描述此类的二进制字节流,获取到二进制流后放到JVM中。ClassLoader只做一件事,就是将.class文件加载到JVM中,不会执行static中的内容。
Class.forName()方法实际上也是调用ClassLoader来实现的,不同的是除了将类的.class文件加载到JVM中之外,还会对类进行初始化,执行类中的static块。
讲一下CopyOnWriteArrayList和CopyOnWriteArraySet?
<b>CopyOnWrite 容器</b>:写时复制的容器。往一个容器添加元素时,不是直接往当前容器添加,而是先将当前容器进行copy,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
添加元素的时候需要加锁,否则多线程写的时候会copy出N个副本
读的时候不需要加锁,多线程读写时,读到的可能还是旧数据,因为读的时候不会对当前容器加锁
CopyOnWrite 并发容器主要用于读多写少的并发场景。
优点:可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素。
缺点
内存占用问题
针对内存占用问题,可以通过压缩容器中的元素来减少大对象的内存消耗,如元素全是10进制的数字,可考虑把它压缩成36进制或者64进制。或者不使用CopyOnWrite容器,而使用其他并发容器,如:ConcurrentHashMap。
数据一致性问题
HashMap了解多少?
HashMap底层实现
HashMap 是用数组 + 链表 + 红黑树(JDK1.8开始增加了红黑树)进行实现的,当添加一个元素(key-value)时,首先计算元素key的hash值,并根据hash值来确定插入数组的位置,如果发生碰撞(存在其他元素已经被放在数组同一位置),这个时候便使用链表来解决哈希冲突,当链表长度太长的时候,便将链表转为红黑树来提高搜索的效率。
数组的容量为16,而容量是以2的次方扩充的,一是为了提高性能使用足够大的数组,二是为了能使用位运算代替取模预算。
数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组,这个0.75就是默认的负载因子,可由构造函数传入。
为了解决碰撞,数组中的元素是单向链表类型。当链表长度达一个阈值时(>=8),会将链表转换为红黑树提高性能。而当链表长度缩小到另一个阈值(<=6)时,又会将红黑树转换回单向链表提高性能。
HashMap的put方法执行流程/步骤
1. 判断数组table是否为null,若为null则执行resize()扩容操作。
2. 根据键key的值计算hash值得到插入的数组索引 i,若table[i] == nulll,则直接新建节点插入,进入步骤6;若table[i]非null,则继续执行下一步。
3. 判断table[i]的首个元素key是否和当前key相同(hashCode和equals均相同),若相同则直接覆盖value,进入步骤6,反之继续执行下一步。
4. 判断table[i]是否为treeNode,若是红黑树,则直接在树中插入键值对并进入步骤6,反之继续执行下一步。
5. 遍历table[i],判断链表长度是否大于8,若>8,则把链表转换为红黑树,在红黑树中执行插入操作;若<8,则进行链表的插入操作;遍历过程中若发现key已存在则会直接覆盖该key的value值。
6. 插入成功后,判断实际存在的键值对数量size是否超过了最大容量threshold,若超过则进行扩容。
HashMap的get方法执行流程/步骤
1. 首先定位到键所在的数组的下标,并获取对应节点n。
2. 判断n是否为null,若n为null,则返回null并结束;反之,继续下一步。
3. 判断n的key和要查找的key是否相同(key相同指的是hashCode和equals均相同),若相同则返回n并结束;反之,继续下一步。
4. 判断是否有后续节点m,若没有则结束;反之,继续下一步。
5. 判断m是否为红黑树,若为红黑树则遍历红黑树,在遍历过程中如果存在某一个节点的key与要找的key相同,则返回该节点;反之,返回null;若非红黑树则继续下一步。
6. 遍历链表,若存在某一个节点的key与要找的key相同,则返回该节点;反之,返回null。
HashMap的扩容机制
扩容是为了防止HashMap中的元素个数超过了阀值,从而影响性能所服务的。而数组是无法自动扩容的,HashMap的扩容是申请一个容量为原数组大小两倍的新数组,然后遍历旧数组,重新计算每个元素的索引位置,并复制到新数组中;又因为HashMap的哈希桶数组大小总是为2的幂次方,所以重新计算后的索引位置要么在原来位置不变,要么就是“原位置+旧数组长度”。其中,threshold和loadFactor两个属性决定着是否扩容。threshold=Length*loadFactor,Length表示table数组的长度(默认值为16),loadFactor为负载因子(默认值为0.75);阀值threshold表示当table数组中存储的元素个数超过该阀值时,即需要扩容。
HashMap的扩容使用新的数组代替旧数组,然后将旧数组中的元素重新计算索引位置并放到新数组中,对旧数组中的元素如何重新映射到新数组中?
HashMap的哈希算法数组扩容
HashMap中数组扩容两倍后位置的变化
HashMap中数组16扩容至32
扩容机制设计的优点
1. 省去了重新计算hash值的时间(由于位运算直接对内存数据进行操作,不需要转成十进制,因此处理速度非常快),只需判断新增的一位是0或1;
2. 由于新增的1位可以认为是随机的0或1,因此扩容过程中会均匀的把之前有冲突的节点分散到新的位置(bucket槽),并且位置的先后顺序不会颠倒;
3. JDK1.7中扩容时,旧链表迁移到新链表的时候,若出现在新链表的数组索引位置相同情况,则链表元素会倒置,但JKD1.8的扩容并不会颠倒相同索引的链表元素。
HashMap 和 Hashtable 的区别
线程安全
HashMap是非线程安全的;
Hashtable是线程安全的,方法被synchronized修饰;
是否允许NULL值
HashMap允许有一个key是NULL,允许值为NULL;
Hashtable无论是key还是value都不允许为NULL;
继承的父类
HashMap和Hashtable都实现了Map接口;
HashMap继承的父类是AbstractMap;
Hashtable继承的父类是Dictionary;
contains()方法
HashMap没有contains()方法,但有containsValue和containsKey方法;
Hashtable保留了contains方法,也有containsValue和containsKey方法;contains方法与containsValue方法效果一样(containsValue方法里调用contains方法)。
计算hash值的方式不同
HashMap 和 TreeMap 的区别
安全
都是非线程安全的Map;
实现
TreeMap:实现SortMap接口,基于红黑树实现;
HashMap:基于哈希散列表实现;
存储
TreeMap:默认按键的升序排序存储;
HashMap:按键的hash值计算存储(可以理解为随机存储)
遍历
TreeMap:Iterator遍历是排序的
HashMap:Iterator遍历是随机的
性能损耗
TreeMap:插入、删除
HashMap:基本无
键值对
TreeMap:键、值都不能为null;
HashMap:允许一个键为null,值允许null;
效率
TreeMap:低;
HashMap:高;
二叉树
某节点的左子树节点值仅包含小于该节点值
某节点的右子树节点值仅包含大于该节点值
左右子树每个也必须是二叉查找树
图示
红黑树
每个节点都有红色或黑色
树的根始终是黑色的
没有两个相邻的红色节点(红色节点不能有红色父节点或红色子节点,<b>并没有说不能出现连续的黑色节点</b>)
从节点(包括根)到其任何后代NULL节点(叶子结点下方挂的两个空节点,并且认为他们是黑色的)的每条路径都具有相同数量的黑色节点。
ConcurrentHashMap的底层实现?
底层数据结构
JDK 1.7 的ConcurrentHashMap底层采用 <b>分段的数组+链表</b> 实现;
JDK 1.8 采用的数据结构跟HashMap 1.8 一致:<b>数组 + 链表 | 红黑二叉树;</b>
实现线程安全的方式
JDK 1.7 ,ConcurrentHashMap(分段锁)对整个桶数据进行了分割分段(Segment),每一把锁只锁容器其中一部分的数据,多线程访问容器中不同数据段的数据,就不会存在锁竞争,提高并发访问率;
JDK 1.8,开始摒弃Segment的概念,并发控制使用 synchronized 和 CAS 来操作,像是优化过且线程安全的HashMap。synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要hash不冲突,就不会产生并发,效率非常高。
ConcurrentHashMap的读操作为什么不需要加锁?
get操作全程不需要加锁是因为Node的成员val是用volatile修饰的,和数组用volatile修饰无关;
数组用volatile修饰主要是保证在数组扩容的时候保证可见性;
总结:定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,而不会读到过期值。由于get操作只需读不需要写共享变量,所以不用加锁。之所以不会读到过期值,依据java内存模型的happen before原则,对volatile字段的写入操作先于读操作,get总能拿到最新值。
HashMap、LinkedHashMap、TreeMap 有什么区别?各自的使用场景?
LinkedHashMap 保存了记录的插入顺序,在用Iterator遍历时,先取到的记录是先插入的,遍历比HashMap慢;
TreeMap 实现SortMap接口,能够把它保存的记录根据键排序(默认按键值升序,也可以指定排序的比较器)。
使用场景
HashMap:一般情况下,使用最多的;适用于Map的插入、删除和定位元素;
TreeMap:在需要按自然顺序或自定义顺序遍历键的情况下;
LinkedHashMap:在需要输出的顺序和输入的顺序相同的情况下;
HashMap 多线程操作死循环问题
由于多线程并发下进行扩容(调用rehash()方法)造成元素之间形成一个循环链表。
正常Rehash过程
并发的Rehash过程
JDK 1.8 已经解决了该问题,但是由于HashMap是非线程安全的,多线程下使用还是会存在其他问题,比如:数据丢失。所以多线程下如果需要更新操作的,建议改用 ConcurrentHashMap。
对象头(Object Header)包含了哪些信息?
Mark Work(对象自身运行时的数据)
哈希码(HashCode)
GC分代年龄
锁状态标志
线程持有的锁
偏向锁ID
指向类的指针
数组长度(只有数组对象才有)
Java语言有啥特点?
简单易学
面向对象(封装、继承、多态、抽象)
跨平台(JVM实现跨平台)
可靠性
安全性
支持多线程
支持网络编程
编译和解析并存
JVM是什么?
JVM(Java虚拟机):是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是使用相同的字节码,它们都会给出相同的结果。
字节码:JVM可以理解的代码,扩展名为 .class 的文件。它不面向任何特定的处理器,只面向虚拟机。Java通过字节码的方式,在一定程度上解决了传统解析型语言执行效率低的问题,同时又保留了解析型语言可移植的特定。所以Java程序运行比较高效。
字节码和不同系统的JVM的实现是Java语言“一次编译,随处可运行”的关键。
Java 程序运行过程
OracleJDK 和 OpenJDK 的对比?
OpenJDK是一个参考模型并且是完全开源的;而OracleJDK是基于OpenJDK7构建的,并不是完全开源的。
OracleJDK比OpenJDK更稳定。两者代码几乎相同,但OracleJDK有更多的类和一些错误的修复。
在响应性和JVM性能方面,OracleJDK更出色一些。
import java 和 javax 有什么区别?
刚开始Java API所必需的包是java开头的包,javax是扩展API包来使用。
后来 javax 逐渐成为 Java API 的组成部分。
字符型常量 和 字符串常量的区别?
形式上:字符型常量是单引号引起的一个字符;字符串常量是双引号引起的0个或若干个字符;
含义上:字符型常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串常量代表一个地址值(在内存中的存放位置);
内存大小:字符型常量只占2个字节;字符串常量占若干个字节;
标识符和关键字的区别?
标识符:是一个名字,类、变量、方法的名字都是标识符。
关键字:被Java语言赋予了特殊含义的标识符。例如:private / public / class / new 等。
Java泛型?类型擦除?常用的通配符?
Java泛型:JDK5引入的一个新特性,提供了编译时类型安全检测的机制。表现为:将类型当作参数传递给一个类或者方法。
泛型类
泛型接口
泛型方法
类型擦除:Java泛型是伪泛型,Java在编译期间,所有的泛型信息都会被擦除掉。
常用通配符
? 表示不确定的java类型
T 表示具体的一个java类型
K / V 分别表示Java键值对的 key value
E 表示 Element
== 和 equals 的区别?
== : 基本数据类型 比较的是值是否相等; 引用类型 比较的是内存地址是否一样(即两个对象是否同一个对象);
equals:判断两个对象是否相等,不能用于比较基本数据类型的变量。
类没有覆盖equals方法:等价于“==”,使用的默认是Object类的equals方法。
类覆盖了equals方法:一般都会覆盖equals方法,来比较两个对象的内容是否相等。
为什么重写equals方法时,必须重写hashCode方法?
hashCode()方法介绍
为什么要有 hashCode()方法?
重写equals方法,必须重写hashCode方法
如果两个对象相等(equals返回true),则它们的hashCode也一定相等。
如果两个对象的hashCode相等,但它们不一定相等(equals不一定返回true)。
为什么说Java中只有值传递?
按值调用(call by value):方法接收的是调用者提供的值。
按引用调用(call by reference):方法接收的是调用者提供的变量地址。
一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。
Java才用按值调用,方法得到的是参数值的一个拷贝。
深拷贝、浅拷贝?
浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递。
深拷贝:对基本数据类型进行值传递,对引用类型,创建一个新的对象,并复制其内容。
定义一个不做事且无参的构造函数的作用?
Java程序在执行子类的构造方法之前,如果没有用super()来调用父类特定的构造方法,则默认会调用父类中“无参构造方法”。因此,如果父类中没有定义“无参构造方法”,而子类构造方法又没有用super()去调用父类特定的构造方法,则编译会发生错误。
成员变量 与 局部变量 的区别?
成员变量属于类的;局部变量是在代码块或方法中定义的变量或方法的参数;
成员变量可以被private、public、static 等修饰符所修饰;局部变量不能被访问控制修饰符及static修饰;
成员变量和局部变量都可以被 final修饰;
成员变量存在于堆内存;局部变量存在于栈内存;
static修饰的成员变量属于类的,随着类的初始化而存在;没有static修饰的成员变量是对象的一部分,随着对象的创建而存在;局部变量随着方法的调用而存在和自动消失;
成员变量如果没有被赋初值,则会自动以类型的默认值赋值(被final修饰的例外,需要显示赋值);局部变量不会自动赋值;
对象实例和对象引用的关系?
对象实例存在堆内存中;对象引用存在栈内存中;
一个对象实例可以有若干个对象引用指向它;一个对象引用指向0个或1个对象实例;
类的构造方法?
作用是:完成对类对象的初始化工作。
一个类没有显示声明构造方法也可以执行,因为Java会给它一个默认的无参构造方法;如果显示声明了构造方法,那Java不会给它再添加默认的构造方法。
特性
名字与类名相同;
没有返回值,并且不能用void声明;
生成类的对象时自动执行,无需调用。
子类在构造方法里调用父类的无参构造方法的目的是?
帮助子类完成初始化工作。
面向对象的特征?
抽象
封装
继承
子类拥有父类的所有属性和方法(包括私有的),但无法访问父类私有的属性和方法,仅仅是拥有。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以覆盖父类的方法。
多态
对象类型和引用类型之间,具有继承(类)或实现(接口)的关系;
引用类型变量发出的方法调用到底是哪个类中的方法,必须在运行期间才能确定;
多态不能调用“只在子类存在但父类不存在”的方法;
如果子类重写了父类的方法,执行的是子类覆盖的方法,否则是父类的方法;
String、StringBuffer、StringBuilder 的区别?
可变性
String类中使用final关键字修饰字符数组来保存字符串(private final char value[]),所以String对象是不可变的。
StringBuffer和StringBuilder都继承自AbstractStringBuilder类,在该类中也是使用字符数组(char[] value)来保存字符串,但没有用final关键字修饰,所以这两种对象都是可变的。
线程安全性
String 对象是不可变的,可以理解成常量,所以线程安全。
AbstractStringBuilder 是StringBuffer、StringBuilder的父类,定义了一些字符串的基本操作方法,如:append、indexOf等公共方法。StringBuffer对方法加了同步锁(public <font color="#dc2d1e">synchronized </font>StringBuffer append(String str)),所以是线程安全的。StringBuilder没有对方法进行加同步锁,所以是非线程安全。
性能
String 类型进行改变的时候,会生成一个新的String对象,然后将指针指向新的String对象;
StringBuffer 进行改变时,会对StringBuffer对象本身进行操作;
StringBuilder与StringBuffer操作一样,但因为没有加同步锁,性能更好一些;
总结
操作少量的数据:适用 String
单线程操作字符串缓存区下大量的数据:适用 StringBuilder
多线程操作字符串缓存区下大量的数据:适用 StringBuffer
Object类常见的方法有哪些?
public final native Class <font color="#3da8f5">getClass()</font>
public native int <font color="#3da8f5">hashCode()</font>
public boolean <font color="#3da8f5">equals(Object obj)</font>
protected native Object <font color="#3da8f5">clone() </font>throws CloneNotSupportedException
public String<font color="#3da8f5"> toString()</font>
public final native void <font color="#3da8f5">notify()</font>
public final native void <font color="#3da8f5">notifyAll()</font>
public final native void <font color="#3da8f5">wait(long timeout)</font> throws InterruptedException
public final void <font color="#3da8f5">wait(long timeout, int nanos)</font> throws InterruptedException
public final void <font color="#3da8f5">wait()</font> throws InterruptedException
protected void <font color="#3da8f5">finalize()</font> throws Throwable { }
Java序列化中如果有些字段不想被序列化,怎么办?
对于不想被序列化的变量,使用 <font color="#dc2d1e">transient </font>关键字修饰。
transient 关键字:阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法。
获取键盘输入常用的两种方法?
通过 Scanner 类
通过 BufferReader 类
反射机制?
什么是反射机制?
静态编译 VS 动态编译
静态编译:编译时确定类型,绑定对象; .java to .class
动态编译:运行时确定类型,绑定对象; .class to 机器码
反射机制的优缺点
优点:运行期类型的判断、动态加载类、提高代码灵活度。
缺点
性能问题:反射相当于一系列解析操作,通知JVM要做的事情,比直接的java代码要慢很多。
安全问题:动态操作改变类的属性,增加了安全隐患。
反射技术的应用
JDBC连接数据库时使用Class.forName() 通过反射加载数据库的驱动程序;
Spring 的IOC创建对象(动态加载管理Bean)、AOP(动态代理)功能;
动态配置实例的属性;
获取Class 对象的两种方式
知道具体类的情况
通过 Class.forName() 传入类的路径获取;
Java异常有哪些?
所有的异常都有一个共同的祖先:java.lang.Throwable类,Throwable类有两个重要的子类Exception(异常)和Error(错误)。Exception 能被程序本身处理(try-catch),Error是无法处理的。
Exception : 程序本身可以处理的异常,可以通过 catch 捕获。
受检异常(必须处理的)
IOException
ClassNotFoundException
SQLException
不受检异常(可以不处理)
NullPointException
NumberFormatException
ArrayIndexOutBoundsException
ArithmeticException
Error:程序无法处理的错误。JVM会选择终止线程。
Virtual MachineError (Java虚拟机运行错误)
OutOfMemoryError (虚拟机内存不够错误)
NoClassDefFoundError (类定义错误)
StackOverflowError (栈溢出错误)
try-catch-finally
try 块:用于捕获异常。其后可以接零个或多个catch块,如果没有catch块,则必须跟一个finally块。
catch 块:用于处理try捕获到的异常。
finally 块:无论是否捕获或处理异常,finally块里的语句都会被执行。当try块或catch块中遇到return语句时,finally块的语句将在方法返回之前被执行,并且finally语句返回值将覆盖原始返回值。
以下3种情况下,finally块不会被执行
在try或finally块中用 System.exit(int)退出程序。并且这句要在异常语句之前。
程序所在的线程死亡。
关闭CPU。
try-with-resources
适用范围:任何实现 java.lang.AutoCloseable 或 java.io.Closeable的对象。
面对需要关闭的资源,我们总是应该优先使用 try-with-resources,而不是try-finally。随之产生的代码更简短,更清晰,产生的异常对我们也更有用。try-with-resources 语句让我们更容易编写必须要关闭的资源代码,若采用try-finally 则几乎做不到这点。---《Effective Java》
InputStream、Scanner等资源都需要调用close()方法手动关闭。
try-catch-finally实现方式
使用Java 7 之后的try-with-resources 语句改造上面的代码:
当多个资源需要关闭的时候,通过使用分号分隔。
Java中IO流分为几种?
按照流的流向分:分为<font color="#3da8f5">输入流</font>和<font color="#3da8f5">输出流</font>;
按照流的操作单元分:分为<font color="#3da8f5">字节流</font>和<font color="#3da8f5">字符流</font>;
按照流的角色分:分为<font color="#3da8f5">节点流</font>和<font color="#3da8f5">处理流</font>;
Java IO流有40多个类,都是从以下4个抽象类基类中派生出来的。
InputStream / Reader :所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream / Writer :所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式
按操作对象
既然有了字节流,为什么还要有字符流?
不管是文件读写还是网络传输,信息的最小存储单元都是字节,那为什么I/O流操作要分为字节流和字符流?
字符流是由于Java虚拟机将字节转换得到的,这个过程比较复杂耗时,并且,如果不知道编码类型很容易出现乱码问题。所以,I/O流提供了一个直接操作字符的接口,方便对字符进行流操作。
怎么解决浮点数进度丢失?
使用BigDecimal
<b>浮点数之间的等值判断,基本数据类型不能用“==”来比较,包装类型不能用“equals”来判断。</b>(具体原理与浮点数的编码方式有关,<b>精度丢失</b>)
使用BigDecimal来定义浮点数,再进行运算操作
在使用BigDecimal时,为防止精度丢失,<font color="#ffaf38">推荐使用</font>它的<font color="#3da8f5">BigDecimal(String) </font>构造方法或者<font color="#3da8f5">BigDecimal.valueOf</font>方法来创建对象,<font color="#ffaf38">禁止使用</font>构造方法<font color="#3da8f5">BigDecimal(double)</font>的方式把double值转化为BigDecimal对象。
BigDecimal a = new BigDecimal(0.1f); 实际存储值为:0.10000000149
BigDecimal a = new BigDecimal("0.1"); 正解
BigDecimal a = new BigDecimal.valueOf(0.1); 正解
工具类Arrays.asList() 使用注意事项?
Arrays.asList() 将数组转换为集合后,底层其实还是数组,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UpsupportedOperationException异常。
Arrays.asList() 的返回对象是一个Arrays 的内部类,并没有实现集合的修改方法。体现的是适配器模式,只是转换接口,后台的数据仍是数组。
传递的数组必须是对象数组,而不是基本类型。(Arrays.asList()是泛型方法,传入的对象必须是对象数组)
0 条评论
下一页