Java 基础面试题总结
1. 8个数据类型
byte、 short、 int、long、float、 double、 char、 boolean
2. 字符集和字符集编码的区别
字符集:多个字符的集合,例如ASCII。
编码:每个字符都有对应的二进制地址,编码就是按照一定的规则去读取这些二进制数,例如UTF-8。
- 编码是依赖于字符集的,就像代码中的接口实现依赖于接口一样;
- 一个字符集可以有多个编码实现,就像一个接口可以有多个实现类一样。
3. short s1=1;si=si+1;对不对
不对,si+1会发生强制数据类型转换变成int,应该写成si=(int)(si+1);。如果写成si++就不会出问题,编译器会自动进行特殊处理。
4. Final修饰词的作用
final可以修饰类,方法,变量(成员变量和局部变量)
(1)Final修饰变量如果修饰的是基本数据类型,那么这个值一经赋值后将无法修改;如果修饰的是引用变量,那么引用不可改变,但是引用中的对象内容是可以改变的;
(2)Final修饰的方法不能被重写;
(3)Final修饰的类不能被继承;
(4)final不能修饰构造方法。
5. Final修饰的好处
(1)可以在多线程环境下保证线程安全;
(2)JVM和java应用会缓存final变量,提高性能;
(3)使用final关键字,JVM会对方法类变量进行优化。
6. 正则表达式及其用途
在编写处理字符串的时候,经常会有查找符合某些复杂规则的字符串的需求。 正则表达式就是记录文本规则的代码。
7. Int和Integer
为了能够将这些基本数据类型当成对象操作,Java为每一个基本数据类型都引入了对应的包装类型。int的包装类型就是integer,从Java5开始引入了自动装箱/拆箱机制,使得二者可以互相转换。
8. 对Java面向对象的理解(包括三大特性)
《Java编程思想》中提到“万物皆为对象”的概念。它将对象视为一种奇特的变量,它除了可以存储数据之外还可以对它自身进行操作。它能够直接反映现实生活中的事物,例如人、车、小鸟等,将其表示为程序中的对象。每个对象都具有各自的状态特征(也可以称为属性)及行为特征(方法),java就是通过对象之间行为的交互来解决问题的。
面向对象就是把构成问题的事物分解成一个个对象,建立对象不是为了实现一个步骤,而是为了描述某个事物在解决问题中的行为。
类是面向对象中的一个很重要的概念,因为类是很多个具有相同属性和行为特征的对象所抽象出来的,对象是类的一个实例。
类具有三个特性:封装、继承和多态。
- 封装:核心思想就是“隐藏细节”、“数据安全”,将对象不需要让外界访问的成员变量和方法私有化,只提供符合开发者意愿的公有方法来访问这些数据和逻辑,保证了数据的安全和程序的稳定。
- 继承:子类可以继承父类的属性和方法,并对其进行拓展。
- 多态:同一种类型的对象执行同一个方法时可以表现出不同的行为特征。通过继承的上下转型、接口的回调以及方法的重写和重载可以实现多态。
9. ==和equals的区别
== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals 默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。
public class Test {
public static void main(String[] args) {
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2);//false
System.out.println(str1.equals(str2));//true
String str3 = "123";
String str4 = "123";
System.out.println(str3 == str4);//true
System.out.println(str3.equals(str4));//true
}
}
对于str1和str2,它们其实创建了两次对象,一次在常量池创建了对象“abc”,所以str1和str2的地址值是不相等的。String不是基本数据类型,且重写了equals()方法所以== 比较的是地址值(引用),equals比较的是数值。
前面的输出结果是false、true,后面的输出结果是true、true。为什么两次==的输出结果不一样呢?这其实涉及到了内存中的常量池,常量池属于方法区的一部分,当运行到创建str3对象时,如果常量池中没有“123”,则在常量池中创建一个"123"对象,运行到str4对象时,由于“123”已经存在常量池,就直接使用,所以str3和str4对象其实是同一个对象,他们的地址引用相同。
10. Hashcode()
hashCode的存在主要是用于查找的快捷性(与单纯使用equals相比),如Hashtable,HashMap,HashSet等,hashCode是用来在散列存储结构中确定对象的存储地址的。
hashCode是jdk根据对象的地址或者字符串或者数字算出来的int类型的数值 。
如果想查找一个集合中是否包含有某个对象,大概的程序代码怎样写呢?
你通常是逐一取出每个元素与要查找的对象进行比较,当发现某个元素与要查找的对象进行equals方法比较的结果相等时,则停止继续查找并返回肯定的信息,否则,返回否定的信息。如果一个集合中有很多个元素,比如有一万个元素,并且没有包含要查找的对象时,则意味着你的程序需要从集合中取出一万个元素进行逐一比较才能得到结论。
哈希算法可以提高从集合中查找元素的效率。这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,可以将哈希码分组(使用不同的hash函数来计算的),每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储在哪个区域,HashSet就是采用哈希算法存取对象的集合,它内部采用对某个数字n进行取余的方式对哈希码进行分组和划分对象的存储区域;Object类中定义了一个hashCode()方法来返回每个Java对象的哈希码,当从HashSet集合中查找某个对象时,Java系统首先调用对象的hashCode()方法获得该对象的哈希码,然后根据哈希吗找到相应的存储区域,最后取得该存储区域内的每个元素与该对象进行equals方法比较。这样就不用遍历集合中的所有元素就可以得到结论,可见,HashSet集合具有很好的对象检索性能。
总结hashCoed 的特性
(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址;
(2)如果两个对象相同, equals方法一定返回true,并且这两个对象的HashCode一定相同;
(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。(两个对象可能有相同的HashCode,比如方法重载)
(4)如果对象的equals方法被重写,那么对象的HashCode也需要重写
解释(4),以hashset为例(set是无序集合,必须保证对象不重复,因此当添加新对象时需要判断是否已存在相同的)。将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。回过来说,在get的时候,集合类也先调key.hashCode()算出数组下标,然后看equals()的结果,如果是true就是找到了,否则就是没找到。
如果更改了equals方法却没有重写HashCode,Object对象中的hashCode方法始终返回的是一个对象的hash地址,而不同对象的这个地址是永远不相等的。所以这时候即使是重写了equals方法,也不会有特定的效果的,因为hashCode方法如果都不想等的话,就不会调用equals方法进行比较了,所以重写equals()就没有意义了。
其次,如果一个类的hashCode()方法没有遵循上述要求,那么,当这个类的两个实例对象用equals()方法比较的结果相等时,他们本来应该无法被同时存储进set集合中,但是,如果将他们存储进HashSet集合中时,由于他们的hashCode()方法的返回值不同(Object中的hashCode方法返回值是永远不同的),第二个对象首先按照哈希码计算可能被放进与第一个对象不同的区域中,这样,它就不可能与第一个对象进行equals方法比较了,也就可能被存储进HashSet集合中了;所以,Object类中的hashCode()方法不能满足对象被存入到HashSet中的要求,因为它的返回值是通过对象的内存地址推算出来的,同一个对象在程序运行期间的任何时候返回的哈希值都是始终不变的,所以,只要是两个不同的实例对象,即使他们的equals方法比较结果相等,他们默认的hashCode方法的返回值是不同的。
(注意:在HashSet中插入同一个元素(hashCode和equals均相等)时,会被舍弃,而在HashMap中插入同一个Key(Value 不同)时,原来的元素会被覆盖。)
10. Java 中操作字符串都有哪些类?它们之间有什么区别?
String(字符串常量)、StringBuffer和StringBuilder(字符串变量)
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。
StringBuffer就是为了解决大量拼接字符串时产生很多中间对象问题而提供的一个类,提供append和add方法,可以将字符串添加到已有序列的末尾或指定位置,它的本质是一个线程安全的可修改的字符序列,把所有修改数据的方法都加上了synchronized。但是保证了线程安全是需要性能的代价的。
在很多情况下我们的字符串拼接操作不需要线程安全,所以一般用StringBuilder。StringBuilder是JDK1.5发布的,它和StringBuffer本质上没什么区别,就是去掉了保证线程安全的那部分,减少了开销。
StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder ,底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。
所以如果我们有大量的字符串拼接,如果能预知大小的话最好在new StringBuffer 或者StringBuilder 的时候设置好capacity,避免多次扩容的开销。扩容要抛弃原有数组,还要进行数组拷贝创建新的数组。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
总结
- 如果要操作少量的数据用String;
- 单线程操作字符串缓冲区下操作大量数据用StringBuilder;
- 多线程操作字符串缓冲区下操作大量数据用StringBuffer。