1. 面向过程和面向对象
面向过程:
分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用。
优点:性能
比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如嵌入式开发、 Linux/Unix 等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展。
面向对象:
构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
优点:易维护、易复用、易扩展
,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统。
缺点:性能比面向过程低。
2. 继承和多态
继承:
继承是指:保持已有类的特性而构造新类的过程。继承后,子类能够利用父类中定义的变量和方法,就像它们属于子类本身一样。
- 单继承:java
类
是单继承的,一个类只允许有一个父类。
public class A extends B{ } //继承单个父类
- 多继承:java
接口
多继承的,一个类允许继承多个接口。
public class A extends B implements C{ } //同时继承父类和接口
public class A implements B,C{ } //继承多个接口
多态:
多态是指:在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
子类对象的多态性使用前提:
- 有类的
继承
; - 由子类对父类方法的
重写
封装、继承和多态三者的主要功能:
封装 | 继承 | 多态 |
---|---|---|
隐藏内部代码 | 复用现有代码 | 改写对象行为 |
3. 抽象类和接口
抽象类:
Java语言中,用
abstract
关键字来修饰一个类时,这个类叫作抽象类。抽象类是它的所有子类的公共属性的集合,是包含一个或多个抽象方法的类。抽象类可以看作是对类的进一步抽象。在面向对象领域,抽象类主要用来进行类型隐藏。
比如创建一个Animal抽象类:
public abstract class Animal {
public abstract void eat();
public abstract void sleep();
}
注意: 抽象方法不能有方法体,在方法后面加一个大括号而里面什么都不写也是不行的,编译器会报 abstract methods do not specify a body
这样一个错误。
public class Cat extends Animal{
@Override //重写抽象类中的eat()方法
public void eat() {
System.out.println("我是猫,我吃的是猫粮呀");
}
@Override //重写抽象类中的sleep()方法
public void sleep() {
System.out.println("我是猫,我比你们人类睡的时间短!");
}
}
在这里需要注意的是:当一个类继承抽象类的时候,这个类必须去重写所继承的抽象类的抽象方法,否则编译器会报 The type Cat must implement the inherited abstract method Animal.eat()
的错误。
两个注意点:
- 如果一个类中有一个抽象方法,那么当前类一定是抽象类;但抽象类中不一定有抽象方法。
- 抽象类中的抽象方法,需要有子类实现,如果子类不实现,则子类也需要定义为抽象的。
接口:
Java接口是一系列方法的声明,是一些方法特征的集合,一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)。
public interface Eat {
public abstract void ioEat();
}
public interface Study {
public void ioStudy();
}
public class Cat implements Sleep,Eat{
@Override
public void ioSleep(int i) {
System.out.println("我是猫,我每天都不用睡觉!!!");
}
@Override
public void ioEat() {
System.out.println("我是猫,我吃猫粮!!!");
}
}
接口中的所有属性
默认为:public static final xxx
;
接口中的所有方法
默认为:public abstract xxx
;
抽象类和接口的区别:
- 抽象类可以有
构造方法
,接口没有构造方法 - 抽象类可以有
普通成员变量
,接口没有普通成员变量 - 抽象类可以有非抽象的
普通方法
,接口中的方法必须是抽象的 - 抽象类中的抽象方法访问类型可以是public,protected,接口中抽象方法必须是
public
类型的 - 抽象类可以包含
静态方法
,接口中不能包含静态方法 - 一个类可以实现
多个接口
,但是只能继承一个抽象类
- 接口中基本数据类型的数据成员,都默认为
static final
,抽象类则不是
3. JRE、JDK、JVM
一句话:JDK包含JRE,JRE包含JVM。有JRE即可运行程序了。
JRE(JavaRuntimeEnvironment,Java运行环境):
JRE也就是Java平台
。所有的Java程序都要在JRE下才能运行。普通用户只需要运行已开发好的java程序,安装JRE即可。
JDK(Java Development Kit,Java开发工具包):
是程序开发者用来编译、调试Java程序
用的开发工具包。JDK的工具也是Java程序,也需要JRE才能运行。为了保持JDK的独立性和完整性,在JDK的安装过程中,JRE也是安装的一部分。所以,在JDK的安装目录下有一个名为jre的目录
,用于存放JRE文件。
JVM(JavaVirtualMachine,Java虚拟机):
JVM是JRE的一部分
。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能
来实现的。JVM有自己完善的硬件架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。Java语言最重要的特点就是跨平台运行。使用JVM就是为了支持与操作系统无关,实现跨平台
。
4. 堆和栈
大多数JVM将内存区域划分为:方法区、堆、程序计数器、虚拟机栈、本地方法栈。其中方法区和堆是线程共享的,而另外三个是非线程共享的。
JVM初始运行时都会分配好方法区和堆,而JVM每遇到一个线程,就会为其分配一个程序计数器、虚拟机栈、本地方法栈。当线程终止时,三者所占的内存空间就会被释放掉。也就是说,非线程共享的那三个区域的生命周期与所属线程相同,而线程共享的区域与JAVA程序运行的生命周期相同,所以这也是系统垃圾回收的场所只发生在线程共享的区域(实际上对大部分虚拟机来说只发生在堆上)的原因。
方法区:
方法区是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息
、常量
、静态变量
、即编译器编译后的代码等数据
。方法区域又被称为“永久代”。Java堆中还必须包含能查找到此对象类型数据的地址信息
(如对象类型、父类、实现的接口、方法等),这些类型数据则保存在方法区中。
运行时常量池
是方法区的一部分,class文件除了有类的字段、接口、方法等描述信息之外,还有常量池用于存放编译期间生成的各种字面量和符号引用。
堆:
堆是Java虚拟机所管理的内存中最大的一块,它是所有线程共享的一块内存区域。几乎所有的对象实例和数组
都在这里分配内存。堆是垃圾收集器管理的主要区域
,因此很多时候也被称为“GC堆
”。根据Java虚拟机规范的规定,Java堆可以处在物理上不连续的内存空间中,只要逻辑上是连续的即可。如果在堆中没有内存可分配时,并且堆也无法扩展时,将会抛出OutOfMemoryError
异常。
程序计数器:
程序计数器是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器
,各条线程之间的计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。当线程在执行的是Native方法(调用本地操作系统方法)时,该计数器的值为空。另外,该内存区域是唯一一个在Java虚拟机规范中没有规定任何OOM(内存溢出:OutOfMemoryError)
情况的区域。
虚拟机栈:
系统自动分配与回收内存,效率较高,快速,存取速度比堆要快;是一块连续的内存的区域,有大小限制,如果超过了就会栈溢出
,并抛出栈溢出的异常StackOverflowError
;Java会自动释放
掉为该变量所分配的内存空间。
注意,JVM栈是每个线程私有
的!每个线程创建的同时都会创建JVM栈,JVM栈中存放的为当前线程中局部基本类型的变量(java中定义的八种基本类型:boolean、char、byte、short、int、long、float、double)、部分的返回结果以及Stack Frame,非基本类型的对象在JVM栈上仅存放一个指向堆上的地址。
每个方法被执行的时候都会同时创建一个栈帧,对于执行引擎来讲,活动线程中,只有栈顶的栈帧是有效的
,称为当前栈帧,这个栈帧所关联的方法称为当前方法,执行引擎所运行的所有字节码指令都只针对当前栈帧进行操作。
栈帧用于存储局部变量表、操作数栈、动态链接、方法返回地址和一些额外的附加信息。在编译程序代码时,栈帧中需要多大的局部变量表、多深的操作数栈都已经完全确定了,并且写入了方法表的Code属性之中(class文件中是属性表里,加载后是方法区里)。
本地方法栈:
本地方法栈与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。
Q: 那么,到底堆和栈的区别是什么呢?
栈内存
:栈内存首先是一片内存区域,存储的都是局部变量
,凡是定义在方法中的
都是局部变量(方法外的是全局变量),for循环内部定义的
也是局部变量,是先加载方法才能进行局部变量的定义,所以方法先进栈,然后再定义变量,变量有自己的作用域,一旦离开作用域,变量就会被释放。栈内存的更新速度很快
,因为局部变量的生命周期都很短。
堆内存
:存储的是数组和对象
(其实数组就是对象),凡是new
建立的都是在堆中,堆中存放的都是实体(对象),实体用于封装数据,而且是封装多个(实体的多个属性),如果一个数据消失,这个实体也没有消失,还可以用,所以堆是不会随时释放的,但是栈不一样,栈里存放的都是单个变量,变量被释放了,那就没有了。堆里的实体虽然不会被释放,但是会被当成垃圾,Java有垃圾回收机制不定时的收取。
- 主函数里的语句
int [] arr=new int [3];
在内存中是怎么被定义的?
首先,主函数先进栈,在栈中定义一个变量arr,接下来为arr赋值,但是右边的堆中并不是一个具体值,而是一个实体
。实体创建在堆里,在堆里首先通过new关键字开辟一个空间,内存在存储数据的时候都是通过地址来体现的,地址是一块连续的二进制,然后给这个实体分配一个内存地址。数组都是有一个索引,数组这个实体在堆内存中产生之后每一个空间都会进行默认的初始化
(这是堆内存的特点,未初始化的数据是不能用的,但在堆里是可以用的,因为默认初始化过了,但是在栈里没有),不同的类型初始化的值不一样。所以堆和栈里就创建了变量和实体:
- 那么堆和栈是怎么联系起来的呢?
由于已经给堆分配了一个地址,那么把堆的地址赋给arr,arr就通过地址指向了数组。所以arr想操纵数组时,就通过地址
,而不是直接把实体都赋给它。这种我们不再叫它基本数据类型,而叫引用数据类型
。称为:arr引用了堆内存当中的实体。
- 如果当
int [] arr=null;
又是如何的呢?
则arr不做任何指向,null
的作用就是取消引用数据类型的指向
。
注意: 当一个实体,没有引用数据类型指向的时候,它在堆内存中不会被释放,而被当做一个垃圾
,在不定时
的时间内自动回收
,因为Java有一个自动回收机制,(而c++没有,需要程序员手动回收,如果不回收就越堆越多,直到撑满内存溢出,所以Java在内存管理上优于c++)。自动回收机制自动监测堆里是否有垃圾,如果有,就会自动的做垃圾回收的动作,但是什么时候收不一定。
如此一来,堆与栈的区别就很明显:
栈
内存存储的是局部变量
;而堆
内存存储的是实体
;栈
内存的更新速度要快于堆内存
,因为局部变量的生命周期很短;栈
内存存放的变量生命周期一旦结束就会被释放
,而堆
内存存放的实体会被垃圾回收机制不定时的回收
。
5. 垃圾回收机制
垃圾回收机制(GC)是用来释放内存中的资源的,可以有效地防止内存泄露,有效地使用空闲的内存。
6. 多线程的实现方式
- 继承Thread类。
Thread
类本质上是实现了Runnable
接口的一个实例
,代表一个线程的实例。让自己的类直接extends Thread
,并在此类中复写run()
方法。启动线程的方法就是通过Thread类的start()
实例方法,start()
方法将启动一个新线程,并执行其中的run()
方法。
public class MyThread extends Thread { //继承Thread类
public void run() { //复写run()方法
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread(); //创建一个myThread实例
MyThread myThread2 = new MyThread();
myThread1.start(); //启动线程
myThread2.start();
- 实现Runnable接口。
如果自己的类已经extends
另一个类了,就无法再直接extends Thread
,此时,可以通过让它来实现Runnable
接口来创建多线程。
public class MyThread extends OtherClass implements Runnable { //实现Runnable接口
public void run() { //复写run()方法
System.out.println("MyThread.run()");
}
}
MyThread myThread = new MyThread(); //创建一个myThread实例
Thread thread = new Thread(myThread); //将自己的myThread传入Thread实例中
thread.start(); //启动线程
- 实现Callable接口,重写call函数。
继承Thread
类实现多线程,但重写run()
方法时没有返回值也不能抛出异常,使用Callable
接口就可以解决这个问题。Callable接口和Runnable接口的不同之处:
Callable
规定的方法是call()
,而Runnable
是run()
;call()
方法可以抛出异常
,但是run()
方法不行;Callable
对象执行后可以有返回值,运行Callable任务可以得到一个Future
对象,通过Future对象可以了解任务执行情况,可以取消任务的执行,而Runnable
不可有返回值。
public interface Callable<V> { //Callable接口
V call() throws Exception;
}
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override //@Override注解表明重写call()方法
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>(); //由Callable<Integer>创建一个FutureTask<Integer>对象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable); //FutureTask<Integer>是一个包装器,它通过接受Callable<Integer>来创建,它同时实现了Future和Runnable接口。
Thread oneThread = new Thread(oneTask); //由FutureTask<Integer>创建一个Thread对象
oneThread.start(); //至此,一个线程就创建完成了。
- 基于线程池的方式。
- Spring的 @Async 注解。
使用Spring比使用JDK原生的并发API更简单。而且我们的应用环境一般都会集成Spring,我们的Bean也都交给Spring来进行管理,那么使用Spring来实现多线程更加简单,更加优雅。只需要在配置类中添加@EnableAsync
就可以使用多线程。在希望执行的并发方法中使用@Async
就可以定义一个线程任务。
7. 多线程中run方法和start方法的区别
run()方法:
是在主线程
中执行方法,和调用普通方法一样(按顺序执行,同步
执行)。
start()方法:
是创建了新的线程
,在新的线程中执行(异步
执行),只有通过调用线程类的start()
方法可能真正达到多线程的目的。单独调用run()
方法,是同步执行;通过start()
调用run()
,是异步执行。
8. 同步和异步
同步:
发送一个请求,等待返回,然后再发送下一个请求。实现:1. synchronized
修饰;2. wait()
和notify()
。同步可以避免出现死锁,读脏数据
的发生,一般共享某一资源的时候用,如果每个人都有修改权限,同时修改一个文件,有可能使一个人读取另一个人已经删除的内容,就会出错,同步就会按顺序来修改。
public void countAdd() { //比如一个计算数字和的方法,可能就需要是同步的,否则会读到脏数据或者死锁等问题。
synchronized(this) { //使用synchronized修饰,表明它是一个同步的方法。
... //方法体
}
}
或者写成:
public synchronized void countAdd() {
... //方法体
}
异步:
发送一个请求,不等待返回,随时可以再发送下一个请求。
同步和异步最大的区别就在于:一个需要等待,一个不需要等待。比如广播,就是一个异步例子。发起者不关心接收者的状态,不需要等待接收者的返回信息。电话,就是一个同步例子。发起者需要等待接收者,接通电话后,通信才开始,需要等待接收者的返回信息。
8. 内存泄漏和内存溢出
内存泄露:
是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制,因而造成了资源的浪费。
Java中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,当我们new
了一个对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这便会造成内存泄露。
内存溢出:
是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限。
9. 重写和重载
重写:
在方法前加上@Override
注解。其实就是在子类中把父类本身有的方法重新写一遍。子类继承了父类原有的方法,但有时子类并不想原封不动的继承父类中的某个方法,所以在方法名
,参数列表
,返回类型
(除过子类中方法的返回值是父类中方法返回值的子类时)都相同的情况下, 对方法体进行修改或重写,这就是重写。但要注意子类函数的访问修饰权限不能少于父类的。
重载:
在一个类中,同名
的方法如果有不同的参数列表
(参数类型
不同、参数个数
不同甚至是参数顺序
不同)则视为重载。同时,重载对返回类型没有要求,可以相同也可以不同,但不能通过返回类型是否相同来判断重载。
方法的重载和重写都是实现多态
的方式,但区别在于:
- 重载实现的是编译时的多态性;而重写实现的是运行时的多态性。
- 重载发生在一个类中;重写发生在子类与父类之间。
10. static
static
:静态。是一个修饰符,用于修饰成员(成员变量和成员函数)
当成员被静态修饰后,就多了一种调用方式,除了可以被对象调用外,还可以直接被类名调用格式:类名.静态成员
I. 静态的特点:
- 随着类的加载而加载。
也就是说,静态会随着类的消失而消失,说明静态的生命周期最长 - 优先于对象的存在。
明确一点:静态是先存在的,对象是后存在的 - 被所有对象共享。
- 可以直接被类名多调用。
II. 类变量和实例变量的区别:
- 存放位置
类变量
随着类的加载存在于方法区
中;实例变量
随着对象的对象的建立存在于堆内存
里 - 生命周期
类变量生命周期最长,随着“类”的加载而加载,随着类的消失而消失;实例变量随着“对象”的消失而消失
III. 静态的使用注意事项:
静态方法
只能访问静态成员
(包括成员变量和成员方法)
非静态方法可以访问静态也可以访问非静态- 静态方法中不可以定义this,super关键字
因为静态优先于对象存在,所以静态方法中不可以出现this,super关键字 - 主函数(
main
)是静态的。
IV. 静态的利与弊:
- 利:对对象的共享数据进行单独空间的存储,节省空间,没有必要没一个对象中都存储一份,可以直接被类名所调用。
- 弊:生命周期过长,访问出现局限性(只能访问静态)。
11. final
I. final修饰类:
被final修饰的类,是不可以被继承
的,这样做的目的可以保证该类不被修改,Java的一些核心的API都是final类,例如String、Integer、Math等。
II. final修饰方法:
子类不可以重写
父类中被final修饰的方法。
III. final修饰实例变量:(类的属性,定义在类内,但是在类内的方法之外)
final修饰实例变量时必须初始化
,且不可再修改
。
IV. final修饰局部变量:(方法体内的变量)
final修饰局部变量时只能初始化(赋值)一次,但也可以不初始化。
V. final修饰方法参数:
final修饰方法参数时,是在调用方法传递参数时候初始化的。
12. String、StringBuilder、StringBuffuer
String 字符串常量(长度不可变)
StringBuilder 字符串变量(长度可变、非线程安全)
StringBuffer 字符串变量(长度可变、线程安全)
从上图中可以看到,初始 String str = “hello”;
,然后在这个字符串后面加上新的字符串“world”,执行 str = str + "World";
这个过程是需要重新在栈堆内存中开辟内存空间的,最终得到了“hello world”字符串也相应的需要开辟内存空间,这样短短的两个字符串,却需要开辟三次
内存空间,不得不说这是对内存空间的极大浪费。为了应对经常性的字符串相关的操作,谷歌引入了两个新的类——StringBuilder
类和StringBuffer
类来对此种变化字符串进行处理。
和 String
类不同的是,StringBuilder
和 StringBuffer
类的对象能够被多次的修改,并且不产生新的未使用对象。
StringBuilder sa = new StringBuilder("This is only a"); //创建StringBuilder对象
sa.append(" simple").append(" test"); //使用append()方法添加字符串
StringBuffer sb = new StringBuffer("123"); //创建StringBuffer对象
sb.append("456"); //使用append()方法添加字符串
三者在执行速度方面的比较:StringBuilder
> StringBuffer
> String
由于 StringBuilder
相较于 StringBuffer
有速度优势,所以多数情况下建议使用 StringBuilder
类。然而在应用程序要求线程安全的情况下,则必须使用 StringBuffer
类。
对于三者使用的总结:
- 如果要操作少量的数据用
String
单
线程操作字符串缓冲区下操作大量数据用StringBuilder
(非线程安全)多
线程操作字符串缓冲区下操作大量数据用StringBuffer
(线程安全)
13. sleep() 和 wait()
sleep()
是线程被调用时,占着cpu休眠,其他线程不能占用cpu,OS认为该线程正在工作,不会让出系统资源,wait()
是进入等待池等待,让出系统资源,其他线程可以占用cpu。
14. & 和 &&
&:逻辑与(and) 运算符两边的表达式均为true时,整个结果才为true。
&&:短路与 如果第一个表达式为false时,第二个表达式就不会计算了。
15. == 和equals
==:
- 基本数据类型比较的是
值
- 引用类型比较的是
地址值
equals(Object o):
- 不能比较基本数据类型,基本数据类型不是类类型
- 比较引用类型时(该方法继承自Object,在object中比较的是地址值)
等同于”==”
;
public boolean equals (Object x){
return this == x;
}
- 如果自己所写的类中已经重写了
equals()
方法,那么就按照用户自定义的方式来比较两个对象
是否相等,如果没有重写过equals()
方法,那么会调用父类(Object)中的equals()
方法进行比较,也就是比较地址值
。
注意:equals(Object o)
方法只能是一个对象来调用,然后参数也应传一个对象。
Q: 什么时候用==
,什么时候用equals()
呢?
- 如果是
基本数据
类型那么就用==
比较 - 如果是引用类型的话,想按照自己的方式去比较,就要
重写
这个类中的equals()
方法;如果没有重写,那么equals()
和==
比较的效果是一样的,都是比较引用的地址值 - 如果是比较字符串,那么直接用equals就可以了,因为String类里面已经重写了
equals()
方法,比较的是字符串的内容,而不是引用的地址值了。
int 和 Integer
见 “LeetCode经典算法题目” 四-12-Q2。
16. public、protected、缺省、private
- public修饰的成员变量和函数可以被类、子类、同一个包中的类以及任意其他类访问。
- protected修饰的成员变量和函数能被类本身、子类及同一个包中的类访问。
- 缺省情况(不写)下,属于一种包访问,即能被类本身以及同一个包中的类访问。
- private修饰的成员变量和函数只能在类本身和内部类中被访问。
17. List、Set、Map、Queue
见“LeetCode经典算法题目” 四-12。
18. Hashtable 和 HashMap
Hashtable:
是线程安全
的,Hashtable中的方法都是synchronized
修饰的,在多线程并发的环境下,可以直接使用Hashtable,不需要自己为它的方法实现同步。
HashMap:
是非线程安全
的,HashMap中的方法在缺省情况下是非synchronized
的。在多线程并发的环境下,可能会产生死锁等问题。需要自己手动增加同步处理。虽然HashMap不是线程安全的,但是它的效率
会比Hashtable要高
很多。这样设计是合理的。在我们的日常使用当中,大部分时间是单线程操作的。HashMap把这部分操作解放出来了。
Hashtable 和 HashMap的区别:
- 继承的父类不同
Hashtable继承自Dictionary
类,而HashMap继承自AbstractMap
类。但二者都实现了Map接口
。 - 线程安全性不同
Hashtable是线程安全的,HashMap是非线程安全的。 - 是否提供
contains()
方法
Hashtable保留了contains()
,containsValue()
和containsKey()
三个方法,其中contains()
和containsValue()
功能相同。
HashMap把Hashtable的contains()
方法去掉了,改成containsValue()
和containsKey()
,因为contains()
方法容易让人引起误解。 - key和value是否允许null值
Hashtable既不支持Null key也不支持Null value;
HashMap支持null作为键,这样的键只有一个,同时,它还支持可以有一个或多个键所对应的值为null。
19. error和exception
Error(错误):
表示系统级的错误 和 程序不必处理的异常,是java运行环境中的内部错误或者硬件问题。比如:内存资源不足等。对于这种错误,程序基本无能为力,除了退出运行外别无选择,它是由Java虚拟机抛出
的。
Exception(违例):
表示需要捕捉 或 需要程序进行处理 的异常,它处理的是因为程序设计的瑕疵而引起的问题或者在外的输入等引起的一般性问题,是程序必须处理
的。它分为两类:
- 运行时异常
runtime exception
,表示无法让程序恢复的异常,导致的原因通常是因为执行了错误的操作,建议终止程序,因此,编译器不检查这些异常。运行时异常我们可以不处理。这样的异常由虚拟机接管
。出现运行时异常后,系统会把异常一直往上层抛,一直遇到处理代码。如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。
常见的运行时异常:数组越界、空指针、数据存储异常(操作数组时类型不一致)等等。 - 受检查异常(一般异常)
checked exception
,是表示程序可以处理的异常,也即表示程序可以修复(由程序自己接受异常并且对其进行catch
处理),所以称之为受检查异常。
20. ArrayList 和 LinkList
见 “LeetCode经典算法题目” 四-12。
21. Socket 和 HTTP
HTTP已经很熟悉了,是应用层协议。而Socket不属于协议范畴,而是一个调用接口
,Socket是对TCP/IP协议的封装
,通过调用Socket,才能使用TCP/IP协议。Socket连接是长连接,理论上客户端和服务器端一旦建立连接将不会主动断开此连接,它属于请求-响应形式,服务端可主动将消息推送给客户端。
22. java创建对象的方式
- 使用new关键字
ObjectName obj = new ObjectName();
- 使用反射机制
- 使用Class类的
newInstance()
方法: - java.lang.reflect.Constructor类里也有一个
newInstance()
方法(需要import这个包):
- 使用Class类的
//方式一:
ObjectName obj = ObjectName.class.newInstance();
//方式二:
Class classA = Class.forName("ClassName");
ObjectName obj = (ObjectName) classA.newInstance();
ObjectName obj = ObjectName.class.getConstructor.newInstance();
- 使用clone方法
类必须先实现Cloneable
接口并重写其clone()
方法,才可使用该方法。
ObjectName obj = obj.clone();
- 使用反序列化
使用反序列化ObjectInputStream
的readObject()
方法:类必须实现Serializable
接口
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(FILE_NAME))) {
ObjectName obj = ois.readObject();
}
23. JDBC使用步骤过程
- 加载JDBC驱动程序
- 提供JDBC连接的URL
- 创建数据库的连接
- 创建一个Statement
- 执行SQL语句
- 处理结果
- 关闭JDBC对象