java是一门开源的面向对象的编程语言,具有面向对象的封装、继承、多态的特点。
封装:将类的某些信息隐藏起来,只提供特定的方法来访问或修改这些隐藏信息,从而防止直接操作类中的某些属性。是通过访问权限修饰符来实现封装的,public——protected——default——private访问权限依次减小,封装多使用private关键词修饰属性或者方法。封装可以提高代码的安全性、将内部实现封装起来提高代码的复用。
继承:通过extends实现子类继承父类,子类如果继承了父类就同时拥有父类公共的特征和行为(属性和方法),私有的属性也可以继承但是不能这直接访问,需要使用super.get/setXXX()方法(如果子类没有重写父类的get/set方法也可以直接使用this.getXXX方法,因为创建子类对象时,会先创建父类,父类的属性和方法与子类特有的属性和方法组成了子类的空间)。继承时不能继承父类的构造器;一个类只能继承一个类,一个类可以被多个类继承,允许多级继承;Object是所有类的父类(超类);子类不能继承父类中的静态变量,但是可以访问(共享不等于继承)。继承可以实现代码的提高复用性。
多态:从表现形式上看,多态就是调用同一个方法有不同的处理方式和处理结果,多态实现的条件有:①存在继承关系,②子类重写父类中的方法,③存在父类的引用指向子类的实例。例如在java中有接口时我们会使用到多态, 多态使得代码更加灵活,在程序执行时根据具体的实例对象调用方法。
java与C和C++ 相比去除了难以理解的指针,并且实现了自动垃圾回收,可以让程序员专注于业务代码的编写。
java还做到了跨平台性。java虚拟机jvm是实现跨平台的关键,编写好的源代码是.java文件,经过编译后形成字节码.class文件,.class文件并不能直接被执行,需要jvm将字节码转换为机器能读懂的机器码并执行指令,所以只需要在不同的平台上安装不让的jvm就可以实现”一次编译,到处使用“。(具体的jvm加载过程属于jvm中类加载器内容)
JDK:是整个java的核心,包含了java运行环境JRE、java工具和java基础类。
JRE:是运行java程序所必须的环境集合,包含jvm以及java核心类库
JVM:是整个java实现跨平台的核心部分。
java属于强类型语言,其中数据类型可以分为基本数据类型和引用数据类型。
8种基本数据类型:整数型、字符型、浮点型、布尔型
整数型:
byte:1个字节;short:2个字节;int:4个字节;long:8个字节。
字符型:
char:2个字节
浮点型:
float:4个字节;double:8个字节。
布尔型(boolean):只包含两个值true/false(不能用0/1表示)
应用类型:类、数组等
隐式也称为自动类型转换:从存储范围小的到存储范围大的类型。
byte——》short(char)——》int——》long——》float——》double
显示也称为强制类型转换:从存储范围大的到存储范围小的类型。但是存在精度损失的情况。
java为8中基本数据类型提供了包装类,并提供了一系列的操作。
为什么要为基本类型提供包装类呢?
基本数据类型与包装类有什么不同?
包装类是以对象的形式,可以对其进行对象的一些操作,而八种基本数据类型不是对象。
使用方式不同,包装类使用new关键字创建对象,基本数据类型是通过关键字声明。
初始值不同,基本数据类型的初始值由具体数据类型定,包装类的初始值为null。
存储的位置不同,包装类的实例是存储在堆中,基本数据类型存储在栈中。
装箱就是将基本数据类型封装为包装类,拆箱恰好是反过来将包装类型转为基本数据类型。
自动装箱和拆箱就是不用写显示的代码,内部直接进行封箱和拆箱。
//自动封箱
Integer a = 4;
//自动拆箱
Integer integer = new Integer(4);
int b=integer;
System.out.println(b==a);
//true
在封箱的时候底层默认调用的是valueOf()静态方法,在拆箱的时候底层默认调用的是intValue()方法。
值得注意的是在调用valueOf()的时候存在一个问题:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
当值在-128127之间时会取出Integer中的私有类IntegerCache里面的cache数组里的数据(目的是:为了避免频繁的创建和销毁对象而影响系统性能,它也实现了对象的共享,节省内存空间。)。所以在-128127之间的值是同一个值,而区间之外的值是通过new关键字创建出来的新的对象使用==判断时会出现相同的数字结果为false。
switch支持的数据类型有:char、byte、short、int以及他们的包装类、enum和String。是支持String类型的。
//[switch使用tableswitch 和lookupswitch 指令来实现,Java虚拟机的 tableswitch 和 lookupswitch 指令只能作用在int类型的数据]
其实switch中的表达式只支持int类型,char、byte、short都可以向上转型为int类型,包装类型可以自动拆箱;enum枚举类型中有一个private final int ordinal;
实例变量,表示序数,定位枚举常量的位置;支持String是因为在String中有hashCode方法返回的也是一个int类型的数值。
重载:
首先得要知道重载是解决什么问题的:在一个类中的某些功能的名称相同但是具体实现有不同,当然我们可以给它取不同的名字,但是这样取名就已经绞尽脑汁了,而且不方便调用,不建议采用,由此就产生了方法的重载。就例如System.out.println();我们可能打印的是八种基本数据类型,也有可能打印的是引用类型,但是我们使用打印是都只书写一个println()。
构成重载的规则:
同一个类中,
方法名相同,
参数列表不同(类型、顺序、个数),
总结:两同三不同,
注意:只有返回值和访问权限修饰符不同是不能构成重载的。参数列表顺序不同指的是:不同类型参数的顺序。
重写:
重写是为了解决什么问题:重写是为了实现能有与父类不同的表现形式,就是为了实现多态。
我们经常重写的方法就是equals和toString方法,我们重写的目的就是为了让子类有不同于父类的表现形式,equals和toString也是如此。
重写的规则:
子类继承父类或者是实现接口,
方法名称与父类相同,参数列表与父类相同,
返回值类型是父类中方法返回值的类型或者其返回值类型的子类,
抛出的异常不能比父类的异常更大,
访问权限能比父类中方法的小
总结:两同两小一大。
相同点:接口和抽象类都不能被实例化;实现接口的类和继承抽象类的子类只有全部实现了其中抽象方法的子类才能被实例化。
(为什么不能被实例化:接口没有构造方法,所以不能实例化,抽象类有构造方法,但是不是用来实例化的,是用来初始化的。)
不同点:
①抽象类是由abstract关键字修饰的。接口是interface关键字修饰的。
②继承抽象类的关键字是extends。实现接口的关键字是implement,一个类只能继承一个类,可以实现多个接口。
③抽象类可以有普通方法和抽象方法。接口只能有抽象方法。
④抽象类有构造方法。接口没有构造方法。
⑤抽象类的成员变量默认是default修饰,子类可以进行重新定义和重赋值;抽象方法不能使用private,state,synchronize,native修饰。接口的成员变量默认使用public static final 修饰,必须赋初始值,并且赋初始值之后不能修改。成员方法默认都是public abstract修饰的。
接口中增加了静态方法和用default修饰的默认方法。可以通过“接口名.方法名”进行调用。
native int hashCode():用来计算哈希值的;
boolean equals():比较对象是否相等,一般的类都会进行重写,使其比较对象内容是否相等。
native Object clone():克隆;
String toString():将对象转为字符串形式;
native void notify():唤醒一个wait线程;
native void notifyAll():唤醒所有wait线程;
wait():使得线程处于等待状态。
equals是Object类中的方法,==是比较运算符。二者都可以用于比较是否相等。
== :当比较的是基本数据类型时,比较的是对应的值是否相等,如果比较的是引用类型时,比较的是对象的地址是否相同,就是比较两个是不是同一个对象。
equals:不能用于两个基本类型比较,如果引用类型的类中没有对equals方法进行重写则它相当于==符号,比较地址,只有正确重写后才会比较两个对象的内容是否相等。
instanceof可以理解为一个双目运算符,它的作用就是查看左边的对象是不是其右边类的一个实例,用法如
String str = new String("你好");
System.out.println(str instanceof String);
注意:编译器会判断左边的对象能否装换为右边类的实例,如果不能,直接报错,如果不能确定,则交由运行时处理。
专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串。
当通过字面量创建的String 变量,如:
String str = "ert";
会在字符串常量池中查找一遍看有没有“ert”,如果有就会返回该对象的引用地址赋值给str,没有的话就会在字符串常量池中创建一个对象,然后返回地址。
当通过new关键字创建的String对象时,如:
String s = new String("aa")
无论字符串常量池中有没有"aa"都会在堆中开辟空间存储"aa"。这种创建对象的方式会保证字符串常量池和堆中都有"aa",并且返回的是堆中的对象地址。
对于jvm底层,String str = new String(“123”)创建对象流程是什么?
在常量池中查找是否存在"123"这个字符串;若有,则返回对应的引用实例;若无,则创建对应的实例对象;
在堆中new一个String类型的"123"字符串对象;
将对象地址复制给str,然后创建一个应用。
注意:
若常量池里没有"123"字符串,则创建了2个对象;若有该字符串,则创建了一个对象及对应的引用。
String、StringBuffer和StringBulider底层都维护的是字符数组。
String:底层的char数组被final修饰,一创建不可更改,也就是长度不可变,每次在字符需要修改时都会创建一个新的String对象并返回。
可以缓存hash值。因为string的hash值经常被使用,其不可变性也使得其hash值不可变,所以其hash值只需要计算一次。
常量池的优化。String对象创建后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
String a = "Hello";
String b = "你好";
String c = a + "," + b;
//String c = (new StringBuilder()).append(a).append(",").append(b).toString();
当String对象使用“+”拼接字符时,java提供了一个语法糖,其底层原理是:通过查看反编译以后的代码,我们可以发现,原来字符串常量在拼接过程中,是将String转成了StringBuilder后,使用其append方法进行处理的。也就是说 +和StringBuilder的append等价。
语法糖:语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
String常用的方法:
判断功能:
equals()判断两个字符串是否相等
equalsIgnoreCase():忽略大小写,比较内容是否相等
contains():判断是否包含指定字符串
isEmpty()判断是不是为空
startWith():判断字符串是不是以某个字符开头
endWith():判断字符串是不是以某个字符结尾。
获取功能:
int length():返回一个int值,表示字符串的长度;
char charAt(int index):返回一个char类型的值,索引index位置处的值;
int indexOf(String str):返回一个数字,表示str在该字符串中首次出现的位置;
int lastIndexOf(String str):返回一个数,表示从后往前找首次str出现的位置;
int indexOf(String str,int fromIndex):返回一个数字,表示从该字符串的fromIndex位置开始向后检索该字符串中str首次出现的位置;
String substring(int start):返回一个字符串,表示从start位置输出原字符串;
String substring(int start,int end):返回一个字符串,表示从start位置开始到end位置输出原字符串;
转换功能:
byte[] getBytes():转换成字节数
char[] toCharArray():将字符串转换成一个新的字符数组
static String valueOf(char[] chs):将一个字符数组转换成一个字符串(形参基本类型)
String toLowerCase():将字符串中的字母全都转换为小写
String toUpperCase():将字符串中的字母全都转换为大写
String concat(String str):将str链接在原字符串后
Stirng[] split(“分割符”):返回的是一个字符串数组,按照 分割符 将原来的字符串拆分为数组
替换功能:
String replace(char old,char new):如果存在old字符就将它替换为new字符;
String replace(String old,String new):如果存在old字符串就将它替换为new字符串;
replaceAll(String regex, String replacement):是一种模式匹配,替换某一类;
replaceFirst(String regex, String replacement):替换第一次出现的位置。
StringBuffer:底层维护的char数组没有被final修饰,并且是长度可变的。
StringBuffer stringBuffer = new StringBuffer();//默认创建容量为16的char数组
StringBuffer stringBuffer = new StringBuffer(12);//创建容量为12的char数组
StringBuffer stringBuffer = new StringBuffer("aaa");//创建容量为"aaa"长度+16的char数组
在添加字符或者字符串的时候:首先现有容量是否足够容纳判断添加后的字符串,如果不够先将容量扩充到原容量的2倍+2,如果还是不够直接扩容到添加元素后的长度。最大长度不能超过Integer的最大值Integer.MAX_VALUE;
为什么要+2?
StringBuffer和StringBuilder都继承了 AbstractStringBuilder抽象类但是StringBuffer每个方法使用了synchronized所以是线程安全的。
switch可以作用在char、byte、short、int以及他们所对应的包装类型,switch不能作用long、double、float、boolean以及他们的包装类型。jdk1.7之后可以作用于String类型。
主要的用途就是方便在没有创建对象的时候调用方法和属性。
static变量:
static修饰变量称为静态变量,也称为类变量,可以通过类名+变量名调用。在内存中只有一份,类加载的时候被初始化,所有的对象共享这一份。而非静态变量内存中可以有多个,并且每个对象之间互不干扰。
static方法:
静态方法,在静态方法中不能访问非静态成员变量和非静态成员方法,因为非静态方法和变量只能有对象进行调用,而静态方法和变量是可以直接通过类名调用的,但是可以在非静态成员方法中调用静态成员方法和变量的。
静态代码块:
静态代码块的主要作用是优化程序性能,因为他只能在类加载的时候加载一次,很多时候会将一些只需要执行一次初始化的操作都放在static代码块中进行执行。如果程序中有多个static块,在类的初始化被加载的时候,会按照static块的顺序类执行每个static代码块。
初始化的顺序:
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态代码块的初始化顺序取决于他们的位置,如果在继承关系中,初始化的顺序为:父类的静态变量/代码块——子类的静态变量/代码块——父类普通代码块——父类构造方法——子类变量/代码块——子类构造方法
final关键字可以修饰类、变量、方法。
修饰的类不能被继承,
修饰的方法不能被重写,
修饰的变量,如果是基本数据变量,值不能改变;如果是引用数据类型,变量不能再指向其他引用对象,但是变量本事的值是可以改变的。
相同点:
不同点:
在集合中set集合是无序且不可重复,如何实现不可重复呢?就是在使用add()方法添加元素时先判断是否已经存在该元素了,我们可以通过equals()方法判断,但是当集合中有大量的元素时使用equals()方法太慢了,于是就推出了hashcode()方法,hashcode()方法可以为每个对象计算出一个哈希值,然后查看集合中是否存在这个哈希值,如果不存在则可以直接存储,如果存在则需要调用equals()方法比较具体内容,是否相同。hashcode计算哈希值会出现”哈希碰撞“现象,就是两个不同的值计算出来的hash值是一样的,如“通话”和“重地”。
equals和hashcode都可以用来比较两个对象是否相等,但是hashcode效率高,不安全;equals效率低,安全。
集合和数组是十分类似的,都是存储一组特定数据类型的数据的容器。但是数组有个缺点是大小定义后不能更改,而且在定义时就必须明确规定数组大小。但是集合在创建时不强制确定大小,并且是可以扩容的。
在java中集合可以分为单列集合和双列集合(map)
Collection接口的方法有:
isEmpty():判断是否为空
add():添加元素
clear():删除集合中所有的元素
contains(Object o):判断集合中是否包含指定对象元素
iterator():返回一个Iterator迭代器对象,可以遍历集合中的元素
size():返回集合中元素的数目
remover(Object o):从集合中删除一个指定对象
toArray():将集合转为数组,返回一个Object[]
list和set集合的区别:
list按照元素添加的顺序存储元素,而set无序存储;
list支持重复元素,而set不支持重复元素。
List集合有两个常用的两个实现类:ArrayList和LinkedList
ArrayList:是以索引结构实现的单列,底层维护的是一个Object[],可以实现动态扩容,使用无参构造创建对象时,在创建的时候不会给底层数组定义长度,在第一次添加元素时判断添加元素的长度和默认长度10,选择值大的作为底层数组的容积,如果使用的是有参构造指定初始容积创建对象时,直接确定容积。在添加元素时,首先会判断添加这个元素后大小,当前容量是否足够,如果足够直接添加;如果不够,先将容量扩容到原来容量的1.5倍,在判断新容量是否还小于需求容量,如果小于直接以需求容量当做新容量,最后将原来集合中的元素复制到新数组中去。ArrayList没有自动缩容机制。无论是remove方法还是clear方法,它们都不会改变现有数组elementData的长度。但是它们都会把相应位置的元素设置为null,以便垃圾收集器回收掉不使用的元素,节省内存。ArrayList的缩容,需要我们自己手动去调用trimToSize()方法,达到缩容的目的。
ArrayList集合由于是索引结构,它的查询、遍历快,但是删除元素慢。
LinkedList:是以链表结构实现的单列集合,一个节点指向下一个节点,不存在扩容问题,LinkedList集合的优点是,增删效率高,但是查询、遍历的时候效率比较低。
Set集合两个常用的实现类HashSet和TreeSet
HashSet类按照哈希算法来存取集合中的对象,存取速度比较快,底层使用的是map中的key,不安添加元素顺序存储,集合中可以存放一个null值,添加一个元素时,底层会计算出该对象的hashcode值,用hashcode值和底层数组的长度来计算出该对象需要存储的位置,如果该位置上没有元素则直接存,如果有元素证明发生了哈希碰撞,此时调用equals方法来判断具体内容是否相等,如果相等,就不添加,如果不等就链接到上一个节点的后面。
TreeSet类实现了SortedSet接口,能够对集合中的对象进行排序,不允许添加null元素。TreeSet底层数据结构是二叉树。
Map集合存储的每一个元素都是由键值对组成,并且集合中的每个key必须唯一,每个元素并不是按照添加顺序进行存储的。
Map集合遍历的方式有两种:①取出Map集合的key集合,然后由key获得value;②将Map集合的键值对当做一个Enrty对象存储在set集合中然后在遍历;③利用Lambda表达式forEach()。
Map中的方法有:
size():集合大小
isEmpty():判断是否为空
containsKey(Object key):判断是否存在指定key
containsValue(Object value):判断是否包含指定value
put():添加元素
remove(Object key):移除指定key的元素
putAll():添加整个集合
clear():清空集合
keySet():返回一个封装key值的Set集合
values():返回个封装value的Collection集合
Set> entrySet();
Map的实现类:HashMap、HashTable和ConcurrentHashMap
HashMap是Map的非同步的实现(一个对象可以同时被多个线程访问),是线程不安全的,允许存在null键和null值,但是只能有一个null键,可以有多个null值。JDK8之后,底层数据结构是数组+链表/红黑树(当单链表达到阈值8时就会转化为红黑树)
以自定义的类作为key值的类型时,自定义类需要重写equals和hashCode方法,否则会直接调用Object类里的方法。
put添加元素时(首次添加时会创建哈希数组默认长度是16 ,数组只是用来定位元素在哈希表中的位置),根据添加元素的哈希值通过哈希函数计算得到一个小于哈希数组长度的值,将需要添加的元素以链表的形式添加到函数求得的值对应的哈希数组位置中,当哈希数组的某一个位置重复时,将连接到上一次链表的后面,当一个链表的长度达到8时,会将链表转成红黑树,以方便检索,当哈希数组数据存储达到当前数组长度的0.75(称为负载因子)时就会扩容到原来的2倍.
HashMap的主要流程:
1.判断table数组是否被初始化,如果没有就会进行初始化扩容。
2.利用添加元素的key的hash值与数组长度-1进行&运算,得到一个小于数组长度的值,就是对应添加元素应该添加的数组位置。
3.获取到这个位置上的头结点,判断当前节点是否为空,为空的话直接添加元素。
4.不为空时,判断当前节点的hash值是否与添加元素key的hash值相等,如果相等,判断当前节点的key是否与添加元素key是同一个对象,如果是,直接用添加元素value替换当前节点的value,如果不是,调用equals方法判断当前节点的key与添加元素的key是否内容相等,如果相等,也直接用添加元素value替换当前节点的value。如果前面的条件都不满足进行下一步。
5.判断当前节点是不是树的头结点,如果是,直接在树种寻找位置添加。
6.如果不是树的头结点,那么必定是链表。接下来死循环遍历当前链表,如果链表上存在某个节点的key与添加元素的key相同,就直接替换value,如果不存在,就直接在链表的末尾添加元素,添加成功后判断当前链表长度是否≥7,如果是就将链表转换为红黑树,判断当前HashMap对象中的元素达到当前数组的0.75就会进行resize进行扩容,扩容到原来的2倍。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//执行流程前的初始化定义
//定义桶数组
Node<K,V>[] tab = table;
//定义当前key对应的节点
Node<K,V> p;
//初始化数组长度
int n = tab.length;
//开始执行
//如果桶不存在,初始化扩容
if (tab == null || n == 0){
//扩容并重新赋值数组长度
n = (tab = resize()).length;
}
//将数组长度减一与hash值做&运算,得到0到数组长度减一之间的数字【如数组长度为16,那么得到的i是0~15之间的数字】
//i就是对应的数组桶下标
int i= (n - 1) & hash;
//通过得到数组桶坐标的链表头节点
p = tab[i];
//如果头结点为空,创建新节点
if (p == null){
tab[i] = newNode(hash, key, value, null);
} else {
//如果不为空,那么确定节点已经位于链表上了
Node<K,V> e; K k;
//和链表中的头节点比较,如果相同,那么就取得了相同key的节点
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))){
e = p;
} else if (p instanceof TreeNode){
//如果是树节点,那么找到头部节点为树节点,那么将对应的树中的节点进行赋值
//如果返回了e = null,表示在树上新创建了一个节点 - 详情见hashMap - putTreeVal详解
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
} else {
//匹配链表上的节点,如果能匹配上,就返回对应节点,
//如果匹配不上,那么创建一个新的节点,并且验证是否需要树化
//树化的条件:
//condition1: 大于等于阈值-1即7;
//condition2: 数组长度大于MIN_TREEIFY_CAPACITY即64
for (int binCount = 0; ; ++binCount) {
//如果下一个节点为空(尾节点),那么创建节点,并且判断是否需要树化
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//有匹配的节点,那么返回
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
break;
}
p = e;
}
}
//如果在链表中有值,那么将值替换
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//hashMap未实现,linkedHashMap因为带序问题会实现
afterNodeAccess(e);
return oldValue;
}
}
//修改次数加一
++modCount;
//如果添加元素之后的大小超过了阈值,需要扩容
if (++size > threshold)
resize();
//hashMap没有实现,linkedHashMap实现了,因为是带序的
afterNodeInsertion(evict);
return null;
}
HashTable是Map的同步实现(synchronized实现,一个对象只能被一个线程访问),线程安全,但是并发效率极其低下,不允许key和value的值为null。底层数据结构是数组+链表
HashTable初始默认容积是11,扩容时并不是2倍,而是2倍+1,HashTable现在已经还少使用,单线程多使用HashMap多线程使用ConcurrentHashMap。
ConcurrentHashMap同样是Map的同步实现,虽然HashTable已经实现了同步,但是其效率太低,与HashTable不同的是它的锁的粒度更小,底层使用了与HashMap相同的数组+链表/红黑树的结构,线程安全使用synchronized+CAS。在大数据量,高并发情况下ConcurrentHashMap更加实用。
lambda表达式是JDK8的新特性,在JDK8之前我们想要吧某个功能传递给某个方法,我们只能写匿名内部类。如为集合进行排序的时候,我们会为Comparator类创建一个匿名内部类对象,重写其中的compare方法。
//创建一个匿名内部类
alist.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.id-o2.id;
}
});
为了简化匿名内部类,JDK8之后提供了简单的写法,使用lambda表达式。
list.sort((o1,o2)->{
return o1.compareTo(o2);
});
当花括号里的语句只有一条时,可以将花括号省略。
需要注意的是Lambda表达式中只能是函数式接口(接口中只有一个抽象方法需要实现)
Lambda表达式的标准书写形式:(参数列表)->{方法体}。
Lambda表达式可以简化书写:
①小括号里参数列表的参数类型可以不写,
②当参数只有一个时小括号可以不写,
③当方法体只有一条语句时,花括号,return,分号都可以不写。
异常狭义理解就是在程序运行时,出现不正常的情况,导致jvm被迫停止运行,异常指的并不是语法错误,语法出错编译时期就会报错,是不会产生字节码文件,根本不运行;java等一些面向对象语言中的异常本身就是一个类,产生异常就是创建一个异常对象然后抛出一个异常对象。
为什么要设计异常?引入异常后我们就可以将可能会有异常的代码与正常的代码分离出来,单独处理,这样使得代码更加整洁;其次,在出现特殊情况的时候还可以抛出一个检查异常,让调度者处理。
Throwable是所有Error(错误)和Exception(异常)的父类。
Error:(又称错误) java虚拟机无法解决的问题.一般不编写代码处理.
Exception:(程序异常,狭义的异常)因编程错误或者是偶然的外在因素导致的一般性问题,可以编写针对性代码进行解决处理的
异常有运行时异常和非运行时期异常,
RuntimeException此类异常,Java 编译器不会检查它,属于不受检查的异常。此类异常会由JVM自动抛出并自动捕获(就算我们没写异常捕获语句运行时也会抛出错误!!),此类异常的出现绝大多数情况是代码本身有问题,应该从逻辑上去解决并改进代码。
非运行时异常:Exception中除 RuntimeException 及其子类之外的异常。此类异常, Java 编译器会检查它。如果程序中出现此类异常,从程序语法角度讲是必须进行处理得异常。例如:ClassNotFoundException(没有找到指定的类异常),IOException(IO流异常),要么通过throws 进行声明抛出,要么通过try-catch进行捕获处理,否则不能通过编译。
异常处理分为抛出异常(throw)、捕获异常(try-catch-finally)、声明异常(throws)
五个关键字:
throw:抛出异常,在方法内部抛出一个Throwable 类型的异常。任何Java代码都可以通过throw语句抛出异常。
throws:在方法上声明异常交由调度者处理,
try-catch-finally有三种组合:try-catch 、try-finally、try-catch-finally。try必须要有,catch可以有0至多个,finally可以有一个或者没有。
try用与捕获异常,catch用于处理异常,finally无论异常是否发生都会执行的代码块
try {
// 可能会发生异常的程序代码
} catch (异常类型A e){
// 捕获并处置try抛出的异常类型A
} finally {
// 无论是否发生异常,都将执行的语句块
}
注意:try-finally只是捕获到了异常,并没有处理