【Java】知识梳理

本文详细介绍了Java的基础知识,包括面向对象的三大特征(封装、继承和多态),访问修饰符的权限,以及代码执行和类型转换的规则。还讨论了String类的特性,如常量池、自动装箱和比较方法。此外,深入探讨了并发与并行的概念,线程的生命周期,以及线程池的工作原理。最后,提到了HashMap的实现和扩容策略,以及Java内存模型中的对象创建和内存分布。
摘要由CSDN通过智能技术生成

基础知识

本文为Java基础知识个人梳理

1.面向对象的三个基本特征

封装、继承和多态

封装:隐藏部分对象的属性和实现细节。通过这种方式对象对内部数据提供了不同级别的保护。

继承:让某个类型的对象获得另一个类型的对象的属性的方法。

多态:对于同一行为,不同子类可以具有不同的表现形式。多态存在的三个条件:1.继承2.重写3.父类引用指向子类对象

2.访问修饰符的权限

public:当前类、同包、子类、其他包

protected:当前类、同包、子类

private:当前类

3.以下两个代码块能正常编译和执行吗?

    // 代码块1
    short s1 = 1; s1 = s1 + 1;
    // 代码块2
    short s1 = 1; s1 += 1;

代码块1编译报错,错误原因:从int转换到short可能会有损失。

代码块2正常。s1 += 1 等同于 s1 = (short)(s1 + 1)

4.以下指令输出结果

public static void main(String[] args) {
    Integer a = 128, b = 128, c = 127, d = 127;
    System.out.println(a == b);
    System.out.println(c == d);
}

false,true

执行 Integer a = 128,相当于执行:Integer a = Integer.valueOf(128),基本类型自动转换为包装类的过程称为自动装箱(autoboxing)。

在 Integer 中引入了 IntegerCache 来缓存一定范围的值,IntegerCache 默认情况下范围为:-128~127。

所以,c和d是相同对象,a和b是不同对象。

5.最有效率的方法计算2乘以8?

2<<3

6.&和&&的区别?

&&:逻辑与运算符。当运算符左右两边的表达式都为 true,才返回 true。同时具有短路性,如果第一个表达式为 false,则直接返回 false。

&:逻辑与运算符、按位与运算符。

​ 按位与运算符:用于二进制的计算,只有对应的两个二进位均为1时,结果位才为1 ,否则为0。

​ 逻辑与运算符:& 在用于逻辑与时,和 && 的区别是不具有短路性。所在通常使用逻辑与运算符都会使用 &&,而 & 更多的适用于位运算。

7.String是Java基本数据类型吗?

不是, Java 中的基本数据类型只有8个:byte、short、int、long、float、double、char、boolean;除了基本类型(primitive type),剩下的都是引用类型(reference type) 。

基本数据类型:数据直接存储在栈上

引用数据类型区别:数据存储在堆上,栈上只存储引用地址

8.String类可以继承吗?

不行。String 类使用 final 修饰,无法被继承。

9.String和StringBuilder、StringBuffer的区别?

String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。

StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。

StringBuilder:StringBuffer 的非线程安全版本,没有使用 synchronized,具有更高的性能,推荐优先使用。

10.String s = new String(“xyz”) 创建了几个字符串对象?

一个或两个。如果字符串常量池已经有“xyz”,则是一个;否则,两个。

当字符创常量池没有 “xyz”,此时会创建如下两个对象:

一个是字符串字面量 “xyz” 所对应的、驻留(intern)在一个全局共享的字符串常量池中的实例,此时该实例也是在堆中,字符串常量池只放引用。

另一个是通过 new String() 创建并初始化的,内容与"xyz"相同的实例,也是在堆中。

11.== 和 equals 的区别是什么?

==: 运算符,用于比较基础类型变量和引用类型变量。

	对于基础类型变量,比较的变量保存的值是否相同,类型不一定要相同。 

	对于引用类型变量,比较的是两个对象的地址是否相同。 

equals:Object 类中定义的方法,通常用于比较两个对象的值是否相等。

​ equals 在 Object 方法中其实等同于 ==,但是在实际的使用中,equals 通常被重写用于比较两个对象的值是否相同。

12.什么是反射

反射是指在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为反射机制。

13.深拷贝和浅拷贝的区别是什么

浅拷贝:

​ 对于基础数据类型:直接复制数据值;

​ 对于引用数据类型:只是复制了对象的引用地址,新旧对象指向同一个内存地址,修改其中一个对象的值,另一个对象的值随之改变。

深拷贝:

​ 对于基础数据类型:直接复制数据值;

​ 对于引用数据类型:开辟新的内存空间,在新的内存空间里复制一个一模一样的对象,新老对象不共享内存,修改其中一个对象的值,不会影响另一个对象。

14.并发和并行有什么区别?

并发:两个或多个事件在同一时间间隔发生。

并行:两个或者多个事件在同一时刻发生。

并行是真正意义上,同一时刻做多件事情,而并发在同一时刻只会做一件事件,只是可以将时间切碎,交替做多件事情。

15.Java静态变量和成员变量的区别

成员变量存在于堆内存中。静态变量存在于方法区中。

成员变量与对象共存亡,随着对象创建而存在,随着对象被回收而释放。静态变量与类共存亡,随着类的加载而存在,随着类的消失而消失。

成员变量所属于对象,所以也称为实例变量。静态变量所属于类,所以也称为类变量。

成员变量只能被对象所调用 。静态变量可以被对象调用,也可以被类名调用。

16.求以下程序运行结果

public class InitialTest {
    public static void main(String[] args) {
        A ab = new B();
        ab = new B();
    }
}
class A {
    static { // 父类静态代码块
        System.out.print("A");
    }
    public A() { // 父类构造器
        System.out.print("a");
    }
}
class B extends A {
    static { // 子类静态代码块
        System.out.print("B");
    }
    public B() { // 子类构造器
        System.out.print("b");
    }
}

执行结果:ABabab

1.静态变量只会初始化(执行)一次。

2.当有父类时,完整的初始化顺序为:父类静态变量(静态代码块)->子类静态变量(静态代码块)->父类非静态变量(非静态代码块)->父类构造器 ->子类非静态变量(非静态代码块)->子类构造器 。

17.重载(Overload)和重写(Override)的区别?

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。

重载:一个类中有多个同名的方法,但是具有有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)。

重写:发生在子类与父类之间,子类对父类的方法进行重写,参数都不能改变,返回值类型可以不相同,但是必须是父类返回值的派生类。即外壳不变,核心重写!重写的好处在于子类可以根据需要,定义特定于自己的行为。

18.抽象类(abstract class)和接口(interface)有什么区别?

抽象类只能单继承,接口可以多实现。

抽象类可以有构造方法,接口中不能有构造方法。

抽象类中可以有成员变量,接口中没有成员变量,只能有常量(默认就是 public static final)

抽象类中可以包含非抽象的方法,在 Java 7 之前接口中的所有方法都是抽象的,在 Java 8 之后,接口支持非抽象方法:default 方法、静态方法等。Java 9 支持私有方法、私有静态方法。

抽象类中的抽象方法类型可以是任意修饰符,Java 8 之前接口中的方法只能是 public 类型,Java 9 支持 private 类型。

19.Error 和 Exception 有什么区别?

Error 和 Exception 都是 Throwable 的子类,用于表示程序出现了不正常的情况。区别在于:

Error 表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题,比如内存溢出,不可能指望程序能处理这样的情况。

Exception 表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题,也就是说,它表示如果程序运行正常,从不会发生的情况。

20.jdk1.8新特性

接口默认方法:Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可

Lambda 表达式和函数式接口:Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中) 。

Stream API:用函数式编程方式在集合类上进行复杂操作的工具,配合Lambda表达式可以方便的对集合进行处理。

21.wait() 和 sleep() 方法的区别

来源不同:sleep() 来自 Thread 类,wait() 来自 Object 类。

对于同步锁的影响不同:sleep() 不会该表同步锁的行为,如果当前线程持有同步锁,那么 sleep 是不会让线程释放同步锁的。wait() 会释放同步锁,让其他线程进入 synchronized 代码块执行。

使用范围不同:sleep() 可以在任何地方使用。wait() 只能在同步控制方法或者同步控制块里面使用,否则会抛 IllegalMonitorStateException。

恢复方式不同:两者会暂停当前线程,但是在恢复上不太一样。sleep() 在时间到了之后会重新恢复;wait() 则需要其他线程调用同一对象的 notify()/nofityAll() 才能重新恢复。

22.线程的 join() 方法是干啥用的?

用于等待当前线程终止。如果一个线程A执行了 threadB.join() 语句,其含义是:当前线程A等待 threadB 线程终止之后才从 threadB.join() 返回继续往下执行自己的代码。

23.编写多线程程序有几种实现方式?

通常来说,可以认为有三种方式:1)继承 Thread 类;2)实现 Runnable 接口;3)实现 Callable 接口。

其中,Thread 其实也是实现了 Runable 接口。Runnable 和 Callable 的主要区别在于是否有返回值。

24.Thread 调用 start() 方法和调用 run() 方法的区别

run():普通的方法调用,在主线程中执行,不会新建一个线程来执行。

start():新启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到 CPU 时间片,就开始执行 run() 方法。

25.如何检测死锁

死锁的四个必要条件:

1.互斥条件: 在一段时间内某资源仅为一个进程所占有。

2.请求和保持条件: 进程已经获得了至少一个资源,但又对其他资源发出请求,而该资源已被其他进程占有,此时该进程的请求被阻塞,但又对自己获得的资源保持不放。

3.不可剥夺条件: 进程已获得的资源在未使用完毕之前,不可被其他进程强行剥夺,只能由自己释放。

4.环路等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。

26.如何预防死锁

1.打破互斥条件: 一般来说在所列的四个条件中,“互斥”条件是无法破坏的。因此,在死锁预防里主要是破坏其他几个必要条件,而不去涉及破坏“互斥”条件。

2.打破请求和保持条件: 1)采用资源预先分配策略,即进程运行前申请全部资源,满足则运行,不然就等待。 2)每个进程提出新的资源申请前,必须先释放它先前所占有的资源。

3.打破不可剥夺条件: 当进程占有某些资源后又进一步申请其他资源而无法满足,则该进程必须释放它原来占有的资源。

4.打破环路等待条件: 实现资源有序分配策略,将系统的所有资源统一编号,所有进程只能采用按序号递增的形式申请资源。 采用资源有序分配其基本思想是将系统中的所有资源顺序编号,将紧缺的,稀少的采用较大的编号,在申请资源时必须按照编号的顺序进行,一个进程只有获得较小编号的进程才能申请较大编号的进程。

27.为什么要使用线程池?

如果我们在方法中直接new一个线程来处理,当这个方法被调用频繁时就会创建很多线程,不仅会消耗系统资源,还会降低系统的稳定性。

1.降低资源消耗: 通过重复利用已创建的线程,降低线程创建和销毁造成的消耗。

2.提高响应速度: 当任务到达时,任务可以不需要等到线程创建就能立即执行。

3.增加线程的可管理性: 线程是稀缺资源,使用线程池可以进行统一分配,调优和监控。

28.List、Set、Map三者的区别?

List(对付顺序的好帮手): List 接口存储一组不唯一(可以有多个元素引用相同的对象)、有序的对象。

Set(注重独一无二的性质):不允许重复的集合,不会有多个元素引用相同的对象。

Map(用Key来搜索的专业户): 使用键值对存储。Map 会维护与 Key 有关联的值。两个 Key可以引用相同的对象,但 Key 不能重复,典型的 Key 是String类型,但也可以是任何对象。

29.ArrayList 和 LinkedList 的区别

底层实现: ArrayList 底层基于动态数组实现,LinkedList 底层基于链表实现。

30.类加载的过程

类加载的过程包括:加载、验证、准备、解析、初始化,其中验证、准备、解析统称为连接。

加载:通过一个类的全限定名来获取定义此类的二进制字节流,在内存中生成一个代表这个类的java.lang.Class对象。

验证:确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

准备:为静态变量分配内存并设置静态变量初始值,这里所说的初始值“通常情况”下是数据类型的零值。

解析:将常量池内的符号引用替换为直接引用。

初始化:到了初始化阶段,才真正开始执行类中定义的 Java 初始化程序代码。主要是静态变量赋值动作和静态语句块(static{})中的语句。

31.HashMap的实现原理

(1)jdk1.7的HashMap是用数组+链表实现的

(2)jdk1.8的HashMap是用数组+链表+红黑树实现的

32.HashMap在什么条件下扩容

(1)jdk1.7

​ 超过扩容的阈值

​ 发生碰撞

(2)jkd1.8

​ 超过扩容的阈值

33.线程池的七个核心参数

public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,	
                            ThreadFactory threadFactory,
                            RejectedExecutionHandler handler);

corePoolSize:线程池核心线程大小, 线程池中维护的一个最少的线程数量,即使这些线程处于空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。

maximumPoolSize:线程池最大线程数量, 一个任务被提交到线程池之后,首先会到工作队列中,如果工作队列满了,则会创建一个新的线程,然后从工作队列中取出一个任务交给新线程处理,而将刚提交上来的任务放入到工作队列中。线程池最大的线程数量由maximunPoolSize来指定。

keepAliveTime:空闲线程存活时间, 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定的时间后,这个空闲的线程将被销毁,这个指定的时间就是keepAliveTime。

unit:空闲线程存活时间单位, keepAliveTime的计量单位,是一个枚举java.util.concurrent.TimeUnit。

workQueue:工作队列,新任务被提交之后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk一共提供了四种工作队列。

ArrayBlockingQueue
数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO(先进先出),使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
LinkedBlockingQueue
链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。 DelayQueue
延时队列:无界,元素有过期时间,过期的元素才能被取出。
threadFactory:线程工厂, 创建新线程的时候使用的工厂,可以用来指定线程名,是否为daemon线程等等。

handler:拒绝策略,当工作队列中的任务已经达到了最大的限制,并且线程池中线程数量达到了最大限制,如果这时候有新任务进来,就会采取拒绝策略,jdk中提供了四种拒绝策略。

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
CallerRunsPolicy:由调用线程处理该任务。

34.在Java多线程中,哪种方式不会使线程进入阻塞状态

yield(),yield()会是线程进入就绪状态(sleep、wait、suspend都会进入阻塞状态)

35.线程池execute()与submit()的区别

方法传参不同:execute()方法只能传Runnable对象;submit()方法可以传Runnable对象和Callable对象

方法返回值不同:execute()方法传递Runnable对象,重写的run方法是由void修饰的,没有返回值;submit()方法穿Runnable对象时也是没有返回值的,但是传递Callable对象时,重写的是call方法,它是有返回值的

异常处理不同:execute()方法会直接抛出异常;submit()方法不会直接抛出,异常会赋值outcome字段,可以使用future.get()获取异常

36.hashcode相等两个类一定相等吗?equals呢?相反呢?

不一定相等。当两个对象的地址相同(即为同一个对象)时,计算出的哈希值是相同的,但是哈希值相同并不能代表两个对象的地址相同

(1)hashcode方法和equals方法没有重写时

​ hashcode相等两个类不一定相等

​ equals返回true的两个类一定相等(为同一个对象)

​ 两个类相等hashcode不一定相等

​ 两个类相等equals不一定返回true

(2)hashcode方法和equals方法均已按规范重写时

​ hashcode相等两个类不一定相等(存在哈希冲突)

​ equals返回true的两个类一定相等

​ 两个类相等hashcode一定相等

​ 两个类相等equals一定返回true

37.HashMap的大小为什么是2的幂

为了通过hash值确定元素在数组中存的位置,我们需要进行如下操作hash%length,当时%操作比较耗时间,所以优化为 hash & (length - 1)

当length为2的n次方时,hash & (length - 1) =hash % length

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值