JavaSE常见面试题目的解答

每当产生异常后,如果没有程序进行相应的处理,则程序将出现中断现象,那么,此时实际上一旦产生异常之后,JVM会抛出一个异常类的实例化对象,如果此时使用try语句进行捕获的话,则可以进行异常出出处理,如果没有的话,则交给JVM进行处理,当try语句捕获到异常之后,会与catch中的异常类型进行匹配,如果匹配成功,则使用此catch语句进行处理。

Java中的异常处理机制的简单原理和应用

每当产生异常后,如果没有程序进行相应的处理,则程序将出现中断现象。那么,此时实际上一旦产生异常之后,JVM会抛出一个异常类的实例化对象,如果此时使用try语句进行捕获的话,则可以进行异常处理。如果没有的话,则交给JVM进行处理,当try语句捕获到异常之后,会与catch中的异常类型进行匹配,如果匹配成功,则使用此catch语句进行处理。

应用:简单的引用,就是在所有有throws关键字的地方加入try...catch,如:

// 定义一个方法
public void method() throws Exception;

// 调用方法(method),然后使用try catch进行捕获
try {
    method();
} catch(IOException e) {
    ...
} catch(Exception e) {
    ...
}

如果按照一个标准做法的话,try、catch、finally、throw、throws关键字应该一起使用。

垃圾回收的优点和原理

垃圾回收是将无用的对象空间进行释放,两种回收:

  • 自动回收(JVM根据垃圾回收算法进行自动回收)

  • 手工调用System.gc()方法,实际上调用System.gc()就相当于调用了Runtime.getRuntime().gc()方法。

Error和Exception的区别

  • Error 是 Throwable 的子类,用于合理的指示应用程序不应该试图捕获的严重问题。大多数这样的错误都是有异常条件。虽然 ThreadDeath 错误是一个“正规”的条件,但它也是 Error 的子类,因为大多数应用程序都不应该试图捕获它。

  • Exception是可以由程序进行处理的,使用try...catch语句进行处理。

总结,Error错误是严重的不可逆转的,不推荐进行捕获;Exception错误是可逆转的,大多数情况下用户是希望进行捕获的。

谈谈final、finally、finalize的区别

  • final是定义常量、方法、类的、声明的方法不能被覆写、声明的类不能被继承。如下:

public final class Test {
    public static final String FIELD = "";
    public final void showMsg() {
        ...    
    }
}
  • fianlly是异常的统一出口,配合try...catch使用。

public class Test {
    public static void main(String[] args) {
        try {
            // ...
        } catch(Exception e) {
            // ...
        } finally {
            // ...
        }
        
        // 或者
        try {
            // ...
        } finally {
            // ...
        }
    }
}
  • finalize是垃圾回收前的收尾工作,是Object类中定义的。如下:

public class Object {
    ...
    protected void finalize() throws Throwable { }
    ...
}

匿名内部类是否可以继承其他类,是否可以实现接口

允许继承和实现,因为匿名内部类就是在抽象类和接口的基础之上发展起来的。

静态嵌套类和内部类的不同

使用static声明的内部类就是外部类,可以通过外部类直接访问。如下:

package com.huangx.innerClass.statics;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 测试静态内部类的创建和使用
 * 
 * @author Administrator
 * @date 2018-02-22 14:17
 */
public class StaticInnerClass1 {
    private static final Logger log = LoggerFactory.getLogger(StaticInnerClass1.class);
    
    public static void main(String[] args) {
        MyStaticInnerClass t = new MyStaticInnerClass();
        t.show();
    }
    
    static class MyStaticInnerClass {
        public MyStaticInnerClass() {
            log.debug("MyStaticInnerClass()");
        }
        
        public void show() {
            log.debug("MyStaticInnerClass >> show()");
        }
    }
}

普通的内部类是不能够直接被外部所访问的,需要通过外部类实例再找到内部类的实例。如下:

package com.huangx.innerClass;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 创建内部类,且使用内部类语法
 *
 * @author Administrator
 * @date 2017-10-17
 */
public class InnerClass1 {
    private static final Logger log = LoggerFactory.getLogger(InnerClass1.class);

    public static void main(String[] args) {
        // 创建实例
        InnerClass1 t = new InnerClass1();
        // 创建内部类实例
        Inner01 inner01 = t.new Inner01("ZhangSan", 28);
        inner01.show();
    }

    class Inner01 {
        private String name;
        private int age;

        public Inner01(String name, int age) {
            this.name = name;
            this.age = age;
            log.debug("Inner01({}, {});", name, age);
        }

        public void show() {
            log.debug("show() :: name={}, age={}", this.name, this.age);
        }
    }
}

HashMap和HashTable的区别

HashMap

    |-JDK1.2之后推出,是新类

    |-采用异步处理方式,性能较高,但是属于非线程安全

    |-允许设置null

HashTable

    |-JDK1.0时推出,是旧类

    |-采用同步处理方式,性能较低,但是属于线程安全

    |-不允许设置null,否则将出现NullPointerException

什么时候用assert?

JDK1.4之后增加的新关键字assert,表示断言,即程序执行到某个地方之后值肯定是预计好的。

一般在开发中很少使用assert。如果你想要使用断言,必须使用-ea参数。如下:

package com.huangx.asserts;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AssertTest1 {
    private static final Logger log = LoggerFactory.getLogger(AssertTest1.class);

    public static void main(String[] args) {
        int a = 10;
        assert a > 0;
        log.debug("a = {}", a);
        
        int b = -1;
        assert b > 0 : "b Must be greater than 0";
        log.debug("b = {}", b);
    }
    
}

必须使用-ea参数开启assert功能:

java -ea AssertTest1

GC是什么?为什么要有GC?

GC即垃圾收集。使用GC可以进行垃圾空间的释放JVM内存资源。

String s = new String("xyz");

创建了几个String Object?产生了两个实例对象,一个是匿名对象"xyz",另一个是通过关键字new实例化的。

sleep()和wait()的区别?

  • sleep()是Thread中定义的方法,表示线程的休眠,会自动唤醒。

  • wait()是Object中定义的方法,需要手动的调用notify()或notifyAll()方法。

Java没有goto?

goto属于保留的关键字,Java中存在goto关键字,只是无法使用。

数组有没有length()方法,String有没有length()方法?

数组中存在length属性,String中存在length()方法。

Overload和Override的区别,Overload的方法是否可以改变返回值的类型?

重载(Overload):

    |- 在一个定义的若干方法

    |-所有的方法名称相同,但是参数的类型或个数不同

覆写(Override):

    |-在继承的关系中

    |-子类定义与父类同名的方法,参数类型个数最好完全相同,注意访问权限不能更加严格

重载的时候不是依靠返回值类型区分的,而是靠参数。

Set里面的元素是不能重复的,那么用什么方法类区分重复与否呢?是用==还是equals()?它们有何区别?

hashCode()和equals()方法。使用的是equals()方法,“==”属于地址值比较,而equals()属于内容比较。如下:

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Set))
        return false;
    Collection c = (Collection) o;
    if (c.size() != size())
        return false;
    try {
        return containsAll(c);
    } catch (ClassCastException unused)   {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }
}

上面是HashSet中对equals方法的实现(在AbstractSet类中进行实现)。

列举出一些最常见的运行时异常

NumberFormatException(数字格式化异常)

ArrayOutIndexofBoundException(数组下标溢出)

NullPointerException(空指针异常)

ClassCastException(类转换异常)

abstract class和interface有什么区别?

抽象类:

    |-有抽象方法和常量、变量、全局变量、构造方法、普通方法组成

    |-使用abstract声明

    |-子类要通过extends继承抽象类,子类如果不是抽象类,则必须覆写抽象类中的所有抽象方法

    |-存在单继承局限,一个子类只能继承一个抽象类

    |-抽象类可以实现若干个接口

接口:

    |-全部由抽象方法和全局常量组成

    |-使用interface声明

    |-子类通过implements实现接口,子类如果不是抽象类,则必须覆写接口中的所有抽象方法

    |-不存在单继承局限,一个类可以同时实现多个接口

    |-接口不能继承一个抽象类,但是允许继承多个接口

启动一个线程使用run()还是start()?

使用start()方法,因为要通知JVM进行CPU资源分配。如果直接调用run()方法,则和调用普通的方法类似。

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    /**
     * Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. 
     */
    group.add(this);

    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /**
             * do nothing. If start0 threw a Throwable then 
             * it will be passed up the call stack 
             */
        }
    }
}

private native void start0();

从上面代码可以看出,start方法内部是调用本地start0方法来启动线程。

try语句有return,fianlly里的代码会执行吗?

try{}里有一个return语句,那么紧跟在try后面的fianlly{}里面的代码会不会执行,什么时候执行,在return前还是在之后呢?

会执行,在return之前执行。测试代码如下:

private String test1M() {
    try {
        log.debug("## try"); // 1
        return "## return"; // 3
    } finally {
        log.debug("## finally"); // 2
    }
}

@Test
public void test1() {
    log.debug( test1M() );
}

/*
结果:
13:58:47 DEBUG [com.huangx.test.Test01] - ## try
13:58:47 DEBUG [com.huangx.test.Test01] - ## finally
13:58:47 DEBUG [com.huangx.test.Test01] - ## return
*/

但是如果使用类System.exit(0)将不会执行。测试代码如下:

private String test2M() {
    try {
        log.debug("## try"); // 1
        System.exit(0);
        return "## return";
    } finally {
        log.debug("## finally");
    }
}

@Test
public void test2() {
    log.debug(test2M());
}
/*
结果:
13:59:22 DEBUG [com.huangx.test.Test01] - ## try
*/

写出一个单例类模式

单例模式分为饿汉式和懒汉式两种实现方式,下面将使用饿汉式实现,代码如下:

package com.test;

public class Singleton{
    private static final Singleton single = new Singleton();
    
    private Singleton(){}
    
    public static getInstance(){
        return single;
    }
}

编一段代码,实现在控制台输入一组数字后,排序后在控制台输出。

假设数字中间使用空格进行分隔。

package com.test;
import java.io.*;
import java.util.*;
public class Demo{
    public static void main(String[] args)thorws Exception{
        BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一串数字,中间使用空格分隔:");
        String str = buf.readLine();
        Set<Integer> set = new TreeSet<Integer>();
        String[] s = str.split(" ");//进行拆分
        for(String x:s){
            set.add(Integer.parseInt(x));//加入数据
        }
        
        System.out.println("排序后的结果:");
        for(Iterator it = set.iterator(); it.hasNext();){
            System.out.println(it.next() + "、 ");
        }
    }
}

以上的操作中,因为所有数据都已经假设是正确的数字,但是实际中有可能是字符串。

  • 第一种方法:允许有字符串,但是将所有的字符串忽略。

  • 第二种方法:不允许有字符串,如果出现字符串,则程序不能继续向下运行。

改善程序:

package com.test;
import java.io.*;
import java.util.*;
public class Demo{
    public static void main(String[] args)thorws Exception{
        BufferedReader buf = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("请输入一串数字,中间使用空格分隔:");
        String str = buf.readLine();
        Set<Integer> set = new TreeSet<Integer>();
        System.out.println("(" + str.replaceAll("\\D+", " ") + ")");
        String[] s = str.repaceAll("\\D+", " ").trim().split(" +");//进行拆分
        for(String x:s){
            set.add(Integer.parseInt(x));//加入数据
        }
        System.out.println("排序后的结果:");
        for(Iterator it = set.iterator(); it.hasNext();){
            System.out.println(it.next() + "、 ");
        }
    }
}

列出某个文件夹下的所有文件

此时我们需要是File类的listFiles和递归配合使用。如下图:

package com.test;
import java.io.*;
public class ListFiles{
    public static void main(String[] args){
        File file = new File("D:" + File.separator + "doc");
        fun(file);
    }
    
    // 列出文件列表
    public static void fun(File file){
        if(file.isDirectory()){
            File f[] = file.listFiles();
            if(f != null){
                for(int i=0; i<f.length; i++){
                    fun(f[i]); // 递归入口
                }
            }
        }else{
            System.out.println(file); // 递归出口
        }
    }
}

char型变量中能不能存下一个中文汉字?为什么?

可以存放。因为Java使用的是unicode编码。如下:

@Test
public void test1() {
    char[] chars = {'<', '中', '国', '>'};
    log.debug("{}", Arrays.toString(chars));
}

多线程有几种实现方法,都是什么?同步有几种实现方法,都是什么?

多线程有两种实现方法:

  • 继承Thread类

  • 实现Runnable接口

Thread类是Runnable的子类,使用Runnable接口可以实现资源共享的目的。所有的线程操作都必须由Thread类的start()方法启动。实现两种同步的方法:

  • 同步代码块

public void test() {
    synchronized(this) {
        ...
    }
}
  • 同步方法

public synchronized void test() {
    ...
}

float型float f = 3.4是否正确?

不正确,因为默认情况下3.4是double类型。

String和StringBuffer的区别?

String不可修改(不变模式),StringBuffer可以修改。因为StringBuffer是通过char数组进行维护的,因此是可变的。如下:

abstract class AbstractStringBuilder implements Appendable, CharSequence {
    /**
     * The value is used for character storage.
     */
    char[] value;
    ...
}

如上,StringBuffer的大部分功能是在AbstractStringBuilder类进行实现,其中通过char[]数组进行完成。

Class.forName的作用?为什么要用?

Class.forName方法用于向JVM中加载给定的类(通过反射去使用类),例如:加载JDBC驱动,如下:

public void hello() {
    try {
        Class.forName("com.mysql.jdbc.Driver");
        // ...
    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        // 释放资源
    }
}

使用Class.forName方法可以通过程序动态的加载给定的类。

int和Integer有什么区别?

int是基本数据类型、Integer是包装类。在JDK1.5之后,可以实现自动装箱和拆箱的操作。如下:

/**
 * 测试自动装箱和拆箱功能
 */
@Test
public void autoCase() {
    // 拆箱
    int a = Integer.valueOf(100);
    log.debug("a = {}", a);
    
    // 装箱
    Integer b = 200;
    log.debug("b = {}", b);
}

注意:自动装箱和拆箱是JDK1.5+版本添加的,对编写代码更便捷。

用最有效的方法算出2*?=8

该考题主要考察对Java位移操作。本题中,2乘以几是最有效的,无疑是通过位移来进行操作。使用:2<<3(即2^3,2的3次方),更多关于位移操作见下面。

Java中的移位运算符有三种:

  • <<: 左移

  • >>: 右移

  • >>>: 无符号右移

使用方法

左移就是将左边的操作数在内存中的二进制数据左移指定的位数,左边移空的部分补零,右移:如果最高位是0,空位就填0,如果最高位是1,空位就填1。无符号右移无论最高位是什么,空位都补零。

数据在内存中以补码的形式存储

左移和右移的数学意义

对于左移,对于整型a,  a<<n=a*2^n(前提是结果在整型的范围之内),对于右移正的整型a,  a>>n=a/2^n,对于负的整型a,a>>n=-(|a|/2^n+1)。

为什么对于右移,正数和负数的结果不一样呢?我们可以看一个例子:

public class Test {
    public static void main(String[] args) {
        int a = -123;
        System.out.println("原二进制位:" + Integer.toBinaryString(a));
        int b = a >> 2;
        System.out.println("右移两位结果为:" + b);
        System.out.println("右移后二进制位:" + Integer.toBinaryString(b));
        int c = 123;
        System.out.println("原二进制位:" + Integer.toBinaryString(c));
        int d = c >> 2;
        System.out.println("右移后的结果为:" + d);
        System.out.println("右移后二进制位:" + Integer.toBinaryString(d));
    }
}

集合框架的完整结构

集合框架最大的接口:Collection(集合,分为:List列表和Set集合)、Map(映射)、Iteraotr(迭代器)、Enumeration(迭代器)。如:

+ Collection:存放单值
    + List:允许有重复内容、有顺序
        + ArrayList:异步处理,新操作类,非线程安全
        + Vector:同步处理,旧的操作类,线程安全,支持Enumeration输出
    + Set:不允许有重复内容,靠hashCode()和equals()方法进行重复验证,无顺序
        + HashSet:无须存放
        + TreeSet:有序存放,按Comparable排序
+ Map:存放键值对
    + HashMap:新的类,异步处理,非线程安全,允许有null
    + HashTable:旧的类,同步处理,非线程安全,不允许有null
        + Properties:属性操作类
    + TreeMap:有序排列,按key排序,根据Comparable指定排序规则
+ Iterator:迭代输出,依靠Collection接口中的iterator()方法。是新的输出标准
+ Enumeration:是旧的输出类

上面列举了集合框架的大部分API,更多的API请查阅JDK API文档。

String类是否可以被继承?

String类不允许进行继承,因为Sting类使用了final关键字进行声明(final声明的类不允许继承,final声明的方法不允许重写)。部分源码如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;
    ...
}
我们一定要给自己提出这样的任务:第一,学习,第二是学习,第三还是学习。 —— 列宁
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站属于个人的非赢利性网站,转载的文章遵循原作者的版权声明,如果原文没有版权声明,请来信告知:hxstrive@outlook.com
公众号