Java面试题汇总

1. JDK 和 JRE 有什么区别?

  • JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行环境。
  • JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环境。

  具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装 JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。

 

2. 什么是拆箱/装箱?

    装箱就是自动将基本数据类型转换为包装器类型(int-->Integer);调用方法:Integer的
  valueOf(int) 方法
  拆箱就是自动将包装器类型转换为基本数据类型(Integer-->int)。调用方法:Integer的intValue方 法
  在Java SE5之前,如果要生成一个数值为10的Integer对象,必须这样进行:

Integer i = new Integer(10);

  而在从Java SE5开始就提供了自动装箱的特性,如果要生成一个数值为10的Integer对象,只需要这
样就可以了:

Integer i = 10;

分析以下代码输出结果:

public class Main {
public static void main(String[] args) {
  Integer i1 = 100;
  Integer i2 = 100;
  Integer i3 = 200;
  Integer i4 = 200;
  System.out.println(i1==i2);
  System.out.println(i3==i4);
  }
}

结果

 第二个为false是因为当Integer大于默认的[-128,127]的时候就会new一个新对象,所以每次的引用地址都不同,所以才会出现不等于。

 

3. Java中包装类是什么?有哪些包装类?

  Java中的包装器类有两个主要的目的:

    1、提供一种机制,将基本值“包装”到对象中,从而使基本值能够包含在为对象而保留的操作中,比如添加到Collections 中,或者从带对象返回值的方法中返回。注意,java5增加了自动装箱和拆箱,程序员过去需手工执行的许多包装操作,现在可以由java自动处理了。

 

    2、为基本值提供分类功能。这些功能大多数于各种转换有关:在基本值和String对象间相互转换,在基本值和String对象之间按不同基数转换,如二进制、八进制和十六进制。

 

基本类型 包装类 大小
byte Byte  8个bit
int Integer  32bit
long Long 64bit
float Float  32bit
double Double  64bit
char Character  16bit
boolean Boolean  1bit
short Short  16bit

各个类型大小在官方文档:http://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html

 

4. 如何解决浮点型数据运算出现的误差的问题?

  使用Bigdecimal类进行浮点型数据的运算。

  使用方法:

BigDecimal numA = new BigDecimal(0.006);
BigDecimal numB = new BigDecimal(0.008);
BigDecimal result = numA.add(numB);  // 加法
System.out.println(result);

 

 

5. 什么是隐式转换,什么是显式转换? 

  显示转换就是类型强转,把一个大类型的数据强制赋值给小类型的数据。

  隐式转换就是大范围的变量能够接受小范围的数据。

  隐式转换和显式转换其实就是自动类型转换强制类型转换

  自动类型转换的实现需要同时满足两个条件:  

    1. 两种数据类型彼此兼容。

    2. 目标类型的取值范围大于源数据类型(低级类型数据转换成高级类型数据),例如 byte 类型向 short 类型转换时,由于 short 类型的取值范围较大,会自动将 byte 转换为 short 类型。

  自动转换的规则是从低级类型数据转换成高级类型数据,转换规则如下:

  • 数值型数据的转换:byte→short→int→long→float→double。
  • 字符型转换为整型:char→int。

 

 6. String是最基本的数据类型吗?

   不是。

  Java 中的基本数据类型只有 8 个:byte、short、int、long、float、double、 char、boolean,除了基本类型(primitive type),剩下的都是引用类型(reference type),Java 5 以后引入的枚举类型也算是一种比较特殊的引用类型。

  String是lang包下的一个类,不是基本数据类型,它代表的是字符串。String是一个对象,因为对象的默认值是null,所以String的默认值也是null,但它又是一种特殊的对象,有其它对象没有的一些特性。而基本数据生成出来的变量不是具体对象,跟object没有任何关系。

  new String() 和 new String("")都是申明一个新的空字符串,是空串不是null。

  

 7. Java中数组是不是对象?

   java中的数组是对象,这个可以查看The Java Language Specification SE(4.3.1),而且数组是动态创建的。

  数组有以下特点:

  • 每个数组类型的直接超类都是Object。
  • 每个数组类型都实现了Cloneable和java.io.Serializable接口。
  • 计算数组长度可以使用length属性来查看,arr.length。

 第一个输出结果是[I, [ 表示这是一个数组,而且是一维的,“I”表示数组元素是int类型的。

 第二个输出是数组的长度。

 

顺便说一下,获取字符串长度用字符串类的length()方法。

 

8. 静态变量和实例变量的区别

public class StaticTest {
    private static int staticInt = 2;//静态变量
    private int ints = 2;         //实例变量
}

 

加上static关键字的是静态变量,不加的是实例变量。它们的区别是:

  • 初始化的时机不同:静态变量在类初始化时初始化,实例变量在创建对象时候。
  • 共享性:静态变量不属于某个实例对象,而是属于整个类,而实例变量是每个对象是独立的。
  • 存储的位置不同:静态变量在方法区存储,实例变量在堆上。
  • 生命周期不同:静态变量生命周期和类一样,实例变量是每个对象的实例变量与该对象的生命周期一样。
  • get/set方法:静态变量:get/set也是静态的,如果有局部变量与静态变量重名,用“类名.”。实例变量中get/set也是非静态的,如果有局部变量与静态变量重名,用“this.”。

 

 

9. 拷贝和浅拷贝的区别是什么?

  浅拷贝:
    被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。就是说浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。


  深拷贝:
    被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象。而不再是原有的那些被引用的对象。也就是说深拷贝把要复制的对象所引用的对象都复制了一遍。

 

 10. Java创建对象有几种方式?

  java中提供了以下四种创建对象的方式:
  1. new创建新对象
  2. 通过反射机制
  3. 采用clone机制
  4. 通过序列化机制

 

 11.Java 内部类的作用

  在一个类的里面再定义的类,叫做内部类内部类一共分为四种:实例内部类,静态内部类,局部内部类,匿名内部类。

  下面代码就是包含一个内部类

public class OuterClass{
        private int age = 18;
        String name = "songguojun";
public class InnerClass{ String name = "song"; public void show(){ System.out.println(Outer.this.name); System.out.println(name); System.out.println(age); } } public Inner getInnerClass(){ return new InnerClass(); } public static void main(String[] args){ Outer o = new OuterClass(); Inner in = o.new InnerClass(); in.show(); } }

   内部类的作用:

  • 提供了更好的封装,把类隐藏在外部类中,这样其他的类不能调到。
  • 内部类可以使用外部类的所有数据,包括private修饰的。

 

 12. Super与this表示什么?

  • Super表示当前类的父类对象,而这个父类指的是离自己最近的一个父类,这个和PHP里parent类似。可以用 super.xxx 来引用父类的成员
  • this表示当前类的对象。

 

 13. Java中的继承是单继承还是多继承
  Java中既有单继承,又有多继承。对于java类来说只能有一个父类,对于接口来说可以同时继承多个接口,类与类之间只可以单继承。

  从Java 8之前要区分“声明多继承”与“实现多继承”。Java是不允许“实现多继承”,简称不允许“多继承”。但是Java支持“声明多继承”,Java的接口的多继承,一个类可以实现多个接口(“继承”了多个接口上的方法声明),而一个接口可以继承多个接口(同样是“继承”了多个接口上的方法声明)。

   Java类的单继承限制,也避免了实现多继承的问题。这是早期Java为了与C++区分开的一个决定。

  至于早期Java为何要做这样的设计取舍,可以看看这篇:James Gosling on Java, May 1999

  在其他的语言中,多继承经常以traits(PHP中就使用), mix-in(Python)等形式出现。

 

 

14. Java 中操作字符串都有哪些类?它们之间有什么区别?

  操作字符串的类有:String、StringBuffer、StringBuilder。

  String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最好不要使用 String。

  StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

String     StringBuffer     StringBuilder
String是final修饰,不可改变,每次操作都会生成新的String对象。String的值是不可变的,所以这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间,容易引起jvm gc。  StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量  可变类,速度更快
不可变     可变    可变
   线程安全     线程不安全
   多线程操作字符串    单线程操作字符串

 

三者之间的继承关系

StringBuffer和StringBuilder都继承自抽象类AbstractStringBuilder

 

 如果要操作少量的数据用 String。

 多线程操作字符串缓冲区下操作大量数据 StringBuffer。

单线程操作字符串缓冲区下操作大量数据 StringBuilder。

 

 15.  Java的四种引用,强弱软虚

  强引用(FinalReference):强引用是平常中使用最多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方式:

String str = new String("str");

  软引用(SoftReference):软引用在程序内存不足时,会被回收,使用方式:

// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

  可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。

  弱引用(WeakReference):弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:

WeakReference<String>wrf= new WeakReference<String>(str);

  虚引用(PhantomReference):虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入ReferenceQueue中。注意哦,其它引用是被JVM回收后才被传入
  ReferenceQueue中的。由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有ReferenceQueue,使用:

PhantomReference<String>prf=newPhantomReference<String>(new String("str"),newReferenceQueue<>());

 

16. String类的常用方法有那些?

  String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象。String类对象创建后不能修改。

  常用方法如下:
  charAt:返回指定索引处的字符,索引范围为从 0 到 length() - 1。
  indexOf():返回指定字符的索引。
  replace():字符串替换。
  trim():去除字符串两端空白。
  split():分割字符串,返回一个分割后的字符串数组。
  getBytes():返回字符串的byte类型数组。
  length():返回字符串长度。
  toLowerCase():将字符串转成小写字母。
  toUpperCase():将字符串转成大写字符。
  substring():截取字符串。
  format():格式化字符串。
  equals():字符串比较。

 

 

17. 实例化数组后,能不能改变数组长度?

  不能,数组一旦实例化,就在内存开辟出固定大小的空间,所以它的长度就是固定的。

  数组初始化还分为静态初始化和动态初始化。

 

18. == 和 equals 的区别是什么?

  对于基本类型和引用类型 == 的作用效果是不同的,如下所示:

  • 基本类型:比较的是值是否相同。
  • 引用类型:比较的是引用是否相同。

  如下测试代码

String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); 
System.out.println(x==z);
System.out.println(x.equals(y));
System.out.println(x.equals(z));

  查看结果

  

   因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果都为 true。

 

  区分:

  ==是判断两个变量或实例是不是指向同一个内存空间。

  equals 是判断两个变量或实例所指向的内存空间的值是不是相同。

 

 

19. List,Set,Map的区别

  • List:有序且可重复。 List中有ArrayList,LinkedList等等,这些都是有序的。
  • Set:无序,不可重复,Set检索效率低下,删除和插入效率高,插入和删除不会引起元素位置改变,Set中实现类有Hashset,TreeSet等,这些都是无序的。 

    上面的无序是指添加数据进入某个数据结构的顺序和输出的顺序不一定一样。

  • Map:Map是Java.util包中的另一个接口,它和Collection接口没有关系,是相互独立的,但是都属于集合类的一部分。Map 集合类用于存储元素对(称作“键”和“值”),其中每个键映射到一个值,Map不能包含重复的key,但是可以包含相同的value

 

20. Collection与Collections的区别

  Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

  Collections 是一个工具类。它包含有各种有关集合操作的静态多态方法。此类不能实例化,就像一个工具类,服务于Java的Collection框架。

  在Java中,工具类的命名习惯加s结尾。比如Array类在Java中是最基本的一个存储结构,而Arrays类是Java中提供的一个工具类,是在java.util包中,该类包含了一些方法用来直接操作数组,比如如何直接实现数组的排序、搜索等。

 

21. 泛型是什么?

List list = new ArrayList();
list.add("songguojun");
list.add("male");
list.add(100);
for (int i = 0; i < list.size(); i++) {
     String name = (String) list.get(i); // 1
     System.out.println("name:" + name);
}

上述代码在编译器没有问题,但在运行期间,将会报错。因为List的add方法的参数为object(Java是使用Object来代表任意类型的),如果不知道List类型时,通过强转所获的值,这时就会报错了,错误如下:

 

 对于强制类型转换错误的这种情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

 在JDK  5.0后支持泛型,泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,并且还可以提高代码的重用率。

修改使用泛型List<String>,直接限定了list集合中只能含有String类型的元素,这时候编译器就会提示错误,执行后也会报错。

泛型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

下面是例子

<E extends A>相当于定义了E的上限,即E必须为A的子类。
<E super A>相当于定义了E的下线,即A必须为E的子类。

 泛型的好处:

  • 程序更加健壮,将运行期遇到的问题转移到了编译期,只要编译时期没有警告,那么运行时期就不会出现ClassCastException异常
  • 消除强制类型转换
    消除强制类型转换。
  • 提高程序运行性能和效率。

 

 

22.Java 容器都有哪些?

   Java 容器分为 Collection 和 Map 两大类,其下又有很多子类,如下所示:

  • Collection
  • List
    • ArrayList
    • LinkedList
    • Vector
    • Stack
  • Set
    • HashSet
    • LinkedHashSet
    • TreeSet
  • Map
  • HashMap
    • LinkedHashMap
  • TreeMap
  • ConcurrentHashMap
  • Hashtable

 

 

23. ArrayList 和 LinkedList 的区别是什么?

  ArrayList 继承了 AbstractList ,并实现了 List 接口。

  

  • 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是链表的数据结构实现。
  • 随机访问和修改效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为LinkedList是线性的数据存储方式,所以需要移动指针从前往后依次查找,复杂度是O(n)。修改LinkedList也比ArrayList效率高,因为只需要修改节点的值而不需要像ArrayList将修改值后面的所有值进行移动。
  • 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。

  综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。

  以下情况使用 ArrayList :

  • 需要频繁读取集合中的元素时。
  • 只需要在列表末尾进行添加和删除元素操作。

  以下情况使用 LinkedList :

  • 你需要通过循环迭代来访问列表中的某些元素。
  • 需要频繁插入和删除操作元素的操作。

 

24. HashMap 和 Hashtable 有什么区别?

  HashMap和Hashtable都继承了Map接口,但是HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。

  • 存储:HashMap是允许空值,而 Hashtable 不允许,Hashtable既不支持Null key也不支持Null value,而在HashMap中,null可以作为键,这样的键只有一个
  • 线程安全:Hashtable 是线程安全的,它的每个方法中都加入了Synchronize方法。在多线程并发的环境下,可以直接使用Hashtable。而 HashMap 是非线程安全的。虽然HashMap不是线程安全的,但是它的效率会比Hashtable要好很多
  • 推荐使用:在 Hashtable 的类注释可以看到,Hashtable 是保留类不建议使用,推荐在单线程环境下使用 HashMap 替代,如果需要多线程使用concurrent并发包下 ConcurrentHashMap 替代。

 

25. 如何遍历HashMap

  1. 使用 for-each 来迭代 HashMap 中的元素。

// 创建 HashMap 城市对象 citys
HashMap<Integer, String> citys = new HashMap<Integer, String>();
// 添加键值对
citys.put(1, "北京");
citys.put(2, "上海");
citys.put(3, "深圳");
citys.put(4, "广州");
// 输出 key 和 value   keySet()获取key集合对象
for (Integer i : citys.keySet()) {
     System.out.println("key: " + i + " value: " + citys.get(i));
}
// 返回所有 value 值
for(String value: citys.values()) {
    // 输出每一个value
    System.out.print(value + ", ");
}

输出结果

   2. 使用Iteator遍历

// 创建 HashMap 城市对象 citys
HashMap<Integer, String> citys = new HashMap<Integer, String>();
// 添加键值对
citys.put(1, "北京");
citys.put(2, "上海");
citys.put(3, "深圳");
citys.put(4, "广州");
// 使用Iteator遍历hashMap keyset    
Iterator<Integer> iterator = citys.keySet().iterator();
while (iterator.hasNext()) {
    Integer key = iterator.next();
    System.out.println(key);
    System.out.println(citys.get(key));
}

 

posted @ 2021-05-24 15:50  songguojun  阅读(682)  评论(0编辑  收藏  举报