JavaSE基础

107 阅读15分钟

思维导图

Java基础

Java语言的特点

平台无关性

支持多线程

面向对象

可靠性:异常处理和内存管理机制

安全性:访问权限修饰符

高效性:通过引入JIT(JUST IN TIME)的编译器

编译与解释共存:Java程序编译成.class文件,然后再通过JVM将其解释成机器语言

Java面向对象的特点

封装:把一个对象属性私有化,同时提供可访问的接口。

继承:指的是子类对父类的继承。

多态:同一种行为有多种表现形式,常见的表现形式有,多个子类对父类的继承,重写方法,多个类对接口的实现。

Java和C++的异同

首先java和C++都是面向对象的,Java的底层基于C++实现。

不同之处有:

  1. Java舍弃了复杂的指针操作,C++有;
  2. Java是的类是单继承的,但是C++支持多继承,Java的接口可以多继承;
  3. Java不支持操作符重载只支持方法重载,C++可以;
  4. Java有自动的垃圾回收机制,不需要程序员手动释放无用的内存。

JDK、JRE和JVM

JVM:运行java字节码的虚拟机,也是Java一次编译,到处运行的基础,通过JVM将不同操作系统(wondows,Linux)上相同的字节码运行出相同的结果;

JRE:Java运行时环境,在JVM的基础上,增加了Java类库,运行已经编译好的Java程序所需要的所有内容的集合;

JDK:Java Development Kit,是功能齐全的JavaSDK,又在JRE的基础上增加了,javadoc,jdb等其他类库,能够创建和编译程序。

继承关系中代码块的执行顺序

父类静态代码块->子类静态代码块->父类代码块->父类构造函数->子类代码块->子类构造函数

Java的基本数据类型

八种数据类型

数据类型位数字节数范围默认值
byte81-128~1270
short1620
int3240
long6480L
float3240f
double6480d
char162‘u0000’
boolean1false or truefalse

包装类的作用以及异同

包装类就是为了方便对某些数据进行操作;

二者的不同:

  1. 类型不同:包装类是对象;
  2. 存储位置不同:对象在堆中,基本数据类型在栈中;
  3. 初始默认值不同:包装类默认为null,基本数据类型的默认值根据具体的类型改变。
  4. 声明方式不同:基本数据类型直接声明,,包装类通过new。

包装类型的缓存机制

对包装类,为了提高性能,所以有缓存机制。

具体来说就是:对Byte,Integer,Short,Long这几种类型,都设置有范围为-127到128的缓存,如果创建的对象在该范围内就直接返回,否则就新创建。而Character类型为0~127。

由此推出,在缓存区之外的数据,会在堆上产生。所有整型包装类对象之间值的比较 ,全部使用 equals 方法比较

如何解决精度丢失问题?以及使用规范?

设置在某个范围的即为达到要求,或者使用BigDecimal类型。用于涉及到金钱相关的,对精度要求高的场景。

  1. 加减乘除,不是通过运算符,而是调用add,subtract,multiply,divide等方法
  2. 比较的是方法不能够使用equals,而是使用compareTo,用于忽略精度。返回值来看-1表示小于,0等于,1大于,其实就是a.compareTo(b),a-b的结果。
  3. 可以通过setScale设置保留几位小数。

强制类型转换和自动类型转换?

Java中基本数据类型之间可以进行强制类型转换,大范围的数据类型可以自动转换成小范围的自动数据类型,小范围的数据类型需要通过强制类型转换,因为存在精度丢失问题。顺序如下:

变量

成员变量和局部变量的区别

位置不同:成员变量在类中,局部变量在类的方法中;

初始值不同:成员变量有默认值,局部变量需要无默认值需要初始化;

存储的位置不同:成员变量在堆中,局部变量在栈中;

生命周期不同:成员变量随着对象创建和消失,局部变量随着方法的调用创建和消失。

静态变量和实例变量的区别

所属不同:静态变量属于类,随着类加载字节码文件产生,而实例变量属于实例化的对象,随着对象的创建而产生。

调用方式不同:因为所属不同,所以一般调用静态变量都采用的是类名.静态变量,调用实例变量则是实例对象.实例对象

分配内存不同:静态变量在类加载过程中就被分配了一块固定的内存,无论创建了多少对象,都是公用同一个静态变量。

字符常量和字符串常量的区别

形式不同:字符常量使用‘’单引号,字符串常量使用的是“”双引号;

内容不同:字符常量是整数型,ASCALL码,而字符串常量代表一个字符串在内存中的存储地址;

所占字节不同:Java中,char占了2个字节,字符串常量占了若干个字节

重载和重写的区别

重载和重写都是Java多态的一种表现形式。但是,重载发生载同一类中,而重写发生在继承关系中,子类重写父类的方法,具体来说:

重载是指在同一个类中,可以有多个相同方法名,但是参数不同的方法,对于权限访问符、返回值类型,不作要求,类中无参构造方法、不同的有参构造方法就是比较常见的重载。

重写是指子类重写父类的方法:要求方法名相同,参数列表相同,

子类重写的权限访问符要比父类的权限更相同或者更大,

子类的返回值类型范围要求比父类的返回值类型要相同或者更小,

子类声明的抛出异常的类型要比父类声明的异常类型要相同或者更小。

Object

Object中有哪些方法?

Object的是所有的类的父类里面常见的有11种方法,分别为:

  1. getClass方法:native方法,用于返回当前运行时对象的Class对象,使用了final关键字;
  2. toString方法:返回类名字实例的哈希码的字符串,一般都需要重写,打印对象内容;
  3. equals方法:底层其实是==,用来两个对象的内存地址是否相同,String类方法对其重写实现了比较字符串的值是否相等。
  4. hashcode方法:native方法,用于返回对象的哈希码;
  5. clone:native方法,对对象进行浅克隆;返回当前对象的拷贝;
  6. notify:native方法,唤醒该对象监视器上面的某个线程,监视器类似于锁的概念;
  7. notifyall:native方法,唤醒该对象监视器上面的所有线程;
  8. wait方法:线程一直等待,释放锁,sleep不会释放锁;
  9. wait+一个参数等待时间:native方法,暂停线程执行,
  10. wait+两个参数:等待时间和额外时间;
  11. finalize:垃圾回收前调用,只调用一次;

补充:什么是native方法?

native方法也就是本地方法,在Java源码中以关键字“native”声明,不提供函数体。具体是用C/C++实现,在Java另外的文件中编写,编写规则遵循Java本地接口规范。

简而言之,就是声明调用C或者C++实现的方法。

为什么要重写equals也要重写hashCode?

因为两个相同的对象,hashcode一定得相同。也就是说,如果通过equal判断两个对象相同,那么他们的hashcode也得是相同的。如果只是重写equals,就可能会出现,equals判断相同,但是hashcode不同的情况产生冲突。在hash容器,比如hashset,就算插入相同的对象,也识别不出来,违背了规范。

默认情况下,hashset会先判断hashcode是否相同,执行后返回false则不执行equals方法了,直接插入数据。

==和equals的区别?

对于基本数据类型==判断的是值是否相同;对于引用数据类型判断的是,内存地址是否相同;

equals判断的是对象是否相同,是在Object类中。

String

String,Stringbuffer和StringBuilder

String被final修饰,不可变,线程安全存储在线程常量池当中;

Stringbuffer和StringBuilder均继承自AbstractStringBuilder

Stringbuffer:跟String类似,但是值可以被修改,方法被synchronized 修饰线程安全,但是性能相对较差

StringBuilder:跟String类似,但是值可以被修改,线程不安全,性能好一些。

String是怎么拼接的?

在JDK1.8之前,String每一次的+操作,其实创建了新的String;

在JDK1.8之后,拼接操作其实基于Stringbuilder.append实现,在AbstractStringBuilder中append方法,会检查当前字符序列中的字符串是否够用,不够的话进行扩容,并将指定的字符串追加到字符序列的末尾。

String中intern方式是什么?

是native方法,将指定的字符串对象引用保存到字符串常量池当中。

String中字符串常量池?

按照内存来说分为,三个阶段:

JDK1.7之前:字符串常量池存储在永久代当中,永久代是Java堆中的子区域,大小固定,永久代存储了类信息,方法信息和字符串常量等静态数据;他们有不同的生命周期和分配区域,但是他们的大小互相限制,而且永久代大小有限,所以容易出现OOM错误。

JDK1.7的时候:字符串常量池在堆中,因为之前字符串常量池要等full GC之后才可以才可以回收,效率低,放在堆中可以及时的回收内存。

JDK1.8之后:永久代被抛弃,取而代之的是元空间,他是本机的内存区域,和JVM虚拟机的内存区域分开,但是功能和永久代差不多。

元空间是动态大小,不会发生OOM错误;

元数据库的垃圾回收机制和堆中的垃圾收集是分离的;

元数据不和JVM使用同一个内存,所以不会互相影响内存大小,使用的本地内存,也不会产生碎片化问题。

异常

Java中异常概述

Java中的异常分为两大类,error和Exception,都继承自Throwable,而error常见的有

怎么捕获异常?

通过try catch finally.或者通过try with resources,后者多用于涉及到资源的关闭,

重点说后者:资源实现了AutoCloseable接口,所以可以自动关闭资源,如果自定义资源也想要自动关闭的话,只需要实现接口,并且提供close方法。

eg:

try {
    // 可能发生异常的代码
}catch {
   // 异常处理
}finally {
   // 必须执行的代码
}

try (//创建资源);
    //可能发生异常的代码
} catch (IOException e) {
    //异常处理
}

NoClassDefFoundError和ClassNotFoundException有什么区别

首先,前者是一个error,后者是一个异常,前者更严重些
NoClassDefFoundError:这是比较严重的错误,发生在运行时JVM或者ClassLoader在运行时类加载器没在classpath下找不到对应的类,可能产生的原因是,打包时丢失了类,或者是在jar包损坏;

ClassNotFoundException:是一个异常,当动态加载(class.forname)Class对象的时候找不到类时抛出,可能产生的原因是加载的类不存在或者类名写错了。

Throwable中有什么常见的方法?

String getMessage:获得异常的简要信息;

String toString:获得异常的详细信息;

String getLocalizedMessage(): 返回异常对象的本地化信息。Throwable的子类覆盖了该方法则返回本地化信息,否则返回getMessage相同的信息;

void printStackTrace(): 在控制台上打印异常封印的信息。

捕获异常需要注意什么?

这个需要注意的地方非常多,比较常见的要求:

  • 尽量使用 try-with-resource 来关闭资源;
  • 不要捕获 Throwable;
  • 不要记录了异常又抛出了异常
  • 不要在 finally 块中使用 return
  • 抛出具体定义的检查性异常而不是 Exception
  • 不要在生产环境中使用 printStackTrace()
  • 捕获具体的子类而不是捕获 Exception 类
  • finally 块中不要抛出任何异常(原始异常被覆盖)

泛型

对泛型的了解?

泛型是JDK5中引入的新特性,是为了在编译时做类型安全检测机制,在Java中表现形式有泛型接口,泛型方法,泛型类,具体符号指代有:

?:未知的类型;

T(type):具体的Java类型;

K-V:Java的键值对形式;

E(Element):代表Element;

泛型擦除?

泛型擦除也叫类型擦除,就是代码在编译时就会消除类型信息,而在运行时候是看不到泛型的。目的是为了向下兼容。

注解

对注解的理解?

注解是JDK5引入的新特性,是对类,方法,变量进行一种特殊的标记,提供某些信息用于在编译时或者运行时使用。

注解的生命周期一般有三种:

RetentionPolicy.SOURCE:给编译器使用,不写入class文件;

RetentionPolicy.CLASS:写入class文件,类加载阶段丢弃;

RetentionPolicy.RUNTIME:写入class文件,永久保存

注解是需要通过解析使用的:要不就是在运行时直接扫描(@Override),或者编译期通过反射(@Value、@Component),框架中的注解就是多通过反射。

反射

反射的定义,原理,优缺点?

反射是指,在运行过程中动态的获取到类的信息和方法,并且可以调用任一方法,这种动态的获取信息和调用的功能称为反射。

原理:Java需要通过编译和运行两个阶段,经过编译活得class文件之后,通过类加载将类的相关信息加载进入到方法区,反射就是获取到这些信息。

优点:灵活性更好,开箱即用

缺点:更复杂,性能开销大,对安全性产生了影响,比如会绕过编译时的类型检测阶段。

获取class文件的四种方法?

  1. 知道类名,直接类名.class获取;
  2. 有实例对象,对象.getClass方法获取;
  3. 通过Class.forName(“类的全限名”);
  4. 通过ClassLoader.classload(“类的全路径”)。

I/O

I/O流?

分为字符流和字节流,字符流又分为字符输入流和字符输出流,字节流则分为字节输出流和字节输入流。

为什么有字节流了还要设计字符流?一方面时字节处理字符,转换的过程需要消耗型能,同时如果不知道编码类型可能产生乱码问题。

I/O中的设计模式?

装饰器模式:比如说inputStream,和bufferInputStream;

适配器模式:InputStreamReader使用StreamDecoder,可以将字节流转变成字符流;

工厂模式:files中的newInputStream方法,获取到InputStream对象;

观察者模式:NIO对文件目录的监听服务。

BIO,NIO和AIO

BIO:同步阻塞IO,一个线程处理一个请求,应用程序发起请求后就一直阻塞,等待内核准备数据并且返回,不适合高并发的场景;

NIO:多路复用的IO模型,同步非阻塞IO,一个请线程可以处理多个请求,应用程序发起请求之前,会发送select,epoll,poll等操作,等内核将数据准备好之后再发送read请求,返回数据。

AIO:异步非阻塞IO,程序发送请求后就返回,内核让线程处理了请求了后返回数据。

NIO的核心组件,以及零拷贝

NIO的核心组件有:

Buffer:缓冲区,数据的读取都是从缓冲区完成,数据从缓冲区写入到channal,或者从channal读取到缓冲区;

Channel:通道,NIO中进行数据传输的抽象,全双工通信,读取数据流,和写入数据流;

selecter:选择器,允许一个线程管理多个管道,每个管道连接缓冲区,每个缓冲区连接一个客户端,selecter会监视这些通道的事件状态,并在发生事情的时候通知线程,以减少线程的阻塞。

零拷贝:这是指在进行IO操作的时候,不需要将数据从一个内存区域拷贝到另一个内存区域,减少上下文的切换和CPU的拷贝事件,在NIO利用mmap和sendfile实现。

Java8的新特性

  1. 给提供了接口提供了默认方法;
  2. Lambda表达式:本质上是一个以更紧凑可读性更高的方式,定义匿名内部类;
  3. 函数式接口:一个具有单个抽象方法的接口;
  4. Stream流:多用来对集合数据进行操作,比如说过滤、排序、映射、匹配等;
  5. Optional类:解决空指针问题,如果一个方法的返回值可能为null也可能不为null,可以考虑使用Optional类进行包装。

Java中代理模式

代理模式指的就是:使用代理对象来代替真实对象,在不更改原本类的基础上,通过代理类来实现功能的扩展。

Java中有两种代理,为:

JDK代理:这是基于接口的代理模式,被代理类需要实现接口,然后通过反射获得被代理类的信息,在运行时生成代理类;

CGLIB代理:这是基于继承的类,被代理类无需实现接口,通过继承生成被代理的类,重写其中的方法来实现代理,需要调用第三方库ASM。