Java 是一门面向对象的编程语言。
语言特点:面向对象,平台无关性,支持多线程,编译与解释并存
JVM:Java Virtual Machine,Java 虚拟机。
JRE: Java 运⾏时环境。
JDK: Java Development Kit,它是功能⻬全的 Java SDK。
JDK 包含 JRE,JRE 包含 JVM。
Java 程序从源代码到运行主要有三步:
编译:将我们的代码(.java)编译成虚拟机可以识别理解的字节码(.class)
解释:虚拟机执行 Java 字节码,将字节码翻译成机器能识别的机器码
执行:对应的机器执行二进制机器码
Java 语言编译与解释并存
编译型语言是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;解释型语言是指解释器对源程序逐行解释成特定平台的机器码并立即执行。
Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(*.class 文件),这种字节码必须再经过 JVM,解释成操作系统能识别的机器码,在由操作系统执行。因此,我们可以认为 Java 语言编译与解释并存。
Java 有哪些数据类型
Java 语言数据类型分为两种:基本数据类型和引用数据类型。
自动类型转换、强制类型转换
Java 所有的数值型变量可以相互转换,当把一个表数范围小的数值或变量直接赋给另一个表数范围大的变量时,可以进行自动类型转换;反之,需要强制转换。
JAVA可以自动拆箱/封箱、
&和&&,后者是短路与
java7以后,switch(expr)中,exp可以是 byte、short、char、int ,enum ,String
长整型(long)在目前所有的版本中都是不可以
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同)则视为重载;
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。
访问修饰符 public、private、protected、以及不写(默认)时的区别
this 是自身的一个对象,代表对象本身,可以理解为:指向对象本身的一个指针
this 的用法在 Java 中大体可以分为 3 种:
普通的直接引用,this 相当于是指向当前对象本身
形参与成员变量名字重名,用 this 来区分
引用本类的构造函数
抽象类(abstract class)和接口(interface)有什么区别
接⼝的⽅法默认是 public ,所有⽅法在接⼝中不能有实现(Java 8 开始接⼝⽅法可以有默认实现),⽽抽象类可以有⾮抽象的⽅法。
接⼝中除了 static 、 final 变量,不能有其他变量,⽽抽象类中则不⼀定。
⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。
接⼝⽅法默认修饰符是 public ,抽象⽅法可以有 public 、 protected 和 default 这些修饰符(抽象⽅法就是为了被重写所以不能使⽤ private 关键字修饰!)。
从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
jdk 8 的时候接⼝可以有默认⽅法和静态⽅法功能。
成员变量与局部变量
从语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。
从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址。
从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。
成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。
静态变量和实例变量的区别
静态变量: 是被 static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。
实例变量: 必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。静态变量可以实现让多个对象共享内存。
静态方法:static 修饰的方法,也被称为类方法。在外部调⽤静态⽅法时,可以使⽤"类名.⽅法名"的⽅式,也可以使⽤"对象名.⽅法名"的⽅式。静态方法里不能访问类的非静态成员变量和方法。
实例⽅法:依存于类的实例,需要使用"对象名.⽅法名"的⽅式调用;可以访问类的所有成员变量和方法。
final、finally、finalize
final 表示不可变的意思,可用于修饰类、属性和方法:
被 final 修饰的类不可以被继承
被 final 修饰的方法不可以被重写
被 final 修饰的变量不可变,被 final 修饰的变量必须被显式第指定初始值,这里的不可变指的是变量的引用不可变,不是引用指向的内容的不可变。
finally 作为异常处理的一部分,它只能在 try/catch 语句中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下
finalize 是在 java.lang.Object 里定义的方法,也就是说每一个对象都有这么个方法,这个方法在 gc 启动,该对象被回收的时候被调用。
==和 equals
== : 它的作⽤是判断两个对象的地址是不是相等。
equals() : 它的作⽤也是判断两个对象是否相等。但是这个“相等”一般也分两种情况:
默认情况:类没有覆盖 equals() ⽅法。则通过 equals() 比较该类的两个对象时,等价于通过“ == ”比较这两个对象
自定义情况:类覆盖了 equals() ⽅法。我们平时覆盖的 equals()方法一般是比较两个对象的内容是否相同,自定义了一个相等的标准,也就是两个对象的值是否相等。
hashCode 与 equals
hashCode() 的作⽤是获取哈希码,也称为散列码;它实际上是返回⼀个 int 整数,定义在 Object 类中, 是一个本地⽅法,这个⽅法通常⽤来将对象的内存地址转换为整数之后返回。
哈希码主要在哈希表这类集合映射的时候用到
重写 equals 时必须重写 hashCode ⽅法,一般规定,两个对象相等,则它们的hashcode方法应该相等
hashCode() 的默认⾏为是对堆上的对象产⽣独特值。如果没有重写 hashCode() ,则该 class 的两个对象⽆论如何都不会相等
hashcode 值可能会碰撞, hashCode() 所使⽤的散列算法也许刚好会让多个对象传回相同的散列值。
Java 是值传递,Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
深拷贝和浅拷贝
浅拷贝:仅拷贝被拷贝对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。
深拷贝:完全拷贝一个对象,拷贝被拷贝对象的成员变量的值,堆中的对象也会拷贝一份。
深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象。
Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。
String 类使用 final 修饰,是所谓的不可变类,无法被继承
String 和 StringBuilder、StringBuffer
String:String 的值被创建后不能修改,任何对 String 的修改都会引发新的 String 对象的生成。
StringBuffer:跟 String 类似,但是值可以被修改,使用 synchronized 来保证线程安全。
StringBuilder:StringBuffer 的非线程安全版本,性能上更高一些。
intern 方法
如果当前字符串内容存在于字符串常量池(即 equals()方法为 true,也就是内容一样),直接返回字符串常量池中的字符串
否则,将此 String 对象添加到池中,并返回 String 对象的引用
Integer 缓存
因为根据实践发现大部分的数据操作都集中在值比较小的范围,因此 Integer 搞了个缓存池,默认范围是 -128 到 127
Throwable是 Java 语言中所有错误或异常的基类。 Throwable 又分为Error和Exception,其中 Error 是系统内部错误,比如虚拟机异常,是程序无法处理的。Exception是程序问题导致的异常,又分为两种:
CheckedException 受检异常:编译器会强制检查并要求处理的异常。
RuntimeException 运行时异常:程序运行中出现异常,比如我们熟悉的空指针、数组下标越界等等
Java 中IO流的划分
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流
序列化就是把 Java 对象转为二进制流,方便存储和传输
反序列化就是把二进制流恢复成对象
Serializable 接口有什么用?
这个接口只是一个标记,没有具体的作用,但是如果不实现这个接口,在有些序列化场景会报错,所以一般建议,创建的 JavaBean 类都实现 Serializable。
serialVersionUID 就是起验证作用。
这个 ID 其实就是用来验证序列化的对象和反序列化对应的对象 ID 是否一致。
如果没有显示指定 serialVersionUID ,则编译器会根据类的相关信息自动生成一个,可以认为是一个指纹。
所以如果你没有定义一个 serialVersionUID, 结果序列化一个对象之后,在反序列化之前把对象的类的结构改了,比如增加了一个成员变量,则此时的反序列化会失败。
对于不想进行序列化的变量,使用transient关键字修饰。
Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
所谓的泛型擦除,官方名叫“类型擦除”。
Java 的泛型是伪泛型,这是因为 Java 在编译期间,所有的类型信息都会被擦掉。
也就是说,在运行的时候是没有泛型的。
因为 Java 的范型只存在于源码里,编译的时候给你静态地检查一下范型类型是否正确,而到了运行时就不检查了。
Java 注解本质上是一个标记
注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值
注解生命周期有三大类,分别是:
RetentionPolicy.SOURCE:给编译器用的,不会写入 class 文件
RetentionPolicy.CLASS:会写入 class 文件,在类加载阶段丢弃,也就是运行的时候就没这个信息了
RetentionPolicy.RUNTIME:会写入 class 文件,永久保存,可以通过反射获取注解信息
我们通常都是利用new方式来创建对象实例,这可以说就是一种“正射”,这种方式在编译时候就确定了类型信息。
而如果,我们想在运行时候动态地获取类信息、创建类实例、调用类方法这时候就要用到反射。
Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射机制。
像 Spring 里的很多 注解 ,它真正的功能实现就是利用反射。
可以基于反射操作类,然后获取到类/属性/方法/方法的参数上的注解,注解这里就有两个作用,一是标记,我们对注解标记的类/属性/方法进行对应的处理;二是注解本身有一些信息,可以参与到处理的逻辑中。
Java 程序的执行分为编译和运行两步,编译之后会生成字节码(.class)文件,JVM 进行类加载的时候,会加载字节码文件,将类型相关的所有信息加载进方法区,反射就是去获取这些信息,然后进行各种操作。