java基本知识点
数据类型
- 基本数据类型(4类8种)
- 整数类型:byte short int long
- 浮点类型:double float
- 字符类型:char
- 布尔类型:boolean
- 内存结构
- 栈内存:用于存储局部变量,当数据使用完,所占空间会自动释放(基本数据类型变量、对象的引用变量)
- 堆内存:数组和对象,通过new建立的实例都存放到堆内存中 (存放对象实例)
堆栈解释
- static关键字
- 静态的意思,用来修饰成员变量和成员函数
- 静态的特点
- 随着类的加载而加载
- 优先于对象存在
- 对所有对象共享
- 可以被类名直接调用
- 静态注意事项
- 静态方法只能访问静态成员【因为静态的内容是随着类加载而加载,它是先进内存的。】
- 静态方法中不能使用this,super关键字【this,super是对着对象走的】
补充: 静态变量(类变量)、成员变量(实例变量)、局部变量
- 成员变量和局部变量的区别
- 作用域
- 成员变量:针对整个类有效(只能通过创建对象后获取,不可通过类名 获取)
- 局部变量:只在某个范围内有效(一般指的就是方法,语句体内)
- 存储位置
- 成员变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆 内存中
- 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存 中。当方法调用完,或者语句结束后,就自动释放。
- 初始值
- 成员变量:必须有默认值
- 局部变量:没有默认值,使用前必须赋值。
- 静态变量和成员变量区别
- 调用方式
- 静态变量也成为类变量,可以直接通过类名调用。也可以通过对象名调 用。这个变量属于类。
- 成员变量也成为实例变量,只能通过对象名调用。这个变量属于对象。
- 存储位置
- 静态变量存储在方法区中的静态区
- 成员变量存储在堆内存中
- 生命周期
- 静态变量随着类的加载而存在,随着类的消失而消失。生命周期长
- 成员变量随着对象的创建而存在,随着对象的消失而消失。
- 与对象的相关性
- 静态变量是所有对象共享的数据
- 成员变量是所有对象所持有的数据
- 单例模式 (双重锁方式)
public class Singleton3 {
private volatile static Singleton3 instance = null;
public Singleton3(){}
public static Singleton3 getInstance(){
if (null == instance){
synchronized (Singleton3.class){
if (null == instance){
instance = new Singleton3();
}
}
}
return instance;
}
}
- 模板模式
- 模板模式类似抽象类被子类实现后,各自的实现子类实现抽象类的抽象方法,从而实现不同的业务逻辑
/**
* @author BOBO
* @Title: wukong-master
* @Package com.wukong.pattern.template
* @Description: 抽象模板角色类
* @date 2018/5/29上午11:19
*/
public abstract class Account {
/**
* 模板方法,计算利息数额
* @return 返回利息数额
*/
public final double calculateInterest(){
//获取费率
double interestRate = doCalculateInterestRate();
//获取继承子类的存款类型
String accountType = doCalculateAccountType();
//通过子类的存款类型获取对应的费用
double amount = calculateAmount(accountType);
//根据不同的费用和费率获取不同的金额
return amount * interestRate;
}
/**
* 基本方法留给子类实现
*/
protected abstract String doCalculateAccountType();
/**
* 基本方法留给子类实现
*/
protected abstract double doCalculateInterestRate();
/**
* 基本方法,已经实现 通过继承子类的不同类型执行不同的业务逻辑
*/
private double calculateAmount(String accountType){
/**
* 省略相关的业务逻辑
*/
if("MM".equals(accountType)){
return 7243.00;
}else {
return 8000.00;
}
}
/**
* @author BOBO
* @Title: wukong-master
* @Package com.wukong.pattern.template
* @Description: 具体模板角色类
* @date 2018/5/29上午11:26
*/
@Service
public class CDAccount extends Account {
@Override
protected String doCalculateAccountType() {
return "CC";
}
@Override
protected double doCalculateInterestRate() {
return 0.06;
}
}
/**
* @author BOBO
* @Title: wukong-master
* @Package com.wukong.pattern.template
* @Description: 具体模板角色类
* @date 2018/5/29上午11:24
*/
@Service
public class MoneyMarketAccount extends Account {
@Override
protected String doCalculateAccountType() {
return "MM";
}
@Override
protected double doCalculateInterestRate() {
return 0.045;
}
}
/**
* @author BOBO
* @Title: wukong-master
* @Package com.wukong.pattern.template
* @Description:
* @date 2018/5/29上午11:28
*/
@Service
public class DoHandelTemplateService {
@Resource
MoneyMarketAccount moneyMarketAccount;
@Resource
CDAccount cdAccount;
public void doHandelLv1(){
System.out.println("货币市场账号的利息数额为:" + moneyMarketAccount.calculateInterest());
System.out.println("定期账号的利息数额为:" + cdAccount.calculateInterest());
}
}
- final关键字
- 可以修饰类、方法、变量
- final修饰类不能被继承
- final修饰方法不能内重写
- final修饰变量是一个常量,只能内赋值一次
- 内部类只能访问内final修饰的局部变量
- 抽象类
- 多各类有相同的方法声明,当时具体的方法体不一样。这个时候,就需要将方法声明进行抽取出一个抽象方法,然后让子类继承,不同的子类去实现具体的方法体。
- 抽象类
- 包含抽象方法的类就是抽象类
- 抽象类的特点
- 抽象类和抽象方法都用abstract进行修饰
- 抽象类不能被实例化
- 抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中数据的特点
- 抽象类中可以有变量、常量
- 抽象类中可以有抽象方法,也可以没有抽象方法
- 抽象类中可以有构造方法 ( 抽象类不可以实例化,但是构造方法是为了子类实例化后使用)
- 抽象关键字abstract和那些关键字不能共存
- private
- 私有内容子类继承不到,所以不能重写
- 但是abstract修饰的方法,要求被重写。两者冲突
- final
- final修饰的方法不能被重写
- 但是abstract修饰的方法,要求内重写。两者冲突
- static
- 假如一个抽象方法被static修饰,那么他可以通过类名获取。但是被abstract修饰的方法是一个没有具体实现的空方法。
- 接口类 Interface
- 当一个勒种的方法都是抽象方法时,就没必要再已抽象类存在,就需要另外一种方式 - ==接口==
- 接口的成员特点
- 成员变量 默认修饰为 public static final;默认为静态变量
- 成员方法 默认修饰为 public abstractl
- 关系
- 接口和接口之间可以多继承 【public interface A extends B,C】
- 一个类可以实现多个接口,也可以在继承的同时实现多个接口
- 抽象类和接口区别
- 抽象类只能被单继承,接口可以多实现
- 抽象类中的数据特点
- 成员变量:可以是变量,也可以是变量
- 成员方法:可以使抽象方法,也可以是普通方法
- 构造方法:可以有构造方法
- 接口中的数据特点
- 成员变量:是常量 默认 public static final
- 成员方法:都是抽象方法 默认 public abstract
- 构造方法:不可以有构造方法
- equals和==区别
- equals
- Object类的equals比较的是引用类型的变量所指向的对象的地址
- 诸如String、Date等类对equals方法进行了重写的话,比较的是所指向 的对象的内容
- ==
- 对于==,如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等
- 如果作用于引用类型的变量,则比较的是所指向的对象的地址
- 异常
- 程序运行过程中的不正常现象叫异常
- 所有异常的根类:Throwable
- Error 重大的问题,我们处理不了。也不需要编写代码处理
- OutOfMemoryError 内存溢出
- StackOverFlowError 栈溢出 (A类调用A类,出现死循环)
- Exception 一般性的错误,是需要我们编写代码进行处理的
- RuntimeException 运行时异常
- ClassNotFoundException 找不到定义的类
- NullPointerException 空指针
- ArrayIndexOutOfBoundsExcetion 数组越界
- UnkownTypeException 未知定义类型
- IllegalArgumentException 非法参数
- IOException 文件异常 必须处理
- FileNotFoundException 文件未找到
- 异常分类
- 检查型异常 -所有检查型异常都继承Exception,检查型异常在javac时,如果不处理是编译不通过的。必须通过try catch或者throws来处理的。这种异常比如有:IOException、FileNotFoundException、SQLException异常。
- 非检查型异常
- 所有非检查性异常都继承RuntimeException,非检查型异常在javac时是不会报错的。但是运行后,由于代码写的不够严实会出现非检查型异常。这种异常一般有 NullPointerException、ArrayIndexOutOfBoundsExcetion等。
- 异常特点
- cath异常如果有父子关系,应该将子类异常放到前面,父类异常放到后边 【因为异常抓取原则是:先逮小的,再逮大的】
- 在同一try…catch…finally块中 ,如果try中抛出异常,且有匹配的catch块,则先执行catch块,再执行finally块。如果没有catch块匹配,则先执行finally,然后去外面的调用者中寻找合适的catch块。
- 在同一try…catch…finally块中 ,try发生异常,且匹配的catch块中处理异常时也抛出异常,那么后面的finally也会执行:首先执行finally块,然后去外围调用者中寻找合适的catch块
- try…catch…finally 抛出异常后,先执行catch后再执行finally块中业务后,再执行try块中的return操作
- finally中的return 会覆盖 try 或者catch中的返回值
- finally中的return会抑制(消灭)前面try或者catch块中的异常
- finally中的异常会覆盖(消灭)前面try或者catch中的异常
- finally块中不应该有return操作,应该是一些清理释放资源的操作。
- 异常建议(主要是对finally使用提出的建议)
- 不要在fianlly中使用return
- 不要在finally中抛出异常
- 减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的
- 将尽量将所有的return写在函数的最后面,而不是try … catch … finally中
- 自定义异常
- 如果要自定义异常类,则扩展Exception类即可,因此这样的自定义异常都属于检查异常(checked exception)。如果要自定义非检查异常,则扩展自RuntimeException
- 按照国际惯例,自定义的异常应该总是包含如下的构造函数
- 一个无参构造函数
- 一个带有String参数的构造函数,并传递给父类的构造函数
- 一个带有String参数和Throwable参数,并都传递给父类构造函数
- 一个带有Throwable 参数的构造函数,并传递给父类的构造函数
public class IOException extends Exception
{
static final long serialVersionUID = 7818375828146090155L;
public IOException()
{
super();
}
public IOException(String message)
{
super(message);
}
public IOException(String message, Throwable cause)
{
super(message, cause);
}
public IOException(Throwable cause)
{
super(cause);
}
}
- 集合(两大分支)整体架构图
- Collection | 架构图
- List:必须保持元素特有的顺序
- Set:不能包含重复数据
- Queue:保持一个队列(先进先出)的顺序
- Map | 架构图
- 键值对的数据类型
- Iterable (Collection继承Iterable)
- Iterator:迭代器,它是Java集合的顶层接口(不包含Map系列接口)
- List还额外的提供了一个ListIterator对象,接口提供了一个listIterator()方法,该接口继承Iterable接口
- Iterator和Iterable区别
- Iterator是Iterable接口中的一个抽象方法 图解
- 所有实现Collection的接口子类都有可以进行迭代处理
- Collection [Collection继承Iterator迭代器]
- List (有序,可重复的集合)
- ArrayList (底层结构是数组、查询快、增删慢,线程不安全、元素可为空。效率高)
- 长度达到饱和后,以50%的速度增长
- LinkedList (底层结构是链表、查询慢、增删快,线程不安全元素可为空。效率高)
- Vector(底层结构是数组、查询快、增删慢,线程安全。效率低。是已经被淘汰的集合)
- 长度达到饱和后,以100%的速度增长
- Stack (是Vector的一个子类。被淘汰的集合,可以不考虑,知道有这么一个集合就OK)
ArrayList和LinkedList都是线程不安全的,所以效率比较高。如果想将一个线程
不安全的集合整合为线程安全。不推荐直接用线程安全集合,而是用Concurrent包下的类。
例如:
List<String> list = Collections.synchronizedList(new ArrayList<String>());
ArrayList和LinkedList 为什么一个查询快,一个查询慢。一个增删慢,一个增删快
数组就像身上编了号站成一排的人,要找第10个人很容易,根据人身上的编号很快就能找到。但插入、删除慢,要望某个位置插入或删除一个人时,后面的人身上的编号都要变。当然,加入或删除的人始终末尾的也快。
链表就像手牵着手站成一圈的人,要找第10个人不容易,必须从第一个人一个个数过去。但插入、删除快。插入时只要解开两个人的手,并重新牵上新加进来的人的手就可以。删除一样的道理。
- Set
- HashSet (线程不安全,不可重复的集合、无序、集合元素可为空)
- hashSet底层是一个Node对象数组,数组索引就是hash(value),数组值就是HashSet.add()的值
- 每一个存储到 哈希 表中的对象,都得提供 hashCode() 和 equals() 方法的实现,用来判断是否是同一个对象对于 HashSet 集合,我们要保证如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同
- 当向HashSet集合中存入一个元素时,HashSet会先调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据hashCode值决定该对象在HashSet中的存储位置
- 如果 hashCode 值不同,直接把该元素存储到 hashCode() 指定的位置
- 如果 hashCode 值相同,那么会继续判断该元素和集合对象的 equals() 作比较
- hashCode 相同,equals 为 true,则视为同一个对象,不保存在 hashSet()中
- hashCode 相同,equals 为 false,则存储在之前对象同槽位的链表上,这非常麻烦,我们应该避免这种情况,即保证:如果两个对象通过 equals() 方法返回 true,这两个对象的 hashCode 值也应该相同
- LinkedHashSet (线程不安全,不可重复的集合、有序、集合元素可为空)
- LinkedHashSetTest是HashSet的子类
- 因为底层采用 链表 和 哈希表的算法。链表保证元素的添加顺序,哈希表保证元素的唯一性
- LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet的性能,但在迭代访问Set里的全部元素时(遍历)将有很好的性能(链表很适合)
- 如果使用 TreeSet() 无参数的构造器创建一个 TreeSet 对象, 则要求放入其中的元素的类必须实现 Comparable 接口所以, 在其中不能放入 null 元素进行遍历)
- EnumSet (线程不安全,不可重复的集合、有序)
- EnumSet是一个专门为枚举类设计的集合类,EnumSet中所有元素都必须是指定枚举类型的枚举值,该枚举类型在创建EnumSet时显式、或隐式地指定。EnumSet的集合元素也是有序的,它们以枚举值在Enum类内的定义顺序来决定集合元素的顺序
- TreeSet(线程不安全,不可重复的集合、有序,集合元素不可为空、底层采用红黑树算法,擅长区域查找)
- TreeSet在进行add操作时,必须放入同样类的对象
- 自动排序:创建自定义对象时。 必须实现Comparable接口,并且需要覆盖compareTo(Object obj) 方法来自定义比较规则。此时treeSet不能放入null的集合元素
- 如果 this > obj,返回正数 1
- 如果 this < obj,返回负数 -1
- 如果 this = obj,返回 0 ,则认为这两个对象相等
- 定制排序:创建 TreeSet 对象时, 传入 Comparator 接口的实现类. 要求: Comparator 接口的 compare 方法的返回值和 两个元素的 equals() 方法具有一致的返
treeSet有两种排序方式:自然排序、定制排序
首先说一下自然排序,自然排序需要调用集合元素的compareTo(Object obj)方法来比较元素的大小关系。自然排序时,treeSet添加元素不能为空
/**
* TreeSet存储的自定义对象,在自然排序是必须实现Comparable接口,
*且必须重写compareTo方法,其中compareTo方法返回的值,参考上边的解释。
*
*/
@Data
public class SetDto implements Comparable {
private int id;
private String name;
@Override
public int compareTo(Object o) {
return -1;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SetDto setDto = (SetDto) o;
return id == setDto.id &&
Objects.equals(name, setDto.name);
}
}
/**
* 实现方法,如果是自动排序方式,在创建TreeSet时,需要构建无参的对象
*
*/
public static void main(String[] args) {
SetDto setDto = new SetDto();
setDto.setId(1);
setDto.setName("222");
SetDto setDto1 = new SetDto();
setDto1.setId(2);
setDto1.setName("111");
//自然排序创建无参的TreeSet时,sortSet.add添加元素时,不能为空
Set<SetDto> sortSet = new TreeSet<>();
sortSet.add(setDto);
sortSet.add(setDto1);
System.out.println(StringUtils.collectionToCommaDelimitedString(sortSet));
}
TreeSet定制排序时,需要实现Comparator接口,且必须重写compare方法,在防范重自定义排序规则。
实现方式有两种,首先第一种是在对象元素类中实现Comparator方式
public class TreeSetTest {
public static void main(String[] args) {
Person p1 = new Person(1);
Person p2 = new Person(2);
Person p3 = new Person(3);
//此方式的TreeSet不能添加为null的集合元素
Set<Person> set = new TreeSet<>(new Person());
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println(set); //结果为[1, 2, 3]
}
}
class Person implements Comparator<Person>{
public int age;
public Person(){}
public Person(int age){
this.age = age;
}
@Override
/***
* 根据年龄大小进行排序
*/
public int compare(Person o1, Person o2) {
// TODO Auto-generated method stub
if(o1.age > o2.age){
return 1;
}else if(o1.age < o2.age){
return -1;
}else{
return 0;
}
}
@Override
public String toString() {
// TODO Auto-generated method stub
return ""+this.age;
}
}
第二种方式是在创建TreeSet时,传入一个有参的Comparator接口。
class M
{
int age;
public M(int age)
{
this.age = age;
}
public String toString()
{
return "M[age:" + age + "]";
}
}
public class TreeSetTest4
{
public static void main(String[] args)
{
TreeSet ts = new TreeSet(new Comparator()
{
//根据M对象的age属性来决定大小
public int compare(Object o1, Object o2)
{
M m1 = (M)o1;
M m2 = (M)o2;
return m1.age > m2.age ? -1
: m1.age < m2.age ? 1 : 0;
}
});
//此时,ts.add可以添加为空的结合元素。
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
查看源码解析图例
- Queue (有序,元素不可为空)
- Queue用于模拟”队列”这种数据结构(先进先出 FIFO)。队列的头部保存着队列中存放时间最长的元素,队列的尾部保存着队列中存放时间最短的元素。新元素插入(offer)到队列的尾部。
- 访问元素(poll)操作会返回队列头部的元素,队列不允许随机访问队列中的元素。结合生活中常见的排队就会很好理解这个概念
- PriorityQueue
- PriorityQueue并不是一个比较标准的队列实现,PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序,这点从它的类名也可以看出来。
- PriorityQueue也有两种排序方式,和TreeSet类似。一个自然排序,一个定制排序。
- Deque
- Deque接口代表一个“双端队列”,双端队列可以同时从两端来添加、删除元素,因此Deque的实现类既可以当成队列使用、也可以当成栈使用
- ArrayDeque
- 是一个基于数组的双端队列,和ArrayList类似,它们的底层都采用一个动态的、可重分配的Object[]数组来存储集合元素,当集合元素超出该数组的容量时,系统会在底层重新分配一个Object[]数组来存储集合元素
- LinkedList
- linkedList是Deque的一个成员类也是List的成员类。他有Deque和List都有的特性。
Queue代表队列,Deque代表了双端队列(既可以作为队列使用、也可以作为栈使用)
PriorityQueue集合使用
import java.util.*;
public class PriorityQueueTest
{
public static void main(String[] args)
{
PriorityQueue pq = new PriorityQueue();
//下面代码依次向pq中加入四个元素
pq.offer(6);
pq.offer(-3);
pq.offer(9);
pq.offer(0);
//输出pq队列,并不是按元素的加入顺序排列,
//而是按元素的大小顺序排列,输出[-3, 0, 9, 6]
System.out.println(pq);
//访问队列第一个元素,其实就是队列中最小的元素:-3
System.out.println(pq.poll());
}
}
Deque使用规则
import java.util.*;
public class ArrayDequeTest
{
public static void main(String[] args)
{
ArrayDeque stack = new ArrayDeque();
//依次将三个元素push入"栈"
stack.push("疯狂Java讲义");
stack.push("轻量级Java EE企业应用实战");
stack.push("疯狂Android讲义");
//输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义]
System.out.println(stack);
//访问第一个元素,但并不将其pop出"栈",输出:疯狂Android讲义
System.out.println(stack.peek());
//依然输出:[疯狂Java讲义, 轻量级Java EE企业应用实战 , 疯狂Android讲义]
System.out.println(stack);
//pop出第一个元素,输出:疯狂Android讲义
System.out.println(stack.pop());
//输出:[疯狂Java讲义, 轻量级Java EE企业应用实战]
System.out.println(stack);
}
}
- Map (Key不允许重复,Value可以重复)
- Map中的Key不许重复,即同一个Map对象的任何两个key通过equals方法进行比较,关于Map,从源码角度理解,java先实现Map,然后通过包装了一个所有value为null的map就实现了Set集合【因为Set不许重复也是通过equals来比较的】
- Map的这些实现类和子接口中key集的存储形式和Set集合完全相同(即key不能重复)
- Map的这些实现类和子接口中value集的存储形式和List非常类似(即value可以重复、根据索引来查找)
- Map子类
- HashMap(Key不允许重复,Value可以重复、线程不安全)
- 采用Hash算法存储key
- 不保证添加时的先后顺序,
- 判断重复标准是:key1和key2是否equals为true,并且hashCode相等
- 和HashSet类似
- LinkedHashMap (Key不允许重复,Value可以重复、线程不安全)
- 采用链表和Hash算法存储key
- 保证添加时的先后顺序
- 判断重复标准是:key1和key2是否equals为true,并且hashCode相等
- HashTable(Key不允许重复,Value可以重复、线程安全)
- 是HashMap的前身,目前已经作废。以为新能太差。知道有这么一个类就OK啦
- TreeMap(Key不允许重复,Value可以重复、线程不安全)
- 采用红黑树算法来确定元素存储位置
- Map中的key会按照自然顺序或者特定顺序排列
- key不允许重复的标准是compareTo、compare方法返回值是否为0
- 实现方式和TreeSet类似
- WeakHashMap(Key不允许重复,Value可以重复、线程不安全)
- WeakHashMap与HashMap用法基本相似,区别在于,HashMap的key保留了对实际对象的“强引用”。
- HashMap对象不销毁,该HashMap所引用对象就不会被销毁
- WeakHashMap的key保留了对实际对象的“弱引用”
- WeakHashMap对象的key所引用的对象没有被其他强引用变量引用,则这些key所引用的对象就会被垃圾回收,回收后WeakHashMap也会把当前key-value对删除
- IdentityHashMap(Key不允许重复,Value可以重复、线程不安全)
- IdentityHashMap的实现机制与HashMap基本相似,在IdentityHashMap中,当且仅当两个key严格相等(key1 == key2)时,IdentityHashMap才认为两个key相等
- EnumMap(Key不允许重复,Value可以重复、线程不安全)
- EnumMap是一个与枚举类一起使用的Map实现,EnumMap中的所有key都必须是单个枚举类的枚举值。创建EnumMap时必须显式或隐式指定它对应的枚举类。EnumMap根据key的自然顺序
- Map和Set关系
- HashMap和HashSet都采用哈希表算法
- TreeMap和TreeSet都采用 红黑树算法
- LinkedHashMap和LinkedHashSet都采用哈希表算法和红黑树算法
- Set底层实现就是一个key不重复且value为null的Map