java知识整理1

1.谈谈对Java多态的理解

多态是指父类的某个方法被子类重写时,可以产生自己的功能行为,同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。

多态的三个必要条件:

  1. 继承父类
  2. 重写父类的方法
  3. 父类的引用指向子类对象

2.静态方法与静态成员变量可以被继承吗?为什么?
静态方法与静态成员变量可以被继承,但是不能被重写。它对子类隐藏,因此静态方法也不能实现多态。

3.为什么Java里的匿名内部类只能访问final修饰的外部变量?
因为匿名内部类最终会编译成一个单独的类,而被该类使用的变量会以构造函数参数的形式传递给该类,例如:

Integer paramInteger

如果变量不定义成final的,paramInteger在匿名内部类可以被修改,进而造成和外部的paramInteger不一致的问题,为了避免这种不一致的情况,因此Java规定匿名内部类只能访问final修饰的外部变量。

4.讲一下Java的编码方式

  1. 为什么需要编码?
    计算机存储信息的最小单元是一个字节即 8bit,所以能表示的范围时0~2555,这个范围无法保存所有的字符,所以需要一个新的数据结构char来表示这些字符,从char到byte需要编码。

  2. 常见的编码方式有以下几种
    ASCII:总共有128个,用一个字节的低7位表示
    GBK:码范围时8140~FEFE(去掉XX7F)总共23940个码位,它能表示21003个汉字,他的编码是和GB2312兼容的。
    UTF-16:UTF-16具体定义了Unicode字符在计算机中存取方式。UTF-16用两个字节来表示Unicode转化格式,这个是定长的表示方法,不论什么字符都可以用两个字节表示,两个字节是16个bit所以叫UTF-16。UTF-16表示字符非常方便,每两个字表示一个字符,这个在字符串操作时就大大简化了操作,这也是Java以UTF-16作为内存的字符存储格式的一个很重要的原因。
    TUF-8:同一采用两个字节表示一个字符,虽然在表示上非常方便,但是也有其缺点,有很大一部分字符用一个字节就可以表示的现在要两个字节表示,存储空间放大了一倍,会增加网络传输的流量,而且也没必要。而UTF-8采用一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以是由1~6个字节组成。

Java中需要编码的地方一般都在字符到字节的转换上,这个一般包括磁盘IO和网络IO

Reader类是Java的I/O中读字符的父类,而InputStream类是读字节的父类
InputStreamReader类就是关联字节到字符的桥梁,它负责在I/O过程中处理字节到字符的转换,而具体字节到字符的解码实现它由StreamDecoder去实现,在StreamCoder解码个过程中必须由用户指定Charset编码格式。

5.静态代理与动态代理区别是什么?分别用在什么样的场景里?
静态代理与动态代理的区别在于代理类生成的时间不同,如果需要对多个类进行代理,并且代理的功能都是一样的,用静态代理重复编写代理类就非常麻烦,就可以用动态代理动态的生成代理类。

6.描述下Java的异常体系

  1. Error是程序无法处理的错误,比如OutOfMemoryError,ThreadDeath等。这些异常发生时,JVM一般会选择线程终止。
  2. Exception是程序本身可以一处理的异常,这种异常分为两个大类:运行时异常和非运行时异常,程序中应当尽可能去处理这些异常。运行时异常都是RuntimeException类及其子类异常,如NullPointException,IndexOutOfBoundsException等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这些异常的发生。

7.如何描绘一个类的加载过程
Person person = new Person();

  1. 查找Person.class,并加载到内存中。
  2. 执行类里面的静态代码块
  3. 在堆内存里开辟内存空间,并分配内存地址
  4. 在堆内存里建立对象的属性,并进行默认的初始化
  5. 对属性进行显示初始化
  6. 对对象进行构造代码块初始化
  7. 调用对象的构造函数进行初始化
  8. 将对象的地址赋值给person变量

8.Java对象的生命周期是什么?

  1. 加载:将类的信息加载到JVM的方法区,然后在堆区中实例化一个java.lang.Class对象,作为方法区中这个类的信息入口。
  2. 链接:验证:验证类是否合法。准备:为静态变量分配内存并设置JVM默认值,非静态变量不会分配内存。解析:将常量池里面的符号引用转化为直接引用。
  3. 初始化:初始化类的静态赋值语句和静态代码块,主动引用会被触发类的初始化,被动引用不会触发类的初始化。
  4. 使用:执行类的初始化,主动引用会被触发类的初始化,被动引用不会触发类的初始化。
  5. 卸载:卸载过程就是清除堆里面类的信息,一下情况会被卸载:

    1. 类的所有实例都已经被回收
    2. 类的ClassLoader被回收
    3. 类的Class对象没有被任何地方引用,无法在任何地方通过反射访问该类

9.描述一下类的加载机制

类的加载就是虚拟机通过一个类的权限定名来获取描述此类的二进制字节流,而完成这个加载动作的就是类加载器

类和类加载器息息相关,判定两个类是否相等,只有在这两个类被同一类加载器加载的情况下才有意义,否则即便是两个类来自同一个Class文件,被不同类加载器加载,他们也是不相等的。(这里的相等包含Class对象的equals()方法,isAssignableFrom()方法,isInstance()方法的返回结果以及Instance关键字对对象所属关系的判定结果等。

类加载器可以分为三类:

  1. 启动类加载器
  2. 扩展类加载器
  3. 应用类加载器

这么多类加载器,那么当类在加载的时候会使用那个加载器呢?
这个时候就要提到类加载器的双亲委派模型!

双亲委派模型:
如果一个类加载器收到了加载类的请求,它不会自己立即去加载类,它会先去请求父类加载器,每个层次的类加载器都是如此。层层传递,知道传递到最高层的类加载器,只有当父类加载器反馈自己无法加载这个类,才会有当前子类加载器去加载该类。

为什么要这么做呢?
这是为了要让越基础的类由越高层的类加载器加载,例如Object类,无论哪个类加载器去尝试加载这个类,最终都会传递给最高层的类加载器去加载,前面也说过了,类的相等性是由类与其类加载器共同判定的,这样Object类无论在何种加载器环境下都是同一个类。
相反如果没有了双亲委派机制,那么每个类加载器都会加载Object,那么系统中就会出现多个不同的Object类了,如此一来系统的最基础的行为也就无法保证了。

10.描述下GC的原理的回收策略?
提到垃圾回收,我们可以思考下,如果我们去做垃圾回收需要解决哪些问题?
一般来说,需要解决三点:

  1. 那些内存回收?
  2. 什么时候回收?
  3. 如何回收?

这些问题分别对应着引用管理和回收策略等方案。

Java中有四种引用类型:

  1. 强引用:代码中普遍存在的,只要强引用还存在,垃圾收集器就不会回收被引用的对象。
  2. 软引用:SoftReference,用来描述还有用但是非必须的对象,当内存不足的时候会回收这类对象
  3. 弱引用:WeakReference,用来描述非必需对象,弱引用的对象只能生存到下一次GC发生时,当GC发生时,无论内存是否足够,都会回收该对象。
  4. 虚引用:Phantomreference,一个对象是否有虚引用的存在,完全不会对其生存时间产生影响,也无法通过虚引用取得一个对象的引用,它存在的唯一目的是在这个对象被回收时可以收到一个系统通知。

不同的引用类型,在做GC时会区别对待,我们平时生成的Java对象,默认都是强引用,也就是说只要强引用还在,GC就不会回收,那么如何判断强引用是否存在呢???

一个简单思路就是:引用计数法,有对这个对象的引用就+1,不在引用就-1.但这种方法看起来美好却不能解决循环引用计数的问题。

因此可达性分析算法登上历史舞台!用它来判断对象的引用是否存在!

可达性分析算法通过一系列称为GC Roots的对象作为起始点,从这些节点从上向下搜索,搜索走过的路径称为引用链,当一个对象没有任何引用链与GC Roots连接时就说明此对象不可用,也即是对象不可达。

GC Roots对象通常包括:

  1. 虚拟机栈中引用的对象(栈帧中的本地变量表)
  2. 方法区中类的静态属性引用的对象
  3. 方法区中常量引用的对象
  4. Native方法引用的对象

11.接口和抽象类有什么区别
共同点:

  1. 都是上层的抽象类
  2. 都不能被实例化
  3. 都能包含抽象的方法,这些抽象的方法用于描述类具备的功能,但是不提供具体的实现

区别:

  1. 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势,接口中只能有抽象的方法。
  2. 一个类只能继承一个直接父类,这个父类可以是具体的类也可以是抽象类,但是一个类可以实现多个接口。

12.内部类,静态内部类在业务中的应用场景是什么?
静态内部类:这是为了降低包的深度,方便类的使用,静态内部类适用于包含类当中,但又不依赖于外在的类,不适用外在类的非静态属性和方法,只是为了方便管理类结构而定义。在创建静态内部类的时候,不需要外部类对象的引用。
非静态内部类:持有外部类的引用,可以自由使用外部类的所有变量和方法。

13.线程为什么阻塞,为何要使用多线程?
使用多线程更多的是为了提高CPU的并发,可以让CPU同时处理多个事情,多线程场景的使用场景:

  1. 为了不让耗时操作阻塞主线程,开启新线程执行耗时操作
  2. 某种任务虽然耗时但是不消耗CPU,例如:磁盘IO,可以开启新线程来做,可以显著的提高效率
  3. 优先级比较低的任务,但是需要经常去做,例如:GC,可以开启新线程来做

14.了解线程的生命周期吗?描述一下

线程状态流程图
这里写图片描述

  • NEW:创建状态,线程创建后,但是还未启动
  • RUNNABLE:运行状态,处于运行状态的线程,但有可能处于等待状态,例如等待CPU,IO等
  • WAITING:等待状态,一般是调用了wait(),join(),LockSupport.spark()等方法。
  • TIMED_WAITING:超时等待状态,也就是带时间的等待状态。一般是调用了wait(time),join(time),LockSupport.sparkNanos(),LockSupport.sparkUnit()等方法
  • BLOCKED:阻塞状态,等待锁的释放,例如:调用了synchronized增加了锁。
  • TERMINATED:终止状态,一般是线程完成任务后退出或者异常终止。

重点说下RUNNABLE运行状态和BLOCKED阻塞态。

线程进入RUNNABLE运行态一般分为五种情况:

  • 线程调用sleep(time)后查出了休眠时间
  • 线程调用了阻塞式IO的方法,在该方法返回前,该线程被阻塞
  • 线程视图获得一个资源锁,但是该资源锁正被其他线程锁持有
  • 线程正在等待某个通知
  • 线程调度器调用suspend()方法将该线程挂起

再来看一下和线程状态相关的一些方法:

  • sleep():让当前正在执行的线程在指定时间内暂停执行,正在执行的线程可以通过Thread.currentThread()方法获取。
  • yield():放弃线程持有的CPU资源,将其让给其它任务去占用CPU执行时间。但放弃的时间不确定,有可能刚刚放弃,马上又获得CPU时间片。
  • wait():当前执行代码的线程进行等待,将当前线程放入预执行队列,并在wait()所在的代码处停止执行,直到接到通知或者被中断为止。该方法可以使得调用该方法的线程释放共享资源的锁,然后从运行状态退出,进入等待队列,知道再次被唤醒。该方法只能在同步代码块里调用,否则会抛出IllegalMonitorStateException异常。
  • wait(long millis):等待一段时间内是否 有线程对锁进行唤醒,如果超过了这个时间则自动唤醒。
  • notify():用来通知那些可能等待该对象的对象锁的其他线程,该方法可以随机唤醒等待队列中等同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态。
  • notifyAll():可以使所有正在等待队列中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,一般会是优先级高的线程先执行,但是根据虚拟机的实现不同,也有可能是随机执行。
  • join():可以让调用它的线程正常执行完成后,再去执行该线程后面的代码,它具有让线程排队的作用。

15.线程池了解吗?有几种线程池,应用场景是什么?

线程池用来管理线程,避免频繁的CPU时间片切换造成的CPU资源消耗。

Executors类提供了一系列工厂方法用来创建线程池,这些线程池适用于不同的场景。

  • newCachedThreadPool():无界可自动回收线程池,查看线程池中有没有以前建立的线程,如果有则复用,如果没有则建立一个新的线程加入池中,池中的线程超过60s不活动则自动终止。适用于生命周期较短的异步任务。
  • newFixedThreadPool(int nThreads):固定大小线程池,与newCachedThreadPool()类似,但是池中持有固定数目的线程,不能随时创建线程,如果创建新线程时,超过了固定线程数,则放在队列里等待,直到池中的某个线程被移除时,才加入池中。适用于很稳定,很正规的并发线程,多用于服务器
  • newScheduledThreadPool(int corePoolSize):周期任务线程池,该线程池的线程可以按照delay依次执行线程,也可以周期执行
  • newSingleThreadExecutor():单例线程池,任意时间池中只有一个线程

16.ThreadLocal的原理了解吗?
ThreadLocal是一个关于创建线程局部变量的类。使用场景如下:

  1. 实现单个线程单例以及单个线程上下文信息存储,比如交易id等
  2. 实现线程安全,非线程安全的对象使用ThreadLocal之后就会变得线程安全,因为每个线程都会有一个对应的实例
  3. 承载一些线程相关的数据,避免在方法中来回传递参数

17.String为什么要设计成不可变,StringBuffer与StringBuilder有什么区别?

  1. String是不可变的(修改String时,不会在原有的内存地址修改,而是重新指向一个新对象),String用final修饰,不可继承,String本质上是个final的char[]数组,所以char[]数组的内存地址不会被修改,而且String也没有对外暴露修改char[]数组的方法。不可变性可以保证线程安全以及字符串串常量池的实现。
  2. StringBuffer是线程安全的
  3. StringBuilder是非线程安全的

18.Java泛型了解吗?知道它的运行机制吗?

泛型是为了参数化类型

为什么使用泛型?

  1. 相对使用Object这种简单粗暴的方式,泛型提供一种参数化能力,使得数据的类型可以像参数一样被传递进来,这提供了一种扩展能力。
  2. 当数据类型确定后,提供了一种类型检测机制,只有相匹配的数据才能正常赋值,否则编译错误,增强安全性。
  3. 泛型提高了代码的可读性,不必等到运行时采取执行类型转换,在编写代码阶段,程序眼就可以通过参数书写正确的数据类型

19.Java的类型擦除,知道他的原理吗?

泛型信息只存在代码编译阶段,在进入JVM之前,与泛型相关的信息都会被擦除掉

20.闭包了解吗?Java中有闭包吗?

【函数】和【函数内部能访问到的变量】(也叫环境)的总和,就是一个闭包。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值