Java后端面试题(Java基础)

1.JAVA中的几种基本数据类型是什么,各自占用多少字节.

先了解2个单词先:
1、bit --位:位是计算机中存储数据的最小单位,指二进制数中的一个位数,其值为“0”或“1”。
2、byte --字节:字节是计算机存储容量的基本单位,一个字节由8位二进制数组成。在计算机内部,一个字节可以表示一个数据,也可以表示一个英文字母,两个字节可以表示一个汉字。
1B=8bit 
1Byte=8bit
1KB=1024Byte(字节)=8*1024bit
1MB=1024KB
1GB=1024MB
1TB=1024GB

3Java基本数据类型
int     32bit
short   16bit
long     64bit
byte     8bit
char     16bit
float   32bit
double   64bit
boolean 1bit
(boolean 的备注+翻译)
This data type represents one bit of information, but its "size" isn't something that's precisely defined.(ref)
这种数据类型代表一个比特的信息,但它的“大小”没有明确的定义。(参考)

基本数据类型注意事项:
1、未带有字符后缀标识的整数默认为int类型;未带有字符后缀标识的浮点数默认为double类型。

2、如果一个整数的值超出了int类型能够表示的范围,则必须增加后缀“L”(不区分大小写,建议用大写,因为小写的L与阿拉伯数字1很容易混淆),表示为long型。

3、带有“F”(不区分大小写)后缀的整数和浮点数都是float类型的;带有“D”(不区分大小写)后缀的整数和浮点数都是double类型的。

4、编译器会在编译期对byte、short、int、long、float、double、char型变量的值进行检查,如果超出了它们的取值范围就会报错。

5、int型值可以赋给所有数值类型的变量;

long型值可以赋给long、float、double类型的变量;

float型值可以赋给float、double类型的变量;

double型值只能赋给double类型变量

基本数据类型取值范围:
取值范围如图所示:


2.String 类能被继承吗?为什么?

String 被声明为 final,因此它不可被继承。

内部使用 char 数组存储数据,该数组被声明为 final,这意味着 value 数组初始化之后就不能再引用其它数组。并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

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

 

不可变的好处

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

Program Creek : Why String is immutable in Java?

3.String ,StringBuffer,StringBuilder的区别?

1. 可变性

  • String 不可变
  • StringBuffer 和 StringBuilder 可变

2. 线程安全

  • String 不可变,因此是线程安全的
  • StringBuilder 不是线程安全的
  • StringBuffer 是线程安全的,内部使用 synchronized 进行同步

StackOverflow : String, StringBuffer, and StringBuilder

4.ArrayList和LinkedList有什么区别?

5.讲讲类的实例化顺序,⽐如⽗类静态数据,构造函数,字段,⼦类静态数据,构造函数,字段,当new的时候,他们的执⾏顺序。

1. 父类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
2. 子类静态成员和静态初始化块 ,按在代码中出现的顺序依次执行
3. 父类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
4. 父类构造方法
5. 子类实例成员和实例初始化块 ,按在代码中出现的顺序依次执行
6. 子类构造方法

结论:对象初始化的顺序,先静态方法,再构造方法,每个又是先基类后子类。

6.⽤过哪些Map类,都有什么区别,HashMap是线程安全的吗,并发下使⽤的Map是什么,他们内部原理分别是什么,⽐如存储⽅式,hashcode,扩容,默认容量等。

7.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。

8.有没有有顺序的Map实现类,如果有,他们是怎么保证有序的。

9.抽象类和接口的区别,类可以继承多个类么,接⼜可以继承多个接⼜么,类可以实现多个接⼜么。

1. 抽象类

抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。

抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。

public abstract class AbstractClassExample {

    protected int x;
    private int y;

    public abstract void func1();

    public void func2() {
        System.out.println("func2");
    }
}
public class AbstractExtendClassExample extends AbstractClassExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}
// AbstractClassExample ac1 = new AbstractClassExample(); // 'AbstractClassExample' is abstract; cannot be instantiated
AbstractClassExample ac2 = new AbstractExtendClassExample();
ac2.func1();

2. 接口

接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。

从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。

接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。

接口的字段默认都是 static 和 final 的。

public interface InterfaceExample {

    void func1();

    default void func2(){
        System.out.println("func2");
    }

    int x = 123;
    // int y;               // Variable 'y' might not have been initialized
    public int z = 0;       // Modifier 'public' is redundant for interface fields
    // private int k = 0;   // Modifier 'private' not allowed here
    // protected int l = 0; // Modifier 'protected' not allowed here
    // private void fun3(); // Modifier 'private' not allowed here
}
public class InterfaceImplementExample implements InterfaceExample {
    @Override
    public void func1() {
        System.out.println("func1");
    }
}
// InterfaceExample ie1 = new InterfaceExample(); // 'InterfaceExample' is abstract; cannot be instantiated
InterfaceExample ie2 = new InterfaceImplementExample();
ie2.func1();
System.out.println(InterfaceExample.x);

3. 比较

  • 从设计层面上看,抽象类提供了一种 IS-A 关系,那么就必须满足里式替换原则,即子类对象必须能够替换掉所有父类对象。而接口更像是一种 LIKE-A 关系,它只是提供一种方法实现契约,并不要求接口和实现接口的类具有 IS-A 关系。
  • 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
  • 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
  • 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。

4. 使用选择

使用接口:

  • 需要让不相关的类都实现一个方法,例如不相关的类都可以实现 Compareable 接口中的 compareTo() 方法;
  • 需要使用多重继承。

使用抽象类:

  • 需要在几个相关的类中共享代码。
  • 需要能控制继承来的成员的访问权限,而不是都为 public。
  • 需要继承非静态和非常量字段。

在很多情况下,接口优先于抽象类。因为接口没有抽象类严格的类层次结构要求,可以灵活地为一个类添加行为。并且从 Java 8 开始,接口也可以有默认的方法实现,使得修改接口的成本也变的很低。

深入理解 abstract class 和 interface

When to Use Abstract Class and Interface

10.继承和聚合的区别在哪。

继承指的是一个类继承另外的一个类的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系;在Java中此类关系通过关键字extends明确标识。

聚合体现的是整体与部分、拥有的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期;比如计算机与CPU、公司与员工的关系等;

11.IO模型有哪些,讲讲你理解的nio ,他和bio,aio的区别是啥,谈谈reactor模型。

12.反射的原理,反射创建类实例的三种⽅式是什么。

13.反射中,Class.forName和ClassLoader区别 。

14.描述动态代理的⼏种实现⽅式,分别说出相应的优缺点。

15.动态代理与cglib实现的区别。

16.为什么CGlib⽅式可以对接⼜实现代理。

17.final的⽤途。

final可以修饰类、方法、成员变量

  • 当final修饰类的时候,说明该类不能被继承
  • 当final修饰方法的时候,说明该方法不能被重写
    • 在早期,可能使用final修饰的方法,编译器针对这些方法的所有调用都转成内嵌调用,这样提高效率(但到现在一般我们不会去管这事了,编译器和JVM都越来越聪明了)
  • 当final修饰成员变量时,有两种情况:
    • 如果修饰的是基本类型,说明这个变量的所代表数值永不能变(不能重新赋值)!
    • 如果修饰的是引用类型,该变量所的引用不能变,但引用所代表的对象内容是可变的!

值得一说的是:并不是被final修饰的成员变量就一定是编译期常量了。比如说我们可以写出这样的代码:private final int java3y = new Randon().nextInt(20);

你有没有这样的编程经验,在编译器写代码时,某个场景下一定要将变量声明为final,否则会出现编译不通过的情况。为什么要这样设计?

在编写匿名内部类的时候就可能会出现这种情况,匿名内部类可能会使用到的变量:

  • 外部类实例变量
  • 方法或作用域内的局部变量
  • 方法的参数
class Outer {


    // string:外部类的实例变量

    String string = "";


    //ch:方法的参数

    void outerTest(final char ch) {

        // integer:方法内局部变量

        final Integer integer = 1;

        new Inner() {

            void innerTest() {

                System.out.println(string);

                System.out.println(ch);

                System.out.println(integer);

            }

        };

    }

    public static void main(String[] args) {

        new Outer().outerTest(' ');

    }

    class Inner {

    }

}

其中我们可以看到:方法或作用域内的局部变量和方法参数都要显示使用final关键字来修饰(在jdk1.7下)!

如果切换到jdk1.8编译环境下,可以通过编译的~

下面我们首先来说一下显示声明为final的原因:为了保持内部外部数据一致性

  • Java只是实现了capture-by-value形式的闭包,也就是匿名函数内部会重新拷贝一份自由变量,然后函数外部和函数内部就有两份数据。
  • 要想实现内部外部数据一致性目的,只能要求两处变量不变。JDK8之前要求使用final修饰,JDK8聪明些了,可以使用effectively final的方式

为什么仅仅针对方法中的参数限制final,而访问外部类的属性就可以随意

内部类中是保存着一个指向外部类实例的引用,内部类访问外部类的成员变量都是通过这个引用。

  • 在内部类修改了这个引用的数据,外部类再获取时拿到的数据是一致的!

那当你在匿名内部类里面尝试改变外部基本类型的变量的值的时候,或者改变外部引用变量的指向的时候,表面上看起来好像都成功了,但实际上并不会影响到外部的变量。所以,Java为了不让自己看起来那么奇怪,才加了这个final的限制。

参考资料:

  • java为什么匿名内部类的参数引用时final?https://www.zhihu.com/question/21395848

18.写出三种单例模式实现 。

单例模式:单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

单例模式有三种:懒汉式单例,饿汉式单例,登记式单例。

单例模式的好处

    1.对于频繁使用的对象,可以省略new操作花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销;

    2.由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻GC压力,缩短GC停顿时间。

1.懒汉式单例

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}  //此类不能被实例化
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

优点:第一次调用才初始化,避免内存浪费。

缺点:必须加锁synchronized 才能保证单例,(如果两个线程同时调用getInstance方法,会chuxia)但加锁会影响效率。

2.饿汉式单例

public class Singleton {
    private static final Singleton SINGLETON = new Singleton();
    private Singleton() {}  //此类不能被实例化
    public static Singleton getInstance() {
        return SINGLETON;
    }
}

优点:没有加锁,执行效率会提高。

缺点:类加载时就初始化,浪费内存。

3.登记式模式(holder)

public class Singleton {
    private Singleton() {} //构造方法是私有的,从而避免外界利用构造方法直接创建任意多实例。
    public static Singleton getInstance() {
        return Holder.SINGLETON;
    }
    private static class Holder {
       private static final Singleton SINGLETON = new Singleton();
    }
}

内部类只有在外部类被调用才加载,产生SINGLETON实例;又不用加锁。此模式有上述两个模式的优点,屏蔽了它们的缺点,是最好的单例模式。

19.如何在⽗类中为子类⾃动完成所有的hashcode和equals实现?这么做有何优劣。

20.请结合OO设计理念,谈谈访问修饰符public、private、protected、default在应⽤设计中的作⽤。

21.深拷贝和浅拷贝区别。

1. cloneable

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

public class CloneExample {
    private int a;
    private int b;
}
CloneExample e1 = new CloneExample();
// CloneExample e2 = e1.clone(); // 'clone()' has protected access in 'java.lang.Object'

重写 clone() 得到以下实现:

public class CloneExample {
    private int a;
    private int b;

    @Override
    public CloneExample clone() throws CloneNotSupportedException {
        return (CloneExample)super.clone();
    }
}
CloneExample e1 = new CloneExample();
try {
    CloneExample e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
java.lang.CloneNotSupportedException: CloneExample

以上抛出了 CloneNotSupportedException,这是因为 CloneExample 没有实现 Cloneable 接口。

应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。

public class CloneExample implements Cloneable {
    private int a;
    private int b;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

2. 浅拷贝

拷贝对象和原始对象的引用类型引用同一个对象。

public class ShallowCloneExample implements Cloneable {

    private int[] arr;

    public ShallowCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected ShallowCloneExample clone() throws CloneNotSupportedException {
        return (ShallowCloneExample) super.clone();
    }
}
ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222

3. 深拷贝

拷贝对象和原始对象的引用类型引用不同对象。

public class DeepCloneExample implements Cloneable {

    private int[] arr;

    public DeepCloneExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }

    @Override
    protected DeepCloneExample clone() throws CloneNotSupportedException {
        DeepCloneExample result = (DeepCloneExample) super.clone();
        result.arr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            result.arr[i] = arr[i];
        }
        return result;
    }
}
DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
    e2 = e1.clone();
} catch (CloneNotSupportedException e) {
    e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

4. clone() 的替代方案

使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

public class CloneConstructorExample {

    private int[] arr;

    public CloneConstructorExample() {
        arr = new int[10];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = i;
        }
    }

    public CloneConstructorExample(CloneConstructorExample original) {
        arr = new int[original.arr.length];
        for (int i = 0; i < original.arr.length; i++) {
            arr[i] = original.arr[i];
        }
    }

    public void set(int index, int value) {
        arr[index] = value;
    }

    public int get(int index) {
        return arr[index];
    }
}
CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

22.数组和链表数据结构描述,各⾃的时间复杂度。

23.error和exception的区别,CheckedException,RuntimeException的区别。

24.请列出5个运⾏时异常。

java运行时异常是可能在java虚拟机正常工作时抛出的异常。

java提供了两种异常机制。一种是运行时异常(RuntimeExepction),一种是检查式异常(checked execption)。

检查式异常:我们经常遇到的IO异常及sql异常就属于检查式异常。对于这种异常,java编译器要求我们必须对出现的这些异常进行catch 所以 面对这种异常不管我们是否愿意,只能自己去写一堆catch来捕捉这些异常。

运行时异常:我们可以不处理。当出现这样的异常时,总是由虚拟机接管。比如:我们从来没有人去处理过NullPointerException异常,它就是运行时异常,并且这种异常还是最常见的异常之一。

RuntimeExecption在java.lang包下。

常见的几种如下:

ClassCastException(类转换异常)

IndexOutOfBoundsException(下标越界异常)

NullPointerException(空指针异常)

ArrayStoreException(数据存储异常,操作数组时类型不一致)

BufferOverflowException(还有IO操作的,缓冲溢出异常)
IllegalArgumentException - 传递非法参数异常。
ArithmeticException - 算术运算异常
ArrayStoreException - 向数组中存放与声明类型不兼容对象异常
NegativeArraySizeException - 创建一个大小为负数的数组错误异常
NumberFormatException - 数字格式异常
SecurityException - 安全异常
UnsupportedOperationException - 不支持的操作异常

25.在⾃⼰的代码中,如果创建⼀个java.lang.String类,这个类是否可以被类加载器加载?为什么?

26.说⼀说你对java.lang.Object对象中hashCode和equals⽅法的理解。在什么场景下需要重新实现这两个⽅法。

27.在jdk1.5中,引⼊了泛型,泛型的存在是⽤来解决什么问题。

28.这样的a.hashcode() 有什么⽤,与a.equals(b)有什么关系。

29.有没有可能2个不相等的对象有相同的hashcode。

30.Java中的HashSet内部是如何⼯作的。

31.什么是序列化,怎么序列化,为什么序列化,反序列化会遇到什么问题,如何解决。

32.java8的新特性。

猜你喜欢

转载自blog.csdn.net/qq_21822741/article/details/84501615