Java基础面试题
总结各个大佬的面试经验,给出Java面试中常见的问题以及答案
文章目录
- Java基础面试题
- 一、前提
- 二、基本类型
- 三、面向对象
- 四、数据结构
- 五、String
- 六、关键字
- 七、异常
- 八、IO流
- 九、按值传递
- 十、反射
- 十一、拷贝
- 十二、动态代理
- 十三、注解
- 链接
一、前提
1、字节序定义
字节序是指多字节数据在计算机内存中存储或网络传输时个字节的存储顺序。通常由小端和大端两组方
式。
- 小端:低位字节存放在内存的低地址端,高位字节存放在内存的高地址端。
- 大端:高位字节存放在内存的低地址端,低位字节存放在内存的高地址端。
Java语言的字节序是大端。
2、Java语言的特点
- 简单:Java摒弃了C++中容易引发程序错误的地方,如指针和内存管理。并且提供了丰富得类库。
- 面向对象:Java语言的设计完全是面向对象的,它不支持面向过程的程序设计技术。Java支持静态和动态缝合的代码继承及重用。
- 分布式:Java包括一个支持HTTP和FTP等基于TCP/IP协议的子库。因此Java应用程序可凭借URL打开并访问网络上的对象,其访问方式与访问本地文件系统几乎完全相同。
- 健壮:Java致力于检查程序在编译和运行时的错误,类型检查帮助检查出许多开发早期出现的错误。
- 安全:Java删除了指针和释放内存等功能,避免了非法内存操作。Java语言在机器上执行之前,经过了很多次测试,如代码校验,检查代码段格式等。
- 可移植性:Java虚拟机能掩盖不同CPU之间的差别,使编译器产生的目标代码(J-Code)能运行于任何具有Java虚拟机的机器上。
- 解释性:Java解释器能直接运行目标代码指令。
3、面向过程和面向对象的区别
- 面向对象:面向对象是把构成问题的事务分解成各个对象,建立对象的目的是描述某个事务在整个解决问题的步骤中的行为
优点:有封装、继承、多态等特点,易维护,易扩展,易复用,是系统更灵活。 - 面向过程:面向过程就是分析出解决问题所需要的的步骤,然后用函数把这些步骤一步一步实现,使用的时候依次调用即可。
优点:性能比面向对象高。
4、JVM,JDK,JRE
- JVM:Java virtual machine 即Java虚拟机,是整个Java体系的底层支撑平台,把源文件编译成平台无关的字节码文件,屏蔽了Java源码与平台相关的信息,所以可以跨平台运行。
- JDK:Java Development Kit Java开发工具包,提供Java开发环境和运行环境,包含JRE。
- JRE:Java Runtime Environment 支持Java程序运行的标准环境,包括JVM和Java类库中Java SE API子集,核心配置工具。
5、Java和C++的区别
- Java源码会先经过一次编译,成为中间码,中间码再被解释器解释成机器码。对于Java而言,中间码就是字节码(.class),而解释器在JVM中内置了。
- C++源码一次编译,直接在编译的过程中链接了,形成了机器码。
- C++比Java执行速度快,但是Java可以利用JVM跨平台。
- Java是纯面向对象的语言,所有代码(包括函数、变量)都必须在类中定义。而C++中还有面向过程的东西,比如是全局变量和全局函数。
- C++中有指针,Java中没有,但是有引用。
- C++支持多继承,Java中类都是单继承的。但是继承都有传递性,同时Java中的接口是多继承,类对接口的实现也是多实现。
- C++中,开发需要自己去管理内存,但是Java中JVM有自己的GC机制,虽然有自己的GC机制,但是也会出现OOM和内存泄漏的问题。C++中有析构函数,Java中Object的finalize方法。
- C++运算符可以重载,但是Java中不可以。同时C++中支持强制自动转型,Java中不行,会出现ClassCastException(类型不匹配)。
6、按照技术服务领域划分
- Java card:支持Java小程序运行的小内存设备上的平台
- Java ME(Micro Edition) :支持Java程序运行在移动终端上的平台
- Java SE(Standard Edition):支持面向桌面级应用(如Windows下的应用程序)的平台
- Java EE(Enterprise Edition):支持使用多层架构的企业级应用的平台
7、为什么Java不支持多重继承
- 为了程序的结构能够更加清晰从而便于维护。假设Java语言支持多重继承,类C继承自类A和类B,如果类A和B都有自定义的成员方法f(),那么当代码中调用类C的f()会产生二义性。Java语言通过实现多个接口间接支持多重继承,接口由于只包含方法定义,不能有方法的实现,类C继承接口A与接口B时即使它们都有方法f(),也不能直接调用方法,需实现具体的f()方法才能调用,不会产生二义性。
- 多重继承会使类型转换、构造方法的调用顺序变得复杂,会影响到性能。
8、序列化和反序列化
- 序列化:将Java对象转化为字节序列,由此可以通过网络对象进行传输
- 反序列化:将字节序列转化为java对象
具体实现:实现Serializable接口,或实现Externalizable接口中的writeExternal与readExternal方法。
9、Object类常用方法
- hashcode:通过对象计算出散列码。用于map型或equals方法。需要保证同一个对象多次调用该方法,总返回相同的整型值
- equals:判断两个对象是否一致
- 自反性:对于任何非空引用x,x.equals(x)应该返回true
- 对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true
- 传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true
- 一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果
- 非空性:对于任意非空引用x,x.equals(null)应该返回false
- toString:用字符串表示该对象
- clone:深拷贝一个对象
10、equals与hashCode之间的关系
equals和hashCode这两个方法都是从object类中继承过来的,equals主要用于判断对象的内存地址引用是否是同一个地址;hashCode根据定义的哈希规则将对象的内存地址转换为一个哈希码。HashSet中存储的元素是不能重复的,主要通过hashCode与equals两个方法来判断存储的对象是否相同:
- 如果两个对象的hashCode值不同,说明两个对象不相同。
- 如果两个对象的hashCode值相同,接着会调用对象的equals方法,如果equlas方法的返回结果为true,那么说明两个对象相同,否则不相同。
11、使用clone时需要注意的问题
- 需要克隆的对象需要实现cloneable接口
- 当克隆的对象内部包含引用类型的数据时,也需要对它进行一个深拷贝,内部对象也需要实现cloneable接口
二、基本类型
1、八大类型
//基本类型和封装类
byte Byte //8bit
short Short //16bit
int Integer //32bit
long Long //64bit
float Float //32bit
double Double //64bit
boolean Boolean //未定
char Character //16bit
2、装箱和拆箱
装箱是把基本类型转化为包装类,拆箱是把包装类转化为基本数据类型。Java支持自动装箱和拆箱。
Integer i = new Integer(5);
Integer i = 5;//装箱
int n=i ; //拆箱
装箱过程是通过调用包装器的valueOf( )方法实现的,而拆箱过程是通过调用包装器的 xxxValue方法实现的。(xxx代表对应的基本数据类型)。
在通过valueOf( )方法创建Integer对象的时候,如果数值在[-128,127]之间,便返回指向IntegerCache.cache中已经存在的对象的引用;否则创建一个新的Integer对象。
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
在某个范围内的整型数值的个数是有限的,而浮点数却不是。
总结,Integer、Short、Byte、Character、Long这几个类的valueOf( )方法的实现是类似的。
Double、Float的valueOf( )方法的实现是类似的。
3、字符常量和字符串常量的区别
字符常量是基本类型,可以参加表达式的运算,只占一个字节。
字符串声明的是不可变的对象,每次操作都会生成新的对象然后将指针指向新的对象
4、&与&&的区别
都是逻辑运算符,只要一端有假,结果都为false,如果&&左边的表达式的值是false,右边的表达式会被屏蔽,不会进行运算;但是,&即使在左边为false的情况下依旧会对右边的式子做出计算。当&两边的操作符不是布尔类型时,表示按位与运算。
5、char能否存储中文
可以,Java采用Unicode编码,编码集包含了中文汉字。每个char变量占用两个字节,因此可以存储中文,其中英文字符占一个字节,中文字符占两个字节。
6、short
short s1 = 1; s1 = s1+1; //s1+1为int 类型,需要加上(short)强制类型转换
s1 += 1 // s1 += 1 与 s1 = (short)(s1+1)等效 因此通常用 s1 += 1避免出错
7、泛型
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
好处:
- 类型安全,提供编译期间的类型检测
- 前后兼容
- 泛化代码,代码可以更多的重复利用
- 性能较高,用GJ(泛型JAVA)编写的代码可以为java编译器和虚拟机带来更多的类型信息,这些信息对java程序做进一步优化提供条件
泛型擦除
Java编译器生成的字节码是不包涵泛型信息的,泛型类型信息将在编译处理时被擦除,这个过程被称为
泛型擦除。
三、面向对象
1、构造器Constructor是否可被Override
构造器Constructor不能被继承,因此不能被重写(Override),但是可以被重载(Overload);
如果父类自定义了有参构造函数,则子类无论定义构造函数与否,定义有参构造函数与否,都会报错,正确的做法是在子类的构造方法中添上super(参数),以表明子类构造之前先构造父类,而这句话必须放在第一句,否则报"Constructor call must be the first statement in a constructor"的错误。
2、构造方法有哪些特性
- 构造方法与类同名
- 无返回值
- 可以默认无参构造,或者多个构造构造函数,构成重载关系
- 不能被继承
- 不能手动调用
3、面向对象的三大特性
- 封装:封装是在抽象的基础上决定信息是否公开,以及公开等级,核心是以什么样的方式公开哪些信息,封装的主要任务是对属性、数据、部分内部敏感行为实现隐藏。
- 继承:继承是面向对象编程的基石,允许创建具有逻辑等级结构的类体系,某些基础模块可以被直接复用,间接复用或增强复用。
- 多态:根据运行时的实际对象类型,同一个方法产生不同的运行结果,多态是以覆写为基础来实现面向对象特性,在运行时期由JVM进行动态绑定,调用方法体。
多态的机制
- 重载(编译时多态):重载是指同一个类中有多个同名的方法,但这些方法有不同的参数,在编译期间就可以确定
调用哪个方法。 - 覆写(运行时多态):覆盖是指派生类重写基类的方法,使用基类指向其子类的实例对象,或接口的引用变量指向
其实现类的实例对象,在程序调用的运行期根据引用变量所指的具体实例对象调用正在运行的那个
对象的方法,即需要到运行期才能确定调用哪个方法
运行时多态
运行时多态的实现:主要依靠方法表,方法表中最先存放的是Object类的方法,接下来是该类的父类的
方法,最后是该类本身的方法。如果子类改写了父类的方法,那么子类和父类的那些同名方法共享一个
方法表项,都被认作是父类的方法。因此可以实现运行时多态
4、成员变量与局部变量的区别
- 成员变量:在类中方法外、在堆中存储,生命周期与所关联的对象有关,有默认值
- 局部变量:在方法中定义或方法声明上,在栈中存储,生命周期与绑定的方法关联,无默认值,必须赋值才能使用。
5、接口和抽象类的区别
接口与抽象类是对实体类进行更高层次的抽象,仅定义公共行为和特征。
- 共同点:都不能被实例化
- 区别:
- 抽象类是用于捕捉子类的特征,而接口是抽象方法的集合
- 抽象类不能被实例化,只能用作子类的超类,是用来创建子类的模板
- 接口只能有方法定义,不能有方法的实现,而抽象类可以有方法的定义与实现
- 抽象类可以有构造器,而接口不能
- 接口的方法默认修饰符为public,不能使用其他修饰符。
- 一个类只能继承一个抽象类,可以实现多个接口
使用场景
当子类和父类之间存在逻辑上的层次结构,推荐使用抽象类,有利于功能的累积。当功能不需要,
希望支持差别较大的两个或更多对象间的特定交互行为,推荐使用接口。使用接口能降低软件系统
的耦合度,便于日后维护或添加删除方法。
6、重载和覆写的区别
- override 覆写:是子类实现接口或者继承父类时,保持方法签名完全相同,实现不同的方法体。
- overlord 重载:方法名相同,但是参数类型或个数不同。
7、创建一个对象用什么运算符?对象实体与对象引用有什么不同
new 运算符,new 创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象引用存放在栈内存中)。一个对象引用可以指向 0 个或 1 个对象;一个对象可以有 n 个引用指向它。
8、对象的相等于指向他们的引用相等,两者有什么不同
对象相等表示两个对象是同一块内存,内容相同,引用相等是指向对象地址的值相等。
9、讲讲类的实例化顺序,比如父类静态数据,构造函数,字段,子类静态数据,构造函数,字段,当 new 的时候,他们的执行顺序
详细的先后顺序:父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变
量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数。
链接: https://blog.csdn.net/u013182381/article/details/74574278.
10、内部类
- 成员内部类:作为成员对象的内部类。可以访问private及以上外部类的属性和方法。外部类想要访
问内部类属性或方法时,必须要创建一个内部类对象,然后通过该对象访问内部类的属性或方法。
外部类也可访问private修饰的内部类属性。 - 局部内部类:存在于方法中的内部类。访问权限类似局部变量,只能访问外部类的final变量。
- 匿名内部类:只能使用一次,没有类名,只能访问外部类的final变量。
- 静态内部类:类似类的静态成员变量。
11、代码块执行顺序
- 父类静态代码块(只执行一次)
- 子类静态代码块(只执行一次)
- 父类构造代码块
- 父类构造函数
- 子类构造代码块
- 子类构造函数
- 普通代码块
四、数据结构
1、ArrayList 、Vector和 LinkedList 有什么区别
- ArrayLsit底层是使用数组实现的,因此查询速度快,但是增删慢。非线程安全的
- Vector底层也是用数组实现的,且是线程安全的,大部分是直接或间接同步的
- LinkedList底层是使用双向链表实现的,因此增删速度快,但是查询速度慢。非线程安全的
2、用过哪些 Map 类,都有什么区别,HashMap 是线程安全的吗,并发下使用的 Map 是什么,他们内部原理分别是什么,比如存储方式,hashcode,扩容,默认容量等。
- HashMap:HashMap是一个最常用的Map,jdk8后采用数据+链表/红黑树。当链表节点超过8之后会转为红黑树。若当前数据/总数据容量>负载因子,Hashmap将执行扩容操作。默认初始化容量为 16,扩容容量必须是 2 的幂次方、最大容量为 1<< 30 、默认加载因子为 0.75。它根据HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,区的数据的顺序是完全随机的。HashMap最多只允许一条记录的键为null;允许多条记录的值为null。HashMap不支持线程同步,是非线程安全的,即任一时刻可以有多个线程同时写HashMap,可能会导致数据的不一致。如果需要同步,可以用 Collections和synchronizedMap方法使HashMap具有同步能力,或者使用ConcurrentHashMap。HashMap在并发场景中可能造成死循环。因为在并发操作中,HashMap的扩容可能会在桶上形成一个环境链表,当后面的操作一旦定位到这个HashEntry,就会产生死循环。
- HashTable:与HashMap类似,继承Dictionary类,不同的是,它不支持记录空键或空值。支持线程同步,是线程安全的,但是也导致了写入比较慢。当链表超过8时,链表转为红黑树,利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。扩充过程,创建一个新Map,容量为原来的两倍,然后重新计算HashCode,将原数据全部放入新的Map中,删除原Map。
HashMap - LinkedHashMap:LinkedHashMap保存了记录的插入顺序,在用Iterator在遍历LinkedHashMap时,先得到的记录肯定是先插入的。在遍历的时候会比HashMap慢,不过当HashMap容量很大但是实际数据较少时,遍历起来会比LinkHashMap慢,因为LinkedHashMap是遍历速度只和实际数据有关,和容量无关,而HashMap遍历速度和容量有关。
- TreeMap: TreeMap实现SortMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。底层是采用红黑树实现的有序和查找。
TreeMap
3、HashMap解决冲突的方法
- 开放地址法:Hi = ( H(Key) + di ) MOD m
- 线性探测再散列 di = 1,2,3,4,5,…,m-1
- 平方探测再散列 di = 1,-1,4,-4,9,-9,…k,-k(取相应数的平方)
- 随机探测再散列 di是一组伪随机的数列 - 链地址法:将所有哈希地址相同的记录都链接在同一个链表中
- 再哈希法:产生冲突时计算另一个哈希函数的地址,直到冲突不再发生
- 建立公共溢出区:将存储区域分成两部分,一部分称为基本表,另一部分为溢出表,用于存放冲突的元素。特点,不会发生聚集,可能会出现都满的情况;一旦发生冲突,查找时间与溢出表大小有关;
4、concurrentHashMap
(1)jdk7-分段锁(segment)
JDK7采用锁分段技术。首先将数据分成 Segment 数据段,然后给每一个数据段配一把锁,当一个线程占用锁访问其中一个段的数据时,其他段的数据也能被其他线程访问。
- get 除读到空值不需要加锁。该方法先经过一次再散列,再用这个散列值通过散列运算定位到 Segment,最后通过散列算法定位到元素。
- put 须加锁,
- 首先定位到 Segment,获取segment锁,通过key的hashcode定位到HashEntry
- 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value
- 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容
- 最后会解除在 1 中所获取当前 Segment 的锁
(2)jdk8-synchronized+CAS
取消分段锁机制,采用CAS算法进行值的设置,如果CAS失败再使用 synchronized 加锁添加元素 引入红黑树结构,当某个槽内的元素个数超过8且 Node数组 容量大于 64 时,链表转为红黑树。使用了更加优化的方式统计集合内的元素数量。
put方法顺序:
- 根据key计算出hashcode
- 判断是否需要进行初始化
- f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功
- 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容
- 如果都不满足,则利用 synchronized 锁写入数据
- 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树
get方法:
- 根据计算出来的hashcode寻址,如果与桶上就直接返回值
- 如果是红黑树就按照红黑树的方式获取
- 如果不满足就按照链表的方式遍历获取
(3)concurrentHashMap弃用分段锁的原因
原因:
- 加入多个分段锁浪费内存空间
- 生产环境中,map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待
- 为了提高GC的效率
- 链表查询效率低
好处:
- 锁的粒度变小
- 通过红黑树弥补了put get的性能差距
- 扩容:其它线程可以通过检测数组中节点决定是否对这条链表进行扩容,减小了扩容的粒度,提高了扩容效率。
(4)为什么使用synchronized而不是ReentranLock
- 减少内存开销:假设使用重入锁来获取同步支持,那每个节点都需要通过继承继承AQS来获取同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点需要同步,这回造成大量的内存浪费
- 获得JVM的支持:可重入锁是API级别的,后续性能优化空间很小。synchronized则是JVM直接支持的,JVM能够在运行时做出相应的优化措施:锁粗化、锁消除、锁自旋等。这就让synchronized能够随着JDK版本升级而不改动代码的前提下获得性能提升
5、简述红黑树
红黑树的特性:
- 每个节点或者是黑色,或者是红色。
- 根节点是黑色。
- 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
(1)红黑树和平衡二叉树的区别
红黑树是一种平衡二叉查找树的变体,它的左右子树高差有可能大于 1,所以红黑树不是严格意义上的平衡二叉树(AVL),添加和删除元素会比AVL树快一点吗,对之进行平衡的代价较低, 其平均统计性能要强于 AVL
6、Java线程安全的数据结构有哪些
- HashTable
- ConcurrentHashMap
- Vector
- Stack(线程安全版)
- BlockingQueue
7、Collection和Collections有什么区别?
- Collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,所有集合都是它的子类,比如List、Set等。
- Collections是一个包装类,包含了很多静态方法、不能被实例化,而是作为工具类使用,比如提供
的排序方法:
Collections.sort(list);提供的反转方法:Collections.reverse(list)
五、String
1、创建对象
String A = "123" ; //创建一个对象 A 为指向常量池中字符串的引用
String B= new String("123"); //创建两个对象 B指向字符串的引用,String引用指向字符串
// new 作为类初始化条件之一在这里出现,首先去创建一个实例,会然后调用 String 类型的构造器,
//在堆中创建一个对象,并将指向该对象的引用赋给 B,并会在StringTable中驻留引用。所以后面的String A = "12"才没有创建对象
类采用final定义为不可变类,属性Value为不可变数组,也使用final修饰,即定义多少,String中字符数组的长度就是多少,不存在扩容这一说。
new 字节码出现几次就代表创建了多少对应实例。在 JVM 里,“new” 字节码指令只负责把实例创建出来(包括分配空间、设定类型、所有字段设置默认值等工作),并且把指向新创建对象的引用压到操作数栈顶。此时该引用还不能直接使用,处于未初始化状态
A == B.intern();
//如果常量池中已经有了这个字符串,那么直接返回代表常量池中字符串的引用,如果没有,那就将字符串保存一份到常量池,然后直接返回String对象引用。
2、StringBuffer和StringBuilder的区别?
区别:String声明的是不可变对象,每次操作都会生成新的String对象,然后将指针指向新的对象;StringBuffer和StringBuilder是可变类,它所代指的字符串的改变都不会产生新的对象。StringBuffer是线程安全的,而StringBuilder是非线程安全的,但是在单线程下,后者比前者性能强,因此单线程下使用Builder,多线程下使用Buffer。StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的。
实现原理:StringBuffer类继承自AbstractStringBuilder抽象类,实现Serializable序列化接口和CharSequence接口。
- StringBuffer()的初始容量可以容纳16个字符,当该对象的实体存放的字符的长度大于16时,实体容量就自动增加。StringBuffer对象可以通过length()方法获取实体中存放的字符序列长度,通过capacity()方法来获取当前实体的实际容量。
- 在添加时会先判断当前容量是否足够,不足够的话进行扩容,然后将字符串拷贝到字符数组里面。
- 扩容时,会先将容量变为之前的两倍再加2,如果还不够,则直接扩容到需要的大小。
- toString时,StringBuilder直接new 一个String对象,将StringBuilder对象的value进行一个拷贝,重新生成一个对象,不共享之前StringBuilder的char[]。而StringBuffer中维护了一个共享变量,用来存储当前的值,在多次调用其toString方法时,其new出来的String对象是会共享同一个char[] 内存的,达到共享的目的。但是StringBuffer只要做了修改,其toStringCache属性值都会置null处理。
3、String为什么是不可变的?
- 字符串常量池的需要
字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 - 允许String对象缓存HashCode
Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。
字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. - 安全性
String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。
3、== 和 equals 的区别
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。两个对象的 hashCode()相同,equals()不一定 true
4、String 类能被继承吗,为什么?
不能,因为String类是倍final修饰的。final修饰过的类不能被继承、final修饰过的变量不能被修改。
六、关键字
- static:变量和方法都可以用static修饰,只有内部类可以使用static修饰,被修饰的可以直接使用名字调用。
- final:方法和类都可以用fina修饰,final类不能被继承,final方法不能被重写,final变量就是常量。
- this:当前类的不累对象,表示调用这个方法的对象。
- super:调用父类的方法。
- this和super都不能在静态代码块中使用。且在构造方法中都只能出现一次。因为都要求出现在构造函数的第一行。构造器默认调用父类空参构造器。
1、final,finally,finalize区别
- final:final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally:finally是在异常处理时提供finally块来执行任何清除操作。不管有没有异常被抛出、捕获,finally块都会被执行。try块中的内容是在无异常时执行到结束。catch块中的内容,是在try块内容发生catch所声明的异常时,跳转到catch块中执行。finally块则是无论异常是否发生,都会执行finally块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在finally块中。在try块中强制退出的话,不会执行finally块
- finalize:finalize是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者被执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。
2、访问修饰符
- default:默认访问修饰符,在同一包内可见
- private:在同一类内可见,不能修饰类
- protected:对同一包内的类和所有子类可见,不能修饰类(父类用protected修饰的方法,子类在覆写的时候可以用public和protected修饰)
- public:对所有类可见
3、static
(1)作用
- 为某种特定数据类型或对象分配与创建对象个数无关的单一的存储空间。
- 使得某个方法或属性与类而不是对象关联在一起,即在不创建对象的情况下可通过类直接调用方法或使用类的属性。
(2)使用方法
- 修饰成员变量。用static关键字修饰的静态变量在内存中只有一个副本。只要静态变量所在的类被加载,这个静态变量就会被分配空间,可以使用’‘类.静态变量’‘和’‘对象.静态变量’'的方法使用。
- 修饰成员方法。static修饰的方法无需创建对象就可以被调用。static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和静态成员方法。
- 修饰代码块。JVM在加载类的时候会执行static代码块。static代码块常用于初始化静态变量。static代码块只会被执行一次。
- 修饰内部类。static内部类可以不依赖外部类实例对象而被实例化。静态内部类不能与外部类有相同的名字,不能访问普通成员变量,只能访问外部类中的静态成员和静态成员方法。
七、异常
- Error:程序在执行过程中所遇到的硬件或操作系统的错误,将导致程序无法运行。程序本身无法处理,只能依靠外界干预,由JVM跑出,包括动态链接失败,虚拟机错误(堆溢出、栈溢出)等
- Exception:异常可以导致程序非正常终止,也可以被预先检测并处理。
- NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
- SQLException:提供关于数据库访问错误或其他错误信息的异常。
- IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
- NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
- FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
- IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
- ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
- ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
- IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
- ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
- NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
- NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
- SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
- UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
(1)常见的运行时异常
- NullPointerException:空指针异常
- IndexOutOfBoundsException:数组越界
- ClassCaseException:类转换异常
- ArithemeticException:算数异常
(2)throw与throws的区别
- throw一般是用在方法体内部,由开发者定义程序语句出现问题后主动抛出一个异常
- throws一般用于方法声明上,代表该方法可能会抛出异常列表
八、IO流
1、获取键盘输入常用的方法
- 通过Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();
input.close();
- 通过BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
2、Java中IO流分为几种
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流;
3、字节流和字符流
java中提供了专用于输入输出功能的包Java.io,其中包括:
- InputStream,OutputStream,Reader,Writer
- InputStream 和OutputStream,两个是为字节流设计的,主要用来处理字节或二进制对象,
- Reader和 Writer.两个是为字符流(一个字符占两个字节)设计的,主要用来处理字符或字符串.
字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串,而字节流处理单元为1个字节,操作字节和字节数组。所以字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。
字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串; 2. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
4、BIO、NIO、AIO有什么区别
- BIO (Blocking I/O): 同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
- NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
- AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改进版 NIO 2,它是异步非阻塞的IO模型。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了。
九、按值传递
1、为什么Java中只有按值传递
值传递:是指在调用函数时,将实际参数复制一份传递给函数,这样在函数中修改参数时,不会影响到实际参数。其实,就是在说值传递时,只会改变形参,不会改变实参。
引用传递:是指在调用函数时,将实际参数的地址传递给函数,这样在函数中对参数的修改,将影响到实际参数。
值传递,不论传递的参数类型是值类型还是引用类型,都会在调用栈上创建一个形参的副本。不同的是,对于值类型来说,复制的就是整个原始值的复制。而对于引用类型来说,由于在调用栈中只存储对象的引用,因此复制的只是这个引用,而不是原始对象
2、示例
基本值传递的值为不会再函数中被改变,数组对象会被函数改变,引用类型传递实参和形参指向的是相同的内容,只是复制了指向对象的引用;String类型例外。
十、反射
Java反射机制是在运行状态中对任意一个类,都能知道这个类的所有属性和方法;对于任意一个对象,都能调用它的任意一个方法和属性,这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。
Java反射机制是Java语言被视为“准动态”语言的关键性质。Java反射机制的核心就是允许在运行时通过Java Reflection APIs来取得已知名字的class类的内部信息(包括其modifiers(诸如public, static等等)、superclass(例如Object)、实现interfaces(例如Serializable),也包括fields和methods的所有信息),动态地生成此类,并调用其方法或修改其域(甚至是本身声明为private的域或方法)。
Java反射机制主要提供了以下功能:
- 在运行时判断任意一个对象所属的类。
- 在运行时构造任意一个类的对象。
- 在运行时判断任意一个类所具有的成员变量和方法。
- 在运行时调用任意一个对象的方法。
- 生成动态代理
1、静态编译和动态编译
- 静态编译:在编译时确定类型,绑定对象
- 动态编译:在运行时确定类型,绑定对象
2、反射机制的优点
可以实现动态创建对象和编译,体现出很大的灵活性,
3、反射的应用场景
Java程序中许多对象在运行时都会出现两种类型:编译时类型和运行时类型,编译时类型由声明对象时使用的类型来决定,运行时的类型由实际复制给对象的类型来决定,程序在运行时,有时需要注入外部资源,那么这个外部资源在编译时是object,程序只能靠运行时信息来发现该对象和类的信息,这时就要用到反射。
比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
4、获取class对象的三种方式
- 通过new对象实现反射机制
- 通过路径实现反射
- 通过类名实现反射机制
public class Student {
private int id;
String name;
protected boolean sex;
public float score;
}
public class Get {
//获取反射机制三种方式
public static void main(String[] args) throws ClassNotFoundException {
//方式一(通过建立对象)
Student stu = new Student();
Class classobj1 = stu.getClass();
System.out.println(classobj1.getName());
//方式二(所在通过路径-相对路径)
Class classobj2 = Class.forName("fanshe.Student");
System.out.println(classobj2.getName());
//方式三(通过类名)
Class classobj3 = Student.class;
System.out.println(classobj3.getName());
}
}
5、反射api
- Class类:可获得类属性方法
- Field类:获得类的成员变量
- Method类:获取类的方法信息
- Construct类:获取类的构造方法等信息
十一、拷贝
1、浅拷贝和深拷贝
- 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值就会随之变化,这就是浅拷贝。
- 深拷贝是一个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度慢且花销大。
2、深拷贝例子
@Override
public class Student implements Cloneable
@Override
public Object clone() {
//浅拷贝
try {
// 直接调用父类的clone()方法
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
十二、动态代理
Java实现动态代理的两种方式
- JDK反射机制进行代理
- CGLIB,引入cglib包,重写被代理类方法,用cglib代理
十三、注解
(1)简述注解
Java注解用于Java代码提供元数据。作为元数据、注解不直接影响代码执行,但也有一些类型的注解实际上用于这一目的。
其可以用于提供信息给编译器,在编译阶段时给软件提供信息进行相关的处理,在运行时处理响应代码,做对应操作。
(2)元注解
即注解的注解,在注解中使用,实现想要的功能。
- @Retention: 表示注解存在阶段是保留在源码,还是在字节码(类加载)或者运行期(JVM中运
行)。 - @Target:表示注解作用的范围。
- @Documented:将注解中的元素包含到 Javadoc 中去。
- @Inherited:一个被@Inherited注解了的注解修饰了一个父类,如果他的子类没有被其他注解修
饰,则它的子类也继承了父类的注解。 - @Repeatable:被这个元注解修饰的注解可以同时作用一个对象多次,但是每次作用注解又可以代
表不同的含义。