Java基础
面向对象的特性
- 封装,将程序实现的细节隐藏起来,公开的方法显示对象的功能。
- 继承,子类继承父类后,具有父类的功能和属性,避免代码重复。
- 多态,父类可以声明子类,运行时依然保持子类的特征。
Java与C++的区别
1 Java是解释型语言,一次编译到处运行,Java首先需要将代码编译为字节码文件,之后由虚拟机解释执行,C++为编译型语言,则编译时将代码编译为机器指令,机器可以直接运行效率高但是不能跨平台。
2 Java不具有指针的概念,由虚拟机负责管理内存,消除了野指针带来的影响,C++可以使用指针操作内存,程序运行有风险。
3 Java不需要程序员释放内存,释放内存的操作由虚拟机的垃圾回收来完成,C++需要在使用完对象后析构函数释放内存。
4 Java不支持多继承,但提供了接口,可以实现多个接口,C++支持多继承的语法。
初始化顺序
Java的类加载过程决定了初始化的顺序,静态对象或者代码加载过一次之后就不再进行加载,如果类未曾被虚拟机加载过,则按照以下顺序加载:
1 父类的静态对象和静态代码
2 子类的静态对象和静态代码
3 父类的非静态对象和代码
4 父类的构造器
5 子类的非静态对象和代码
6 子类的构造器
权限访问控制
Java中采用了4个关键字进行类、方法、变量的权限控制,分别为:
- public 可以被所有类访问
- protected 同一个包下的类或者不同包下的子类
- default 在同一个包下的类访问
- private 只能在同一个类中访问
深复制、浅复制
- 浅复制只会复制对象的基本数据类型,对象如果存在引用的话只复制地址,当拷贝的对象改变引用中的基本数据类型时,被拷贝的对象的基本数据类型也会变化。
- 深复制需要将基本数据类型和引用类型中的所有数据均复制,两个对象进行任何操作时不会相互影响。
反射机制
通过class.forname()可以获取其他对象的类信息、方法信息以及变量信息,通过反射可以创建类的对象,调用对象方法等操作。
多态
多态分为编译时多态和运行时多态,分别通过重载和重写实现的,重载在同一个类中实现,同一个方法可以具有不用的参数类型或者个数,而重写指的是子类对父类重写,子类可以获取父类的方法和属性,父类声明的子类对象在编译时为父类类型,但在运行时为调用子类的方法。
抽象和接口
- 抽象可以有自己的普通方法,而且可以声明权限,接口只能包括抽象方法,子类在继承抽象类或者实现接口时都需要实现抽象方法。
- 抽象中的变量权限范围更加广泛,接口中只能为public final static类型的变量,在之后的版本中加入了default控制。
- 一个类只能继承一个抽象类,但可以实现多个接口。
- 在设计理念上,接口反应的是like a的关系,而抽象指的是is a的关系。
内部类
内部类主要包括四种,分别为静态内部类,非静态内部类,局部内部类,匿名类。
this super
this用来指代当前类的对象实例,super可以调用父类的方法。分别可以用于重载和重写中。
final
final可以用来修饰成员变量,表示该变量不可修改,修饰成员方法则代表该方法不可以被重写,修饰类则表明该类不能被继承。
instanceof
instanceof是一个运算符,不是关键字,用于判断前者实例是否为后者类的子类对象。
基本数据类型
基本数据类型包括char、byte、short、int、double、float、long、boolean八种类型
基本类型 | 所占字节 | 范围 |
---|---|---|
byte | 1 | -2^7~2*7-1 |
char | 2 | 0~2^16-1 |
short | 2 | -2^15~2*15-1 |
int | 4 | -2^31~2*31-1 |
float | 4 | 32位单精度 |
double | 8 | 64位双精度 |
long | 8 | -2^63~2*63-1 |
自动装箱
自动装箱是Java引入的语法糖,方便了引用与基本类型的转换操作,可以将基本数据直接赋值给该数据类型的包装类,即完成了自动装箱操作,反之,也可以将包装类型的数据赋值给基本类型的变量,即完成了拆箱操作。由于装箱过程的底层实现原因,Integer范围在[-128-127]的数值,不会创建新的对象,此范围的的数值比较时返回为true,而此范围之外的数值比较返回值为false。
类型转换
类型转换主要分为两种,自动类型转换和强制类型转换
范围低的数据类型向范围高的数据类型转换时,发生自动类型转换,未补满的位置为0
范围高的数据类型向范围低的数据类型转换时,发生强制类型转换,多余的位强制丢弃
编码方式
Java的编码方式为Unicode,每个字符占有2个字节,范围为0-2*16-1,String是由char组成的,但是中英文所占的字节不相同,中文占用两个字节,英文占用一个字节。
euqals和hashcode
equals和hashcode都是Object类的默认方法,它们都是用来比较两个类是否相同,两个对象equals方法返回值为true时,hashcode返回值也一定为true,但是如果hashcode方法返回为true时,equals方法返回值不一定为true。hashcode()方法主要用于解决Java中的集合问题,向一个集合中添加元素时,判断其中是否已经有一个同样的值如果用equals方法遍历耗费时间,所以采用hash结构存储数据,可以减少查询时间。
当重写equals方法时,同时也需要重写hashcode方法。
String StringBuffer StringBuilder
String是不可变类,当对象被创建后,字符串的值不能再改变。
StringBuffer是可变类,用于多线程下操作大量数据。
StringBuilder也是可变类,用于单线程下操作大量数据。
Error和Exception
异常主要分为检查型异常和非检查型异常,检查型异常在编译器阶段就会检测出来,而非检查型异常需要等到程序运行时才能检测,主要分为Error和Exception,Error是系统错误包括内存溢出,栈溢出等,程序无法控制,而Exception是程序可以控制的,出现异常时可能为数组越界异常、空指针异常、参数类型错误异常、运算错误异常等。
字节流和字符流
文件传输时可以采用两种方式传输,以字节的方式传递,主要继承InputStream和OutputStream,以字符的方式传递,主要继承Reader和Writer。
NIO BIO AIO
BIO是同步阻塞的IO,一个线程需要请求数据时,如果数据未能及时返回,则一直处于等待状态。
NIO是同步非阻塞的,一个线程需要请求数据时,如果数据未能及时返回,则会将现在的状态返回该线程,线程则不断发出请求直到返回数据。
BIO是异步非阻塞的,当一个线程请求数据时,可以先完成其他的任务,当数据返回时,发送给线程数据返回完成的状态。
NIO操作面向缓冲区,数据从Channel读取到Buffer缓冲区,随后在Buffer中处理数据。NIO主要包括三个组件:
- buffer,当写入数据到buffer中时,buffer会记录已经写入的数据大小。当需要读数据时,通过flip()方法把buffer从写模式调整为读模式;在读模式下,可以读取所有已经写入的数据。当读取完数据后,需要清空buffer,以满足后续写入操作。
- channel,提供map()方法可以将文件映射到内存也就是buffer中,通道可以进行读和写,也可以进行异步操作,通道不与程序直接交互,而是通过buffer进行传递。
- selector,用于检查一个或多个NIO Channel的状态是否处于可读、可写。
序列化
为了使创建的Java对象保存在文件或者进行网络传输,需要对其进行序列化处理,需要该类实现Serializable和Externalizable两个接口之一。反序列化则是将保存的二进制文件转化为对象的操作。
ArrayList和LinkedList
ArrayList是基于数组实现的,默认数组大小为10,当数组已满时,扩展数组大小为原来的1.5倍,按下标访问或设置数组元素时效率很高,当按下标插入或者删除元素时,则由于对齐原因,需要移动部分元素的位置。LinkedList则是基于链表实现的,方便插入和删除元素,但是读取元素使需要遍历整个链表,用时较长,但是由于本身采用链表实现,没有容量限制。
HashMap和TreeMap
HashMap采用hash的方法解决冲突问题,它是基于数组和链表实现的,当插入一个元素时,首先计算该元素的hash值,如何在数组中没有冲突,则将此元素插入到该位置,如果在数组中有冲突,则需要在该冲突点的链表中查找是否有相同的元素,如果没有,则在链表中插入新值,如果有相同元素,则新值不能插入,当插入新值时,map的容量已经达到临界条件时,即容量*负载因子,需要进行扩容操作,将容量扩展为原来的2倍,重新计算每个元素的hash值,完成元素的重新划分。
TreeMap在插入新的元素时,会对其进行排序操作,当遍历TreeMap时,得到的序列是有序的。
LinkedHashMap与HashMap不同的是,它是按照数据的插入顺序存储的,当遍历LinkedHashMap存储的元素时,得到的结果与插入时的顺序一致。
创建对象
创建对象主要发生在以下的几种情况下:
1 最常用的新建一个对象
2 实现clone方法实现对象克隆
3 序列化一个对象,之后反序列
4 反射方法创建对象
内存划分
JVM负责内存的划分,主要包括程序计数器、虚拟机栈、虚拟机堆、方法区等部分。
程序计数器,记录当前正在执行指令的行号,是线程私有的。
虚拟机栈,是方法的运行空间,局部变量都存储在栈中,当方法运行结束后,虚拟机栈重新置为空,是线程私有的。
虚拟机堆,Java创建的对象都存在堆中,引用存在栈中,引用指向堆中的地址,是线程共享的。
方法区,用于存储类加载的信息,是线程共享的。
常量池,是方法区的一部分,用于存储符号引用以及字符串信息,是线程公有的。
垃圾回收
垃圾回收是Java特有的内存回收机制,Java创建对象后会占用大量的内存,在对象不再使用后需要将该内存区域释放,判断对象是否还需使用主要采用了引用计数法和可达性分析。
引用计数法指的是每个对象在创建后会有一个引用指向堆中的位置,当引用被改变或者达到生命周期时,引用变量-1,当有一个新的引用指向该实例后,该实例的引用变量+1,当引用变量为0时,就可以释放该区域了,引用计数法存在的问题是如果有两个对象中的变量互相指向对方,则无法释放两个对象的内存。
可达性分析算法是指建立一个根节点,其他对象实例的节点都指向根节点,当某个实例与根节点不再相连时,即可以释放该实例内存区域。
常用垃圾回收算法主要包括三种,标记清理算法、复制算法和标记整理算法。
- 标记清理算法指的是当内存占用空间已满时,清理需要释放的内存区域,不能释放的区域位置不变。
- 复制算法指的是将内存区域分为两个部分,当其中部分已满时,将不能释放的区域复制在另外半边,之前半边的内存区域全部清空。
- 标记整理算法指的是当内存占有空间已满时,清理需要释放的内存区域,将不能释放的区域移至内存区域的一边对齐。
另外根据对象的生存时间不同,将其分为新生代和老生代,新生代又被分为Eden和Surviver区,新生代按照复制算法清理垃圾,老生代按照标记清理或标记整理算法回收内存。当新建一个对象实例后,首先在Eden区分配空间,当Eden空间已满并且该对象实例不需要清理,则将此实例转入Surviver区,如果在该区经过多次GC后依然存在,则将其转入老年代。
类加载
类的加载是由类加载器完成的,类加载器分为根加载器、扩展类加载器、自定义类加载器,当一个类需要加载时,按照以上顺序依次加载,类加载器完成的工作包括加载、连接和初始化,其中连接包括验证、准备和解析。
- 加载,装载二进制文件
- 验证,验证class文件是否符合要求
- 准备,为类变量分配空间,在方法区完成
- 解析,将符号引用转化为直接引用
- 初始化,调用类加载器完成类的初始化
类的初始化发生情况如下所示:
- 新建一个对象
- 获取或重置类静态变量
- 调用类的静态方法
- 反射方式创建对象
- 子类初始化前对父类初始化