八股文01:基础篇

发布于:2025-06-26 ⋅ 阅读:(23) ⋅ 点赞:(0)

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

一共有8种:

boolean    1 bit,不到一个字节
byte    8 bit,1字节
short    16 bit,2字节
char    16 bit,2字节
int    32 bit,4字节
float    32 bit,4字节
long    64 bit,8字节
double    64 bit,8字节

基本类型 大小(字节) 默认值 封装类
byte 1 (byte)0 Byte
short 2 (short)0 Short
int 4 0 Integer
long 8 0L Long
float 4 0.0f Float
double 8 0.0d Double
boolean - false Boolean
char 2 \u0000(null) Character

说明:

1、int是基本数据类型,Integer是包装类型。int的默认值为0,Integer的默认值为null,因此Integer能区分出null和0。

2、当声明基本数据类型的变量时,JVM会自动为其分配空间,而声明引用类型变量时,JVM只会为其引用分配空间,必须通过实例化对象为其开辟空间才可以赋值。

3、数组对象也是一个引用类型,当将一个数组变量赋值给另一个变量时,仅是复制了地址,当另一个引用对数组元素修改时,在另一个数组引用中是可以看到的。

4、boolean类型虽然有定义,但是并没有精确的定义,java虚拟机并没有为boolean定义任何字节码指令。经过编译后,代码中的boolean变量会使用int来代替,而boolea数组使用byte数组代替。使用int的目的是便于存取。

2.Java语言有哪些特点

1、简单易学、有丰富的类库

2、面向对象(Java最重要的特性,让程序耦合度更低,内聚性更高)

3、与平台无关性(JVM是Java跨平台使用的根本)

4、可靠安全

5、支持多线程

3.线程池都有哪些状态:

1.running:运行中

2.shoutdown:关闭中

3.stop:停止中

4.terminated:终止

线程池的状态

4.Volatile关键字的特性:

可见性+有序性,没有原子性

Volatile

5.面向对象和面向过程的区别:

面向过程:

指的是分析解决问题的步骤,通过函数将这些步骤一一的实现,在使用的时候直接调用即可,面向过程具有较高的性能.

嵌入式,单片机开发等都使用面向对象开发的.

面向对象:

把构成问题的事物分解成对象,通过创建对象,将现实世界中抽象的或具体的事物抽象出来;创建对象的目的是为了描述某个事物在解决整个问题过程中产生的行为.面向对象具有封装,继承,多态的特性,因此具有可扩展性,维护性和复用性.具有较高的内聚性.

性能来说,面向对象的性能要低于面向过程.

6.标识符的命名规则:

硬性要求

标识符可以包含数字,字母,_,$,不能以数字开头,不能是关键字。

非硬性要求

      类名规范:首字母单词大写,后面的每个单词的首字母大写(采用大驼峰的形式);

      方法名和变量名规范:首单词字母小写,后面每个单词首字母大写(小驼峰形式);

7.instanceof关键字的作用:

instanceof关键字是一个双目运算符,用来判断一个对象是否为一个类的实例。

语法: boolean t = obj  instanceof  class;

obj为一个对象,class是一个类或接口;当obj为class的实例,或其直接或间接子类的实例,或其接口的实现类时,返回值都为true,否则返回false.

注意: 

1.obj 必须为引用类型,不能时基本数据类型。当obj为基本数据类型时,编译不通过。

2,当obj为null是,默认返回false.

8.java自动装箱和拆箱:

装箱:将基本数据类型转换成引用类型,

        eg:int -> Integer.通过调用Integer的valueOf(int)方法;

拆箱:将引用类型转换成基本数据类型,

        eg:Integer -> int 通过调用Integer的IntValue()方法.

在javaSE5之前,若创建一个值为10的Integer类型的变量,需要这样操作:

Integer n = new Integer(10);

javaSE5之后,提供了自动拆装箱的特性,创建一个值为10的Integer类型的变量,只需要这样:

Integer n = 10;

扩展:分别创建两个值为10和200的Integer对象的时候,判断是否相等:

Integer n1= 10;

Integer n2 = 10;

Integer n3= 200;

Integer n4 = 200;

System.out.println(n1==n2);

System.out.println(n3==n4);
//结果: true
//     false

当创建包装类的时候,底层会调用Integer类的valueOf(int)方法,当值在-127--128时,返回的是已经创建好的对象的引用,而不在该范围内的对象,就会重新分配内存,创建一个新的对象。(具体实现细节可以看Integer类的valueOf方法的Integer.Cache类的代码)

上面的10是直接获取的引用,指向的都是同一个对象;而200是每次创建一个新的对象;因此两个结果不同。

再看这段代码:

Double n1= 10.0;
Double n2 = 10.0;

Double n3= 200.0;
Double n4 = 200.0;

System.out.println(n1==n2);
System.out.println(n3==n4);

这个结果都为false,

因为在某个范围内的整型数值的个数是有限的,而浮点数却不是。

9.重写和重载的区别

重载(Overload):在一个类中,两个或多个方法的方法名相同,参数列表不同,这多个同名方法就构成了重载

注意:1,重载发生在同一个类中,是多态性的一种表现,

           2、参数列表可以是个数,类型,或顺序不同,

          3.构成重载方法的返回类型可以相同,也可以不同,但不能以返回类型不同构成重载。

/**
 * 重载
 */
public class OverLoad01 {
    //这三个方发都构成重载
    public int sum(int n1,int n2){
        return n1+n2;
    }
     public int sum(int n1,int n2,int n3){
        return n1+n2+n3;
    }
     public double sum(double d1,double d2,double d3){
        return d1+d2+d3;
    }
}

重写(Override):当子类继承父类的某个方法时,不想完全使用父类的方法功能,想实现自己的功能,对该方法的方法体进行重新实现,这就是重写。

注意:1.重写是发生在父子类上的

        2、子类重写方法的方法名,参数列表,返回类型(子类方法的返回类型是父类返回类型的直接或间接子类可以)都不能改变。

        3.子类函数的访问修饰权限不能少于父类的。

访问权限范围: public>protected>默认>private

        4.重写不能抛出新的检查异常,或比被重写方法申明更加宽泛的检查型异常。

/**
 * 重写
 */
class Parent{
    
    public int sum(int n1,int n2){
        return n1+n2;
    }
}
class Sun extends Parent{
    //子类重写了父类的sum方法
    @Override
    public int sum(int n1, int n2) {
       return n1*10 + n2*10;
    }
}

10.== 和equals的区别:

==:比较的是变量的地址,判断两个对象是否指向同一个对象.

注意:1.操作符两边必须是同一类型(或父子关系),才能编译过去,

        2,比较的是地址

        3.若是阿拉伯数字返回的是其值是否相等。

int a = 10;
Long b = 10L;
Double c = 10.0;
//这三个值的比较都为true,因为都是指向值为10的堆

equals:  比较的是两个对象的内容是否相等,由于所有类都继承自java.long.Object,所以适用于所有对象。若类未重写equals方法,调用的就是Object类的equals方法,而Object类的equals方法返回的是 == 的判断就结果。

注意:1.使用equals方法对常量进行比较时,把常量写在前面,因为调用Object的equals方法,当object为空时,会抛出NullPointException空指针异常.

11.HashCode的作用:

java集合类有两类:List,Set,List中的元素是有序且可重复的;Set集合中的元素是无序,不可重复的,若集合中已经存在很多元素了,当向Set集合中插入元素时,就需要多次调用equals方法,用来判断要插入的元素是否已经存在,效率较低。

于是哈希算法就是用来提高在集合中查找元素效率的:将集合分成若干个存储区域,每个对象都可以计算出一个哈希码,将哈希码进行分组,每组对应不同的存储区域,根据一个对象的哈希码就可以确定所属的存储区域了。

HashCode 的作用就是根据对象的内存地址计算出来一个值。

当要向Set集合中新增元素时,先调用该对象的HashCode方法,计算出哈希值,就能定位到它的物理地址,若该位置没有元素,则直接插入;若已经存在元素,就调用equals方法,与要插入的元素进行比较,若为true,则该元素已经存在,放弃插入;若不相等,则将其散列到哈希表的其他地址。这样调用equals方法的次数就会大大降低。

12.HashMap和HashTable的区别:

1.两者的父类不同:

HashMap的父类是AbstractMap,而HashTable的父类是Dictionary.

他们都实现了cloneable,map,serializable接口

2.对外提供的接口不同:

HashTable多提供了elements()方法和contains()方法,这两个方法HashMap都没有。

elements方法,继承自HashTable的父类Dictionary,用于返回Value的枚举;

contains方法,判断HashTable集合是否包含传入的value值;与HashMap中的containsKey用法相同,底层上HashMap的containsKey方法调用的就是HashTable的contains方法。

3.线程安全问题:

HashMap是线程不安全的,在多线程下,可能会产生思死锁问题,需要自己手动解决。

HashTable在每个方法上加了同步锁,是线程安全的,可直接用在多线程中。但是HashTable的效率远降低HashMap;在大多数情况下,都是单线程,更多使用HahdMap。在多线程操作下,想让线程安全,可以使用CurrentHahsMap,CurrentHashMap也是线程安全的,但效率要高于HashTable.CurrentHashMap使用的是分段锁,并没有对整个数据都上锁。 

4.初始容量及扩容方式不同:

扩容时机为 当前元素个数>=当前容量*负载因子时,就进行扩容,两者的负载因子默认都是0.75

HashMap的初始容量为16,扩容方式为原来的2倍。

HashTable的初始容量为11,每次扩容为原来的2倍+1;

5.对null的支持不同:

HashMap的key和val都可以为空,但key需要保证其唯一性,只能有一个null,val可以有多个null;

HashTable的key和val都不可以为null;

6.底层结构不同:

jdk1.7之前都是数组+链表;1.7之后,HashMap底层加了红黑树。

7.添加元素时计算哈希值的方法不同:

HashTable时直接调用key的HashCode方法;

HashMap采用的是自定义的哈希算法。

13.OOM的情况有哪些:

OOM:内存溢出异常.除了程序计数器,其他几个内存区域都有可能发生内存溢出异常.

1.堆溢出:

异常信息为:java.lang.OutOfMemoryError:Java heap spacess

堆是用来创建对象实例的,当不断的创建对象,并且保证GC roots到对象的路径是可达的,当创建对象的个数达到内存的最大容量时,就会发生内存溢出异常.

2.虚拟机栈溢出和本地方法栈溢出:

当线程请求的深度大于虚拟机所允许的最大深度,就会抛出:StackOverflowError异常。

当虚拟机在扩展栈时,我发分配到所需的内存空间,就会抛出: OutOfMemoryError异常

3.方法区溢出:

方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可能是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。

14.线程,进程和程序的区别:

线程是比进程更小的执行单位,一个进程在执行的过程中,可以产生多个线程。与进程不同的是,同一个进程产生的多个线程共享一片存储空间和一组系统资源,因此当创建一个线程或多个线程相互切换工作的时候,负担要比进程小得多。线程也被称为轻量级进程。

进程是程序的一次执行过程,是程序执行的基本单位,因此,进程是动态的,系统执行的一个程序,就是从创建,运行到销毁的过程,

程序是含有指令和文件的数据,被存储在磁盘或其他数据存储的设备中,程序就是静态的代码.

15.Collection和Collections的区别:

Collection是java集合类的上层接口,包含多个实类:Set,List,ArrayList,LinkedList,Stack,Vecor等.

Collecations是一个工具类,里面有很多对集合操作的静态方法,相当于集合的帮助类.用于实现对集合的搜索,排序,想爱你成安全等操作,该类不能实例化.

16.java的四种引用:

强引用:强引用是平时使用最多的引用,通过new出来的对象都是强引用,当内存不足时,强引用的对象不能被垃圾收集器回收,只有该对象不再被使用了,才会被回收。

软引用:软引用是比强引用弱一点的引用,当内存不足时,会被垃圾收集器回收.

弱引用:弱引用是比软引用更弱一点的引用,一旦JVM垃圾收集器发现了他,就会将他回收。

虚引用:虚引用的回收机制和弱引用差不多,会被放在ReferenceQueue中,其他引用是被回收后才放到ReferenceQueue队列中,因此虚引用一般用于引用销毁前的处理工作.

17、java创建对象的几种方式:

1.通过new关键字:

User user = new User();

2.通过反射:

通过反射创建对象分为两步:

1>.获取到类对象的Class.

2>.通过反射创建类对象的实例。

获取到类对象的Class,有三种方法:

1、对象名.getClass()    eg:user.getClass();

2.类名.class()    eg: User.class();

3.Class.forName("类的全路径名”)  eg: Class.forName("com.test.User")

通过反射创建类对象的实例对象,有两种方法:

1,获取到类对象的Class后,调用newInstances()方法

2.调用类对象的构造方法。

注意:通过反射创建对象时,给对象类必须显示无参构造方法,否则会报错。

class Use {
    private String name;
    private int age;
    private Dog dog;
//通过反射创建对象时,必须要有显示的无参构造方法,否则会报错
    public User() {
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

class Dog implements  Serializable{
    private String dogName;
//    public Dog(){};

    public Dog(String dogName) {
        this.dogName = dogName;
    }

    public String getDogName() {
        return dogName;
    }

    public void setDogName(String dogName) {
        this.dogName = dogName;
    }

}

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        User user = User.class.newInstance();
        user.setDog(new Dog("小狗"));
        user.setName("zhang");
        user.setAge(10);
        System.out.println("通过反射获取的对象: "+user);
    }

结果:

通过反射获取的对象: name: zhang , age: 10 ,dog:小狗

3.使用clone:

要克隆的对象的类需要实现Cloneable接口才可以克隆。

克隆没有调用任何过剖构造函数,在clone方法中,将要拷贝的对象重新分配一块存储空间,将对象全部拷贝过去。

//创建User对象,实现Cloneable接口:
class User implements Cloneable{
    private String name;
    private int age;

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    public static void main(String[] args) throws CloneNotSupportedException {
        //创建对象:
        User user = new User("zhangsan", 20);
        //通过克隆方法,创建对象:(clone方法存在受查异常,需要处理,这里向上抛出)
        Object cloneUser = user.clone();
        System.out.println("user:"+ user);
        System.out.println("cloneUser: "+ cloneUser);
    }
}

结果:

user:name: zhangsan , age: 20
cloneUser: name: zhangsan , age: 20

4.反序列化

反序列化的前提是已经将对象进行序列化了,

序列化又是啥呢?就是将创建的对象的信息状态 转换为可以存储的过程。

序列化和反序列化是互逆的过程。

反序列化就是将存储的对象的信息再给读取出来。

就类似与买东西,在超市用袋子将物品装起来,到家了再将他拿出来,装的过程就是序列化,往外拿的过程就是反序列化。

要想实现序列化,还需要是实现Serializable接口


import java.io.*;

class Dog implements  Serializable{
    private String dogName;

    public Dog(String dogName) {
        this.dogName = dogName;
    }

    public String getDogName() {
        return dogName;
    }

    public void setDogName(String dogName) {
        this.dogName = dogName;
    }
}

class User implements Serializable {
    private String name;
    private int age;
    private Dog dog;

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

    public User(Dog dog, int age, String name) {
        this.dog = dog;
        this.age = age;
        this.name = name;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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


    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //序列化和反序列化:
        Dog dog1 = new Dog("dog1");
        User user1 = new User(dog1, 20, "haha");
        User user2 = new User(dog1, 10, "lisi");
        //序列化:
//将对象装到一个文件包中:
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("user"));
        outputStream.writeObject(user1);
        outputStream.writeObject(user2);
        //反序列化:
//从文件中将存储的对象取出来:
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("user"));
        User userOne = (User) inputStream.readObject();
        User userTwo = (User) inputStream.readObject();
        System.out.println("user1: "+userOne);
        System.out.println("user2: "+userTwo);
    }
}

结果:成功获取到对象:

user1: name: haha , age: 20 ,dog:dog1
user2: name: lisi , age: 10 ,dog:dog1

18.ArrayList和LinkedList的区别:

ArrayList是基于动态数组的数据结构,地址是连续的,一旦存储好了,查询效率就会很高,但由于地址来连续,当要修改数据时,效率较低。

LinkedList是基于链表的数据结构,地址是任意的,所以开辟地址空间的时候,不需要等一个连续的地址空间,对于新增和删除的操作,LinkedList比较有优势,当执行查询操作的时候,就需要遍历整个链表,查询效率较低。

19.已经有了数组,为啥还要ArrayList?

创建数组时,需要指定数组大小,在不确定要插入多少数据的情况下,使用数组来存储数据就很不合适了。

初始化的时候使用的时默认大小,当元素个数达到一定程度后,就会自动扩容。ArrayList的底层是动态数组。

20.HashMap的长度为什么必须是2的N次方?

HashMap为了提高存取数据的效率,计算hash值时要尽量减少哈希碰撞,尽量把数据均匀分布,通过取模操作进行散列。

计算存储索引公式:index = hash % length;

在计算机中,%运算的效率很低,当length的大小为2的N次幂时,hash%length等价于 hash&(lengh-1) . 这就是为什么hashMap的长度为2的N次方了。

21.红黑树的特点:

1.每个节点都是黑色或红色

2.根节点是黑色

3.叶子节点一定时黑色(null节点)

4.若一个节点是红色,其子节点一定是黑色

5,从任意节点到其子孙节点的所有路径上的黑色节点个数都相同。

22.java异常的处理方式:

1.通过try-catch-finally进行异常捕获:

try:监控可能出现异常的代码

catch:对出现的异常进行捕获,并进行处理

finally:负责清除各种资源,不管石佛出现异常,只要有finally代码块就会执行。

2.将异常抛出:

通过throw new XXException,将异常抛出,可以是自定义异常,也可以是已经定义过的异常。

3.声明异常:

通过throw,一般在方法名的后面。

23,深拷贝和浅拷贝:

浅拷贝:仅创建了一个对象的引用,引用指向的还是原来的对象。(两个引用指向同一个对象)

深拷贝:创建一个对象的引用,并开辟一块内存空间,将原来引用的对象也进行拷贝一份,新的引用指向新创建的对象地址。


网站公告

今日签到

点亮在社区的每一天
去签到