Java基础知识点汇总
0. Java语言特性
- 简单易学
- 具有面向对象三大特性:继承、封装、多态
- 平台无关性(由JVM虚拟机实现与平台无关)
- 可靠性
- 安全性
- 支持多线程
- 支持网络编程
- 编译与解释并存
- 与C++相比:
- 都是面向对象的语言,都支持继承封装多态
- Java不提供指针来直接访问内存,更加安全
- Java支持单继承,C++支持多重继承,但是Java中接口支持多重继承
- Java有自动内存管理机制,不用程序员手动释放无用内存
1. 面向对象和面向过程区别
- 面向对象:易复用、易维护、易扩展。因为面向对象有继承、封装、多态的特性,实现的代码的低耦合度,使系统更加灵活、便于维护。
- 面向过程:性能要比面向对象高。因为类的调用需要实例化,消耗资源。所以当性能是首要选择的时候,我们常常选择面向过程开发。如单片机、嵌入式等。
- 总结:面向对象低耦合便于维护、易复用、易维护,但是性能没有面向过程高。
2. 面向对象的三大特性
- 继承:
- 是什么:继承是以已有的类为基础,扩展新类的技术。新类可以增加新的数据或者新的功能,他也可以复用父类的功能,但是不能选择性的继承父类,要继承父类的全部。
- 有什么用:通过使用继承我们可以很方便的复用以前的代码。
- 总结:
- 子类拥有父类的所有方法和属性,包括私有的方法和属性,但是私有的方法和属性子类无法直接调用,需要父类提供特定的方法去调用。
- 子类可以有自己的属性和方法。实现对父类的扩展。
- 子类可以用自己的方式实现父类的方法。即使用对父类方法的重写。
- 封装:把一个对象的属性进行私有化,然后提供可以被外界访问的属性的方法。典型就是实体类的标准Jaen。
- 多态:
- 什么是多态:就是多种形态,具体来说就是同一件事情,发生在不同对象身上,就会产生不同的结果。
- 多态实现条件:
- 继承体系下
- 子类对父类方法的重写
- 通过父类的引用调用重写的方法
- 多态的体现:在代码运行的时候,传递不同类对现象的时候,会调用对应类中的方法
public class Main { public static void main(String[] args) { //向上转型 Animal dog = new Dog(); dog.eat(); // dog.dogShow;//编译看左边 在编译的时候animal类没有.dogShow方法,编译就会报错 Animal cat = new Cat(); cat.eat(); // cat.catShow; //结论:编译看左边,发生向上转型的时候只能调用父类有的成员和方法,不能调用子类特有的 //向下转型 Animal animal = dog; Dog dog1 = (Dog) animal;//animal本来就是dog,所以强制转换后不会抛出异常; // Cat cat1 = (Cat) animal;//强制将狗转换成猫,运行时抛出异常:ClassCastException //引入instanceof对类型进行判断,如果安全则为true if (animal instanceof Cat){ Cat cat2 = (Cat) animal; cat2.catShow(); } if (animal instanceof Dog){ Dog dog2 = (Dog) animal; dog2.dogShow(); } //sout //吃骨头 //吃猫粮 //汪汪汪 } } class Animal{ public void eat(){ System.out.println("吃饭"); } } class Dog extends Animal{ @Override //@Override重写注解,用于对重写的方法进行检查,重写的方法名返回值和方法的参数列表一定要相同,且对访问修饰符不能做更严格的限制。 public void eat() { System.out.println("吃骨头"); } public void dogShow(){ System.out.println("汪汪汪"); } } class Cat extends Animal{ @Override public void eat() { System.out.println("吃猫粮"); } public void catShow(){ System.out.println("喵喵喵"); } }
- 重写涉及到动态绑定机制:
- 静态绑定(前期绑定):编译器在编译的时候就可以确定调用的方法
final、static、private修饰的方法和构造函数为静态绑定 - 动态绑定(后期绑定):编译器在运行时才可以确定调用的方法
- Java动态绑定机制
- 当调用对象方法时,该方法会和该对象的内存地址绑定。
- 当调用对象属性时,没有动态绑定,哪里声明哪里使用。
- 动态绑定的发生条件:
- 向上转型
Animal dog = new Dog();
- 重写
class Dog extends Animal{ @Override public void eat() { System.out.println("吃骨头"); } }
- 通过父类引用调用子类重写的父类方法。
dog.eat();
- 向上转型
- 静态绑定(前期绑定):编译器在编译的时候就可以确定调用的方法
- 向上转型特点:
- 可以调用父类里的所有成员
- 不能调用子类所特有的成员和方法。
- 运行时要看子类的具体表现,也就是子类所重写的父类方法。然后调用。
- 优点:让代码实现更加简单灵活。
- 缺点:不能调用子类所特有的成员和方法。
- 向下转型特点:
- 向下转型本质是在堆上创建了一个子类的对象赋值给父类的引用然后在回到子类的引用,可以使用子类所特有的方法。
- 因为在对上创建的子类对象不同,所以在最后回到子类的引用的时候可能是不安全的。有可能出现子类猫的堆赋值给了狗的引用,这是不安全的。因此Java引入了instanceof来判断是否安全
if (animal instanceof Cat){ Cat cat2 = (Cat) animal; cat2.catShow(); }
- 总结多态优缺点:
- 优点:降低圈复杂度,就是减少大量使用if-else,只需要在父类里定义方法,然后子类重写该方法,最后在使用的时候进行向上转型即可。可扩展能力强,只需创建继承父类的子类然后在重写方法即可。对于调用者来说只要创建新类的实例就可以了。
- 缺点:代码的运行效率低。属性没有多态,当父类和子类有同名的属性时,通过父类引用,只能引用到父类的成员属性。构造方法没有多态。
3. 抽象类
- 是什么:类中存在仅定义而未实现的方法
- 类中只要包含仅定义的方法均为抽象类
- 若子类未实现父类的全部方法,则也需要定义为抽象类
- 抽象类不可实例化
- 抽象类的定义格式
class abstract 类名 extends 父类{
权限修饰符 属性1
权限修饰符 属性N
权限修饰符 类名1//构造器
权限修饰符 类名N
权限修饰符 方法1
权限修饰符 abstract 方法N
}
* 可以通过<font color="red">default</font>指定默认的实现方法
* 抽象类可以定义多个不需要实现的方法(用abstract修饰)
* 可以定义抽象类型的变量
4. 接口
- 定义:只有定义没有方法体的方法和全局常量组成的类。
- 接口的特性:
- 接口不可以被实例化,接口中不能有构造方法。
- 不可在接口里定义变量,接口里的变量都自动被
public static final
修饰 - 接口里的方法都自动被
public abstract
修饰,即接口中所有的方法都是抽象方法。 - 接口的实现类必须实现接口的全部方法,否则必须定义为抽象类
- 作用:实现多重继承的效果,同时避免复杂度和低效性。
- 接口的定义
访问修饰符 interface 接口名 extends 父接口1,父接口2,...{
常量
方法
default 方法N(参数){
//提供默认实现方法
}
}
- 接口的实现
访问修饰符 class 类名 extends 父类名 implements 接口1,接口2,...{
自定义程序
实现接口方法
}
- 类可以同时实现多个接口,如果接口有冲突,需要在类里解决
- 如果父类继承的方法和接口的方法冲突则默认接口的方法被省略
- 接口规范(SPI):接口和实现类的解耦
- 本质:应用程序根据接口调用实现类
- 实现徐奥引入依赖的
java.util.ServiceLoader
- SPI需遍历并加载所有的实现类(无法做到按需加载)
- SPI调用流程
- 接口优点:
- 可实现一个类多个接口,打破了类继承的局限性
- 对外提供规则接口
- 降低了程序的耦合性,可实现模块化开发,定义好规则,提高了开发的效率
5. 抽象类和接口的区别
- 共性:不断抽取共性,没有具体的实现方法,都不能实例化。
- 区别
- 接口没有构造方法,抽象类有。
- 接口是对行为的抽象,是行为规范。抽象类是对类的抽象,是一种模板设计。
- 接口不能有具体的方法体,java1.8中可以定义default默认方法体,抽象类中可以有抽象方法也可以有普通带方法体的方法。
- 接口的实现类可以多接口实现,抽象类只能单继承。
- 接口成员变量和方法默认会被修饰,抽象类中有普通的方法,必须有被abstract修饰的抽象方法。
6. Java类的加载机制
- 作用:实现从二进制的数据文件到JVM虚拟机的内存中以供使用
- 加载过程:加载、连接、初始化
- 在类的加载过程中需要遵守JVM规范实现。
- 类的生命周期
-
加载:将源码转换为JVM字节流
- 通过全限定名获取对应类的二进制字节流
- 把二进制字节流的静态存储结构转换为方法区的运行时数据结构
- 在堆中创建代表这个类的
Java.lang.Class
对象,作为方法区的运行时数据结构的访问入口。
-
验证:验证获取的字节流是否符合JVM规范
- 文件格式:验证字节流是否符合Class文件格式规范。
- 元数据:分析字节码语义是否符合Java语言规范。
- 字节码:分析控制流和数据流,确保语义合法符合逻辑。
- 符号引用:验证类合法性,确保解析能正常执行。
-
准备:为类的静态变量分配内存,初始化为默认值
- 为被
static
修饰的静态变量在方法区中分配内存 - 被分配内存的静态变量默认设置初始值为零值(0,0L,null,false等),在初始化阶段才去赋值。
- 如果类字段的字段属性中存在
ConstantValue
属性也就是,变量被static和final修饰,则一定要在准备阶段进行赋予ConstantVa属性的指定的值。
- 为被
-
解析:JVM将常量池中的符号引用转换为直接引用
- 作用:将类中特定的符号标记转换为实际储存的地址信息
- 解析主要针对:类、接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符
符号引用:用特定的符号来描述目标
直接引用:直接指向目标的指针、定位目标的句柄
-
初始化:JVM对类变量进行初始化,静态变量赋予初始值。
- JVM初始化步骤:
- 如果类没有被加载、连接,则先对类进行加载、连接。
- 如果该类的直接父类没有被初始化,则先初始化直接父类。
- 如果有初始化语句,则顺序执行初始化语句。
- 执行初始化方法(clinit()方法)
- clinit()方法线程安全,多线程下存在堵塞风险。
- clinit()方法是编译器自动生成的。
- JVM初始化的触发条件:
- 创建类的实例,new操作
- 直接父类没有初始化先初始化直接父类
- 调用类或接口的静态变量或者对静态常量的赋值
- 调用类的静态方法
- 反射
- Java虚拟机设置默认启动类的类
- JVM初始化步骤:
-
卸载:将字节流对象回收GC
- 回收类中所有实例化对象
- 回收类的ClassLoader
- 回收没有被对象引用的类
-
- 类加载器(ClassLoader):实现类的加载
- 本质:将源码转化为JVM中的字节流
- 每一个类都有对应的ClassLoader
- 数组类通过JVM创建,获取ClassLoader会基于元素的数据类型判断
- ClassLoader加载类的流程
- BootstrapClassLoader(启动类加载器): 加载JDK内部核心库
- ExtClassLoader(扩展类加载类):加载JDK扩展库
- AppClassLoader(应用程序类加载器):用户类路径(ClassPath)所指定的类
- BootstrapClassLoader是由C++实现的属于虚拟机的一部分,无法Java程序直接引用。其他类加载器都是继承自
java.lang.ClassLoader
抽象类,这些类加载器需要由启动类加载器加载到内存中后才能去加载其它类。 - 自定义ClassLoader需要实现loadClass()或findClass():
- loadClass():加载指定二进制名称的类(打破双亲委派机制)
- findClass():查找指定二进制名称的类
- 双亲委派:当ClassLoader加载时先交给其父加载器加载。最终由BootstrapClassLoader加载,如未找到再往下尝试加载类。
- 所有的ClassLoader都要遵循双亲委派(BootstrapClassLoader除外)
- 父ClassLoader不仅尝试加载类,还会查找相关类的相关资源
- 双亲委派不是强制约束,仅时JDK建议的方式
- 优点:
- 避免类被重复加载和核心库被修改
- 通过责任链设计模式实现类加载器的高扩展和解耦性。
7. GC机制
什么是GC:GC是一种自动的存储管理机制,当程序分配内存使用完成后,这部分内存就会成为垃圾,需要释放。这种存储资源管理就称为垃圾回收。对于Java而言就是,自动进行垃圾回收的机制。