java八股文复习-----2024/03/04----基础

时间:2024-03-09 12:04:46

相关资源
大彬八股文


2024八股文


2024秋招八股文


1.了解Java的包装类型吗?为什么需要包装类?

Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。

为了让基本类型也具有对象的特征,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

List<Inteter> list=new ArrayList<Integer>();

list集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。

2.自动装箱和拆箱

Java中基础数据类型与它们对应的包装类见下表:

原始类型 包装类型
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short
double Double

装箱:将基础类型转化为包装类型。

拆箱:将包装类型转化为基础类型。

当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:

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

示例代码:

Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()

下面看一道常见的面试题:

Integer a = 100;
Integer b = 100;
System.out.println(a == b);

Integer c = 200;
Integer d = 200;
System.out.println(c == d);

输出:

true
false

为什么第三个输出是false?看看 Integer 类的源码就知道啦。

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

Integer c = 200; 会调用 调⽤Integer.valueOf(200)。而从Integer的valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做一个cache。

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;
    }
 
}

这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然比较的结果是不相等的。

3.int和Integer有什么区别

实现原理:javac编译器的语法糖,底层是通过Integer.valueOf()和Integer.intValue()方法实现。

区别:

Integer是int的包装类,int则是java的一种基本数据类型
Integer变量必须实例化后才能使用,而int变量不需要
Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值
Integer的默认值是null,int的默认值是0

4.String能被继承吗? 为什么用final修饰 ?

不能被继承,因为String类有final修饰符,而final修饰的类是不能被继承的。

String 类是最常用的类之一,为了效率,禁止被继承和重写。
为了安全。String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。


5.String 为什么不可变?

先看看什么是不可变的对象。

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

接着来看Java8 String类的源码:

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
}

从源码可以看出,String对象其实在内部就是一个个字符,存储在这个value数组里面的。

value数组用final修饰,final 修饰的变量,值不能被修改。因此value不可以指向其他对象。

String类内部所有的字段都是私有的,也就是被private修饰。而且String没有对外提供修改内部状态的方法,因此value数组不能改变。

所以,String是不可变的。

那为什么String要设计成不可变的?

主要有以下几点原因:

1.线程安全。同一个字符串实例可以被多个线程共享,因为字符串不可变,本身就是线程安全的。
2.支持hash映射和缓存。因为String的hash值经常会使用到,比如作为 Map 的键,不可变的特性使得 hash 值也不会变,不需要重新计算。
3.出于安全考虑。网络地址URL、文件路径path、密码通常情况下都是以String类型保存,假若String不是固定不变的,将会引起各种安全隐患。比如将密码用String的类型保存,那么它将一直留在内存中,直到垃圾收集器把它清除。假如String类不是固定不变的,那么这个密码可能会被改变,导致出现安全隐患。
4.字符串常量池优化。String对象创建之后,会缓存到字符串常量池中,下次需要创建同样的对象时,可以直接返回缓存的引用。

既然我们的String是不可变的,
它内部还有很多substring, replace, replaceAll这些操作的方法。
这些方法好像会改变String对象?怎么解释呢?

其实不是的,我们每次调用replace等方法,其实会在堆内存中创建了一个新的对象。然后其value数组引用指向不同的对象

6.String, StringBuffer 和 StringBuilder区别

  1. 可变性

String 不可变
StringBuffer 和 StringBuilder 可变
2. 线程安全

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


StringBuffer 与 StringBuilder 中的方法和功能完全是等价的,

只是StringBuffer 中的方法大都采用了 synchronized 关键字进行修饰,因此是线程安全的,而 StringBuilder 没有这个修饰,可以被认为是线程不安全的。

在单线程程序下,StringBuilder效率更快,因为它不需要加锁,不具备多线程安全而StringBuffer则每次都需要判断锁,效率相对更低

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

indexOf():返回指定字符的索引。
charAt():返回指定索引处的字符。
replace():字符串替换。
trim():去除字符串两端空白。
split():分割字符串,返回一个分割后的字符串数组。
getBytes():返回字符串的 byte 类型数组。
length():返回字符串长度。
toLowerCase():将字符串转成小写字母。
toUpperCase():将字符串转成大写字符。
substring():截取字符串。
equals():字符串比较。

8.String s = “xyz” 和 String s = new String(“xyz”) 区别?

两个语句都会先去字符串常量池中检查是否已经存在 “xyz”,如果有则直接使用,如果没有则会在常量池中创建 “xyz” 对象

另外,String s = new String(“xyz”) 还会通过 new String() 在堆里创建一个内容与 “xyz” 相同的对象实例。

所以前者其实理解为被后者的所包含。

9.基本类型有哪些转换方式?

分为自动类型转换和强制类型转换两种。

1.自动类型转换:是一种小类型到大类型的转换
2.强制类型转换:需要在强制类型转换的变量前面加上括号,然后在括号里面标注要转换的类型。

在这里插入图片描述

10.&和&&的区别?

& 是按位与运算符,
&& 是短路与运算符,它只在第一个操作数为true时才计算第二个操作数。

& 可以用于任何类型的操作数:
&& 只能用于布尔类型的操作数。

11.java数据类型

在这里插入图片描述

12.JVM执行过程

在这里插入图片描述

13.那String一般都存储在JVM的哪块区域呢?

字符串在JVM中的存储分两种情况,

一种是String对象,存储在JVM的堆栈中。

一种是字符串常量,存储在常量池里面。

14.什么情况下字符串会存储在常量池呢?

当通过字面量进行字符串声明时,

比如String s = “程序新大彬”;,这个字符串在编译之后会以常量的形式进入到常量池。

15.Object常用方法有哪些?

Java面试经常会出现的一道题目,Object的常用方法。下面给大家整理一下。

Object常用方法有:toString()、equals()、hashCode()、clone()等。

toString

默认输出对象地址。

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public static void main(String[] args) {
        System.out.println(new Person(18, "程序员大彬").toString());
    }
    //output
    //me.tyson.java.core.Person@4554617c
}

可以重写toString方法,按照重写逻辑输出对象值。

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return name + ":" + age;
    }

    public static void main(String[] args) {
        System.out.println(new Person(18, "程序员大彬").toString());
    }
    //output
    //程序员大彬:18
}

equals
默认比较两个引用变量是否指向同一个对象(内存地址)。

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
       this.age = age;
       this.name = name;
    }

    public static void main(String[] args) {
        String name = "程序员大彬";
        Person p1 = new Person(18, name);
        Person p2 = new Person(18, name);

        System.out.println(p1.equals(p2));
    }
    //output
    //false
}

可以重写equals方法,按照age和name是否相等来判断:

public class Person {
    private int age;
    private String name;

    public Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (o instanceof Person) {
            Person p = (Person) o;
            return age == p.age && name.equals(p.name);
        }
        return false;
    }

    public static void main(String[] args) {
        String name = "程序员大彬";
        Person p1 = new Person(18, name);
        Person p2 = new Person(18, name);

        System.out.println(p1.equals(p2));
    }
    //output
    //true
}

hashCode
将与对象相关的信息映射成一个哈希值,默认的实现hashCode值是根据内存地址换算出来。

public class Cat {
    public static void main(String[] args) {
        System.out.println(new Cat().hashCode());
    }
    //out
    //1349277854
}

clone
Java赋值是复制对象引用,如果我们想要得到一个对象的副本,使用赋值操作是无法达到目的的。Object对象有个clone()方法,实现了对象中各个属性的复制,但它的可见范围是protected的。

protected native Object clone() throws CloneNotSupportedException;

所以实体类使用克隆的前提是:

实现Cloneable接口,这是一个标记接口,自身没有方法,这应该是一种约定。调用clone方法时,会判断有没有实现Cloneable接口,没有实现Cloneable的话会抛异常CloneNotSupportedException。
覆盖clone()方法,可见性提升为public。

public class Cat implements Cloneable {
    private String name;

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

    public static void main(String[] args) throws CloneNotSupportedException {
        Cat c = new Cat();
        c.name = "程序员大彬";
        Cat cloneCat = (Cat) c.clone();
        c.name = "大彬";
        System.out.println(cloneCat.name);
    }
    //output
    //程序员大彬
}

getClass
返回此 Object 的运行时类,常用于java反射机制。

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public static void main(String[] args) {
        Person p = new Person("程序员大彬");
        Class clz = p.getClass();
        System.out.println(clz);
        //获取类名
        System.out.println(clz.getName());
    }
    /**
     * class com.tyson.basic.Person
     * com.tyson.basic.Person
     */
}

wait
**当前线程调用对象的wait()方法之后,当前线程会释放对象锁,进入等待状态。**等待其他线程调用此对象的notify()/notifyAll()唤醒或者等待超时时间wait(long timeout)自动唤醒。线程需要获取obj对象锁之后才能调用 obj.wait()。

notify
obj.notify()唤醒在此对象上等待的单个线程,选择是任意性的
notifyAll()唤醒在此对象上等待的所有线程。

16.讲讲深拷贝和浅拷贝?

浅拷贝:拷⻉对象和原始对象的引⽤类型引用同⼀个对象。

以下例子,Cat对象里面有个Person对象,调用clone之后,克隆对象和原对象的Person引用的是同一个对象,这就是浅拷贝。

public class Cat implements Cloneable {
    private String name;
    private Person owner;

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

    public static void main(String[] args) throws CloneNotSupportedException {
        Cat c = new Cat();
        Person p = new Person(18, "程序员大彬");
        c.owner = p;

        Cat cloneCat = (Cat) c.clone();
        p.setName("大彬");
        System.out.println(cloneCat.owner.getName());
    }
    //output
    //大彬
}

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

以下例子,在clone函数中不仅调用了super.clone,而且调用Person对象的clone方法(Person也要实现Cloneable接口并重写clone方法),从而实现了深拷贝。可以看到,拷贝对象的值不会受到原对象的影响。

public class Cat implements Cloneable {
    private String name;
    private Person owner;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Cat c = null;
        c = (Cat) super.clone();
        c.owner = (Person) owner.clone();//拷贝Person对象
        return c;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Cat c = new Cat();
        Person p = new Person(18, "程序员大彬");
        c.owner = p;

        Cat cloneCat = (Cat) c.clone();
        p.setName("大彬");
        System.out.println(cloneCat.owner.getName());
    }
    //output
    //程序员大彬
}

浅拷贝:只复制某个对象的引用,而不复制对象本身,新旧对象还是共享同一块内存

深拷贝:深拷贝会创造一个一摸一样的对象,新对象和原对象不共享内存,修改新对象不会改变原对对象。