Fork me on GitHub

Java常见面试题

一、Java基础部分

1.基本数据类型以及字节数

四类八种

整型   -- 默认 是 int
    byte  字节   1字节       [-128,127]
    short   短整型   2字节
    int    整型   4字节
    long  长整型  8字节      数字后需要添加l或者L
浮点型  -- 默认 是 double 无限接近于一个数
    float  单精度   4字节      数字后需要添加f或者F
    double  双精度   8字节
字符型  -- 默认 是 '\u0000'
    char  字符型   2字节   放在单引号中,只有一个字符 'a'
布尔型  -- 默认 是  false
    boolean  1字节   true/false

2.标识符命名规则

标识符的含义:
是指在程序中,我们自己定义的内容,譬如,类的名字,方法名称以及变量名称等等,都是标识符。
命名规则:(硬性要求)
标识符可以包含英文字母,0-9的数字,$以及_
标识符不能以数字开头
标识符不是关键字
命名规范:(非硬性要求)
类名规范:首字符大写,后面每个单词首字母大写(大驼峰式)。
变量名规范:首字母小写,后面每个单词首字母大写(小驼峰式)。
方法名规范:同变量名。

点击进入《Java命名规范》

3.方法重写和重载的区别

  • 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
  • 重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
  • 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。
  • 在面向对象原则里,重写意味着可以重写任何现有方法。

重写(Override)方法的重写规则

  • 参数列表必须完全与被重写方法的相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个方法,则不能重写这个方法。
  • 当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

重载(Overload)

  • 重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
  • 每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
  • 最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

总结

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

4.面向对象的特性,以及你是怎么理解的?(面向对象三大特性,五大基本原则)

三大特性是:封装,继承,多态
五大基本原则

  • 单一职责原则SRP(Single Responsibility Principle)
  • 开放封闭原则OCP(Open-Close Principle)
  • 替换原则(the Liskov Substitution Principle LSP)
  • 依赖原则(the Dependency Inversion Principle DIP)
  • 接口分离原则(the Interface Segregation Principle ISP)

点击进入《Java基础:面向对象三大特征、五大原则》
点击进入《面向对象三大基本特性

5.访问修饰符 public、private、protected、以及不写时的区别

注:
不写时默认为friendly(default),默认为包内使用。

6.子类继承父类,静态代码块、构造代码块、构造函数的执行顺序

  • 1、实现父类公共的静态属性或静态的块级代码
  • 2、实现本身的公共的静态属性
    —————————-静态初始化
  • 3、实现父类普通成员初始化(包括构造代码块)
  • 4、执行父类的构造方法
    —————————-父类初始化
  • 5、实现子类普通成员初始化(包括构造代码块)
  • 6、执行本身的构造函数
    —————————-子类初始化
  • 7、静态方法不执行
    补充:
    1.当父类有多个构造函数,若子类用super(参数信息)指明调用父类有参数的构造函数,则不执行无参数的构造方法
    2.静态代码块的定义是在类被加载进内存中的方法区的时候调用,而加载类到内存中只需要执行一次即可,所以静态代码块也是只执行一次
    static代码块只在类加载时执行,类是用类加载器来读取的,类加载器是带有一个缓存区的,它会把读取到的类缓存起来,
    所以在一次虚拟机运行期间,一个类只会被加载一次,这样的话静态代码块只会运行一次

7.& 和 && 的区别

&和&&都可以用作逻辑与的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。

&&还具有短路的功能,即如果第一个表达式为false,则不再计算第二个表达式。

&还可以用作按位与的运算符,两个表达式的值按二进制位展开,对应的位(bit)按值进行“与”运算,结果保留在该位上。

8.什么是抽象类

  • 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

  • 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

  • 在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

9.一个抽象类没有抽象方法,可不可以定义为抽象类?如果可以,有何意义?

可以定义成抽象类。主要目的是不让外界创建对象。要想访问它只能通过他的子类去使用。
这种场景更多的出现在各种工具类中,如果它的所有方法都是静态的,那么把它定义为抽象的,会从机制上防止实例化。

10.abstract不能和哪些关键字共存,为什么?

  • final。冲突。因为被final修饰的类不能有子类。而被abstract修饰的类一定是一个父类。
  • private。冲突。抽象类中私有的抽象方法不能被子类重写。
  • static。无意义。如果static可以修饰抽象方法,那就可以直接通过类名调用方法,但是抽象方法根本没有方法体,这样抽象方法运行就没意义了。

11.抽象类和接口的区别

  • 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。
  • 一个类只能继承一个直接父类,这个父类可以是具体的类也可是抽象类;但是一个类可以实现多个接口。
  • 实现 抽象类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现。
  • 抽象类可以有构造器,而接口不能有构造器
  • 抽象方法可以有public、protected和default这些修饰符,接口方法默认修饰符是public。你不可以使用其它修饰符。
  • 抽象方法比接口速度要快,接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
  • 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

12.什么是匿名内部类

  • 匿名内部类也就是没有名字的内部类
  • 正因为没有名字,所以匿名内部类只能使用一次,它通常用来简化代码编写
  • 但使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口
1
2
3
new 类名或者接口名() {
重写方法();
};

本质:其实是继承该类或者实现接口的子类匿名对象

这也就是下例中,可以直接使用
new Inner() {}.show(); 的原因 == 子类对象.show();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
interface Inner {
public abstract void show();
}
class Outer {
public void method(){
new Inner() {
public void show() {
System.out.println("HelloWorld");
}
}.show();
}
}
class Test {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}

13.==号和equals方法的区别

== 的作用:

  • 基本类型:比较的就是值是否相同
  • 引用类型:比较的就是地址值是否相同
    equals 的作用:
  • 引用类型:默认情况下,比较的是地址值。
    注:不过,我们可以根据情况自己重写该方法。一般重写都是自动生成,比较对象的成员变量值是否相同。

14.String,StringBuffer,StringBuilder的区别

String 字符串常量
StringBuffer 字符串变量(线程安全)
StringBuilder 字符串变量(非线程安全)

  • StringBuffer、StringBuilder和String一样,也用来代表字符串。String类是不可变类,任何对String的改变都 会引发新的String对象的生成;StringBuffer则是可变类,任何对它所指代的字符串的改变都不会产生新的对象。

  • 先说一下集合的故事,HashTable是线程安全的,很多方法都是synchronized方法,而HashMap不是线程安全的,但其在单线程程序中的性能比HashTable要高。StringBuffer和StringBuilder类的区别也是如此,他们的原理和操作基本相同,区别在于StringBufferd支持并发操作,线性安全的,适 合多线程中使用。StringBuilder不支持并发操作,线性不安全的,不适合多线程中使用。新引入的StringBuilder类不是线程安全的,但其在单线程中的性能比StringBuffer高。

15.为什么会有基本类型包装类

基本类型的优势:数据存储相对简单,运算效率比较高。
包装类的优势:自带方法丰富,集合的元素必须是对象类型,体现了Java一切皆是对象的思想。

我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

另外,当需要往ArrayList,HashMap中放东西时,像int,double这种基本类型是放不进去的,因为容器都是装object的,这是就需要这些基本类型的包装器类了。

点击进入《java中有了基本类型为什么还要有包装类型》

16.自动装箱/拆箱

当上方表格中列出的基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱。

  • 进行 = 赋值操作(装箱或拆箱)
  • 进行+,-,*,/混合运算 (拆箱)
  • 进行>,<,==比较运算(拆箱)
  • 调用equals进行比较(装箱)
  • ArrayList,HashMap等集合类 添加基础类型数据时(装箱)

点击进入《5分钟彻底理解-Java自动装箱、拆箱》

17.数组和集合的区别,分别什么时候用

  • 数组声明了它容纳的元素的类型,而集合不声明。
  • 数组是静态的,一个数组实例具有固定的大小,一旦创建了就无法改变容量了。而集合是可以动态扩展容量,可以根据需要动态改变大小,集合提供更多的成员方法,能满足更多的需求。
  • 数组是大小固定的,一旦创建无法扩容;集合大小不固定。
  • 数组的存放的类型只能是一种,集合存放的类型可以不是一种(不加泛型时添加的类型是Object)。
  • 数组是java语言中内置的数据类型,是线性排列的,执行效率或者类型检查,都是最快的。ArrayList就是基于数组创建的容器类。
  • 若程序时不知道究竟需要多少对象,需要在空间不足时自动扩增容量,则需要使用容器类库,array不适用。

使用相应的toArray()和Arrays.asList()方法可以相互转换。

18.Iterator和ListIterator的区别

Iterator迭代器包含的方法有:

  • hasNext():如果迭代器指向位置后面还有元素,则返回 true,否则返回false
  • next():返回集合中Iterator指向位置后面的元素
  • remove():删除集合中Iterator指向位置后面的元素

ListIterator迭代器包含的方法有:

  • add(E e): 将指定的元素插入列表,插入位置为迭代器当前位置之前
  • hasNext():以正向遍历列表时,如果列表迭代器后面还有元素,则返回 true,否则返回false
  • hasPrevious():如果以逆向遍历列表,列表迭代器前面还有元素,则返回 true,否则返回false
  • next():返回列表中ListIterator指向位置后面的元素
  • nextIndex():返回列表中ListIterator所需位置后面元素的索引
  • previous():返回列表中ListIterator指向位置前面的元素
  • previousIndex():返回列表中ListIterator所需位置前面元素的索引
  • remove():从列表中删除next()或previous()返回的最后一个元素(有点拗口,意思就是对迭代器使用hasNext()方法时,删除ListIterator指向位置后面的元素;当对迭代器使用hasPrevious()方法时,删除ListIterator指向位置前面的元素)
  • set(E e):从列表中将next()或previous()返回的最后一个元素返回的最后一个元素更改为指定元素e

不同点

1.使用范围不同,Iterator可以应用于所有的集合,Set、List和Map和这些集合的子类型。而ListIterator只能用于List及其子类型。

2.ListIterator有add方法,可以向List中添加对象,而Iterator不能。

3.ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator不可以。

4.ListIterator可以定位当前索引的位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。

5.都可实现删除操作,但是ListIterator可以实现对象的修改,set()方法可以实现。Iterator仅能遍历,不能修改。

19.Java集合框架的基础接口有哪些?

总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好。

java.util.Collection [I]

  • java.util.List [I]

    • java.util.ArrayList [C]
    • java.util.LinkedList [C]
    • java.util.Vector [C]
      • java.util.Stack [C]
  • java.util.Set [I]

    • java.util.HashSet [C]
    • java.util.SortedSet [I]
      • java.util.TreeSet [C]

java.util.Map [I]

  • java.util.SortedMap [I]
    • java.util.TreeMap [C]
  • java.util.Hashtable [C]
  • java.util.HashMap [C]
    • java.util.LinkedHashMap [C]
  • java.util.WeakHashMap [C]

20.遍历一个List有哪些不同的方式?

  • 普通for循环。
  • 增强for循环(foreach)
  • 迭代器(Iterator)

21.ArrayList、Vector、LinkedList的区别

  • ArrayList是一个可以处理变长数组的类型,可以存放任意类型的对象。ArrayList的所有方法都是默认在单一线程下进行的,因此ArrayList不具有线程安全性。

  • LinkedList可以看做为一个双向链表,LinkedList也是线程不安全的,在LinkedList的内部实现中,并不是用普通的数组来存放数据的,而是使用结点< node >来存放数据的,有一个指向链表头的结点first和一个指向链表尾的结点last。LinkedList的插入方法的效率要高于ArrayList,但是查询的效率要低一点。

  • Vector也是一个类似于ArrayList的可变长度的数组类型,它的内部也是使用数组来存放数据对象的。值得注意的是Vector与ArrayList唯一的区别是,Vector是线程安全的。在扩展容量的时候,Vector是扩展为原来的2倍,而ArrayList是扩展为原来的1.5倍。

线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。

线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。

22.栈和队列数据结构

栈 (Stack)是一种后进先出(last in first off,LIFO)的数据结构。
而队列(Queue)则是一种先进先出 (fisrt in first out,FIFO)的结构。
如下图:

23.泛型好处

  • 把运行时期的错误转移到编译期。
  • 消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
  • 类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。
  • 潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改。所有工作都在编译器中完成,编译器生成类似于没有泛型(和强制类型转换)时所写的代码,只是更能确保类型安全而已。

24.集合的三种迭代的能否删除元素

  • 普通for循环 可以删除元素但是每删除一个元素下标要–防止漏删。因为当元素被删除时,后面的元素会向前靠也就是下标会改变。
  • 增强for循环(foreach) 不能删
  • 迭代器 可以删除,只能使用迭代器的remove方法来删除

25.HashSet原理

  • 我们使用Set集合都是需要去掉重复元素的, 如果在存储的时候逐个equals()比较, 效率较低,哈希算法提高了去重复的效率, 降低了使用equals()方法的次数
  • 当HashSet调用add()方法存储对象的时候, 先调用对象的hashCode()方法得到一个哈希值, 然后在集合中查找是否有哈希值相同的对象
  • 如果没有哈希值相同的对象就直接存入集合
  • 如果有哈希值相同的对象, 就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入, true则不存

注:将自定义类的对象存入HashSet去重复,类中必须重写hashCode()和equals()方法。

HashSet实现Set接口,由哈希表(实际上是一个HashMap实例)支持。它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。此类允许使用null元素。

HashSet中不允许有重复元素,这是因为HashSet是基于HashMap实现的,HashSet中的元素都存放在HashMap的key上面,而value中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet跟HashMap一样,都是一个存放链表的数组。

点击进入《【数据结构】HashSet原理及实现学习总结》

26.TreeSet原理

  • TreeSet是基于TreeMap实现的,同样的只是用key及其操作,然后把value置为dummy的object。
  • TreeSet是一个有序集合,TreeSet中的元素将按照升序排列,缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。我们可以在构造TreeSet对象时,传递实现Comparator接口的比较器对象实例,
    例如:
    SetsetUser2 = new TreeSet<>(new User2());

27.HashMap和Hashtable的区别

HashMap 不是线程安全的。HashMap 是 map 接口的实现类,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap 允许 null key 和 null value,而 HashTable 不允许。

HashTable 是线程安全 Collection。
HashMap 是 HashTable 的轻量级实现,他们都完成了Map 接口,主要区别在于 HashMap 允许 null key 和 null value,由于非线程安全,效率上可能高于 Hashtable。

  • HashMap允许将 null 作为一个 entry 的 key 或者 value,而 Hashtable 不允许。
  • HashMap 把 Hashtable 的 contains 方法去掉了,改成 containsValue 和 containsKey。因为 contains 方法容易让人引起误解。
  • HashTable 继承自 Dictionary 类,而 HashMap 是 Java1.2 引进的 Map interface 的一个实现。
  • HashTable 的方法是 Synchronize 的,而 HashMap 不是,在多个线程访问 Hashtable 时,不需要自己为它的方法实现同步,而 HashMap 就必须为之提供外同步。
  • Hashtable 和 HashMap 采用的 hash/rehash 算法都大概一样,所以性能不会有很大的差异。

28.throws和throw的区别

  • Throw用于方法内部,Throws用于方法声明上。
  • Throw后跟异常对象,Throws后跟异常类型。
  • Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型。

29.final,finally和finalize的区别

  • final 用于申明属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
  • finally 是异常处理语句结构中,表示总是执行的部分。
  • finallize 表示是object类一个方法,在垃圾回收机制中执行的时候会被调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了,finalize()方法。

30.如果catch里面有return语句,请问finally的代码还会执行吗?如果会,请问是在return前还是return后?

  • 不管有没有出现异常,finally块中代码都会执行;
  • 当try和catch中有return时,finally仍然会执行;
  • finally中最好不要包含return,否则程序会提前退出,返回值不是try或catch中保存的返回值。
  • finally块的语句在try或catch中的return语句执行之后返回之前执行,且finally里的修改语句可能影响也可能不影响try或catch中 return已经确定的返回值,若finally里也有return语句则覆盖try或catch中的return语句直接返回。

点击进入《Java finally语句到底是在return之前还是之后执行?》
点击进入《有return的情况下try catch finally的执行顺序(最有说服力的总结)》

31.是否可以继承String

不可以,因为String类有final修饰符,而final修饰的类是不能被继承的,实现细节不允许改变。

32.break 、continue和return的区别

  • break只跳出当前for循环
  • continue是终止当前循环语句的执行,继续下一条循环语句。
  • return是结束当前方法的执行

33.异常分为哪些种类

  • Error类代表了编译和系统的错误,不允许捕获;
  • Exception类代表了标准Java库方法所激发的异常。
    • Exception类还包含运行异常类Runtime_Exception和非运行异常类Non_RuntimeException这两个直接的子类。

34.5个常见的RuntimeException

NullPointerException - 空指针引用异常
ClassCastException - 类型强制转换异常。
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
IndexOutOfBoundsException 下标越界异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException 不支持的操作异常
java.lang.StackOverflowError堆栈溢出错误。当一个应用递归调用的层次太深而导致堆栈溢出时抛出该错误
java.lang.ThreadDeath线程结束。当调用Thread类的stop方法时抛出该错误,用于指示线程结束。
java.lang.ArithmeticException “数学运算异常”,比如程序中出现了除以零这样的运算就会出这样的异常。

35.switch是否可以作用在byte上?是否可以作用在long上?是否可以作用在String上?

  • switch可作用于char byte short int
  • switch可作用于char byte short int对应的包装类
  • switch不可作用于long double float boolean,包括他们的包装类
  • switch中可以是字符串类型,String(jdk1.7之后才可以作用在string上)
  • switch中可以是枚举类型

36.String是基本数据类型么

基本数据类型和引用类型的区别主要在于基本数据类型是分配在栈上的,而引用类型是分配在堆上的

String不是基本数据类型,而是一个类(class),是Java编程语言中的字符串。String对象是char的有序集合,并且该值是不可变的。因为java.lang.String类是final类型的,因此不可以继承这个类、不能修改这个类。

Java中的String是正宗的引用类型,但是,一定条件下,String会表现出一定的值特性。

37.short s = 1 ; s = s + 1;有什么错? s += 1; 有什么错?

对于short s1 = 1; s1 = s1 + 1; 由于s1+1运算时会自动提升表达式的类型(默认为int),所以结果是int型,再赋值给short类型s1时,编译器将报告需要强制转换类型的错误。
对于short s1 = 1; s1 += 1;由于 += 是java语言规定的运算符,java编译器会对它进行特殊处理,因此可以正确编译。

隐式转换

  • 特征:从小到大,可以隐式转换,数据类型将自动提升。
    byte,short,char –>int –>long –>float –>double
    注意:long是8个字节,float是4个字节。
    long是整数,float是浮点型,整数和浮点数的存储规则不一样,记住一点long的范围是小于float的。
    例 :
    byte a=10;
    int b=a;
    当编译intb=a 时, a隐式转换为int类型。
  • 在使用 =,+=,-=,*=,/= , %= 时隐含有数据类型的转换。

38.String类常用的方法有哪些?

  • int length() 返回此字符串的长度。
  • charAt(int index) 返回指定索引处的 char 值。
  • int compareTo(Object o) 把这个字符串和另一个对象比较。
  • String concat(String str) 将指定字符串连接到此字符串的结尾。
  • int indexOf(String str) 返回指定子字符串在此字符串中第一次出现处的索引。

点击进入《Java String 类》

39.Integer a = 200; Integer b = 200 ; System.out.println(a == b);执行结果是true还是false?

在Object类中中,equals()和”==”是等价的,但是在大部分的包装类中,都重写了equals()方法,所以两者还是有区别的。总的来说”==”是一个关系运算符,如果比较的两端都为原生数据类型,则表示判断两者的值是否相等,如果比较的两端都为引用类型,则比较两者所指向对象的地址是否相同;对于equals()方法,如果这个对象所在的类重写了equals方法,则按照重写的方法进行比较,如果没有,则比较两者所指向对象的地址是否相同,其实就是使用”==”进行比较。

在-128-127之间,a和b都没有new成一个新的对象,可以直接把值进行比较,如果不在这个范围,则new一个新的Integer对象并返回。查看Integer类的源码可以发现,这个数组里面缓存了基本类型-128-127之间的Integer对象。但是由于是两个new出来的对象(引用类型)做比较,所以==肯定返回的false。

40.字节流如何转成字符流?

将字节流转换成字符流的桥梁——InputStreamReader;

  • InputStreamReader(InputStream in) :创建一个使用默认字符集的 InputStreamReader。传入的对象是InputStream类型,而自己本身是Reader的子类。

将字符流转换成字节流的桥梁——OutputStreamWriter。

  • OutputStreamWriter(OutputStream out) :创建使用默认字符编码的 OutputStreamWriter。传入的对象是OutputStream类型,而自己本身是Writer的子类。

点击进入《Java 字节流 字符流 转换流》

41.java中有几种类型的流

字节流和字符流。字节流继承于InputStream、OutputStream,字符流继承于Reader、Writer。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。关于Java的I/O需要注意的有两点:一是两种对称性(输入和输出的对称性,字节和字符的对称性);二是两种设计模式(适配器模式和装潢模式)。

42.字节流和字符流的区别

Java中的字节流处理的最基本单位为单个字节,它通常用来处理二进制数据。Java中最基本的两个字节流类是InputStream和OutputStream,它们分别代表了组基本的输入字节流和输出字节流。InputStream类与OutputStream类均为抽象类,我们在实际使用中通常使用Java类库中提供的它们的一系列子类。

Java中的字符流处理的最基本的单元是Unicode码元(大小2字节),它通常用来处理文本数据。所谓Unicode码元,也就是一个Unicode代码单元,范围是0x0000~0xFFFF。在以上范围内的每个数字都与一个字符相对应,Java中的String类型默认就把字符以Unicode规则编码而后存储在内存中。然而与存储在内存中不同,存储在磁盘上的数据通常有着各种各样的编码方式。使用不同的编码方式,相同的字符会有不同的二进制表示。实际上字符流是这样工作的:

  • 输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中;
  • 输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列从)从而可以存在内存中。

点击进入《理解Java中字符流与字节流的区别》

43.如何跳出多重嵌套循环,继续执行后面的代码?

在java中,使用break可以跳出循环,默认情况下是跳出最里层的循环,假如我们要跳出多层循环怎么办呢,Java替我们已经做好了这一点,就是用 循环标签 :即是对某个循环定义一个名字,然后在 break 后面加上这个名字,当符合 break 条件时,程序就会跳到规定的循环那。

1
2
3
4
5
6
7
8
9
10
11
12
13

public static void main(String[] args){
lableB:
for(int i=0;i<3;i++){
lableA:
for(int j=0;j<3;j++){
System.out.println(j);
if(j==1){
break lableB;
}
}
}
System.out.println("over!");

注:
标签名的命名方法是:java命名规则 和 半角冒号 比如: lableA:
PS:lableB标签的定义需要在使用break lableB语句之前定义。
break只跳出当前for循环
return是结束当前方法的执行
continue是终止当前循环语句的执行,继续下一条循环语句。

44.异常的父类

Throwable是所有异常类的根类,也就是说所有异常类都继承于它。

45.线程的生命周期

点击进入《线程生命周期》

46.线程的实现方式有哪些

  • 方法1: extends Thread ,重写run方法
  • 方法2: implements Runnable ,实现 run方法
  • 方法3:implements Callable,实现call方法

47.线程怎么通信

  • 利用共享对象实现通信
    • 忙等(busy waiting)
    • wait(), notify() and notifyAll()
    • 信号丢失(Missed Signals)
    • 虚假唤醒(Spurious Wakeups)
    • 多个线程等待相同的信号
    • 不要对String对象或者全局对象调用wait方法

线程通信的目的就是让线程间具有互相发送信号通信的能力。
而且,线程通信可以实现,一个线程可以等待来自其他线程的信号。举个例子,一个线程B可能正在等待来自线程A的信号,这个信号告诉线程B数据已经处理好了。

点击进入《Java线程通信(Thread Signaling)》

48.线程中wait和sleep方法的区别

wait()和sleep()的关键的区别在于,wait()是用于线程间通信的,而sleep()是用于短时间暂停当前线程。更加明显的一个区别在于,当一个线程调用wait()方法的时候,会释放它锁持有的对象的管程(monitors)和锁,但是调用sleep()方法的时候,不会释放他所持有的管程。

:在多线程访问共享资源的时候,经常会带来可见性和原子性的安全问题。为了解决这类线程安全的问题,Java提供了同步机制、互斥锁机制,这个机制保证了在同一时刻只有一个线程能访问共享资源。这个机制的保障来源于监视锁Monitor,每个对象都拥有自己的监视锁Monitor。

点击进入《Java中Wait、Sleep和Yield方法的区别》
点击进入《深入理解多线程(四)—— Moniter的实现原理》

49.什么是线程池?如何使用?

java.util.concurrent.Executors提供了一个 java.util.concurrent.Executor接口的实现用于创建线程池。程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

点击进入《如何优雅的使用和理解线程池》

50.常用的线程池有哪些?

  • newFixedThreadPool
    • 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
    • 通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
    • 适用:执行长期的任务,性能好很多
  • newSingleThreadExecutor
    • 底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
    • 通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
    • 适用:一个任务一个任务执行的场景

点击进入《Java 四种线程池的用法分析》
点击进入《Java线程池种类、区别和适用场景》

51.启动线程是用run()还是用start()?

启动线程是用start()

  • start()方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码:
    通过调用Thread类的start()方法来启动一个线程,
    这时此线程是处于就绪状态,
    并没有运行。
    然后通过此Thread类调用方法run()来完成其运行操作的,
    这里方法run()称为线程体,
    它包含了要执行的这个线程的内容,
    Run方法运行结束,
    此线程终止,
    而CPU再运行其它线程。

  • run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码:
    而如果直接用Run方法,
    这只是调用一个方法而已,
    程序中依然只有主线程–这一个线程,
    其程序执行路径还是只有一条,
    这样就没有达到写线程的目的。

注:
Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

52.线程和进程的区别

  • 地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
  • 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。
  • 线程的改变只代表了 CPU 执行过程的改变,而没有发生进程所拥有的资源变化。
  • 计算机内的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
  • 进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
  • 子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。

注:
线程:一个服务器通常需要接收大量且不确定数量用户的并发请求,为每一个请求都创建一个进程显然是行不通的,——无论是从系统资源开销方面或是响应用户请求的效率方面来看。因此,操作系统中线程的概念便被引进了。线程,是进程的一部分。线程有时又被称为轻权进程或轻量级进程,也是 CPU 调度的一个基本单位。线程之间共用一个进程的内存空存空间。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。(线程之间共用一个进程的内存空存空间)在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度。

53.并发和并行的区别是什么?

  • 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
  • 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
本文结束啦 感谢您阅读
路漫漫其修远兮 吾将上下而求索