Java 面试(一) | Java基础部分

文章目录

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.==和equals的区别

最大不同:equals是方法,==是运算符

==

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

equals

  • 本质上就是==,但不能用于基数据本类型变量的比较,如果没有对equals方法重写,则比较的是引用类型变量所指向的对象的地址
  • 大多数类如String、Integer等都重写了equals方法,先根据==判断,如果不等,再根据值判断

3.两个对象equals比较为true,但却可以有不同的hashcode,对吗?

不对,如果两个对象的equals比较为true,那么他们的hashcode应该相同

Java规定

  • 如果两个对象相同(equals方法返回true),那么他们的hashcode一定要相同
  • 如果两个对象的hashcode相同,那么他们不一定相同
  • 虽然可以不按照上述要求做,但是会引起一系列问题,比如对于使用hash存储的系统,如果hashcode频繁冲突会造成存取性能急剧下降

equals方法要满足的特性

  • 自反性(x.equals(x)必须返回true)
  • 对称性(x.equals(y)返回true 时,y.equals(x)也必须返回true)
  • 传递性(x.equals(y)和y.equals(z)都返回true时,x.equals(z)也必须返回true)
  • 一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值)
  • 对于任何非null值的引用x,x.equals(null)必须返回false

如何实现高质量的equals方法

  • 使用==操作符检查"参数是否为这个对象的引用"
  • 使用 instanceof操作符检查"参数是否为正确的类型"
  • 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配
  • 编写完equals方法后,问自己它是否满足对称性、传递性、一致性
  • 重写equals时总是要重写hashCode
  • 不要将equals方法参数中的Object对象替换为其他的类型,在重写时不要忘掉@Override注解

4.String类相关

String类可以被继承吗

String类用final修饰,无法被继承,以下为String类的源代码片段

在这里插入图片描述

String的不可变是如何设计的

  • 什么是不可变

如下图,给一个已有字符串"abcd"第二次赋值成"abcedl",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
在这里插入图片描述

  • String为什么不可变

首先,String中的存储成员是一个final修饰的char[] value数组,因此这个value数组不可变,但这里的不可变是指引用地址不可变,而数组中的成员是可以变的

因此要维护String的不可变,关键是java设计人员在封装String类的时候没有去改变这个value数组中的值,没有暴露value数组中的值给外面,比如将value数组用private修饰,将访问权限降到最低

最后还加了个保险,将String类声明为final,防止被继承,从而被他人破环了所封装的结构
所以String不可变的关键在于底层是如何实现的,而不仅仅是依靠一个final

为什么要把String类定义为不可变类呢?

  • 线程安全:多个线程读同一资源,是不会引发竞态条件的,只有对资源做写操作才有危险,而不可变对象不能写,所以线程安全
  • 支持字符串常量池:用相同的字符串字面量给字符串赋值,它们都指向的是同一块内存地址,这样在大量使用字符串的场景下,可以节省内存空间,提高效率,而之所以可以实现这个特性,String的不可变性是一个基本条件,如果内存里多个对象指向的字符串内容可以任意变化,那么这么做就完全没有意义,因为一个对象改变了这个字符串的值,那么所有指向这个值的对象(的值)都将改变,所以当要改变指向这个字符串的对象时,会重新开辟空间存放值,而不是在原来的内存地址中修改
  • 安全:如果字符串可变,会引发一系列安全问题,大多数字段都是用的字符串来设值,如果字符串可变,那么改变所指向字符串的值,就会引发安全漏洞
  • 因为字符串是不可变的,所以在它创建的时候HashCode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

final除了修饰类,还有哪些用法

  • final修饰的变量,一旦赋值,不可重新赋值
  • final修饰的方法无法被覆盖
  • final修饰的实例变量,必须一创建就手动赋值,或者构造器赋值,不能采用系统默认值
  • final修饰的实例变量一般和static联用,用来声明静态常量,这样的变量只能一开始就赋值,不能借助构造器
  • final不能和abstract联用,因为abstract是希望得到子类的继承或方法的重写
  • 总之,final代表最终的,不可变的

5.&和&&的区别

&和&&

  • &为逻辑与,&&为短路与
  • &和&&的执行结果是完全一样的,但是&&存在短路现象,当&&两边的左边表达式为false时,那么就不用再执行右边的表达式了,因为执不执行最终的结果都是false;而&是不管左边表达式true还是false,都会再执行右边的表达式
  • &运算符还可代表按位与,用于二进制运算,区分逻辑于与按位与是靠两边的操作数类型是boolean型还是整型

|和||

  • |为逻辑或,||为短路或
  • 和上边一样,||存在短路现象,当||左边表达式为true时,右边的表达式不会再执行,因为最终的结果必然会是true
  • |也可代表按位或

6.java中如何跳出多重嵌套循环

在最外层循环前加一个标记如outfor,然后用break outfor,可以跳出多重循环,如下图:
在这里插入图片描述

7.重载和重写的区别?

方法的重载和重写都是实现多态的方式,区别在于前者是实现编译时的多态,而后者实现的是运行时的多态

重载发生在一个类当中,同名的方法如果有不同的参数类型或者不同的参数个数或者不同的参数顺序,那么可以视为重载,不能依靠返回类型判断是否为重载

重写发生在子类和父类当中,要求子类重写的方法和原方法名称、参数、返回类型完全一致,并且访问权限不能低于父类方法,不能比父类方法声明更多的异常

方法重载规则

  • 方法名相同,参数个数、参数类型、参数类型任意一个或多个不同
  • 与返回类型无关,存在于父类和子类、同类中
  • 可以抛出不同的异常,可以有不同的修饰符

方法重写规则

  • 参数列表、返回类型、方法名必须完全一致
  • 方法访问权限不能低于父类
  • 方法抛出的异常不能比父类更宽泛
  • 构造方法不能重写
  • 声明为final的方法不能重写
  • 声明为static的方法不存在重写
  • 方法重写是实现多态的必要条件

8.为什么不能根据返回类型来区分方法重载

看一下以下代码

public void  testMethod(){
    doSome();
}
public void doSome()){}
public int doSome(){return 1;}

在java语言中,调用一个方法,即使这个方法有返回值,我们也可以不接收这个返回值,例如上面这两个方法doSome(),在testMethod()中调用的时候,java编译器无法区分调用的具体是哪一个方法,所以对于编译器来说,doSome()方法不是重载而是重复,编译器报错。所以区分这两个方法不能依靠方法的返回值类型

9.当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里是值传递还是引用传递?

是值传递,java中的参数传递都为值传递

当参数为基本类型的时候,传递的参数是变量值的拷贝

当参数为引用类型的时候,传递的参数是引用变量所指向地址的拷贝,当这个拷贝传入方法后未改变所指向的地址时,改变这个地址所指向的内存空间中的内容,那么原来的内容也会被改变;当传入方法后改变了所指向的地址后,对内容的修改不会影响到原来的值

10.char型变量中能否存储一个中文汉字,为什么?

可以,java中采用unicode编码,而java中的unicode占两个字节,而char也是占两个字节,unicode字符包含了中文,因此可以存储一个中文汉字

char的范围为0~216-1(即0 ~ 65535)

补充:java虚拟机采用UCS2(通用字符集)标准即UTF-16保存字符,所有的字符在内存中都是2个字节,这样虚拟机处理字符串的截取、长度和判断都非常容易。其他语言如PHP、Python也是,在运行时采用固定长度存储字符。
相对应编译后的class,java规定采用UTF-8保存,因为大部分是英文字符,只有一个字节,可以大量节省存储空间。

11.抽象(abstract)方法是否可同时是静态的?是否可同时是本地方法?是否可同时是synchronized?

都不能

  • 抽象方法需要子类重写,而静态方法无法被重写,只能被子类重新定义
  • 本地方法是由本地代码(如c/c++)实现的方法,而抽象方法是没有实现的
  • synchronized和方法的实现细节有关,抽象方法不涉及实现细节,只有一个方法头

12.抽象类和接口有什么异同?

相同点

  • 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承
  • 可以将抽象类和接口类型作为引用类型
  • 接口和抽象类都包含抽象方法
  • 一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,对于抽象类来说如果没有全部实现,那么该类仍然需要被声明为抽象类

不同点

  • 一个类可以实现多个接口,一个类只能继承一个抽象类;
  • 一个类可以实现多个接,一个类只能继承一个抽象类;
  • 接口中不能定义构造器,抽象类中可以定义构造器;抽象类的构造器不是用来创建对象,而是让其子类调用完成抽象类的初始化操作;
  • 接口中的方法全部都是抽象方法(jdk1.8前),抽象类中可以有抽象方法和具体方法;
  • 接口中的成员全都是public 的,抽象类中的成员可以是 private、默认、protected、public;
  • 接口中定义的成员变量实际上都是常量,抽象类中可以定义成员变量;
  • 接口中不能有静态方法,抽象类中可以包含静态方法;(注意:JDK1.8之后接口中可以有静态方法)

13.静态变量和实例变量的区别?

  • 静态变量是属于类的,在类被加载时就会创建,而且内存中有且只有一个,能被多个对象共享
  • 实例变量是属于对象的,只有创建了对象才能访问到它

14.break和continue的区别?

  • 都是用来控制循环的语句
  • break用于完全跳出所在循环
  • continue用于跳出本次循环,继续下次循环

15.String s = “Hello”;s = s + " world!";这两行代码执行后,原始的 String 对象中的内容变了没有?

没有,因为String类创建的对象是不可变对象,如果改变对象的内容,只会重新开辟内存空间新生成一个对象,将String变量指向这个新对象的地址,而原来的对象依然存在,只是String变量不再指向它而已

对于字符串常量,如果内容相同,Java 认为它们代表同一个 String 对象。而用关键字 new 调用构造器,总是会创建一个新的对象,无论内容是否相同。

至于为什么要把 String 类设计成不可变类,是它的用途决定的。其实不只String,很多 Java 标准类库中的类都是不可变的。在开发一个系统的时候,我们有时候也需要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能会造成性能上的问题。所以 Java 标准类库还提供了一个可变版本,即 StringBuffer和StringBuilder。

16.switch能否作用在byte、String、long上?

可以作用在byte和String上,long不行

在java5前严格意义上讲switch(expression)中的expression只支持int类型,但由于可以自动类型转化,所以可以支持byte、short、char、以及它们的包装类:Byte、Short、Character、Integer,从java5开始引入了枚举enum类型,从java7开始,expression还可以是String,但是long是一直不行的。

在这里插入图片描述

17.数组是否有length()方法?String呢?如何获取集合类型数据的长度?

数组没有length()方法,而是有length属性,String有length方法,获取集合数据类型的长度可以用size()方法

JavaScript中获取字符串的长度用的是length属性,容易和Java混淆

18.Java的基本数据类型有哪些?各占几个字节?

  • 基本数据类型:byte、short、int、long、float、double、char、boolean
  • 所占字节数:1、2、4、8、4、8、2、-

boolean所占字节数一直有多种说法:1bit(实际占的空间)、1byte(计算机中实际存储的空间)、4byte(JVM规范:boolean类型当作int处理,而boolean数组当作byte处理)

19.Stirng是基本数据类型吗?

不是,String由class定义,所以是类,即引用数据类型,底层用char型数组实现

20.short s1=1;s1=s1+1;有错吗?short s1=1;s1+=1;有错吗?

第一种有错

Java中赋值为1中的1类型默认为int,s执行hort s1=1语句时,当数值没有超过short范围时,Java底层会进行强制类型转化:short s1=(short)1,超过了范围需要强转;而执行s1=s1+1时,编译时并不知道s1的值,java底层不会进行类型转化,所以相当于:short s1=(int)(s1)+(int)(1),因为java运算时会将数值自动转化成容量最大的类型,所以最终会报错

第二种正确
s1+=1相当于s1=(short)(s1+1),有隐含的强制类型转换

21.int和Integer有什么区别?

java是一个完全面向对象的语言,但是为了编程的方便还是引入了基本数据类型,为了能将这些基本数据类型当作对象来操作,Java为每一种基本数据类型引入了相应的包装类(wrapper class),int的包装类就是Integer

从Java5开始引入了自动装箱和拆箱机制,使得二者可以相互转换

  • 基本数据类型:byte、short、int、long、float、double、char、boolean
  • 对应的包装类:Byte、Short、Integer、Long、Float、Double、Character、Boolean

22.下面Integer类型的数值比较输出的结果是?(自动装箱相关)

public class Test{
    public static void main(String[] args) {
        Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
        System.out.println(f1 == f2);
        System.out.println(f3 == f4);
    }
}

如果不明白原理很容易认为两个输出要么都是 true 要么都是 false。首先需要注意的是 f1、f2、f3、f4 四个变量都是 Integer 对象引用,所以下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当我们给一个Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,如果看看valueOf的源代码就知道发生了什么。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

IntegerCache 是 Integer 的内部类,其代码如下所示:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(I, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(I, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

简单的说,如果整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的Integer对象

  • 不仅是Integer类,Character、Byte、Short、Long这几个包装类的valueOf方法实现原理类似,都会有缓存器
  • 对于Double、Float,它们在-128~127之间的数是无限的,因此没有缓存器
  • 对于Boolean,它在一开始就定义了并创建好了两个Boolean类型对象,因此后面也不会再新建Boolean对象

所以上面的面试题中f1 == f2的结果是 true,而f3 == f4 的结果是false。

23.String类的常用方法?

在这里插入图片描述

24.String、StringBuffer、StringBuilder的区别?

  • 可变不可变

String:字符串常量,在修改时不会改变自身;若修改,等于重新生成新的字符串对象。

StringBuffer:在修改时会改变对象自身,每次操作都是对 StringBuffer 对象本身进行修改,不是生成新的对象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。

  • 线程是否安全

String:对象定义后不可变,线程安全。

StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操作字符串缓冲区大量数据。

StringBuilder:是线程不安全的,适用于单线程下操作字符串缓冲区大量数据。

  • 共同点

StringBuilder与StringBuffer有公共父类 AbstractStringBuilder(抽象类)。

StringBuilder、StringBuffer 的方法都会调用 AbstractStringBuilder 中的公共方法,如 super.append(…)。只是 StringBuffer 会在方法上加 synchronized 关键字,进行同步。最后,如果程序不是多线程的,那么使用StringBuilder 效率高于 StringBuffer。

24.基本数据类型和字符串之间的转换

字符串转基本数据类型

调用基本数据类型对应的包装类中的方法 parseXXX(String)或valueOf(String)即可返回相应基本类型。

valueOf(String)方法实际是内部调用parseXXX(String)方法

基本类型转字符串

  • 一种方法是将基本数据类型与空字符串(“”)连接(+)即可获得其所对应的字符串
  • 另一种方法是调用 String类中的 valueOf()方法返回相应字符串

Java面试面向对象部分

1.面向对象包括哪些特性?怎么理解的?

  • 封装:把类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问

面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装;我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

  • 继承:继承是从已有类得到继承信息创建新类的过程。

提供继承信息的类被称为父类(超类、基类);得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。

  • 多态:多态性是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。

多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当 A系统访问B系统提供的服务时,B 系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:

  • 方法重写(子类继承父类并重写父类中已有的或抽象的方法);
  • 对象造型(用父类型引用指向子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。
  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

2.访问权限修饰符public、protected、private、不写(默认)时的区别?

访问权限由高到低:

  • public:所有类中都可以访问到
  • protected:当前类、同一包下的类以及该类的子类可以访问到
  • 默认:当前类和同一包下的类可以访问到
  • private:只有当前类可以访问到

在这里插入图片描述

3.java中为什么要用到clone?

在实际编程过程中,我们常常要遇到这种情况:有一个对象 A,在某一时刻 A 中已经包含了一些有效值,此时可能会需要一个和 A 完全相同新对象 B,并且此后对 B 任何改动都不会影响到 A 中的值,也就是说,A 与 B 是两个独立的对象,但 B 的初始值是由 A 对象确定的。在 Java 语言中,用简单的赋值语句是不能满足这种需求的。要满足这种需求虽然有很多途径,但clone()方法是其中最简单,也是最高效的手段。

说到克隆,谈谈深克隆和浅克隆?

  • 浅克隆:创建一个新对象,新对象的属性和原来对象完全相同,对于非基本类型属性,仍指向原有属性所指向的对象的内存地址。
  • 深克隆:创建一个新对象,属性中引用的其他对象也会被克隆,不再指向原有对象地址。

继承至java.lang.Object类的clone()方法是浅克隆,若要实现深克隆需要实现Cloneable接口,并重写clone()方法,但要实现真正的深克隆是很麻烦的,因为类中可能会循环声明各种引用类型变量,这样就会重复的进行对引用类型所在的类重写clone方法,直到没有引用类型变量。

4.new一个对象和clone一个对象过程的区别?

  • new 操作符的本意是分配内存。程序执行到 new 操作符时,首先去看 new 操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
  • clone 在第一步是和 new 相似的,都是分配内存,调用 clone 方法时,分配的内存和原对象(即调用 clone 方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域,填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

5.Java中实现多态的机制是什么?

java中的多态靠的是父类或接口所声明的引用变量可以指向子类或实现类的实例对象,而程序调用的方法是在运行期动态绑定的,实际调用的是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的那个方法。

6.谈谈你对面向对象的理解?

所谓面向对象就是一组数据结构和处理它们的方法组成的,重点“数据”包括对象的特性、状态等静态信息;“方法”也就是行为,包括该对象的对数据的操作、功能等能动信息。把相同行为的对象归纳为类,类是一个抽象的概念,对象是类的具体。简单点说:对象就是类的实例。比如:汽车是一个类,那么宝马就是一个对象。

面向对象的目的:解决软件系统的可扩展性、可维护性、可重用性

面向对象的三大特性:封装、多态和继承:

(1)封装(对应可扩展性):隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。封装是通过访问控制符(public protected private)来实现。一个类就可看成一个封装。

(2)继承(重用性和扩展性):子类继承父类,可以继承父类的方法和属性。可以对父类方向进行覆盖(实现了多态)。但是继承破坏了封装,因为他是对子类开放的,修改父类会导致所有子类的改变,因此继承一定程度上又破坏了系统的可扩展性,只有明确的IS-A关系才能使用。继承要慎用,尽量优先使用组合。

(3)多态(可维护性和可扩展性):接口的不同实现方式即为多态。接口是对行为的抽象,刚才在封装提到,找到变化部分并封装起来,但是封装起来后,怎么适应接下来的变化?这正是接口的作用,接口的主要目的是为不相关的类提供通用的处理服务,我们可以想象一下。比如鸟会飞,但是超人也会飞,通过飞这个接口,我们可以让鸟和超人,都实现这个接口。

面向对象编程(OOP)其实就是一种设计思想,在程序设计过程中把每一部分都尽量当成一个对象来考虑,以实现软件系统的可扩展性,可维护性和可重用性

Java面试异常处理部分

1.final、finally、finalize的区别?

  • final:用于修饰属性、方法、类,分别表示属性不可被更改、方法不可被重写、类不可被继承
  • finally:异常处理语句结构的一部分,表示最终一定会执行
  • finalize:Object 类的一个方法,所以Java对象都有这个方法,当某Java对象没有更多的引用指向的时候,会被垃圾回收器回收,该对象被回收之前,由垃圾回收器来负责调用此方法,通常在该方法中进行回收前的准备工作。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则代表该对象即将“死亡”,但是需要注意的是,我们主动行为上去调用该方法并不会导致该对象“死亡”,这是一个被动的方法(其实就是回调方法),不需要我们调用

补充:回调函数

回调函数是指使用者自己定义一个函数,实现这个函数的程序内容,然后把这个函数(入口地址)作为参数传入别人(或系统)的函数中,由别人(或系统)的函数在运行时来调用的函数。函数是你实现的,但由别人(或系统)的函数在运行时通过参数传递的方式调用,这就是所谓的回调函数。简单来说,就是由别人的函数运行期间来回调你实现的函数

2.Java中的异常分为哪些种类?

按照异常处理的时机分为:

  • 编译时异常(受控异常、CheckedException):Java认为checked异常都是可以被处理的异常,所以Java程序必须显式地处理checked异常。如果程序没有处理checked异常,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善的错误处理的代码根本没有机会执行。
  • 运行时异常(非受控异常、UnCheckedException):运行时异常只有在代码运行时才发生异常,编译的时候不需要try…catch。RuntimeException如:除数是零和数组下标越界等,其产生频繁,处理麻烦,若显式声明或捕获会对程序的可读性和运行效率影响很大。所以系统自动检测并将它们交给缺省的异常处理程序,当然如果你有处理需求,也可以显式地捕获它们。

3.Error和Exception的区别?

Error和Exception都是Throwable类的子类,它们的区别如下:

  • Error:就是程序运行时候抛出的最严重级别的错误了,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等,一般是指与虚拟机相关的问题,如:VirtualMachineError,ThreadDeath。抛出了Error的程序从Java设计的角度来讲,程序基本不可以通过后续代码修复,从而理应终止。当然,从语法上来讲,所有这些都可以被写进catch里面,但是Error因为上述原因,不应该被代码处理。
  • Exception:表示程序可以处理的异常,可以捕获而且能够修复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。
  • Exception又分为受检异常(CheckedException)和未受检异常(UnCheckedException、运行时异常),运行时异常如ArithmeticException,IllegalArgumentException编译能通过,但是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用 try…catch 捕获,要么用throws字句声明抛出,交给它的父类处理,否则编译不会通过。

4.调用下面的方法,得到的返回值是什么?(catch和finally的执行过程问题)

public int getNum(){
    try{
        int a = 1/0;
    }catch(Exception e){
        return 2;
    }finally{
        return 3;
    }
}

结果

3

执行流程分析

代码走到第三行的时候会发生运行时异常(java.lang.ArithmeticException),这是第四行代码不会执行,直接进入catch块,而catch块中是一个return语句,它会造成函数终止,java中的异常机制有个原则:如果在catch中遇到了return或者异常等能使该函数终止的话那么有finally就必须先执行完finally代码块里面的代码然后再返回值。因此代码进入finally块,而里面也是一个return方法,导致方法终止,因此该方法会直接返回3,如果finally块中不是return等方法终止语句,那么最终结果将会是2。

5.说出5个最常见的RuntimeException?

  • java.lang.NullPointerException:空指针异常(调用了未初始化的对象或不存在的对象)
  • java.lang.ClassNotFoundException:找不到执行类异常(类的名称或加载路径错误)
  • java.lang.NumberFormatException:字符串转化为数字异常(字符型数据包含非数字型字符)
  • java.lang.IndexOutOfBoundsException:数组下标越界异常
  • java.lang.IllegalArgumentException:方法传递参数不合法异常
  • java.lang.ClassCastException:类型转换异常
  • java.lang.NoClassDefFoundException:未找到类定义异常
  • SQLException:SQL异常
  • java.lang.InstantiationException:实例化异常
  • java.lang.NoSuchMethodException:方法不存在异常

6.throw和throws的区别?

  • throw:抛出异常对象,即需要new,用在方法体中,由方法体内的语句处理
  • throws:抛出异常类,用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理
  • 当我们在方法中throw 了一RuntimeException或其子类对象,那么在方法头中可以不用加throws 相关异常类型
  • 当我们在方法中throw 了一个非RuntimeException类对象,且没有进行try…catch,那么方法头中必须throws 相关异常类型
  • throw在一个方法中只能抛出一次异常对象,多写了会编译失败,因为多写也没用,只会抛出一个异常,执行throw一定是抛出某种异常
  • thows在一个方法中可以抛出多个异常类,因为这只是告诉调用者这个方法可能会抛出的异常类型,throws后并不一定会发生这种异常

Java面试常用API部分

1.Math.round(11.5)的值是多少?Math.round(-11.5)呢?

分别为12、-11

这个方法是取最接近该参数的数值,且向正无穷方向取舍,可以理解为四舍五入,注意传double返回long类型,传float返回int类型

2.String、StringBuilder、StringBuffer的区别?

Java提供两种类型的字符串:String和StringBuilder/StringBuffer,它们都可以存储字符串

  • String是只读字符串,意味着String引用的字符串的内容是不能被修改的:我们可能会存在下面这样的误解:
String str = "abc";
str = "def";

如上,有人可能会说字符串str明明可以改变的呀!其实不然,str只是一个引用对象,它指向了“abc”所在内存空间的地址,当我们将“def”赋值给str的时候,实际上是新开辟了一块内存空间存放“def”,并将str指向其内存地址,而“abc”字符串对象没有任何改变,只是没有引用变量指向它

  • StringBulider/StringBuffer表示的字符串对象是可以直接修改的

StringBuilder是Java5引入的,它和StringBuffer中的方法完全相同,区别在于它是单线程环境下使用的,因为它的所有方法都没被synchronized修饰,因此它的效率要比StringBuffer要高

3.请说出下面程序的输出?(字符串对象的比较)

class StringEqualTest {
    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program";
        String s4 = "ming";
        String s5 = "Program" + "ming";
        String s6 = s3 + s4;
        System.out.println(s1 == s2);    
        System.out.println(s1 == s5);    
        System.out.println(s1 == s6);    
        System.out.println(s1 == s6.intern());    
        System.out.println(s2 == s2.intern());    
    }
}/**output
false
true
false
true
false
*/

结果分析:

  • s1和s2前者是常量池中的对象,后者是堆中的对象
  • s5由两个字符串字面量拼接,java编译器会做优化,直接看成“Programming”,因此和s1是常量池的同一对象
  • s6由两个字符串变量拼接,java编译器并不知道这两个变量中是什么值,因此不会优化,而字符串的拼接本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder对象用toString()方法处理成String对像,因此两个对象不同
  • 关于String的intern()方法,它会得到字符串对象在常量池中对应版本的引用(前提是常量池中有这个字符串与这个String对象的equals方法为true),如果常量池中没有对应的字符串,则会将该字符串添加到常量池中,然后返回常量池中字符串的引用

4.如何取得年月日、小时分钟秒?

import java.time.LocalDateTime;
import java.util.Calendar;

class DateTimeTest {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.get(Calendar.YEAR));
        System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
        System.out.println(cal.get(Calendar.DATE));
        System.out.println(cal.get(Calendar.HOUR_OF_DAY));
        System.out.println(cal.get(Calendar.MINUTE));
        System.out.println(cal.get(Calendar.SECOND));
        // Java 8
        LocalDateTime dt = LocalDateTime.now();
        System.out.println(dt.getYear());
        System.out.println(dt.getMonthValue()); // 1 - 12
        System.out.println(dt.getDayOfMonth());
        System.out.println(dt.getHour());
        System.out.println(dt.getMinute());
        System.out.println(dt.getSecond());
    }
}

5.如何取得从 1970 年 1 月 1 日 0 时 0 分 0 秒到现在的毫秒数?

class GetTime {
    public static void main(String[] args) {
        System.out.println("第一种:" + Calendar.getInstance().getTimeInMillis());
        System.out.println("第二种:" + System.currentTimeMillis());
        System.out.println("第三种:" + Clock.systemDefaultZone().millis());
        System.out.println("第四种:" + new Date().getTime());
    }
}

6.如何格式化日期

class DateFormatTest {
    public static void main(String[] args) {
        SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
        Date date1 = new Date();
        System.out.println(oldFormatter.format(date1));
        // Java 8
        DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        LocalDate date2 = LocalDate.now();
        System.out.println(date2.format(newFormatter));
    }
}

Java面试IO部分

1.Java中流的分类?

按照流的方向

  • 输入流(InputStram)
  • 输出流(OutputStream)

按照实现功能

  • 节点流:可以从或向一个特定的地方(节点)读写数据,如 FileReader
  • 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,如 BufferedReader。

处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接

按照处理数据的单位

  • 字节流:字节流继承于 InputStream 和 OutputStream
  • 字符流:字符流继承于InputStreamReader 和 OutputStreamWriter

在这里插入图片描述

2.字节流如何转化为字符流?

  • 字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
  • 字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。

3.如何将一个 java 对象序列化到文件里?又如何反序列化到程序中读出?

  • ObjectOutputStream:序列化(持久化):对象转化为字节流存到文件/数据库/字节数组(内存),有writeObject()方法
  • ObjectInputStream:反序列化:将上述的字节流转化为对象,有readObject()方法
  • 对象流的构造器中是一个节点流
public class Test {
    public static void main(String[] args) throws Exception {
        //对象输出流
        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
        objectOutputStream.writeObject(new User("zhangsan", 100));
        objectOutputStream.close();
        //对象输入流
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));
        User user = (User) objectInputStream.readObject();
        System.out.println(user);
        objectInputStream.close();
    }
}

在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。

4.字节流和字符流的区别?

  • 字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在 UTF-8 码表中是 3 个字节)时,先去查指定的编码表,将查到的字符返回
  • 字节流可以处理所有类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流
  • 字节流主要是操作 byte 类型数据,以 byte 数组为准,主要操作类就是 OutputStream、InputStream;字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组
  • 所以字符流是由 Java 虚拟机将字节转化为 2 个字节的 Unicode 字符为单位的字符而成的,所以它对多国语言支持性比较好!如果是音频文件、图片、歌曲,就用字节流好点,如果是关系到中文(文本)的,用字符流好点。在程序中一个字符等于两个字节,java 提供了 Reader、Writer 两个专门操作字符流的类

5.什么是Java序列化?如何实现?

  • 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题
  • 序 列 化 的 实 现 : 将 需 要 被 序 列 化 的 类 实 现 Serializable 接 口 , 该 接 口 没 有 需 要 实 现 的 方 法 , implements Serializable 只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象,接着,使用 ObjectOutputStream 对象的 writeObject(Object obj)方法就可以将参数为 obj 的对象写出(即保存其状态),要恢复的话则用输入流。

Java面试集合部分

1.请问ArrayList、HashSet、HashMap是线程安全的吗?如果不是怎么获取线程安全的集合?

通过以上类的源码分析,每个方法都没有加锁,因此都是非线程安全的。

在集合中Vector、HashTable是线程安全的,打开源码会发现其实就是将各自的核心方法加上了synchronized关键字。

Collections工具类提供了相关的API将上述3个非线程安全类变为线程安全的:

Collections.synchronizedCollection(c);
Collections.synchronizedList(list);
Collections.synchronizedMap(m);
Collections.synchronizedSet(s);

上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实原理非常简单,就是将集合的核心方法添加上了synchronized关键字。

2.集合的初始容量、加载因子、扩容增量汇总

集合加载因子初始容量扩容后的容量
Vector110原容量的2倍
ArrayList110原容量的1.5倍
HashSet0.7516原容量的2倍
Hashtablet0.7511原容量的2倍+1
HashMap0.7516原容量的2倍

3.集合中哪些可以存储null值?

  • Vector、ArrayList、LinkedList可以存储null
  • HashSet、LinkedHashSet可以存储null
  • HashMap、LinkedHashMap的key和value均可以为null,TreeMap的key不能为null,value可以为null;Hashtable、ConcurrentHashMap的key和value都不能为null

更详细的Java面试集合部分参见:Java 集合(五)| 集合面试汇总

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值