前言
在之前Java基础知识回顾中,我们回顾了基础数据类型、修饰符和String、三大特性、集合、多线程和IO。本篇文章则对之前学过的知识进行总结。除了简单的复习之外,还会增加一些相应的理解。
基础数据类型
基本数据类型主要有:
byte、short、int、long、float、double、char、boolean
它们可以分为三类:
数值类型:byte、short、int、long、float、double
字符类型:char
布尔型:boolean
其中byte是8位,short是16位, int是32位以及 long是64的整数;而float 32位,double 64 位的浮点数。
数值类型的级别从低到高分别为:
byte,char,short(这三个平级)——>int——>float——>long——>double
其中由低级别转到高级别,是属于自动类型转换,这点是由系统自动转换的。在进行计算的时候,如果级别小于int,最终的数据类型会自动转换为int,如果高于int,最终数据结果会取其中最高的一个。
又高级别转到低级别是强制类型转换。强制类型转换需要注意取值范围和数据的精确度。
char是字符类型,可以储存任何字符。
boolean是布尔类型,只有false或true。
一般我们在用基础数据类型的时候,也会用到包装类型。
这里顺便说下包装类型,也来弥补之前的文章讲述不足。
什么是包装类型?包装类型和基础数据类型的关系。
包装类就是基本类型数据转换为对象的一种类型。
每个基本类型在java.lang包中都有一个相应的包装类。
基础数据类型:
boolean, char, byte,short,int, long, float,double
分别对应的包装数据类型:
Boolean,Character,Byte,Short,Integer,Long,Float,Double
包装类型有什么用?
利于实现基本类型之间的转换;
因为我们了解到基本数据类型之间的相互转换分为自动类型转换和强制类型转换,自动类型转换还好,但是强制类型转换容易出现问题。所以出现了包装类型,它可以很方便的帮助转换。
例如: String类型的转int类型可以通过 Integer.parseInt()转换成int,或使用Integer.valueOf()转换成Integer类型。
便于函数传值;
为什么说方面函数传值呢?假如一个方法的入参是Object 类型, 但是你的入参是个int类型,是无法直接调用这个方法的,所以这时便可以将int类型的数据进行包装成Integer类型,在进行调用便可以了。其实除了这个示例,比较常见的是我们的pojo类型,一般会使用包装类型,这样的话在便可以使用null来进行判断。不止这些,在集合的List、Map和Set等等泛型中的类型是,用的是包装类型,例如: Map<String,Integer> map=new HashMap<String,Integer>();
注意:在使用包装数据类型进行值比较的时候,用equals进行比较,不要用==。例如:
Integer a=127;
Integer b=127;
Integer c=128;
Integer d=128;
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(c == d);
System.out.println(c.equals(d));
输出结果:
true
true
false
true
修饰符
Java修饰符主要分为两类:
访问修饰符
非访问修饰符
其中访问修饰符主要包括 private、default、protected、public。
非访问修饰符主要包括 static、final、abstract、synchronized。
访问修饰符
访问修饰符的访问权限:
修饰符
当前类
同一包内
子类
其它包
public
Y
Y
Y
Y
protected
Y
Y
Y
N
default
Y
Y
N
N
private
Y
N
N
N
非访问修饰符
static: 用来修饰类变量和类方法。
修饰变量
static在修饰类变量的时候,无论该类被实例化了多少次,它的静态变量只有一份拷贝。静态变量也被称为类变量。局部变量是不能被声明为static变量的。
修饰方法
static在修饰类方法的时候,静态方法是不能使用类的非静态变量。静态方法可以直接通过类名调用,因此静态方法中是不能用this和super关键字的。
final :用来修饰类、方法和变量。
final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
abstract :用来创建抽象类和抽象方法。
修饰类
会使这个类成为一个抽象类,这个类将不能生成对象实例,但可以做为对象变量声明的类型(见后面实例),也就是编译时类型。抽象类就相当于一类的半成品,需要子类继承并覆盖其中的抽象方法。
修饰方法
会使这个方法变成抽象方法,也就是只有声明而没有实现,需要子类继承实现。
synchronized: 修饰的方法同一时间只能被一个线程访问。
transient:被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
native: 被native修饰的方法实际是由另一种语言进行实现的本地方法
三大特性
封装
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。要访问该类的代码和数据,必须通过严格的接口控制。
使用封装的好处
良好的封装能够减少耦合。
类内部的结构可以自由修改。
可以对成员变量进行更精确的控制。
隐藏信息,实现细节。
继承
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
优缺点
虽然继承大大提升了代码的复用性,但是也提高了类之间的耦合性!
多态
多态是指事物在运行过程中存在不同的状态。
多态的优点
可替换性(substitutability)。多态对已存在代码具有可替换性。例如,多态对圆Circle类工作,对其他任何圆形几何体,如圆环,也同样工作。
可扩充性(extensibility)。多态对代码具有可扩充性。增加新的子类不影响已存在类的多态性、继承性,以及其他特性的运行和操作。实际上新加子类更容易获得多态功能。例如,在实现了圆锥、半圆锥以及半球体的多态基础上,很容易增添球体类的多态性。
接口性(interface-ability)。多态是超类通过方法签名,向子类提供了一个共同接口,由子类来完善或者覆盖它而实现的。
灵活性(flexibility)。它在应用中体现了灵活多样的操作,提高了使用效率。
简化性(simplicity)。多态简化对应用软件的代码编写和修改过程,尤其在处理大量对象的运算和操作时,这个特点尤为突出和重要。
集合
List
List 接口是继承于 Collection接口并定义 一个允许重复项的有序集合。该接口不但能够对列表的一部分进行处理,还添加了面向位置的操作。
List的常用类
ArrayList:内部是通过数组实现的,它允许对元素进行快随机访问。当从ArrayList的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
LinkedList: 则是链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了List接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
Vector: 通过数组实现的,不同的是它支持线程的同步。访问速度ArrayList慢。
推荐单线程使用ArrayList进行查询和遍历,LinkedList进行插入和删除。
多线程使用Collections.synchronizedList方法对List上锁,效率比Vector高。
Map
Map 接口并不是 Collection 接口的继承。Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Map常用类
HashMap: HashMap的键是根据HashCode来获取,所以根据键可以很快的获取相应的值。不过它的键对象是不可以重复的,它允许键为Null,但是最多只能有一条记录,不过却是可以允许多条记录的值为Null。因为HashMap是非线程安全的,所以它的效率很高。
TreeMap:可以将保存的记录根据键进行排序,默认是按键值的升序排序(自然顺序)。也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。它也是不允许key值为空,并且不是线程安全的。
LinkedHashMap:LinkedHashMap基本和HashMap一致。不过区别在与LinkedHashMap是维护一个双链表,可以将里面的数据按写入 的顺序读出。可以认为LinkedHashMap是HashMap+LinkedList。即它既使用HashMap操作数据结构,又使用LinkedList维护插入元素的先后顺序。它也不是线程安全的。
Hashtable:Hashtable与HashMap类似,可以说是HashMap的线程安全版。不过它是不允许记录的键或者值为null。因为它支持线程的同步,是线程安全的,所以也导致了Hashtale在效率较低。
ConcurrentHashMap: ConcurrentHashMap在Java 1.5作为Hashtable的替代选择新引入的。使用锁分段技术技术来保证线程安全的,可以看作是Hashtable的升级版。
推荐单线程随机查询用HashMap,自然顺序或自定义顺序用TreeMap,插入和删除用LinkedHashMap。
多线程推荐使用ConcurrentHashMap。
Set
Set是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。因为Set是一个抽象的接口,所以是不能直接实例化一个set对象。Set s = new Set() 这种写法是错误的。
推荐单线程随机查询用HashSet,自然顺序或自定义顺序用TreeSet,插入和删除用LinkedHashSet。
多线程
多线程是指在同一程序中有多个顺序流在执行。 简单的说就是在一个程序中有多个任务运行。
线程的状态
创建(new)状态: 准备好了一个多线程的对象
就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
运行(running)状态: 执行run()方法
阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
终止(dead)状态: 线程销毁
线程的创建
通过实现 Runnable 接口;
通过继承 Thread 类本身;
通过实现 Callable接口,然后与Future 和创建线程。
注:线程启动的方法是start而不是run。
推荐创建单线程的时候使用继承 Thread 类方式创建,多线线程的时候使用Runnable、Callable 接口的方式来创建创建线程。
线程的常用方法
sleep:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),不会释放对象锁。
join:指等待t线程终止。
yield:暂停当前正在执行的线程对象,并执行其他线程。
setPriority:设置一个线程的优先级。
interrupt:一个线程是否为守护线程。
wait:强迫一个线程等待。它是Object的方法,也常常和sleep作为比较。需要注意的是wait会释放对象锁,让其它的线程可以访问;使用wait必须要进行异常捕获,并且要对当前所调用,即必须采用synchronized中的对象。
isAlive: 判断一个线程是否存活。
activeCount: 程序中活跃的线程数。
enumerate: 枚举程序中的线程。
currentThread: 得到当前线程。
setDaemon: 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)。
setName: 为线程设置一个名称。
notify(): 通知一个线程继续运行。它也是Object的一个方法,经常和wait方法一起使用。
多线程中经常会使用这几个关键字synchronized、lock和volatile。
synchronized: synchronized是JVM级别的,也就是在运行期由JVM解释的。它是阻塞锁(也就是在同一时间只会有一个线程持有);也是非公平锁(也就是不遵循先来后到的原则,当一个线程A持有锁,而线程B、C处于阻塞状态时,若线程A释放锁,JVM将从线程B、C随机选择一个线程持有锁并使其获得执行权)。可以保证原子性、可见性以及有序性。
lock: lock是通过编码实现的。它是非阻塞锁;也是公平锁。可以保证原子性、可见性以及有序性。相比synchronized,更加灵活和强大。
volatile:轻量级的锁。主要用户保证共享变量对所有线程的可见性,以及禁止指令重排序)。因为无法保证原子性,所以并不能保证线程安全。
线程安全与共享资源
1.局部变量中的基本数据类型(8种)永远是线程安全的。
2.局部变量中的对象类型只要不会被其他线程访问到,也是线程安全的。
3.一个对象实例被多个线程同时访问时,他的成员变量就可能是线程不安全的。
IO流
IO的名称又来是Input与Output的缩写,也就是输入流和输出流。输入流用于从源读取数据,输出流用于向目标写数据。
字符流
字符流有两个抽象类:Writer和Reader类。
其对应子类FileWriter和FileReader可实现文件的读写操作。
BufferedWriter和BufferedReader能够提供缓冲区功能,用以提高效率。
字节流
字节流也有两个抽象类:InputStream和OutputStream类。
其对应子类有FileInputStream和FileOutputStream实现文件读写操作。
BufferedInputStream和BufferedOutputStream提供缓冲区功能
推荐读取文本用字符流,读取图片、视频和图片等二进制文件用字节流。
说起IO流,顺便谈下它的几个孪生兄弟,NIO、BIO和AIO。
IO:
阻塞的,从硬盘读取数据时,程序一直等待,数据读完在继续操作 。
操作时一次一个字节的读取数据,一个输出流一次输出一个字节数据,一个输出流一次消耗一个字节数据,数据的读取和写入效率不好。
I/O属于底层操作,性能依赖与系统环境。
NIO:
同步非阻塞I/O,在读取数据时程序可以继续执行,读取玩数据以后,通知当前程序(即硬件的中断,软件中的回调),然后程序立即或执行完后处理数据。选择器(selector)、缓冲(buffer)、管道(channel) 面向块(缓冲区)。采取“预读方式”。操作中一步产生或消费一个数据块,按块处理数据,同时数据读取到一个稍后可能会处理的缓冲区,需要时也可在缓冲区前后移动。
方式适用于连接数目多且连接比较短(轻操作)的架构。例如聊天工具。毕竟好用的框架Netty和Mina。
BIO:
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
方式适用于连接数目比较小且固定的架构
AIO:
异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理.。
方式使用于连接数目多且连接比较长(重操作)的架构。
其它
Java基础知识的总结篇就介绍到这里了,以后的博文主要编写的方向是Java的进阶知识了,主要内容为设计模式,源码解析和并发编程这块吧!至于后面的这些博文没有信心能够写好,毕竟这些相对于来说还是比较难以理解的。所以以后的这些相关博文我会按照自己的理解写的,如果写的不好,还请多多指点!