1 基础语法

1、说说你对java的理解?
Java本身是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“一次编译,到处执行”,能够非常容易地获得跨平台能力;另外就是垃圾收集,Java通过垃圾收集器回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。
JRE,也就是 Java运行环境,包含了 JVM和 Java类库,以及一些模块等。而 JDK可以看作是 JRE的一个超集,提供了更多工具,比如编译器、各种诊断工具等。
Java中有三大特性,即继承、封装、多态
对于“Java是解释执行”这句话,这个说法不太准确。我们开发的 Java的源代码,首先通过 Javac编译成为“.class”字节码文件,在运行时,通过 Java虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。通常所说的动态编译器,编译器能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。
jdk、jre和jvm的区别?
JDK:(Java开发工具包)。JDK是整个JAVA的核心,包括了Java的运行环境(jre)、一堆Java工具和Java基础的类库;
JRE:(Java运行环境)。在Java平台下,所有的Java程序都需要在JRE下才能运行。只有JVM还不能进行class的执行,因为解释class的时候,JVM需要调用解释所需要的类库lib。JRE里面有两个文件夹bin和lib,这里可以认为bin就是JVM,lib就是JVM所需要的类库,而JVM和lib合起来就称为JRE。
JVM:(Java 虚拟机)JVM是JRE的一部分,它是一个虚拟出来的计算机,JVM是Java实现跨平台最核心的部分,所有的Java程序会首先被编译为.class的类文件,JVM的主要工作是解释自己的指令集(即字节码)并映射到本地的CPU的指令集或OS的系统调用。Java面对不同操作系统使用不同的虚拟机,依次实现了跨平台。JVM对上层的Java源文件是不关心的,它关心的只是由源文件生成的类文件。

1.1 数据类型

Java的数据类型有两种:基本类型————直接存值,引用类型————-存内存地址;
8位 = 1字节

类型 类型名称 字节空间 取值范围
整数型 byte 1 -128~127
short 2 -215到215-1
int 4 -231到231-1
long 8 -263到263-1
浮点型 float 4 单精度
double 8 双精度
字符 char 2 0~65535
布尔 boolean 1 True,false

1.2 运算规则

1.2.1 计算结果的数据类型,与最大类型一致

如 3/2 int/int结果还是int
3d/2=1.5 double/int 得到double,结果类型与最大类型一致

1.2.2 整数运算转换

1--Java基础重难点 - 图1

byte,short,char三种比int小的整数,运算时会自动转成int类型

1.2.3 浮点数转成整数,小数部分直接舍弃掉

不管是0.1还是0.9全都舍弃

1.2.4 其他

  1. 判断特殊值: Infinity(无穷大):3.14/0 NaN:Math.sqrt(-5)(Math.sqrt()方法是开根号);
  2. BigDecimal: 做精确的浮点数运算 BigInteger: 做超大的整数运算;
  3. n.pow(2) h的2次方;
  4. 11111111——255 1111111111111111——65535

1.3 运算符

1.3.1 除法运算

做除法运算时,“%”取余数(求模),“/”取整数

1.4 流程控制

1.4.1 while和do while

do{}while()循环是先判断后循环,while()是先循环后判断,while(true)经常用于死循环,如无限循环输入等;

1.4.2 switch

switch语句只能判断byte、short、char、int类型数据,一般判断时(byte、short、char)都会转换成int类型,还有枚举类型和jdk1.7以后的String类型;

1.5 数组

1.5.1 数据工具类

数组提供了几个操作数组的方法
Arrays.toString(a): 把数组中的数据拼接成字符串
Arrays.sort(a): 数组排序,基本类型数组会优化快速排序,引用类型数组会优化合并排序
Arrays.binarySearch(a,t): 二分法查找(折半查找),在有序的数组中查找目标值的位置下标,找不到就返回 -(插入点+1)
Arrays.copyOf(a,指定长度): 复制数组,复制成指定长度的新数组(此方法会创建新数组)
System.arraycopy(原数组,原数组起始位置,目标数组,目标数组起始位置,复制数量): 数组复制方法,此方法复制数组后不会创建新数组。

1.6 变量

1.6.1 局部变量

定义在方法、局部代码块中,没有初始值,必须手动初始化,第一次赋值时分配内存,局部变量的作用范围在定义的大括号内有效,在作用域中不能重复定义;
局部变量的生命周期:局部变量的作用域就是它的生命周期,如方法里的局部变量,在方法调用完就销毁。

1.6.2 成员变量

定义在类中,自动初始化成默认值,作用域在整个类中,访问范围可以用访问控制符来控制,允许定义同名的局部变量;成员变量属于对象,也叫实例变量;
成员变量的生命周期:成员变量的生命周期贯穿其整个对象的生命周期,在对象销毁时销毁。

1.6.3 静态变量

被static修饰的变量称为静态变量,静态属于类,所以也叫类变量。只要加载了类的字节码对象,静态变量就会被配空分间,静态变量就可以被使用,可以直接用类名来引用。
静态方法中,不能直接调用非静态方法;要创建对象,用对象调用;
静态变量的生命周期:随着类的加载而存在,随着类的消失而消失。

1.6.4 面试题—三种变量之间的区别

1、成员变量属于对象,静态变量属于类;
2、局部变量作用域只在定义的大括号内,成员变量访问范围由访问控制符控制
3、局部变量的作用域就是它的生命周期,成员变量的生命周期贯穿其整个对象的生命周期,在对象销毁时销毁,静态变量生命周期随着类的加载而存在,随着类的消失而消失。
4、成员变量存在堆内存中,静态变量存在方法区中(栈存方法,堆存属性)
5、成员变量只能被对象调用,静态变量可以被对象调用,也可以被类名调用
6、优先级局部变量>成员变量,调用成员变量使用this

1.6.5 值传递和引用传递的区别

值传递是传递变量的值,并不会改变方法外变量的值;引用传递是传递对象的地址,会改变对象本身的值;
值传递:(形式参数类型是基本数据类型):方法调用时,实际参数把它的值传递给对应的形式参数,形式参数只是用实际参数的值初始化自己的存储单元内容,是两个不同的存储单元,所以方法执行中形式参数值的改变不影响实际参数的值。
引用传递:(形式参数类型是引用数据类型参数):也称为传地址。方法调用时,实际参数是对象(或数组),这时实际参数与形式参数指向同一个地址,在方法执行中,对形式参数的操作实际上就是对实际参数的操作,这个结果在方法结束后被保留了下来,所以方法执行中形式参数的改变将会影响实际参数。

1.7 String

String封装了char[]数组,在第一次使用字符串字面值时,会在“字符串常量池”中创建对象,再次使用此字符串面值时,直接访问池中的对象,不用新建对象;
效率:StringBuilder > StringBuffer > String
常用方法:
charAt(i): 取出指定i位置的字符
indexOf(i):查找子串i的位置,没有就返回-1
subString(from):截取from到末尾的字符串
subString(from,end):截取[from,end)的字符串
String.valueOf():任何数据转成字符串
trim():去除两端的空白字符串

1.7.1 面试题

1、Java操作字符串有哪些类?

  • String:是不可变的,每次操作都会产生新的String对象
  • StringBuffer:可以在原有对象的基础上进行操作。是线程安全的(多线程环境使用)
  • StringBuilder:可以在原有对象的基础上进行操作。非线程安全的(单线程环境使用)

2、==和equals的区别
(1)== 解读

  • 基本数据类型:比较的是值是否相同
  • 引用数据类型:比较的是引用是否相同

(2)equals 解读(本质上是 == ,只不过是被重写了而已)

  • 默认情况下是引用比较,只不过很多类都重写了equals方法,都变成了值比较。

1.8 Integer

在Integer类中,有一个Integer对象缓存数组,缓存了256个对象,称为常量池,范围是(-128~127),在指定范围内的值,直接访问缓存对象,超过缓存数组外的值,会新建对象
比如:int a = 127,b=128;
System.out.println(a == b)————->得到false,因为a与b的地址不一样

Integer是java为int提供的封装类,Integer的默认值是null,int的默认值是0

字符串解析成 int 方法
Integer.parseInt(“255“):——255
Integer.parseInt(“11111111“,2):e二进制转换成10进制,255
Integer.parseInt(“ff“,16):—-255
Integer.parseInt(“377“,8):——255

Int整数转换成其他进制字符
Integer.toBinaryString(255):——”11111111”
Integer.toOctalString(255):——”377”
Integer.toHexString(255):——”ff”

1.9 Double

1.9.1BigDecimal、BigInteget

BigDecimal:做精确的浮点数运算
BigInteget:做超大的整数运算

2、面向对象

2.1 this

This是特殊引用,引用当前对象的地址,用法有两种:

  1. 、this.xx 调用成员,当有成员变量时,用this.xx调用成员变量,引用当前对象地址
  2. 、this(xx) 在构造方法之间调用,目的是减少代码重复,方便维护;一般是从参数少的方法,调用参数多的方法;而且this.(xx) 必须在首行代码中;

    2.1.1 重写

    重写是发生在父类和子类之间,子类重写父类的方法,重写发生在运行时,子类不能重写父类中被final修饰的方法;(体现java的继承性)

    2.1.2 重载

    重载发生在本类中,既一个类中有多个同名不同参(不同参数类型、不同参数个数)的方法,重载发生在编译时;本类中被final修饰的方法可以被重载;构造方法可以重载,但不可以重写,因为被重写的前提是被继承,而构造方法根本就不能被继承
    重载的返回值类型可以不一致;
    作用是给予不同的初始条件(参数), 来处理相同的事情

2.1.3 面试题

1、重载(发生在本类,编译器绑定)和重写(发生在父子类,运行期绑定)中的区别?
a、重载发生在本类中,重写发生在父子类中;
b、重载在编译器绑定,重写在运行期绑定;
C、被final修饰的方法不能被重写,但是可以被重载;
d、重写是子类重写父类的方法,但不能重写父类中被final修饰的方法;重载是一个类有多个同名不同参数 的方法,本类中被final修饰的方法可以被重载;

2.2 super

  1. 、super.xx() 重写时,调用父类同一个方法的代码(直接访问并调用父类中的方法)
  2. 、super() 调用父类的构造方法,super(xx)必须放在首行

3)、super.变量/对象名:使用这种方法可以直接访问父类中的变量或对象,进行修改赋值等操作

重载按照父类运行,重写按照子类真实类型运行;

2.2.3 面试题

1、super与this的区别
super用来引用父类的成分,不能操作到本类的属性和方法,能操作到父类的能被父类访问修饰符允许的属性和方法,只有当本类中调用被重写前的效果时使用super.的方法。
this用来引用当前对象,能够操作当前类里的所有属性及方法,以及父类继承而来能被访问修饰符允许访问的属性和方法。

2.3 面向对象

2.3.1 什么是面向对象

java的面向对象是一种软件开发的方法,也是一种编程思想;面向对象可以将复杂的业务逻辑简单化,增强代码复用性,面向对象具有继承、封装、多态、抽象特性;

2.3.2 封装

封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体就是类,类通常对客户隐藏其实现细节,这就是封装的思想。(代码常见在pojo层)
封装的优点:

  1. 良好的封装可以减少耦合;
  2. 类内部的结构可以自由修改;
  3. 可以对成员变量更精准的控制;
  4. 隐藏信息,实现细节;

    2.3.3 继承(单继承多实现)

    (类单继承,接口多实现)子类继承父类。一个类只能有一个父类。

    2.3.4 多态

    多态是同一个行为具有多个不同表现形式或形态的能力。
    多态的优点:可以消除类型之间的耦合关系(不关心子类的类型,把所有子类当作父类看,屏蔽子类的不同);具有可替换性、可扩充性、接口性、灵活性和简化性;
    多态存在的三个必要条件:继承;重写;父类引用指向子类对象;
    如 Parent p = new Children();
    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
    虚函数:虚函数的存在是为了多态;Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
  • 多态分为两种
    • 编译时多态:方法重载
    • 运行时多态:平时说的就是运行时多态
  • 下面都是关于运行时多态的讲解:
    • 多态的定义:指允许不同类的对象对同一消息做出响应。
    • 存在多态的必要条件:继承、重写、父类引用指向子类对象
    • 多态就是对象拥有多种形态:引用多态和方法多态
      • 引用多态:父类的引用可以指向本类对象、父类的引用可以指向子类的对象
      • 方法多态:创建本类对象时,调用的方法为本类的方法;创建子类对象时,调用的方法为子类重写的方法或者继承的方法。

2.4 static

定义:被static修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
1--Java基础重难点 - 图2

2.4.1 static静态变量

被static修饰的变量叫做静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
被static修饰的属性保存在方法区中;

2.4.2 static方法

被static修饰的方法也叫做静态方法,因为对于静态方法来说是不属于任何实例对象的,那么就是说在静态方法内部是不能使用this的。在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

2.4.3 static代码块

static关键字还有一个比较关键的作用就是用来形成静态代码块以优化程序性能,static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。但是静态代码块在类加载时不一定会执行,具体看类的加载方式。

2.4.4 static的几个误区

  1. static关键字不会改变类中成员的访问权限;

    2、能通过this访问静态成员变量(在静态方法里面不能用this);
    public class Main {  
    static int value = 33;
    public static void main(String[] args) throws Exception{
    new Main().printValue();
    }
    private void printValue(){
    int value = 3;
    System.out.println(this.value);
    }
    }
    这里面主要考察对this和static的理解。this代表什么?this代表当前对象,那么通过new Main()来调用printValue的话,当前对象就是通过new Main()生成的对象。而static变量是被对象所享有的,因此在printValue中的this.value的值毫无疑问是33。在printValue方法内部的value是局部变量,根本不可能与this关联,所以输出结果是33。在这里永远要记住一点:静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。
    3、static是不允许用来修饰局部变量;

2.5 final

2.5.1final和abstract关键字的作用(重点)

  • final:
    • 修饰的类不能被继承
    • 修饰的方法不能被重写
    • 修饰的对象,对象的引用地址不能变,但是对象的值可以变!
    • 修饰的变量叫做常量,必须初始化,不能二次修改
  • abstract:
    • 修饰的类需要被继承
    • 修饰的方法需要被重写
    • 不能修饰属性和构造方法

2.5.2 final、finally、finalize的区别(重点)

  • final:上文介绍。
  • finally:异常处理时提供 finally 块来执行任何清除操作。不管是否发生异常,finally都会执行。
  • finalize:是一个方法名。是在垃圾收集器删除对象之前被调用的。 一般情况下,此方法由JVM调用,程序员不用去调用!

2.6 对象创建过程

class A {
int v1 = 1;
static int v2 = 2;
static {…}
A() {…}
}
class B extends A {
int v3 = 3;
static int v4 = 4;
static {…}
B() {…}
}
new B()

第一次用到A和B类,加载两个类
1.加载父类,给父类静态变量分配内存
2.加载子类,给子类静态变量分配内存
3.执行父类的静态变量赋值运算,和静态初始化块
4.执行子类的静态变量赋值运算,和静态初始化块
新建对象
5.新建父类对象,为父类实例变量分配内存
6.新建子类对象,为子类实例变量分配内存
7.父类的实例变量赋值运算,父类的构造方法
8.子类的实例变量赋值运算,子类的构造方法

2.7 内部类

定义:将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。
java内部类有什么好处?为什么需要内部类?
首先举一个简单的例子,如果你想实现一个接口,但是这个接口中的一个方法和你构想的这个类中的一个方法的名称,参数相同,你应该怎么办?这时候,你可以建一个内部类实现这个接口。由于内部类对外部类的所有内容都是可访问的,所以这样做可以完成所有你直接实现这个接口的功能。
1.成员内部类
成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
2.局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
3.匿名内部类
匿名内部类就是没有名字的内部类
但是匿名内部类可以访问外部私有成员
4.静态内部类
指被声明为static的内部类,他可以不依赖内部类而实例,而通常的内部类需要实例化外部类,从而实例化。静态内部类不可以有与外部类有相同的类名。不能访问外部类的普通成员变量,但是可以访问静态成员变量和静态方法(包括私有类型);一个静态内部类去掉static 就是成员内部类,他可以自由的引用外部类的属性和方法,无论是静态还是非静态。但是不可以有静态属性和方法、
作用 :
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整,   
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏。   
3.方便编写事件驱动程序   
4.方便编写线程代码

2 基础API

3 集合

1--Java基础重难点 - 图3

4.1 HashMap

4.1.1 概念简介

  1. HashMap是一个散列表,以键值对(key,value)形式存储数据,并且是无序的;
  2. HashMap继承AbstractMap,实现了Map、Cloneable、java.io.Serializable接口
  3. HashMap,不是同步的,是线程不安全的,它的key和value都可以是null;

    4.1.2 哈希运算过程

  4. HashMap内部使用Entry[]数组存放数据,该数组的默认初始长度是16,调用key.hashCode()方法得到一个哈希值;

  5. 用哈希值和数组长度可以计算下标值i(哈希值 % 数组长度),HashMap存数据时键值对要封装成Entry对象,Entry对象放入i位置时,如果i是空位置,直接放入;若i有数据时,依次用equals()比较是否相等,找到相等的,替换值,没有相等的,链表连接在一起;
  6. 默认加载因子(负载率)是0.75(数据量/数组容量 到 75%),当加载因子到0.75后,新建容量翻倍的新数组,这时所有的数据重新执行哈希运算,放入新数组;
  7. Jdk1.8以后,链表长度到8时,会转变成红黑树,树上的数据减少到6时,会转回链表;

    4.1.3 HashMap的三种遍历方式(★)

    1 遍历HashMap的键值对
    第一步:根据entrySet()获取HashMap的“键值对”的Set集合。
    第二步:通过Iterator迭代器遍历“第一步”得到的集合。
    1. // 假设map是HashMap对象// map中的key是String类型,value是Integer类型
    2. Integer integ = null;
    3. Iterator iter = map.entrySet().iterator();
    4. while(iter.hasNext()) {
    5. Map.Entry entry = (Map.Entry)iter.next();
    6. key = (String)entry.getKey(); // 获取key
    7. integ = (Integer)entry.getValue(); // 获取value
    8. }
    2 遍历HashMap的键
    第一步:根据keySet()获取HashMap的“键”的Set集合。
    第二步:通过Iterator迭代器遍历“第一步”得到的集合。
    1. // 假设map是HashMap对象// map中的key是String类型,value是Integer类型
    2. String key = null; Integer integ = null;
    3. Iterator iter = map.keySet().iterator();
    4. while (iter.hasNext()) {
    5. key = (String)iter.next(); // 获取key
    6. integ = (Integer)map.get(key); // 根据key,获取value
    7. }
    3 遍历HashMap的值
    第一步:根据value()获取HashMap的“值”的集合。
    第二步:通过Iterator迭代器遍历“第一步”得到的集合。
    1. // 假设map是HashMap对象// map中的key是String类型,value是Integer类型
    2. Integer value = null; Collection c = map.values();
    3. Iterator iter= c.iterator();
    4. while (iter.hasNext()) {
    5. value = (Integer)iter.next();
    6. }

4.2 LinkedList

1、LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、 队列或双端队列进行操作。
2、LinkedList是非同步的,即LinkedList是线程不安全的。
优点:添加值很快——添加在list中间也只需要更改指针;长度不固定。
   实现栈和队列方面,LinkedList要优于ArrayList。

4.3 ArrayList

1、ArrayList是一个其容量能够动态增长的动态数组。它继承了AbstractList,实现了List、RandomAccess, Cloneable, java.io.Serializable。
2、基本的ArrayList,长于随机访问元素,但是在List中间插入和移除元素时较慢。同时,ArrayList的操作是线程不安全的!一般在单线程中才使用ArrayList,而在多线程中一般使用Vector或者CopyOnWriteArrayList。
优点:适合随机读取的时候,读取速度快,可以一步get(index)。
缺点:添加值很慢——一方面,添加数据在array中间的时候,需要移动后面的数;另一方面,当长度大于初始长度的时候,每添加一个数,都会需要扩容。

4.4 LinkedList 与ArrayList

相同点:都实现了List接口和collection,都是线程不安全的;
不同点:ArrayList是基于数组实现的,LinkedList基于链表实现的;ArrayList随机查询快,LinkedList插入和删除速度快;

4.5 List,Set以及Map

Java集合大致可分为Set、List和Map三种体系,其中Set代表无序、不可重复的集合;List代表有序、重复的集合;而Map则代表具有映射关系的集合
1、List , Set, Map都是接口,前两个继承至collection接口,Map为独立接口
2、Set下有HashSet,LinkedHashSet,TreeSet
3、List下有ArrayList,Vector,LinkedList
4、Map下有Hashtable,LinkedHashMap,HashMap,TreeMap
5、 Collection接口是Set、List和Queue接口的父接口
1--Java基础重难点 - 图4

List 有序,可重复
ArrayList
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
Vector
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
LinkedList
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高

Set 无序,唯一
HashSet
底层数据结构是哈希表。(无序,唯一)
如何来保证元素唯一性?
1.依赖两个方法:hashCode()和equals()
LinkedHashSet
底层数据结构是链表和哈希表。(FIFO插入有序,唯一)
1.由链表保证元素有序
2.由哈希表保证元素唯一
TreeSet
底层数据结构是红黑树。(唯一,有序)
1. 如何保证元素排序的呢?
自然排序
比较器排序
2.如何保证元素唯一性的呢?
根据比较的返回值是否是0来决定

4.6 Map接口:

Hashtable是线程安全的,HashMap不是线程安全的。
HashMap效率较高,Hashtable效率较低。
Hashtable不允许null值,HashMap允许null值(key和value都允许)

4.7 TreeSet, LinkedHashSet 和HashSet 的区别

TreeSet的主要功能用于排序
LinkedHashSet的主要功能用于保证FIFO即有序的集合(先进先出)
HashSet只是通用的存储数据的集合
相同点:三者都实现Set接口,三者都不是线程安全的
不同点:

  • HashSet插入数据最快,其次LinkHashSet,最慢的是TreeSet因为内部实现排序;
  • HashSet是无序的,LinkHashSet保证FIFO即按插入顺序排序,TreeSet安装内部实现排序,也可以自定义排序规则
  • HashSet和LinkHashSet允许存在null数据,但是TreeSet中插入null数据时会报NullPointerException

4.8 HashMap原理

重点:

  • map.put( k, v ) 实现原理:首先将k ,v封装为Node对象节点中,然后他的底层会调用k的的hashCode()方法得出hash值。然后通过哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上。如果说下标对应的位置上有链表。此时,就会拿着k和链表上每个节点的k进行equal。如果所有的equals方法返回都是false,那么这个新的节点将被添加到链表的末尾。如其中有一个equals返回了true,那么这个节点的value将会被覆盖。
  • map.get( k )实现原理:先调用k的hashCode()方法得出哈希值,并通过哈希算法转换成数组的下标。通过数组下标快速定位到某个位置上。如果这个位置上什么都没有,则返回null。如果这个位置上有单向链表,那么它就会拿着K和单向链表上的每一个节点的K进行equals,如果所有equals方法都返回false,则get方法返回null。如果其中一个节点的K和参数K进行equals返回true,那么此时该节点的value就是我们要找的value了,get方法最终返回这个要找的value。
  • 为何随机增删、查询效率都很高:增删是在链表上完成的,而查询只需扫描部分,则效率高。
  • HashMap红黑树(JDK8引入):当hash表的单一链表长度超过 8 个的时候,链表结构就会转为红黑树结构。当红黑树上节点少于6个的时候,红黑树就会转换为单向链表数据结构。红黑树近似于平衡的二叉查找树,左右子树高度几乎一致,防止退化为链表。
    • 单向链表查询:需要遍历全部元素,时间复杂度为 0(n)
    • 红黑树查询:近似于折半查找,时间复杂度为 O(logn)

注意:初始化容量为16 默认加载因子为0.75 扩容为2倍 底层为数组+单向链表

1、拉链法导致的链表过深问题为什么不用二叉查找树代替,而选择红黑树?为什么不一直使用红黑树?
之所以选择红黑树是为了解决二叉查找树的缺陷,二叉查找树在特殊情况下会变成一条线性结构(这就跟原来使用链表结构一样了,造成很深的问题),遍历查找会非常慢。而红黑树在插入新数据后可能需要通过左旋,右旋、变色这些操作来保持平衡,引入红黑树就是为了查找数据快,解决链表查询深度的问题,我们知道红黑树属于平衡二叉树,但是为了保持“平衡”是需要付出代价的,但是该代价所损耗的资源要比遍历线性链表要少,所以当长度大于8的时候,会使用红黑树,如果链表长度很短的话,根本不需要引入红黑树,引入反而会慢。
2、说说你对红黑树的见解?

  • 每个节点非红即黑
  • 根节点总是黑色的
  • 如果节点是红色的,则它的子节点必须是黑色的(反之不一定)
  • 每个叶子节点都是黑色的空节点(NIL节点)
  • 从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)

3、解决hash 碰撞还有那些办法?
开放定址法。当冲突发生时,使用某种探查技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定的地址。按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、二次探查法、双重散列法等。
4、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这就叫作rehashing,因为它调用hash方法找到新的bucket位置。这个值只可能在两个地方,一个是原下标的位置,另一种是在下标为<原下标+原容量>的位置
5、重新调整HashMap大小存在什么问题吗?
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。(多线程的环境下不使用HashMap)为什么多线程会导致死循环,它是怎么发生的?HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。这时候,HashMap需要扩展它的长度,也就是进行Resize。
1.扩容:创建一个新的Entry空数组,长度是原数组的2倍。
2.ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

4.10 ConcurrentHashMap(★)

保证线程安全
数据结构为:数组、链表、红黑树

4 异常

5.1 异常结构

5.1.1 error和Exception 的区别?
Error是java程序运行中不可预料的异常情况,这种异常发生以后,会直接导致JVM不可处理或者不可恢复的情况。所以这种异常不可能抓取到,比如OutOfMemoryError、NoClassDefFoundError等。
Exception又分为检查性异常和非检查性异常。两个根本的区别在于,检查性异常 必须在编写代码时,使用try catch捕获(比如:IOException异常)。非检查性异常 在代码编写使,可以忽略捕获操作(比如:ArrayIndexOutOfBoundsException),这种异常是在代码编写或者使用过程中通过规范可以避免发生的。 切记,Error是Throw不是Exception 。

6、I/O

  • 字节流:数据流中最小的数据单元是字节;字节流继承于InputStream、OutputStream
  • 字符流:数据流中最小的数据单元是字符, Java中的字符是Unicode编码,一个字符占用两个字节。字符流继承于Reader、Writer

文件类: FileInputStream, FileOutputStream, FileReader, FileWriter。
byte[]类:ByteArrayInputStream, ByteArrayOutputStream。
Char[]类: CharArrayReader, CharArrayWriter。
String类: StringBufferInputStream, StringReader, StringWriter。
网络数据类:InputStream, OutputStream, Reader, Writer。
缓冲类:BufferedInputStream, BufferedOutputStream, BufferedReader, BufferedWriter。
格式化类:PrintStream, PrintWriter
二进制格式(只要不能确定是纯文本的): InputStream, OutputStream及其所有带Stream结束的子类。
纯文本格式(含纯英文与汉字或其他编码方式);Reader, Writer及其所有带Reader, Writer的子类。

6.1 File

文件流,封装一个磁盘路径字符串,提供对文件和文件夹的操作方法;
1、创建对象 new File(“d:/abc/a.txt”)、new File(“d:/abc”,”a.txt”)
2、length( ):文件大小字节量,对文件夹无效,getName( ):文件名,getParent( ):父目录,getLastModified():最后修改时间,毫秒值
3、 isFile()是否是文件,isDirectory()是否是文件夹,exists()是否存在
4、createNewFile():新建文件,文件已经存在,不新建,返回false,文件夹不存在,会出现异常;mkdirs():创建多层目录,delete():删除文件,或删除空目录
5、list():获得 String[] 数组,存放文件、目录名, listFiles():获得 File[] 数组,存放文件、目录的封装对象

6.2 InputStream / OutputStream

InputStream:所有字节输入流的父类,所有的字节输入流都要继承该类,它是一个抽象类。
OutputStream:是所有的输出字节流的父类,所有的字节输出流都要继承该类,它是一个抽象类。

6.3 FileInputStream / FileOutputStream

是字节流子类,文件流,直接插在文件上,直接读写文件数据

6.4 ObjectInputStream / ObjectOutputStream

1、对象的序列化、反序列化,把对象的信息,按照固定的字节格式,转成一串字节序列,输出
2、方法:writeObject(Object obj)序列化输出对象,readObject()反序列化恢复对象。被序列化的对象,必须实现 Serializable 接口
3、不序列化的成员:static静态属于类,不随对象一起被序列化输出
4、transient 临时,只在程序运行期间,在内存中临时存在,不随对象一起被序列化持久保存

6.5 Reader / Writer

  1. Reader是所有的输入字符流的父类,它是一个抽象类。

    CharReader、StringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。
    BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。
    FilterReader是所有自定义具体装饰流的父类,其子类PushbackReader对Reader对象进行装饰,会增加一个行号。

  2. Writer是所有的输出字符流的父类,它是一个抽象类。

CharArrayWriter、StringWriter是两种基本的介质流,它们分别向Char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据,
BufferedWriter是一个装饰器为Writer提供缓冲功能。
PrintWriter和PrintStream极其类似,功能和使用也非常相似。

6.6 InputStreamReader/OutputStreamWriter

字符编码转换流,InputStreamReader读取其他编码转成Unicode;OutputStreamWriter把Unicode转成其他编码输出

  1. InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。Reader中各个类的用途和使用方法基本和InputStream中的类使用一致。后面会有Reader与InputStream的对应关系。
  2. OutputStreamWriter是OutputStream到Writer转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream极其类似.

3、BufferedReader可以一行一行的读取文本数据,readLine() 读取一行数据读取结束,再读取,得到 null

5 线程

7.1 概念

  1. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。<br />线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。(线程之间实际上是轮换执行的)<br />**并发跟并行的区别:**
  • 并发:是指两个或多个事件在同一时间间隔发生,是同一实体上的多个事件
  • 并行:是指两个或多个事件在同一时刻发生,是指不同实体上的多个事件

    7.2 创建线程(★)

    (1)继承Thread (java.lang.Thread)
    定义Thread的子类,重写run()方法,在run()方法中的代码,是与其他代码并行执行的;代码线程启动后,自动执行run()方法

    1. public class MyThread extends Thread{//继承Thread类
    2.   public void run(){
    3.    //重写run方法
    4.   }
    5. }
    6. public class Main {
    7.   public static void main(String[] args){
    8.     new MyThread().start(); //创建并启动线程
    9.   }
    10. }

    (2)实现Runnable (java.lang.Runnable)
    定义Runnable子类,实现run()方法,把Runnable对象,放入Thread线程对象启动,线程启动后,执行Runnable对象的run()方法

    1. public class MyThread2 implements Runnable {//实现Runnable接口
    2.   public void run(){
    3.    //重写run方法
    4.   }
    5. }
    6. public class Main {
    7.   public static void main(String[] args){
    8.     //创建并启动线程
    9.     MyThread2 myThread=new MyThread2();
    10.     Thread thread=new Thread(myThread);
    11.     thread().start();
    12.     //或者 new Thread(new MyThread2()).start();
    13.   }
    14. }

    (3)实现Callable和Future

    1. public class Demo5 implements Callable<String>{
    2. public String call() throws Exception {
    3. System.out.println("正在执行新建线程任务");
    4. Thread.sleep(2000);
    5. return "新建线程睡了2s后返回执行结果";
    6. }
    7. public static void main(String[] args) throws InterruptedException, ExecutionException {
    8. Demo5 d = new Demo5();
    9. /*
    10. call()只是线程任务,对线程任务进行封装
    11. class FutureTask<V> implements RunnableFuture<V>
    12. interface RunnableFuture<V> extends Runnable, Future<V>
    13. */
    14. FutureTask<String> task = new FutureTask<>(d);
    15. Thread t = new Thread(task);
    16. t.start();
    17. System.out.println("提前完成任务...");
    18. //获取任务执行后返回的结果
    19. String result = task.get();
    20. System.out.println("线程执行结果为"+result);
    21. }
    22. }

    7.3 线程的状态

  • 创建

  • 就绪:调用star()方法进入就绪状态
  • 运行:执行run()方法
  • 阻塞:sleep()、suspend()、wait()都导致线程阻塞
  • 死亡:run()方法执行结束或者调用stop()方法

7.4 线程方法

  • Thread.currentThread() 获得正在执行这行代码的线程对象
  • Thread.sleep(毫秒值) 当前线程暂停指定的毫秒时长
  • Thread.yield() 让步,当前线程放弃时间片,让出cpu资源
  • iterrupt() 打断一个线程的暂停状态,被打断的线程出现InterruptedException
  • join() 当前线程,等待被调用的线程结束
  • Object 的方法
    • wait():线程等待
    • notify():通知一个等待线程
    • notifyAll():通知所有等待线程

7.5 线程同步与锁


何时需要同步
在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。
对于非静态字段中可更改的数据,通常使用非静态方法访问。
对于静态字段中可更改的数据,通常使用静态方法访问。

线程死锁
死锁对Java程序来说,是很复杂的,也很难发现问题。当两个线程被阻塞,每个线程在等待另一个线程时就发生死锁。

守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。

7.6 线程池(★)

线程池:java.util.concurrent.Executors创建四种线程池

  • newCachedThreadPool 创建非固定数量,可缓存的线程池,若线程池超过处理需要,可灵活回收空线程,若没有线程可回收,则建新线程
  • newFixedThreadPool 固定长度线程池,底层是无界队列,可控制最大并发数,超出的线程会在队列中等待
  • newScheduledThreadPool 定时执行线程池,支持定时及周期性任务执行
  • newSingleThreadExecutor 单线程化的线程池,只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行

线程池七个参数:

  1. corePoolSize 线程池核心线程大小
  2. maximumPoolSize 线程池最大线程数量
  3. keepAliveTime 空闲线程存活时间
  4. unit 空闲线程存活时间单位
  5. workQueue 工作队列
  6. threadFactory 线程工厂
  7. handler 拒绝策略

(重点)线程池的好处:

  1. 限定线程的个数,不会导致由于线程过多导致系统运行缓慢或崩溃
  2. 线程池每次都不需要去创建和销毁,节约了资源
  3. 线程池不需要每次都去创建,响应时间更快.

7.7 面试题

1.什么是线程?
线程是操作系统能够进行运算调度的最小单位,它被包含在进程当中,是进程的实际运作单位。

2.线程和进程的区别?
一个进程是一个独立的运行环境,它可以被看做是一个程序或应用。线程是进程中执行的一个任务。线程是进程的子集,一个进程可以有多个线程。进程独占一片内存,进程共享内存。

3.线程的run跟start方法有什么区别?

  • run方法:是线程体,通过调用Thread类的start()方法来启动一个线程
  • start方法:是用来启动线程的,实现多线程运行

6、产生死锁的条件?
互斥条件:一个资源每次只能被一个进程使用。
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

7.如何避免死锁?
死锁发生的条件:

  • 存在循环等待
  • 存在资源竞争
  • 不剥夺条件,已经获得的资源不会被剥夺
  • 请求与保持,一个线程因请求资源被阻塞时,拥有资源的线程的状态不会改变

避免死锁只需要破环其中的一个条件就可以了

8.什么是线程安全?Vector是一个线程安全类吗?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。一个线程安全的计数器类的同一个实例对象在被多个线程使用的情况下也不会出现计算失误。很显然你可以将集合类分成两组,线程安全和非线程安全的。Vector 是用同步方法来实现线程安全的, 而和它相似的ArrayList不是线程安全的。

9、wait()与sleep()的区别?

  • sleep():是Thread的静态方法,让线程进入睡眠模式
  • wait() :是Object类的方法,执行wait时就进入等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

10、什么是线程局部变量ThreadLocal?
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

16、ThreadLoal的作用是什么?
简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了.

17、生产者消费者模型的作用是什么?
通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

18、为什么要使用线程池?(★)
避免频繁地创建和销毁线程,达到线程对象的重用。另外,使用线程池还可以根据项目灵活地控制并发的数目。

19、java中用到的线程调度算法是什么?
抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

20、Thread.sleep(0)的作用是什么?
由于Java采用抢占式的线程调度算法,因此可能会出现某条线程常常获取到CPU控制权的情况,为了让某些优先级比较低的线程也能获取到CPU控制权,可以使用Thread.sleep(0)手动触发一次操作系统分配时间片的操作,这也是平衡CPU控制权的一种操作。

21、什么是乐观锁和悲观锁?
乐观锁:乐观锁认为竞争不总是会发生,因此它不需要持有锁,将比较-替换这两个动作作为一个原子操作尝试去修改内存中的变量,如果失败则表示发生冲突,那么就应该有相应的重试逻辑。
悲观锁:悲观锁认为竞争总是会发生,因此每次对某资源进行操作时,都会持有一个独占的锁,就像synchronized,不管三七二十一,直接上了锁就操作资源了。

22、java中的++操作符线程安全么?
不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差

6 反射

9.1 概念

反射是Java中特有的一种技术,是JAVA中自省特性的一种实现(对象运行时动态发现对象成员),可以基于此特性实现java的动态编程(例如对象创建,成员调用等).
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

9.2 获取Class对象方式

获得类对象(三种方式)
1、A.class
2、Class.forName(“day1901.A”)
3、a1.getClass()
获得包名类名
c.getPackage().getName()
c.getName()
c.getSimpleName()
获得成员变量的定义信息
getFields()
得到可见的成员变量,包括继承的变量
getDeclaredFields()
本类定义的所有变量,包括私有变量
不包括继承的变量
getField(变量名)
getDeclaredField(变量名)
获得构造方法的定义信息
getConstructors()
获得可见的构造方法
etDeclaredConstructors()
获得所有构造方法,包括私有
getConstructor(参数类型列表)
getDeclaredConstructor(int.class, String.class)
获得方法的定义信息
getMethods()可见的方法,包括继承的方法

getDeclaredMethods()
本类定义的方法,包括私有方法,
不包括继承的方法
getMethod(方法名, 参数类型列表)
getDeclaredMethod(方法名, int.class, String.class)
反射新建对象
通过“类对象”的反射操作创建对象
执行无参构造
Object obj = c.newInstance();
执行有参构造
//获得构造方法
Constructor t = c.getConstructor(int.class, String.class);
//通过构造方法的反射操作,来新建对象
Object obj = t.newInstance(6, “abc”);
反射调用成员变量
获得成员变量
Field f = c.getDeclaredField(“age”);
使私有变量,也可以被访问
f.setAccssible(true);
赋值f.set(对象, 21);
通知指定对象,访问变量
静态变量,第一个参数给 null
取值
int i = (int) f.get(对象);
静态变量,第一个参数给 null
反射调用成员方法
获得方法
Method t = c.getMethod(方法名, 参数类型列表);
使私有方法允许调用
t.setAccessible(true);
调用方法
Object r = t.invoke(对象, 参数数据);
让指定对象,执行该方法
静态方法,第一个参数给 null
没有返回值得到 null

7 内存对象

10.1 字节码对象

每个类在加载(将类读到内存)时都会创建一个字节码对象,且这个对象在一个JVM内存中是唯一的.此对象中存储的是类的结构信息.
字节码对象的获取方式?(常用方式有三种)

  1. 类名.class
  2. Class.forName(“包名.类名”)
  3. 类的实例对象.getClass();

说明:字节码对象是获取类结构信息的入口.

10.2 内存对象

实例对象是基于类的结构信息创建的对象,在内存中可以有多份.
其创建方式

  1. 编译时借助new关键字构建.(真正创建会发生在运行时)
  2. 运行时可借助反射API创建.

说明:每个实例对象都会独占堆内存中一块连续的内存空间.且对象的创建和销毁都会占用系统资源.

11、两大抽象类型(抽象、接口)

11.1 抽象类

11.1.1 抽象类

抽象类往往用来表征对问题领域进行分析、设计中得出的抽象概念,是对一系列看上去不同,但是本质上相同的具体概念的抽象。(只有方法的定义,没有方法的实现)
抽象类,用abstract修饰,抽象类中和普通类一样,同样可以拥有成员变量和普通的成员方法。注意,抽象类和普通类的主要有三点区别:

  1. 抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,子类便无法实现该方法),缺省情况下默认为public。
  2. 抽象类不能用来创建对象,只能被继承使用
  3. 如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
  4. 抽象类可以有fianl修饰的方法,
  5. 抽象类可以有构造方法,但不能直接创建抽象类的实例对象;
  6. 抽象类可以有静态方法
  7. 声明抽象方法不可写出大括号

    11.1.2 抽象方法

    抽象方法必须用abstract关键字进行修饰,声明抽象方法时不可写出大括号;
    如abstract void fun();

    11.2 接口

    接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。它是对行为的抽象。
    接口和类平级,也是一个数据类型,接口使java间接实现多继承;
    接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

  8. 、一个类中可以实现多个接口,接口本身也可以继承多个接口;

  9. 、接口没有构造方法,不能被实例化;只能包含普通方法;
  10. 、接口的变量和方法都不能用final修饰;
  11. 、接口不能有静态方法;

11.3 面试题

1、抽象类和接口的区别
语法层面上的区别
  1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;
  2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;
  3)接口中不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。
5)抽象类有构造器,接口没有构造器,接口不包含构造方法,抽象类可以包含构造方法
6)抽象类继承Object,接口不继承Object

12、Java对象四大特性

1.封装特性

  1. 广义封装:一个项目有哪些系统构成,一个系统由哪些模块构成,…
  2. 狭义封装:对象属性私有化,方法能公开则公开.

生活中的封装:

  1. 广义:大到国家有多个省份,小到家庭有多少个成员.
  2. 狭义:每个人都有特征(个头高,帅气,漂亮),都有行为(说话,跳舞,唱歌).

框架中的封装:

  1. mybatis:(封装JDBC操作,对JDBC参数处理,结果处理做了减法设计)
  2. 广义:(连接池模块,缓存处理模块,日志模块处理,…)
  3. 狭义:(SqlSession对象中应该有什么,哪些设计为私有,哪些设计为公开)
  4. Spring:(封装了对象的创建,对象的管理)
  5. 广义:(IOC,MVC,AOP,…)
  6. 狭义:(ClassPathXmlApplicationContext对象的构成)

2.继承特性

  1. 优势:实现代码的复用,提高程序的扩展性.(案例分析:自定义ClassLoader)
  2. 劣势:大范围扩展可能会导致类爆炸,会降低代码的可维护性.

继承应用案例分享:

1)生活中的继承:

  1. 子承父业
  2. 文化传承

2)框架中的继承:

  1. Mybatis(PooledDataSource,ContextMap,PersistenceException…)
  2. Spring (ManagedList,FlashMap,AnnotationAttributes….)

如何基于LinkedHashMap实现一个LRU(不常访问淘汰算法)算法的Cahce对象?
提示:(方法:通过继承)

3.多态特性
a)编译时多态:方法的重载
b)运行时多态:同一个行为(方法),因对象不同表现结果可能不同.
说明:此特性基于继承特性,父类引用可以指向子类对象,基于此特性可以更好实现程序之间的解耦合,提高程序可扩展性.
多态案例应用分享:
1)生活中多态:
a) 睡觉:有的人磨牙,有的人说梦话,有的人打呼噜,有的人梦游,…
b) 吃饭:有的人细嚼慢咽,有的人狼吞虎咽,…

2)框架中多态:
a)Mybatis (Executor,SimpleExecutor,CachingExecutor)
b)Spring (BeanbFactory,ClassPathXmlApplicationContext,…)

4.扩展特性应用
组合特性可以理解为面向对象中的一个扩展特性,即可多个对象通过相互关联(协同),共同完成一个业务模块功能.
为什么要组合(相互关联)呢?

  1. 类设计时要遵循单一职责原则,即类设计时不要设计大而全的对象,对象职

责越多引起类变化的原因就会更多。

  1. 类设计要各司其职,各尽所能,这样可扩展性和维护性都会比较好。

13、泛型

13.1 定义

定义:Java 泛型(generics)是JDK 5中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数类型化,也就是操作的数据类型被指定为一个参数。

  1. 参化类型,数是JDK1.5的新特性。(定义泛型时使用参数可以简单理解为形参),例如List,Map
  2. 编译时的一种类型,此类型仅仅在编译阶段有效,运行时无效.例如List在运行时String会被擦除,最终系统会认为都是Object.也叫泛型擦除

泛型特点:泛型是进行类型设计或方法定义时的一种约束规范,基于此规范可以:
1.提高编程时灵活性(有点抽象,后续结合实例理解)。
2.提高程序运行时的性能。(在编译阶段解决一些运行时需要关注的问题,例如强转)
说明:泛型应用相对比较简单,难点在泛型类或方法的设计上,通过这样的设计如何对现有类进行”减法设计”,提高类或方法的通用性.

13.2 泛型类

用法:class 类名<泛型,…>{}
类泛型定义:(用于约束类中方法参数和方法返回值类型)

class Container{//类泛型:类名<泛型>
public void add(T t){}//通过类泛型约束方法参数类型
public T get(){//通过类泛型约束方法返回值类型
return null;
}
}

类泛型应用:关键代码分析

Container c1=new Container<>();
c1.add(100);//自定封箱 Integer.valueOf(100)
//c1.add(“ABC”);
Integer t1=c1.get();

说明:泛型应用时相当于实参传给形参,但是实参必须为对象类型。

13.3 泛型接口

接口泛型定义:

定义接口时指定泛型,用于约束接口方法参数类型以及方法返回值类型,这里无须关心此类要做什么,重点先了解语法.
interface Task{//思考map中的泛型Map
/
此方法用于执行任务
@param arg 其类型由泛型参数Param决定
*
@return* 其类型由泛型参数result决定
/
Result execute(Param arg1);
}

接口泛型应用

class ConvertTask implements Task{
@Override
public Integer execute(String arg) {
// TODO Auto-generated method stub
return Integer.parseInt(arg);
}
}

13.4 泛型方法

框架中相关泛型的应用:

方法泛型应用:

1)Mybatis
class DefaultSqlSession{
/泛型方法*/
public T getMapper(Class cls){
return null;
}
}
2)Spring
class ClassPathXmlApplicationContext{
//泛型方法:写一个getBean方法(仿照spring官方)
public T getBean(Class cls){
return null;
}
public T getBean(String id,Class cls){
return null**;
}
}

总结

  1. 泛型类和泛型接口用于约束类或接口中实例方法参数类型,返回值类型.
  2. 泛型类或泛型接口中实际泛型类型可以在定义子类或构建其对象时传入.
  3. 泛型方法用于约束本方法(实例方法或静态方法)的参数类型或返回值类型.
  4. 泛型类上的泛型不能约束类中静态方法的泛型类型.

    13.5 泛型通配符

    通配符一般可以理解为一种通用的类型,在这里的通配符泛指一种不确定性类型.
    1.泛型应用时有一个特殊符号”?”,可以代表一种任意参数类型(实参类型)。
    2.通配符泛型只能应用于变量的定义。例如:Class<?> c1;

/
泛型通配符”?”的应用
说明:”?”代表一种不确定的类型,
当使用一个泛型类时假如其类型不确定可以使用”?”替代
/
public class TestGeneric05 {
public static void main(String[] args)throws Exception {
Class c1=Object.class; //类对象
//System.out.println(c1.toString());
//“?”为泛型应用的一个通配符
//当泛型应用时,无法判定具体类型时,使用”?”替代
//此案例在编译阶段根本无法确定字符串中代理的类型具体为何种类型.
Class<?> c2=Class.forName(“java.lang.Object”);
//Class c3=Class.forName(“java.lang.Object”); 错误
System.
out**.println(c1==c2);
}
}

13.6 泛型的上下界问题?

泛型在应用时通常要指定对象的上届和下届,其实现方式如下:
1.指定泛型下界:<? super 类型>
2.指定泛型上界:<? extends 类型>

14、序列化

14.1 定义

序列化和反序列化是java中进行数据存储和数据传输的一种方式.

  1. 序列化:将对象转换为字节的过程。
  2. 反序列化:将字节转换为对象的过程。

说明:在当前软件行业中有时也会将对象转换为字符串的过程理解为序列化,例如将对象转换为json格式的字符串。

14.2 对象序列化与反序列化

类 ObjectOutputStream 和ObjectInputStream是高层次的数据流,它们包含序列化和反序列化对象的方法

java中如何实现对象的序列化和反序列化呢?一般要遵循如下几个步骤

  1. 对象要实现Serializable接口
  2. 添加序列化id(为反序列化提供保障)
  3. 借助对象流对象实现序列化和反序列化?

代码实现:

定义一需要序列化的java类对象(用户行为日志对象)
class SysLog implements Serializable{
private static final long serialVersionUID = -5296788134693797316L;
/日志id*/
private Integer id;
/
操作用户/
private String username;
//private Date createdTime;
public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
*return
“SysLog [id=” + id + “, username=” + username + “]”;
}
}

定义测试类:

public class TestSerializable01 {
public static void main(String[] args)throws Exception {
//1.构建日志对象,并存储数据
SysLog log=new SysLog();
log.setId(1);
log.setUsername(“tmooc”);
//2.构建对象输出流,将日志对象存储到文件
ObjectOutputStream out=
new ObjectOutputStream(
new FileOutputStream(“f1.data”));
out.writeObject(log);
//out.writeInt(100);//整数序列化
System.out.println(“序列化ok”);
out.close();
//3.将文件中的日志数据读出
ObjectInputStream in=
new ObjectInputStream(new FileInputStream(“f1.data”));
SysLog obj=(SysLog)in.readObject();
//Integer result=in.readInt();//整数反序列化
//System.out.println(result);
in.close();
System.out.println(obj);

}

说明:

  1. Serializable接口只起一个标识性的作用.
  2. 建议实现序列化接口的类自动生成一个序列化id.假如没有在类中显式添加此id,不会影响对象的序列化,但可能会对反序列化有影响.
  3. 系统底层会基于类的结构信息自动生成序列化id.
  4. 序列化和反序列化的顺序应该是一致的(先序列化谁,就先反序列化谁).

    14.3 解决序列化安全问题

    java中的默认序列化是存在一些安全问题的,例如对象序列化以后的字节通过网络传输,有可能在网络中被截取。那如何保证数据安全呢?通常可以在对象序列化时对对象内容进行加密,对象反序列化时对内容进行解密。
    具体实现过程分析:

  5. 在序列化对象中添加writeObject(ObjectOutpuStream out)方法对内容进行加密再执行序列化。

  6. 在序列化对象中添加readObject(ObjectInputStream in)方法对内容先进行反序列化然后在执行解密操作

代码实现:

class SysLog implements Serializable{
private static final long serialVersionUID = -5296788134693797316L;
/日志id*/
private Integer id;
/
操作用户/
private String username;
//private Date createdTime;

/**此方法会在调用对象流的的writeObject方法时执行
/
private void writeObject(ObjectOutputStream out) throws IOException{
//1.获取一个加密对象(java.util)
Base64.Encoder encoder=Base64.getEncoder();
//2.对内容进行加密
byte[] array=encoder.encode(username.getBytes());
//3.将加密结果重新赋值给username
username=new String(array);
//4.执行默认序列化
out.defaultWriteObject();//序列化
}//方法的声明是一种规范

private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException{
//1.执行默认反序列化
in.defaultReadObject();
//2.获取解密对象
Base64.Decoder decoder=Base64.getDecoder();
//3.执行解密操作
byte[] array=decoder.decode(username);
username=new String(array);
}

public void setId(Integer id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String toString() {
return “SysLog [id=” + id + “, username=” + username + “]”;
}
}

说明: writeObject/readObject方法:

  1. 访问修饰符,返回值类型,方法名,参数应与如上代码相同(java规范中定义)
  2. 两个方法会在序列化和反序列化时由系统底层通过反射调用.

14.4 序列化的粒度如何控制?

所谓序列化粒度一般指对象序列化时,如何控制对象属性的序列化。例如哪些序列化,哪些属性不序列化。java中的具体方案一般有两种:
方案1:不需要序列化的属性使用Transient修饰.当少量属性不需要序列化时,使用此关键字修饰比较方便.例如 private transient Integer id;
方案2:让序列化对象实现Externalizable接口,自己指定属性的序列化和反序列化过程, 但是要序列化的对象必须使用public修饰.

代码实现:

public class Message implements Externalizable{
private Integer id;//10
private String title;
private String content;
private String createdTime;
//序列化时调用
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeUTF(title);
}
//反序列化调用
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
title=in.readUTF();
}
public void setId(Integer id) {
this.id = id;
}
public void setTitle(String title) {
this.title = title;
}
public void setContent(String content) {
this.content = content;
}
public void setCreatedTime(String createdTime) {
this.createdTime = createdTime;
}
@Override
public String toString() {
return “Message [id=” + id + “, title=” + title + “, content=” + content + “, createdTime=” + createdTime
+ “]”;
}
}

其中:序列化和反序列化需要在Externalizable接口方法中进行实现.

14.5 序列化的性能问题及如何优化?

序列化性能问题目前市场上会借助一些第三方的框架进行实现,例如kryo。
说明:可基于kryo尝试将一个对象进行序列化.

8 注解

15.1 概念

注解(Annotation)是:
1) JDK1.5推出的一种新的应用类型(特殊的class)
2)元数据(Meta Data):一种描述性类型,用于描述对象.例如@Override
3)Java提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。

15.2 注解分类

按运行机制分:

  1. 源码注解
    注解只在源码中存在,编译成.class文件就不存在了
  2. 编译时注解
    注解在源码和.class文件中都会存在。比如说@Override
  3. 运行时注解
    在运行阶段还会起作用,甚至会影响运行逻辑的注解。比如说@Autowired

按来源分:

  1. JDK内置注解
  2. java第三方注解
  3. 自定义注解
  4. 元注解

    15.3 常见的注解

    | @Target | 表示该注解可以用于什么地方,可能的ElementType参数有:
    CONSTRUCTOR:构造器的声明
    FIELD:域声明(包括enum实例)
    LOCAL_VARIABLE:局部变量声明
    METHOD:方法声明
    PACKAGE:包声明
    PARAMETER:参数声明
    TYPE:类、接口(包括注解类型)或enum声明 | | —- | —- | | @Retention | 表示需要在什么级别保存该注解信息。可选的RetentionPolicy参数包括:
    SOURCE:注解将被编译器丢弃
    CLASS:注解在class文件中可用,但会被VM丢弃
    RUNTIME:VM将在运行期间保留注解,因此可以通过反射机制读取注解的信息。 | | @Document | 将注解包含在Javadoc中 | | @Inherited | 允许子类继承父类中的注解 |

@Retention

Retention 的英文意为保留期的意思。当 @Retention 应用到一个注解上的时候,它解释说明了这个注解的的存活时间。
它的取值如下:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。

    @Documented

    顾名思义,这个元注解肯定是和文档有关。它的作用是能够将注解中的元素包含到 Javadoc 中去。

    @Target

    Target 是目标的意思,@Target 指定了注解运用的地方。
    你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。
    类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。@Target 有下面的取值

  • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解

  • ElementType.CONSTRUCTOR 可以给构造方法进行注解
  • ElementType.FIELD 可以给属性进行注解
  • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  • ElementType.METHOD 可以给方法进行注解
  • ElementType.PACKAGE 可以给一个包进行注解
  • ElementType.PARAMETER 可以给一个方法内的参数进行注解
  • ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

    @Inherited

    Inherited 是继承的意思,但是它并不是说注解本身可以继承,而是说如果一个超类被 @Inherited 注解过的注解进行注解的话,那么如果它的子类没有被任何注解应用的话,那么这个子类就继承了超类的注解。
    说的比较抽象。代码来解释。

@Repeatable

Repeatable 自然是可重复的意思。@Repeatable 是 Java 1.8 才加进来的,所以算是一个新的特性。

注解的属性

注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。