1、什么是异常?java提供异常处理机制有什么用?
以下程序执行过程中发生了不正常的情况,而这种不正常的情况叫做:异常
java语言是很完善的语言,提供了异常的处理方式,以下程序执行过程中出现了不正常情况,
java把该异常信息打印输出到控制台,供程序员参考。程序员看到异常信息之后,可以对程序进行修改,让程序更加健壮。
什么是异常:程序执行过程中的不正常情况
异常的作用:增强程序的健壮性
java中异常以类和对象的形式存在。
2、异常的承继结构图
2.1、Object
Object下有Throwable(可抛出的)
Throwable下有两个分支:Error(不可处理,直接退出JVM)和Exception(可处理的)
Exception下有两个分支:
Exception的直接子类:编译时异常(要求程序员在编写程序阶段必须预先对这些异常进行处理,如果不处理编译器报错,因此得名编译时异常。)。
RuntimeException:运行时异常(在编写程序阶段程序员可以预先处理,也可以不管,都行。)
2.2、编译时异常和运行时异常,都是发生在运行阶段。编译阶段异常是不会发生的。
编译时异常因为什么而得名?
因为编译时异常必须在编译(编写)阶段预先处理,如果不处理编译器报错,因此得名。
所有异常都是在运行阶段发生的。因为只有程序运行阶段才可以new对象。
因为异常的发生就是new异常对象。
2.3、编译时异常和运行时异常的区别?
编译时异常一般发生的概率比较高。
举个例子:
你看到外面下雨了,倾盆大雨的。
你出门之前会预料到:如果不打伞,我可能会生病(生病是一种异常)。
而且这个异常发生的概率很高,所以我们出门之前要拿一把伞。
“拿一把伞”就是对“生病异常”发生之前的一种处理方式。
对于一些发生概率较高的异常,需要在运行之前对其进行预处理。
运行时异常一般发生的概率比较低。
举个例子:
小明走在大街上,可能会被天上的飞机轮子砸到。
被飞机轮子砸到也算一种异常。
但是这种异常发生概率较低。
在出门之前你没必要提前对这种发生概率较低的异常进行预处理。
如果你预处理这种异常,你将活的很累。
假设你在出门之前,你把能够发生的异常都预先处理,你这个人会更加
的安全,但是你这个人活的很累。
假设java中没有对异常进行划分,没有分为:编译时异常和运行时异常,
所有的异常都需要在编写程序阶段对其进行预处理,将是怎样的效果呢?
首先,如果这样的话,程序肯定是绝对的安全的。
但是程序员编写程序太累,代码到处都是处理异常
的代码。
2.4、编译时异常还有其他名字:
受检异常:CheckedException
受控异常
2.5、运行时异常还有其它名字:
未受检异常:UnCheckedException
非受控异常
2.6、再次强调:所有异常都是发生在运行阶段的。
3、Java语言中对异常的处理包括两种方式:
第一种方式:在方法声明的位置上,使用throws关键字,抛给上一级。
谁调用我,我就抛给谁。抛给上一级。
第二种方式:使用try..catch语句进行异常的捕捉。
这件事发生了,谁也不知道,因为我给抓住了。
举个例子:
我是某集团的一个销售员,因为我的失误,导致公司损失了1000元,
“损失1000元”这可以看做是一个异常发生了。我有两种处理方式,
第一种方式:我把这件事告诉我的领导【异常上抛】
第二种方式:我自己掏腰包把这个钱补上。【异常的捕捉】
张三 --> 李四 ---> 王五 --> CEO
思考:
异常发生之后,如果我选择了上抛,抛给了我的调用者,调用者需要
对这个异常继续处理,那么调用者处理这个异常同样有两种处理方式。
注意:Java中异常发生之后如果一直上抛,最终抛给了main方法,main方法继续向上抛,抛给了调用者JVM,JVM知道这个异常发生,只有一个结果。终止java程序的执行。
4、Java中怎么自定义异常呢?
第一步:编写一个类继承Exception或者RuntimeException
第二步:提供两个构造方法,一个无参数的,一个带有String参数的。
面试题:(1)throw和throws的区别?
位置不同:
(2)final finally finalize有什么区别?
final关键字
final修饰的类无法继承
final修饰的方法无法覆盖
final修饰的变量不能重新赋值。
finally关键字
和try一起联合使用
finally语句块中的代码是必须执行的。
finalize 标识符
是一个Object类中的方法名。
这个方法是由垃圾回收器GC负责调用的。
一、集合概述
1.1、什么是集合?有什么用?
数组其实就是个集合。集合实际上就是一个容器。可以来容纳其他类型的数据。
集合为什么说在开发中使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象。在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
1.2、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的都是引用。)
list.add(100); // 自动装箱Integer
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”。
1.3、在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。什么是数据结构?数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:
数组、二叉树、链表、哈希表。。。
以上这些都是常见的数据结构。
你往集合c1中放数据,可能是放到数组上了。
你玩集合c2中放数据,可能是放到二叉树上了。
。。。
你使用不同的集合等同于使用了不同的数据结构。
1.4、集合在java JDK中哪个包下?
java.util.*;
所有的集合类和集合接口都在java.util包下。
1.5、为了能掌握集合这块的内容,最好能将集合的继承结构图背会!
1.6、在java中集合分为两大类:
一类是单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
一类是以键值对的方式存储元素
以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;
二、Collection接口
1、关于java.util.Collection接口中常用的方法。
(1)Collection中能存放什么元素?
没有使用"泛型"之间,Collection中可以存储Object的所有子类型。
使用了"泛型"之后,Collection中只能存储某个具体的类型。
集合后期我们会学习"泛型"语法。目前先不用管。Collection中什么都能存,
只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存
java对象,只能存java对象的内存地址。)
(2)Collection中常用方法
boolean add(Object e) 想集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素
boolean isEmpty() 判断该集合中元素的个数是否为0
Object[] toArray() 调用这个方法可以把集合转换成数组。【作为了解,使用不多。】
2、深入Collection集合的contains方法:
boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true,如果不包含返回false。
contains是用来判断集合中是否包含某个元素的方法,
那么它在底层是怎么判断集合中是否包含某个元素的呢?
调用了equals方法进行比对。
equals方法返回true,就表示包含这个元素。
3、关于集合元素的remove
重点:当集合的结构发生改变时,迭代器必须重新获取,如果还是用以前老的迭代器,会出现异常:java.util.ConcurrentModificationException
重点:在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:
c.remove(o); 迭代过程中不能这样。
会出现:java.util.ConcurrentModificationException
重点:在迭代元素的过程中,一定要使用迭代器Iterator的remove方法,删除元素,不要使用集合自带的remove方法删除元素。
加粗样式三、List接口
1、List接口中的常用方法。
List是Collection接口的子接口。所以List接口中有一些特有的方法。
void add(int index, Object element)
Object set(int index, Object element)
Object get(int index)
int indexOf(Object o)
int lastIndexOf(Object o)
Object remove(int index)
2、迭代器迭代元素的过程中不能使用集合对象的remove方法删除元素,
要使用迭代器Iterator的remove方法来删除元素,防止出现异常:
ConcurrentModificationException
3、ArrayList
ArrayList集合初始化容量10
扩容为原容量1.5倍。
底层是数组。
数组优点和缺点要能够说出来!
另外要注意:ArrayList集合末尾增删元素效率还是可以的。
(1)默认初始化容量10
(2)集合底层是一个Object[]数组。
(3)构造方法:
new ArrayList();
new ArrayList(20);
(4)ArrayList集合的扩容:
原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合
的时候预估计元素的个数,给定一个初始化容量。
(5)数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
(6)数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
(7)向数组末尾添加元素,效率很高,不受影响。
(8)面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。
(9)ArrayList集合是非线程安全的。(不是线程安全的集合。)
4、LinkedList
第一:单向链表和双向链表数据结构要理解。
第二:链表数据结构的优点和缺点要能够说出来。
链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
使用LinkedList。
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
较低。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
5、Vector
Vector初始化容量是10.
扩容为原容量的2倍。
底层是数组。
Vector底层是线程安全的。
怎么得到一个线程安全的List:
Collections.synchronizedList(list);
6、JDK5.0新特性:泛型
第一:集合使用泛型来减少向下转型的操作。
第二:怎么使用泛型?
第三:怎么自定义泛型?
7、JDK5.0新特性:
foreach
语法:
for (元素类型 变量名 : 数组或集合) {
System.out.println(变量名);
}
对数组怎么遍历?
for(int i : arr) {
System.out.println(i);
}
对集合怎么遍历?
for(String s : list) {
System.out.println(s);
}
(1)泛型这种语法机制,只在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用!)
(2)使用了泛型好处是什么?
第一:集合中存储的元素类型统一了。
第二:从集合中取出的元素类型是泛型指定的类型,不需要进行大量的“向下转型”!
(3)泛型的缺点是什么?
导致集合中存储的元素缺乏多样性!
大多数业务中,集合中元素的类型还是统一的。所以这种泛型特性被大家所认可。
8、JDK8新特性:钻石表达式
List list = new ArrayList<>();
类型自动推断!
四、Map
1、掌握Map接口中常用方法。
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear() 清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty() 判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection values() 获取Map集合中所有的value,返回一个 Collection
2、遍历Map集合的两种方式都要精通。
第一种:获取所有key,遍历每个key,通过key获取value。
// 第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 遍历Map集合
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
// 遍历key,通过key获取value
// 迭代器可以
/*Iterator it = keys.iterator();
while (it.hasNext()) {
// 取出其中一个key
Integer key = it.next();
// 通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}*/
// foreach也可以
for (Integer key : keys) {
System.out.println(key + "=" + map.get(key));
}
第二种:获取Set
调用entry.getKey() entry.getValue()
// 第二种方式:Set> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer, String>> set = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 迭代器
/*Iterator> it2 = set.iterator();
while (it2.hasNext()) {
Map.Entry node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}*/
// foreach
// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值
// 这种方式比较适合于大数据量。
for (Map.Entry<Integer, String> node : set) {
System.out.println(node.getKey() + "--->" + node.getValue());
}
4、存放在HashMap集合key部分和HashSet集合中的元素需要同时重写hashCode和equals。
5、HashMap和Hashtable的区别。
HashMap:
初始化容量16,扩容2倍。
非线程安全
key和value可以为null。
Hashtable
初始化容量11,扩容2倍+1
线程安全
key和value都不能是null。
6、Properties类的常用两个方法。
setProperty();
getProperty();
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。
7、了解自平衡二叉树数据结构。
左小右大原则存储。
中序遍历方式。
8、TreeMap的key或者TreeSet集合中的元素要想排序,有两种实现方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候,单独编写一个比较器Comparator接口。
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
Comparator接口的设计符合OCP原则。
9、集合工具类Collections:
synchronizedList方法
sort方法(要求集合中元素实现Comparable接口。)
集合框架总结
数组:之前使用数组存储数据
数组只能存同一类型的数据
数组长度一旦确定不能改变
数组对象中基本没有可用调用的方法来操作数组
数组类型是使用 已有的类型+[] 组合而成
集合:现在使用集合存储数据
集合中能存储任意类型的对象
集合的长度可以自动增长
集合对象本身有很多方法可以调用,来操作集合中的数据
集合是由API中定义好的接口和实现类组成的集合框架
同时,集合框架中还有很多不同特点的集合可以选择使用
Collection和Map是集合框架中的俩大类型
Collection类型的集合:
1.可以使用Iterator迭代器
因为Collection接口继承了Iterable接口
2.直接把数据单独存放到集合中即可
3.Collection接口有俩个常用的子接口
List
有序可重复
Set
无序不可重复
Set下面有一个子接口SortedSet,允许用户给集合中的元素进行排序
4.List接口常用的实现类
ArrayList
数组实现,通过下标访问效率高,在集合的中间位置插入数据效率低。
LinkedList
链表实现,通过下标访问效率低,在集合的中间位置插入数据效率高。
Vector
线程安全的集合。
5.Set接口常用的实现类
HashSet
内部调用HashMap来实现功能。
无序:
需要使用数据的哈希值来存放数据
不可重复
根据对象的hashCode和equals方法来确定对象是否重复
哈希值不同,俩个对象就一定不同。
哈希值相同,俩个对象有可能相同,也能不同,这时候再使用equals进一步确定是否相同。
TreeSet(实现的是SortedSet接口,SortedSet继承了Set接口)
内部调用TreeMap来实现功能。
可以给存储到集合中的元素见排序。
自然排序:
只要对象实现了Comparable接口,对象中就一定一个compareTo方法,该方法可以比较俩个对象的大小。
通过方法的返回值是正数、负数、零来确定对象的大小或者相等。
TreeSet集合使用元素的compareTo方法比较出俩个对的大小后,就可以从小到大给对象进行排序了
客户化排序:
可以在创建TreeSet集合的时候,在构造器的参数中,传一个比较器的对象Comparator。
使用这个比较器,也可以比较出集合中俩个对象之间的大小。
这个时候即使对象没有实现Comparable接口也是可以比较的,因为这个比较器。
比较得出大小后,TreeSet依然可以从小到大给元素进行排序。
Map类型的集合:
1.不能使用Iterator迭代器,Map接口没有继承Iterable接口
2.需要使用key-value的方式来存放数据
3.Map下面有一个常用的子接口SortedMap
4.Map接口的实现类
HashMap
通过key-value存储数据
key不能重复,如果重复会覆盖之前的数据,value可以重复,也就是需要保证key值无序不可重复。其实HashSet的无序不可重复就是利用了HashMap中的key的这一个特点来实现的。
key值是无序:
需要使用数据的哈希值来存放数据
key值是不可重复的:
根据对象的hashCode和equals方法来确定对象是否重复
哈希值不同,俩个对象就一定不同。
哈希值相同,俩个对象有可能相同,也能不同,这时候再使用equals进一步确定是否相同。
Hashtable 实现了Map接口,它的效果和Vector类似,都是线程安全的集合。
5.SortedMap接口的实现类
TreeMap
可以给key值进行排序:
自然排序
只要对象实现了Comparable接口,对象中就一定一个compareTo方法,该方法可以比较俩个对象的大小。
通过方法的返回值是正数、负数、零来确定对象的大小或者相等。
TreeMap集合中的key使用对象中的compareTo方法比较出俩个对的大小后,就可以从小到大给key进行排序了
客户化排序
可以在创建TreeSet集合的时候,在构造器的参数中,传一个比较器的对象Comparator。
使用这个比较器,也可以比较出集合中俩个对象之间的大小。
这个时候即使对象没有实现Comparable接口也是可以比较的,因为这个比较器。
比较得出大小后,TreeMap依然可以从小到大给key进行排序。
在集合中,掌握几个核心:
1.怎么利用接口和实现类创建出集合对象
Collection c = new List接口或者Set接口的实现类
List list = new 接口的实现类(ArrayList Linked Vector)
Set set = new set接口的实现类(HashSet TreeSet)
Map map = new Map接口的实现类或者SortedMap接口实现类
HashMap TreeMap HashTable
2.怎么往集合中添加数据
Collection类型集合 add方法
Map类型集合 put方法
3.怎么从集合中删除数据
remove方法
4.怎么在集合中查找数据
list类型集合 可以实现下标查找数据 get方法
set类型集合 比较麻烦,没有下标,可以从集合中一个一个拿出数据对比,看是否是自己想查找的数据。
Map类型集合,可以使用key直接获取到对应的value
5.怎么循环遍历集合拿到每一个元素
Collection类型的集合都可以使用迭代器来循环拿到每个数据
List类型的集合除了迭代器的方式之外,还可以for循环结合下标的方式拿出每个数据
Map类型的集合
keySet方法 取出所有key值
values方法 取出所有value值
entrySet方法 取出所有的键值对
6.每种集合的特点
7.常用集合内部的实现方式
1、反射机制(比较简单,因为只要会查帮助文档,就可以了。)
1.1、反射机制有什么用?
通过java语言中的反射机制可以操作字节码文件。
优点类似于黑客。(可以读和修改字节码文件。)
通过反射机制可以操作代码片段。(class文件。)
1.2、反射机制的相关类在哪个包下?
java.lang.reflect.*;
1.3、反射机制相关的重要的类有哪些?
java.lang.Class:代表整个字节码,代表一个类型,代表整个类。
java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
java.lang.reflect.Constructor:代表字节码中的构造方法字节码。代表类中的构造方法
java.lang.reflect.Field:代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)。
java.lang.Class:
public class User{
// Field
int no;
// Constructor
public User(){
}
public User(int no){
this.no = no;
}
// Method
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
2、关于JDK中自带的类加载器:(聊一聊,不需要掌握,知道当然最好!)
2.1、什么是类加载器?
专门负责加载类的命令/工具。
ClassLoader
2.2、JDK中自带了3个类加载器
启动类加载器:rt.jar
扩展类加载器:ext/*.jar
应用类加载器:classpath
2.3、假设有这样一段代码:
String s = “abc”;
代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class
文件,找到就加载,那么是怎么进行加载的呢?
首先通过“启动类加载器”加载。
注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\rt.jar
rt.jar中都是JDK最核心的类库。
如果通过“启动类加载器”加载不到的时候,
会通过"扩展类加载器"加载。
注意:扩展类加载器专门加载:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar
如果“扩展类加载器”没有加载到,那么
会通过“应用类加载器”加载。
注意:应用类加载器专门加载:classpath中的类。
2.4、java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,这个称为“父”
“父”无法加载到,再从扩展类加载器中加载,
这个称为“母”。双亲委派。如果都加载不到,
才会考虑从应用类加载器中加载。直到加载
到为止。
1、回顾反射机制
1.1、什么是反射机制?反射机制有什么用?
反射机制:可以操作字节码文件
作用:可以让程序更加灵活。
1.2、反射机制相关的类在哪个包下?
java.lang.reflect.*;
1.3、反射机制相关的主要的类?
java.lang.Class
java.lang.reflect.Method;
java.lang.reflect.Constructor;
java.lang.reflect.Field;
1.4、在java中获取Class的三种方式?
第一种:
Class c = Class.forName("完整类名");
第二种:
Class c = 对象.getClass();
第三种:
Class c = int.class;
Class c = String.class;
1.5、获取了Class之后,可以调用无参数构造方法来实例化对象
//c代表的就是日期Date类型
Class c = Class.forName("java.util.Date");
//实例化一个Date日期类型的对象
Object obj = c.newInstance();
一定要注意:
newInstance()底层调用的是该类型的无参数构造方法。
如果没有这个无参数构造方法会出现"实例化"异常。
1.6、如果你只想让一个类的“静态代码块”执行的话,你可以怎么做?
Class.forName("该类的类名");
这样类就加载,类加载的时候,静态代码块执行!!!!
在这里,对该方法的返回值不感兴趣,主要是为了使用“类加载”这个动作。
1.7、关于路径问题?
String path = Thread.currentThread().getContextClassLoader()
.getResource("写相对路径,但是这个相对路径从src出发开始找").getPath();
String path = Thread.currentThread().getContextClassLoader()
.getResource("abc").getPath(); //必须保证src下有abc文件。
String path = Thread.currentThread().getContextClassLoader()
.getResource("a/db").getPath(); //必须保证src下有a目录,a目录下有db文件。
String path = Thread.currentThread().getContextClassLoader()
.getResource("com/bjpowernode/test.properties").getPath();
//必须保证src下有com目录,com目录下有bjpowernode目录。
//bjpowernode目录下有test.properties文件。
这种方式是为了获取一个文件的绝对路径。(通用方式,不会受到环境移植的影响。)
但是该文件要求放在类路径下,换句话说:也就是放到src下面。
src下是类的根路径。
直接以流的形式返回:
InputStream in = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("com/bjpowernode/test.properties");
1.8、IO + Properties,怎么快速绑定属性资源文件?
//要求:第一这个文件必须在类路径下
//第二这个文件必须是以.properties结尾。
ResourceBundle bundle = ResourceBundle.getBundle("com/bjpowernode/test");
String value = bundle.getString(key);
2、今日反射机制的重点内容
2.1、通过反射机制访问对象的某个属性。
2.2、通过反射机制调用对象的某个方法。
2.3、通过反射机制调用某个构造方法实例化对象。
2.4、通过反射机制获取父类以及父类型接口。
1、什么是进程?什么是线程?
进程是一个应用程序。(一个进程是一个软件)(程序在计算机上进行资源调度和分配的最小单元)
线程是一个进程中的执行场景/执行单元。(一个进程启动至少一条线程,该线程叫做主线程,线程是程序内部进行资源调度的最小单元)
一个进程可以启动多个线程。
2、对于java来说,当在Dos命令窗口中输入:
Java HelloWorld 回车之后。
会先启动JVM,而JVM就是一个进程。
JVM再启动一个主线程调用main方法。
同时再启动一个垃圾回收线程负责看护,回收垃圾。
最起码,现在的java程序中至少有两个线程并发,
一个是垃圾回收线程,一个是执行main方法的主线程。
3、进程和线程是什么关系?举个例子
阿里巴巴:进程
马云:阿里巴巴的一个线程
童文红:阿里巴巴的一个线程
京东:进程
强东:京东的一个线程
妹妹:京东的一个线程
进程可以看做是现实生活当中的公司。
线程可以看做是公司当中的某个员工。
注意:进程A和进程B的内存独立不共享。(阿里巴巴和京东资源不会共享的!)
线程A和线程B呢?
在java语言中:
线程A和线程B,堆内存和方法区内存共享。
但是栈内存独立,一个线程一个栈。
假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,
互不干扰,各自执行各自的,这就是多线程并发。
火车站,可以看做是一个进程。
火车站中的每一个售票窗口可以看做是一个线程。
我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。
所以多线程并发可以提高效率。
java中之所以有多线程机制,目的就是为了提高程序的处理效率。
4、思考一个问题:
使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。main方法结束只是主程序结束了,主栈空了,其他的栈(线程)可能还在压栈弹栈。
分析一个问题:对于单核CPU来说,真的可以做到真正的多线程并发吗?
对于多核的CPU电脑来说,真正的多线程并发是没问题的。
4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。
什么是真正的多线程并发?
t1线程执行t1的。
t2线程执行t2的。
t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。
单核的CPU表示只有一个大脑:
不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。
线程A:播放音乐
线程B:运行魔兽游戏
线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,给我们的感觉是同时并发的。
电影院采用胶卷播放电影,一个胶卷一个胶卷播放速度达到一定程度之后,人类的眼睛产生了错觉,感觉是动画的。这说明人类的反应速度很慢,就像一根钢针扎到手上,到最终感觉到疼,这个过程是需要“很长的”时间的,在这个期间计算机可以进行亿万次的循环。所以计算机的执行速度很快。
5、java语言中,实现线程有两种方式,那两种方式呢?
java支持多线程机制。并且java已经将多线程实现了,我们只需要继承就行了。
第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。
// 定义线程类
public class MyThread extends Thread{
public void run(){
}
}
// 创建线程对象
MyThread t = new MyThread();
// 启动线程
t.start();
第二种方法:编写一个类,实现java.lang.Runnable接口,实现run方法。
// 定义一个可运行的类
public class MyRunnable implements Runnable {
public void run(){
}
}
// 创建线程对象
Thread t = new Thread(new MyRunnable());
// 启动线程
t.start();
注意:第二种方式实现接口比较常用,因为一个类实现类接口,它还可以去继承其他的类,更灵活。
6、线程生命周期
新建状态
就绪状态
运行状态
阻塞状态
死亡状态
7、关于多线程并发环境下,数据的安全问题。
7.1、为什么这个是重点?
以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。
最重要的是,你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。
7.2、什么时候数据在多线程并发的环境下会存在安全问题呢?
三个条件:
条件一:多线程并发。
条件二:有共享数据。
条件三:共享数据有修改的行为。
满足以上三个条件之后,就会存在线程安全问题。
7.3、怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题。怎么解决这个问题?
线程排队执行。(不能并发)。
用排队解决线程安全问题。
这种机制被称为:线程同步机制。
专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。
线程同步就是线程排队,线程排队了就会牺牲一部分效率。
7.4、说到线程同步这块,涉及到这两个专业术语:
异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。其实就是多线程并发(效率较高。)
异步就是并发。
同步编程模型:
线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。效率较低,线程排队执行。
同步就是排队。
8、(这部分内容属于了解)关于线程的调度
8.1、常见的线程调度模型有哪些?
抢占式调度模型:
那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
java采用的就是抢占式调度模型。
均分式调度模型:
平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
平均分配,一切平等。
有一些编程语言,线程调度模型采用的是这种方式。
8.2、java中提供了哪些方法是和线程调度有关系的呢?
实例方法:
void setPriority(int newPriority) 设置线程的优先级
int getPriority() 获取线程优先级
最低优先级1
默认优先级是5
最高优先级10
优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)
静态方法:
static void yield() 让位方法
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
实例方法:
void join()
合并线程
class MyThread1 extends Thread {
public void doSome(){
MyThread2 t = new MyThread2();
t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
}
}
class MyThread2 extends Thread {
}
9、Java中有三大变量?【重要的内容。】
实例变量:在堆中。
静态变量:在方法区。
局部变量:在栈中。
以上三大变量中:
局部变量永远都不会存在线程安全问题。
因为局部变量不共享。(一个线程一个栈。)
局部变量在栈中。所以局部变量永远都不会共享。
实例变量在堆中,堆只有1个。
静态变量在方法区中,方法区只有1个。
堆和方法区都是多线程共享的,所以可能存在线程安全问题。
局部变量+常量:不会有线程安全问题。
成员变量:可能会有线程安全问题。
10、如果使用局部变量的话:
建议使用:StringBuilder。
因为局部变量不存在线程安全问题。选择StringBuilder。
StringBuffer效率比较低。
ArrayList是非线程安全的。
Vector是线程安全的。
HashMap HashSet是非线程安全的。
Hashtable是线程安全的。
11、总结:
synchronized有三种写法:
第一种:同步代码块
灵活
synchronized( 线程共享对象 ) {
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
12、聊一聊,我们以后开发中应该怎么解决线程安全问题?
是一上来就选择线程同步吗?synchronized
不是,synchronized会让程序的执行效率降低,用户体验不好。
系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择
线程同步机制。
第一种方案:尽量使用局部变量代替“实例变量和静态变量”。
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样
实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,
对象不共享,就没有数据安全问题了。)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候
就只能选择synchronized了。线程同步机制。
13、线程这块还有那些内容呢?列举一下
13.1、守护线程
13.2、定时器
13.3、实现线程的第三种方式:FutureTask方式,实现Callable接口。(JDK8新特性。)
13.4、关于Object类中的wait和notify方法。(生产者和消费者模式!)
14、线程这块还有那些内容呢?列举一下
14.1、守护线程
java语言中线程分为两大类:
一类是:用户线程
一类是:守护线程(后台线程)
其中具有代表性的就是:垃圾回收线程(守护线程)。
守护线程的特点:
一般守护线程是一个死循环,所有的用户线程只要结束,
守护线程自动结束。
注意:主线程main方法是一个用户线程。
守护线程用在什么地方呢?
每天00:00的时候系统数据自动备份。
这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
一直在那里看着,没到00:00的时候就备份一次。所有的用户线程
如果结束了,守护线程自动退出,没有必要进行数据备份了。
14.2、定时器
定时器的作用:
间隔特定的时间,执行特定的程序。
每周要进行银行账户的总账操作。
每天要进行数据的备份操作。
在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,
那么在java中其实可以采用多种方式实现:
可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行
任务。这种方式是最原始的定时器。(比较low)
在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。
在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。
14.3、实现线程的第三种方式:实现Callable接口。(JDK8新特性。)
这种方式实现的线程可以获取线程的返回值。
之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void。
思考:
系统委派一个线程去执行一个任务,该线程执行完任务之后,可能
会有一个执行结果,我们怎么能拿到这个执行结果呢?
使用第三种方式:实现Callable接口方式。
14.4、关于Object类中的wait和notify方法。(生产者和消费者模式!)
第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象
都有的方法,因为这两个方式是Object类中自带的。
wait方法和notify方法不是通过线程对象调用,
不是这样的:t.wait(),也不是这样的:t.notify()..不对。
第二:wait()方法作用?
Object o = new Object();
o.wait();
表示:
让正在o对象上活动的线程进入等待状态,无期限等待,
直到被唤醒为止。
o.wait();方法的调用,会让“当前线程(正在o对象上
活动的线程)”进入等待状态。
第三:notify()方法作用?
Object o = new Object();
o.notify();
表示:
唤醒正在o对象上等待的线程。
还有一个notifyAll()方法:
这个方法是唤醒o对象上处于等待的所有线程。
1、什么是IO?
I:Input
O:Output
通过IO可以完成硬盘的读和写。
2、IO的分类:
有多种分类方式:
一种方式是按照流的方向进行分类:
以内存作为参照物,
往内存中去,叫做输入(Input)。或者叫做读(Read)。
从内存中出来,叫做输出(Output)。或者叫做写(Write)。
另一种方式是按照读取方式不同进行分类:
有的流是按照字节的方式进行读取数据,一次读取一个字节byte,
这种流是万能的,什么类型的文件都可以读取。包括:文本文件、图片、声音文件、视频文件。
有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取普通文本文件而存在,这种流不能读取:图片、声音、视频等文件。只能读取纯文本文件,连word文件都无法读取。
综上所述:
1)根据数据的流向
输入流和输出流
2)根据数据的类型
字节流和字符流
字节流读取的最小单位是一个字节(1byte=8bit),而字符流一次可以读取一个字符(1char = 2byte = 16bit)
3)按照功能的不同
节点流和包装流
节点流是可以"直接"从一个数据源中读写数据的流。
包装流也可以称为功能流或者处理流,它是可以对节点流进行封装的一种流,封装后可以增加节点流的功能。
例如:FileInputStream是一个节点流,可以直接从文件读取数据,而BufferedInputStream可以包装 FileInputStream,使得其有缓冲数据的功能。
4)除了以上三种分类外,还有其他的一些类型的:对象流、缓冲流、压缩流、文件流等等,其实这些都是节点流或者包装流的子分类。当然还可以分出来其他的流类型,如果有这样需要的话。
5)不管流的分类是多么的丰富和复杂,其根源来自于四个基本的父类
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
注:这四个父类都是抽象类
3、java中的IO流都已经写好了,我们程序员不需要关心,我们最主要还是掌握,在java中已经提供了哪些流,每个流的特点是什么,每个流对象上的常用方法有哪些?
java中所有的流都是在:java.io.*;下。
java中主要还是研究:
怎么new流对象。
调用流对象的哪个方法是读,哪个方法是写。
4、java IO流的四大家族:
四大家族的首领:
java.io.InputStream 字节输入流
java.io.OutputStream 字节输出流
java.io.Reader 字符输入流
java.io.Writer 字符输出流
四大家族的首领都是抽象类。
所有的流都实现了:
java.io.Closeable接口,都是可关闭的,都有close( )方法。
流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,
不然会耗费很多资源。
注意:在java中只要“类名”以Stream结尾的都是字节流。以“Reader/Writer”结尾的都是字符流
5、java.io包下需要掌握的流有16个:
文件专属:
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter
转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
6、字节流中的常用节点流
注:java中常用的io流都在java.io包中
1)InputStream
//从输入流中读取数据的下一个字节
//如果到达流的末尾则返回 -1
public abstract int read();
//把读到的字节存到字节数组b中,并返回本次读到了多少个字节
public int read(byte[] b){..}
//把读到的字节存到字节数组b中,同时指定开始存的位置以及最大字节数,并返回本次读到了多少个字节
public int read(byte[] b,int off,int len){..}
//返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available(){..}
//跳过此输入流中数据的 n 个字节
public long skip(long n){..}
//关闭此输入流并释放与该流关联的所有系统资源
public void close(){..}
//测试此输入流是否支持 mark 和 reset 方法
public boolean markSupported(){..}
//在此输入流中标记当前的位置
public void mark(int readlimit){..}
//将此流重新定位到最后一次对此输入流调用mark方法时的位置
public void reset(){..}
2)OutputStream
//将指定的字节写入此输出流
public abstract void write(int b);
//将字节数组b中的所有字节写入此输出流
public void write(byte[] b){..}
//将字节数组b中的字节写入此输出流,指定开始位置及最大字节数
public void write(byte[] b,int off,int len){..}
//刷新此输出流并强制写出所有缓冲的输出字节
public void flush(){..}
//关闭此输出流并释放与此流有关的所有系统资源
public void close(){..}
3)InputStream的子类和OutputStream的子类几乎都是成对出现的,一个负责读数据的工作,一个负责写数据的工作
4)System.out和System.in
System类的部分源码:
public final class System{
//标准输入流
public final static InputStream in = null;
//标准输出流。
public final static PrintStream out = null;
//标准错误输出流
public final static PrintStream err = null;
public static void setIn(InputStream in){..}
public static void setOut(PrintStream out){..}
public static void setErr(PrintStream err){..}
}
标准输入流会默认从控制台读取数据
标准输出流会默认把数据输出到控制台
System.out.println(System.in.getClass());
System.out.println(System.out.getClass());
输出结果为:
class java.io.BufferedInputStream
class java.io.PrintStream
5)ByteArrayInputStream和ByteArrayOutputStream
ByteArrayInputStream可以从数组中读取字节
ByteArrayOutputStream可以把字节写到对象中的缓冲区里面,其实就是一个字节数组
6)FileInputStream和FileOutputStream
FileInputStream可以读取文件中的字节
FileOutputStream可以向文件中写进去字节
7)PipedInputStream和PipedOutputStream
PipedInputStream管道字节输入流
PipedOutputStream管道字节输出流
注:使用时需要把俩个管道进行对接
8)ObjectInputStream和ObjectOutputStream
在序列化中要使用的对象输入流和对象输出流,之后再来使用测试
9)java.io.File类
File类型对象可以表示一个文件也可以表示一个目录.
7、字节流中的常用包转流
也可以称为功能流或者处理流,因为它是对节点流进行包装的一种流,包装后可以增加节点流的功能。但是包装流本身并不能直接读写数据
1)BufferedInputStream和BufferedOutputStream
可以给字节流中的节点流提供代码缓冲区的输入/输出流
2)DataInputStream和DataOutputStream
可以给字节流中的节点流提供输入/输出java中不同类型的数据
3)PrintStream
PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
8、字符流
1)Reader
public int read(){..}
public int read(char[] cbuf){..}
public abstract int read(char[] cbuf, int off,int len){..}
//指定缓冲区
//@since 1.5
public int read(CharBuffer target){..}
abstract public void close();
public long skip(long n){..}
public boolean markSupported(){..}
public void mark(int readAheadLimit){..}
public void reset(){..}
//Tells whether this stream is ready to be read
public boolean ready(){..}
2)Writer
public void write(int c){..}
public void write(char cbuf[]){..}
abstract public void write(char cbuf[], int off, int len);
public void write(String str){..}
public void write(String str, int off, int len){..}
abstract public void flush();
abstract public void close();
//@since 1.5
//和out.write(c)的效果一样
public Writer append(char c){..}
public Writer append(CharSequence csq){..}
public Writer append(CharSequence csq, int start, int end){..}
3)CharArrayReader和CharArrayWriter
CharArrayReader可以读取字符数组中的内容
CharArrayWriter可以向字符数组中写内容
4)FileReader和FileWriter
FileReader读取文件内容的便捷类,InputStreamReader的子类
FileWriter写入文件内容的便捷类,OutputStreamWriter的子类
5)PipedReader和PipedReader
PipedReader管道字符输入流
PipedReader管道字符输出流
6)BufferedReader和BufferedWriter
这两个流属于包转流,它们本身并不能读取数据,它们的作用是包装在其他节点流上面,为其提供额外的功能
7)PrintWriter
一般会把BufferedReader和PrintWriter配合在一起使用,因为BufferedReader可以一次读一行字符串,而PrintWriter可以一次写一行字符串(自动换行)。
9、转换流
InputStreamReader和OutputStreamWriter是一对名字中既有Stream,又有Reader或Writer的流,因为它们是转换流,负责把一个字节流转换为字符流。所以它们是字节流和字符串之间的桥梁.
注:在把字节流转换为字符流的过程中,还可以指定字符编码,避免乱码的出现。
10、对象流
1)序列化和反序列化
Java中的序列化是指把Java对象转换为字节序列的过程
对象—序列化—>01010101
Java中的反序列化是指把字节序列恢复为Java对象的过程
01010101—反序列化—>对象
思考:为什么需要序列化和反序列化?
2)如何实现序列化和反序列化
使用对象流即可实现对象的序列化和反序列化
ObjectOutputStream类中的方法可以完成对象的序列化:
public final void writeObject(Object obj){…}
ObjectInputStream类中的方法可以完成对象的反序列化:
public final Object readObject(){…}
注:这俩个对象流都属于字节流
3)序列化的要求
只有实现了java.io.Serializable接口的类的对象才可以被序列化,否则序列化时会报错
思考:测试序列化版本号的作用是什么?
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
4)transient关键字
在对象序列化的时候,被transient修饰的属性的值,在序列化过程中是会被忽略掉的。transient关键字表示游离,不参与序列化。
11、随机访问流
java.io.RandomAccessFile类
public class RandomAccessFile extends Object{…}
这是个特殊的流,它不属于之前那些流的体系。
这个流的既可以用来读文件,也可以用来给文件中写内容,并且该类中的方法可以用来定位文件中的位置:
public native void seek(long pos);
构造器中需要设置该流的操作模式:
//对文件只读
RandomAccessFile r = new RandomAccessFile(filePath,“r”);
//对文件即可读又可写
//但是一旦确定了读或者写,那么就不能在变
RandomAccessFile rw = new RandomAccessFile(filePath,“rw”);
12、IO + Properties联合使用。
IO流:文件的读和写
Properties:是一个Map集合,key和value都是String类型。
1、注解,或者叫做注释类型(Annotation)
2、注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件
3、怎么自定义注解呢?语法格式?
【修饰符列表】@interface 注解类型名 {
}
4、注解怎么使用,用在什么地方?
第一:注解使用时的语法格式是:
@注解类型名
第二:注解可以出现在类上、属性上、方法上、变量上等。。。
注解还可以出现在注解类型上。
5、JDK内置了哪些注解呢?