50道面试题检验自己的Java基础过不过关

475 阅读17分钟
1、用最有效率的方法计算2乘以8?

2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

2、数组有没有length()方法?String有没有length()方法?

数组没有length()方法,有length 的属性;String有length()方法;集合使用的是size()方法。

3、构造器(constructor)是否可被重写(override)?
public final class Constructor<T> extends Executable

由于被final关键字修饰,所以不能被继承,进而导致不能被重写,但是可以被重载。

4、String和StringBuffer、StringBuilder的区别?
  • String:被final修饰,所以长度不可变,底层使用的是数组(private final char value[]);
  • StringBuffer:JDK1.0就有,长度可变,线程安全,效率低,继承自AbstractStringBuilder;
  • StringBuilder:jdk1.5引入进来的,长度可变,线程不安全,效率高,继承自AbstractStringBuilder;
  • StringBuffer和StringBuilder里面的方法作用都相同,不同之处在于StringBuffer中的每个方法都有synchronized关键字修饰;
  • 三者都是final类,都不能被继承。
5、字符串拼接为什么不建议使用“+”

因为String长度不可变,使用“+”拼接,每次拼接都会产生一个新的对象,这样会有大量对象产生,造成内存浪费;

通常情况下如果不要求线程的安全性,使用StringBuilder的append()方法进行字符串拼接;

6、单例设计模式中的懒汉式和饿汉式有什么区别

饿汉式:

package singleton;

/**
 * 饿汉式单例
 * 特点:类一旦加载就创建一个单例,保证在调用getInstance方法之前单例已经存在了
 */
public class HungrySingleton {
    private static final HungrySingleton instance = new HungrySingleton();
    //私有构造,避免外部初始化
    private HungrySingleton() {
    }
    //静态公有方法,获取该实例对象
    public static HungrySingleton getInstance() {
        return instance;
    }
}

懒汉式第一种:

package singleton;

/**
 * 懒汉式单例
 * 特点:只有在调用getInstance方法时才会创建实例对象
 */
public class LazySingleton {
    private static LazySingleton instance = null;
    private LazySingleton() {
    }
    /**
     * 问题:
     * 使用synchronized关键字修饰,可以保证线程安全性;
     * 但同时有synchronized关键字修饰,所以该多线程访问时,都要判断锁,会导致效率较低
     */
    public static synchronized LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

懒汉式第二种:

package singleton;

/**
 * 懒汉式更好的解决方案:使用双判断的方式
 */
public class Lazy {
    public static Lazy instance = null;
    private Lazy() {
    }
    /**
     * 使用双重判断的方式来解决懒汉式的效率问题,即当一个线程创建了该实例对象,以后的线程访问的时候该对象      * 就不为null,既不用再去判断锁了,这样可以减少判断锁的次数,提高了效率
     */
    public static Lazy getInstance() {
        if (instance == null) {
            //静态方法的锁使用的是该静态方法所在类的class文件对象
            synchronized (Lazy.class) {
                if (instance == null) {
                    instance = new Lazy();
                }
            }
        }
        return instance;
    }
}

1.懒汉式和饿汉式的区别:懒汉式的特点在于实例的延迟(所以是汉式)加载;

2.懒汉式有什么问题:如果多线程访问时会出现安全问题,可以加同步(synchronized)来解决,但效率较低;

3.怎么解决懒汉式效率低的问题:可以使用双重判断可以解决这个问题;

4.加同步的时候,使用的锁是哪一个:该类所属的字节码文件对象(静态)。

7、synchronized关键字

修饰非静态方法:使用的锁是this;

public synchronized void method() {}//有synchronized关键字修饰就是同步的

修饰代码块:使用的锁由自己决定,只要是对象就可以;

Object obj = new Object()
    
synchronized (obj){
//......
}

修饰静态方法:使用的锁是该方法所属类的字节码文件。

public static synchronized void sale() {}
8、Java中String直接初始化赋值和new的区别

1.初始化:

String s2 = "abc";

2.new出来:

String s1 = new String("abc");

3.区别:

  • 初始化可能创建一个或者不创建对象,如果”abc”这个字符串在String池中不存在,则会在String池中创建一个对象,无论以后用这种方式(初始化方式)创建多少个值为”abc”的字符串对象,始终只有一个内存地址被分配,之后的都是String的拷贝,Java中称为“字符串驻留”;
  • new出来的至少创建一个对象,也可能两个。因为用到new关键字,肯定会在堆中创建一个对象,它的值是“abc”,同时如果“abc”在String池里不存在,还会在java池里创建这个String对象“abc”。

4.垃圾回收:

在JVM里,考虑到垃圾回收(Garbage Collection)的方便,将heap(堆)划分为三部分:

young generation(新生代)

old generation(旧生代)

permanent generation(永生代)

字符串为了解决字符串重复问题,生命周期长,存于permanent generation中(永生代)

5.代码:

package demo;
public class Demo {
    public static void main(String[] args) {
        String s1 = new String("abc");
        String s2 = "abc";
        String s3 = new String("abc");
        String s4 = "abc";
        System.out.println(s1.equals(s2));//true
        System.out.println(s1 == s2);//false
        System.out.println(s1 == s3);//false
        System.out.println(s2 == s3);//false
        System.out.println(s2 == s4);//true
    }
}
9、多线程实现的两种方式

第一种:继承Thread类;

第二种:实现Runnable接口;

区别:

实现Runnable接口的方式可以避免由于Java单继承带来局限性,Java语言中只能继承一个类,但是可以实现多个接口,所以使用实现Runnable接口的方式更为灵活。

10、如何使用多线程
  1. 继承Thread类或实现Runnable接口;
  2. 重写run()方法;
  3. 掉用start()方法开启线程。
11、线程状态(线程的生命周期)

注意:冻结状态又可分为睡眠状态和等待状态

12、访问修饰符public等的区别?
修饰符 当前类 同 包 子 类 其他
public
protected ×
default × ×
private × × ×
13、String 是最基本的数据类型吗?

Java中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;

除了基本类型(primitive type)和枚举类型(enumeration type),剩下的都是引用类型(reference type)。

14、Java基本数据类型转换
类型: 字节: 范围: 默认值:
byte 1 -128~127 0
short 2 -32768~32767 0
char 2 0~65535 '\u0000'
int 4 -2147483648~2147483647 0
long 8 -2^63~2^63-1 0L或0l
float 4 3.402823e38~1.401298e-45 0.0F或0.0f
double 8 1.797693e308~4.9000000e-324 0.0D或0.0d
boolean 1 false 或 true false

基本数据类型的精度(亦称容量大小)排序为(从小到大):(byte->short->char)->int->long->float->double;

向下转换:高精度向低精度转换,需要强制转换,可能会导致精度损失;

向上转换:低精度向高精度转换,一般由编译器完成,当几个基本数据类型参与运算时,计算结果的类型是参与运算中精度最高的那个运算数的类型,其他运算数的类型将被自动转为精度最高的那个运算数的类型;

之所以要给byte,short,char三个类型加上括号,是因为,当它们参与运算时首先会被提升为int类型,也就说明他们之间不会发生隐式类型转换。

15、switch (expr)

expr可以是byte、short、int、char、enum、String类型,但是long类型不能

16、内存中栈(stack)、堆(heap)和静态区(static area)的用法

栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间;

String str = new String("hello");

变量str放在栈上,用new创建出来的字符串对象放在堆上,而”hello”这个字面量放在静态区。

17、重载和重写的区别

重载:发生在一个类中,方法名相同,参数列表不同(参数类型不同、参数个数不同甚至是参数顺序不同);

重写:发生在父类与子类之间,方法名、参数列表、返回值类型相同,子类方法的访问修饰符权限不能严于父类,子类抛出的异常范围小于父类。

18、接口和抽象类的区别
参数 抽象类 接口
默认的方法实现 可以有默认的方法实现 不存在方法的实现
实现 子类需要实现抽象类的所有抽象方法,否则继续抽象 需要实现接口中的所有方法
构造函数 可以有,但是不能实例化对象 不能有
访问修饰符 public、protected、default、private都可以 默认修饰符是public
main方法 可以有main方法 没有main方法,jdk1.8以后可有
  • 抽象类和接口都不能够实例化;
  • 接口中的变量都默认是public static final的全局常量;
  • 有抽象方法的类必须被声明为抽象类,而抽象类可以没有抽象方法。
19、接口是否可继承接口?抽象类是否可实现接口?抽象类是否可继承具体类?
  • 接口可以继承接口,而且支持多重继承;
  • 抽象类可以实现接口;
  • 抽象类可以继承具体的类也可以继承抽象类。
20、如何将字符串转换为基本数据类型?如何将基本数据类型转换为字符串?
package demo02;
public class StringToBasic {
    /**
     * 字符串转基本数据类型:
     * 调用基本数据类型对应的包装类中的方法parseXxx();
     * 注意:如果字符串为"abc",则无法转换,因为Character没有提供parseXxx()方法。
     */
    private static void stringToBasic() {
        String s = "123";
        int i = Integer.parseInt(s);
        double d = Double.parseDouble(s);
        System.out.println(i);//123
        System.out.println(d);//123.0
    }
    
    /**
     * 基本数据类型转换为字符串:
     * 1.将基本数据类型与空字符串("")使用+连接即可获得其所对应的字符串(常用);
     * 2.调用String类中的valueOf()方法返回相应字符串;
     * 3.如果基本数据类型使用的是其包装类,则可直接调用toString()方法。
     */
    public static void basicToString() {
        int i = 100;
        String s1 = i + "";//第一种方法
        String s2 = String.valueOf(i);//第二种方法
        System.out.println(s1);
        System.out.println(s2);
        Integer b = 99;
        String s3 = b.toString();//第三种方法
        System.out.println(s3);
    }
}
21、递归实现字符串反转
/**
 * 递归实现字符串反转:
 * substring(i)用于返回数组下标为i及之后的元素
 * charAt(i)用于返回数组下标为i的元素
 *
 * @param originStr
 * @return
 */
public static String reverse(String originStr) {
    if (originStr == null || originStr.length() <= 1) {
        return originStr;
    }
    return reverse(originStr.substring(1)) + originStr.charAt(0);
}
22、Object类中的方法

Object是所有类的父类,任何类都能继承自Object,Object中有如下方法,也就是说所有的类都有如下方法:

clone():实现Cloneable接口才能调用此方法,实现对象的浅拷贝;

getClass():final方法,不能被覆写,获得方法所在类运行时类型;

toString():一般都会覆写该方法,使该对象以字符串形式输出;

finalize():用于释放资源,较少使用;

equals():注意区别equals和==的不同,在Object中是一样的,都是用于比较对象的地址是否相同。但子类一般会覆写该方法,String类已覆写了该方法,覆写过后equals一般用于比较内容,==用于比较地址。

hashCode():一般覆写equals的同时都要覆写hashCode方法;

wait():使当前线程等待该对象的锁;

notify():唤醒在该对象上等待的某个线程;

notifyAll():唤醒在该对象上等待的所有线程。

23、Java异常分类

非运行时异常(检查异常、编译异常):指的是在编写代码时JVM提示的异常(JVM编译都不能通过),比如:IOException、SQLException,必须手动抛出或捕获;

运行时异常(非检查异常):程序运行时出现的异常,比如:

ArithmeticException(算术运算异常);

ClassCastException (类型转换异常);

IllegalArgumentException (非法参数异常);

IndexOutOfBoundsException (数组下标越界异常);

NullPointerException (空指针异常)。

24、Java集合框架

图示:

1.常见的集合有哪些?

答:Map接口和Collection接口是所有集合框架的父接口:

(1)Collection接口的子接口包括:Set接口和List接口

(2)Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等

(3)Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等

(4)List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等

25、Thread类的sleep()方法和对象的wait()方法都可以让线程暂停执行,它们有什么区别?

sleep():是线程类Thread的静态方法,调用该方法使线程暂停执行指定的时间,将CPU让给其他线程,并不释放所持有的对象锁,休眠时间结束后线程回到就绪状态。

wait():Object类的方法,调用wait()方法,线程释放所持有的对象锁,进入等待池中,只有调用notify()方法或notifyAll()方法,才能唤醒等待池中的线程进入等锁池,若线程获得对象的锁,则线程重新进入就绪状态。

26、当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能,因为synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B方法的线程就只能在等锁池中等待对象的锁。

27、请说出与线程同步以及线程调度相关的方法

wait():使当前执行线程处于等待状态,并且释放所持有的对象的锁;

sleep():使当前执行线程睡眠指定的时间,不释放持有的对象锁,静态方法,调用此方法要处理InterruptedException异常;

notify():唤醒一个处于等待状态的线程,调用此方法并不能保证唤醒哪个线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

notityAll():唤醒所有处于等待状态的线程,只有获得锁的线程才能进入就绪状态;

28、synchronized和Lock区别
类别 synchronized Lock
存放层次 Java关键字,JVM层面 是一个类,位于java.util包下
锁释放 1.执行完同步代码,自动释放锁 2.发生异常,JVM会让线程释放锁 手动在finally中释放,不然会发生死锁
锁状态 无法判断 可以判断
锁类型 可重入锁、不可中断、非公平锁 可重入锁、可判断、公平锁
性能 少量同步 大量同步
29、事务的ACID(四大特性)是指什么?
  • 原子性(Atomic):事务中各项操作,要么全做要么全不做;
  • 一致性(Consistent):事务结束后系统状态是一致的;
  • 隔离性(Isolated):并发执行的事务彼此独立;
  • 持久性(Durable):事务完成后所做的改动都会被持久化到数据库。
30、事务并发问题

**脏读:**事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据(读取到了未提交数据);

**不可重复读:**事务A多次读取同一数据,事务B在事务A多次读取的过程中对数据作了更新并提交,导致事务A多次读取同一数据时结果不一致;

**幻读:**事务A重复读取数据,读取到了被事务B修改过的数据,像发生了幻觉一样,这就叫幻读;

**注意:**不可重复读和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。

31、事务的隔离级别

读取未提交:不可避免幻读,脏读及不可重复读;

读取已提交:可避免脏读,不可避免幻读和不可重复读,;

可重复读:可避免脏读和不可重复读,不可避免幻读;

可串行化:可避免幻读,脏读及不可重复读。

32、成员变量和局部变量的区别?
  • 从语法形式看:成员变量存在于类中,局部变量存在于方法中或作为方法的参数存在;成员变量可以被public、static等修饰符所修饰,局部变量不可以(因为访问修饰符用于区分访问范围,位于方法中的变量的作用域只限于方法体内,而static修饰的变量一般用作共享数据使用);
  • 在内存中的存储位置看:若static修饰成员变量,则属于类变量(静态随着类的加载而加载),否则属于实例变量。对象那存在于堆内存,局部变量存在于栈内存;
  • 在内存中的生命周期:成员变量随着实例对象而创建,局部变量随着方法结束而消亡;
  • 默认初始值:成员变量可以没有初始值(final修饰的成员变量需要显示赋值),局部变量必须有初始值。
33、构造方法的作用

构造方法与类名相同,无返回值(不是返回值为void),用于完成对象的初始化。如果一个类没有定义构造方法,则系统会默认提供一个无参构造。

34、静态方法和实例方法(非静态方法)区别?

调用方式:使用“类名.方法名”或者“对象.方法名”调用,实例方法只能用“对象.方法名”调用;

访问形式:静态方法只能访问静态成员,因为静态方法随着类加载,实例方法随着对象加载,先有类才有对象。有可能出现对象还没创建,静态方法就去调用实例方法,这时就会把报错。

35、静态方法调用的三种方式
  • 通过实例对象调用;
  • 通过类名直接调用;
  • 一个类中的静态方法可以直接调用。
36、super和this关键字的使用

super关键字:用来访问父类内容

  • 在子类成员方法中,访问父类成员变量;
  • 在子类成员方法中,调用父类成员方法;
  • 在子类构造方法中,调用父类构造方法。
public class Zi extends Fu {
    int num = 20;
    public  Zi(){
        super();//访问父类构造方法
    }
    public void  methodZi(){
        System.out.println(super.num); //访问父类的员变量
    }
    public void method(){
        super.method(); //访问父类成员方法
    }
}

public class Fu {
    int num = 10;
    public void method(){
        System.out.println("hello world!");
    }
}

this关键字:用来访问本类内容

  • 在本类成员方法中,访问本类成员变量;
  • 在本类成员方法中,调用本类其他成员方法;
  • 在本类构造方法中,调用本类其他构造方法。

注意:

在本类构造方法中使用this关键字调用本类其他构造方法,需放在构造方法第一行;

super和this不能同时调用构造方法。

public class Zi extends Fu{
        int num = 20;
        public Zi(){
	    //super();
        this(134); //本类的无参构造,调用本类的有参构造
        }
        public Zi(int n ){

        }
        public Zi(int n, int m ){
            
        }
        public void showNum(){
            int num = 10;
            System.out.println(this.num); //访问本类成员变量
            System.out.println(super.num); //访问父类成员变量
        }
}
public class Fu {
    int num =30;
}
37、静态代码块、非静态代码块和构造方法的执行顺序
public class Test {
    public Test() {
        System.out.println("构造方法");
    }
    {
        System.out.println("非静态代码块");
    }
    static {
        System.out.println("静态代码块");
    }

    public static void main(String[] args) {
        new Test();
    }
}
/**
输出结果:
静态代码块
非静态代码块
构造方法
*/
  • 被static修饰的随着类加载,所以最先执行;
  • 非静态代码块是给所有对象进行统一初始化;
  • 而构造函数是给对应的对象初始化。
38、序列化与反序列化
  • 为什么要序列化Java对象
    • 数据在计算机上是以字节形式进行传输的,而不是以对象的形式进行传输,所以需要将对象进行序列化;
  • 概念:
    • 序列化:把Java对象转换为字节流的过程;
    • 反序列化:把字节流恢复为Java对象的过程。
  • 用途:
    • 把对象永久地保存到硬盘上,比如保存至数据库等地方;
    • 在网络上进行对象的传输。
39、命名规范
  • 名称只能由字母、数字、下划线、$符号组成;
  • 不能以数字开头;
  • 名称不能使用Java中的关键字;
  • 坚决不允许出现中文及拼音命名。
40、抽象类和抽象方法

抽象类:由abstract关键字修饰的类,不能被实例化,只能被继承;

抽象方法:由abstract关键字修饰的方法,没有方法体,不能被调用;

两者关系:含有抽象方法的类一定为抽象类,抽象类中可以有非抽象方法;

一旦一个类继承了抽象类,那这个类要么实现抽象类中的所有抽象方法,要么继续抽象下去;

41、如何遍历Map集合
package stringdemo;
import java.util.*;
public class StringDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        map.put("刘能", "42");
        map.put("谢广坤", "46");
        map.put("赵四", "47");
        map.put("谢腾飞", "12");
        
        //只获取值
        Collection<String> values = map.values();
        for (String value : values) {
            System.out.println(value);
        }
        
        //通过keySet()方法获取key的集合,进而获取value
        Set<String> keySet = map.keySet();
        for (Object o : keySet) {
            System.out.println(o + "---" + map.get(o));
        }
       
        //通过entrySet()方法获取此集合K-V映射关系的Set集合,进而遍历此集合得到键和值
        Set<Map.Entry<String, String>> entries = map.entrySet();
        for (Map.Entry<String, String> entry : entries) {
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }
        
        //通过迭代器遍历
        Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, String> entry = it.next();
            System.out.println(entry.getKey() + "---" + entry.getValue());
        }
    }
}
42、接口说明

接口中的方法默认为public abstract;变量默认为public static final。

43、sleep()、wait()和yield()方法的区别

sleep()和wait()的区别:

  • sleep方法是Thread的静态方法,wait方法是Object类的普通方法;
  • sleep方法不释放同步锁,wait方法释放同步锁;
  • wait方法用于线程间通信,而sleep方法用于短暂的暂停线程;
  • sleep()方法的休眠时间结束后自动进入就绪状态(而不是运行状态),wait()方法需要调用notify()或者notifyAll()方法才能进入就绪状态。

sleep()和yield()的区别:

  • sleep()方法不考虑线程优先级;yield()方法只会给相同优先级或者更高优先级线程机会;
  • 线程执行sleep()方法进入阻塞状态,执行yield()方法进入就绪状态。
44、instanceof运算符的作用

用来判断一个对象是不是一个类的实例、子类的实例及一个接口的实现类,如果是则返回true,否则返回false。

格式:引用类型变量 instanceof(类、抽象类或接口)

45、节点流和处理流

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。

节点流:可以从一个特定的地方(节点)读写数据,如FileReader;

处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写,如BufferedReader。处理流的构造方法总是要带一个其他的流对象作参数。

46、类的初始化过程

父类静态-->子类静态-->父类非静态-->父类构造-->子类非静态-->子类构造

规律:父类优先先子类,静态优先非静态;其中静态包含静态代码块与静态方法,这个谁在前面,则先执行谁。

47、多线程相关方法
  • sleep会使当前线程睡眠指定时间,不释放锁;
  • yield会使当前线程重回到就绪状态,等待cpu的调度,不释放锁;
  • wait会使当前线程回到线程池中等待,释放锁,当被其他线程使用notify,notifyAll唤醒时进入就绪状态;
  • 当前线程调用某线程join方法时,会使当前线程等待某线程执行完毕再结束,底层调用了wait,释放锁。
48、Java标识符

Java标识符由**数字、字母、下划线(_)、美元符号($)**组成, 首位不能是数字,并且 Java关键字不能作为标识符

49、Integer的缓存范围

-128—127

50、abstract和static不能一起用?

被static修饰的属于类级别,是被共享的资源,而不是被子类继承的;

被abstract修饰的则必须被子类所实现;

两个关键字同时出现显然是矛盾的。