Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)

发布时间:2024-07-06 16:01

Java笔记整理五

1.1Iterator接口

Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。

  • public Iterator iterator(): 获取集合对应的迭代器,用来遍历集合中的元素的。

迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。

  • public E next():返回迭代的下一个元素。
  • public boolean hasNext():如果仍有元素可以迭代,则返回 true。

迭代器的使用步骤(重点):
1. 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
2. 使用Iterator接口中的方法hasNext判断还有没有下一个元素
3. 使用Iterator接口中的方法next取出集合中的下一个元素

1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 注意: Iterator接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型 多态 接口
实现类对象 Iterator it = coll.iterator();

发现使用迭代器取出集合中元素的代码,是一个重复的过程所以我们可以使用循环优化 不知道集合中有多少元素,使用while循环
循环结束的条件,hasNext方法返回false

    while(it.hasNext()){
        String e = it.next();
        System.out.println(e);
    }

迭代器的实现原理

coll.iterator();             获取迭代器实现类对象,并且吧指针(索引)指向集合-1索引。
it.hasNext()       判断有没有下一个元素。

it.next();     1.取出下一个元素2.把指针向后移动一位

增强for循环

底层使用迭代器,简化书写
所有单列集合都可使用

格式:

for(元素的数据类型  变量 : Collection集合or数组){ 
  	//写操作代码
}

           int[] arr = {3,5,6,87};
       	//使用增强for遍历数组
		for(int a : arr){//a代表数组中的每个元素
			System.out.println(a);
		}

2.泛型

一种未知的数据类型,是一种变量用来接收数据类型。

E e:Element 元素  未知的数据类型

T t : Type 类型

ArrayList 集合在定义的时候,不知道会存储什么数据,所以类型使用泛型。

创建集合对象的时候,就会确定泛型的数据类型

ArrayList <String> list = new ArrayList <String>();

会把数据类型作为参数传递,赋值给泛型E。


使用泛型的好处:

创建集合对象,不使用泛型

  • 好处:默认类型是object类型,可以存储任意类型的数据。
  • 弊端:不安全,会引发异常

创建集合对象,使用泛型

  • 好处:
  1. 避免了类型转换的麻烦,存储是什么类型,取出是什么类型。
  2. 把运行期的异常(代码运行后抛出的异常),提升到编译期(写代码时会报错)
  • 弊端:泛型是什么类型,就只能存储什么类型的数据。

泛型的定义和使用

定义和使用含有泛型的类

定义格式:

修饰符 class 类名<代表泛型的变量> {  }

例如,API中的ArrayList集合:

class ArrayList<E>{ 
    public boolean add(E e){ }

    public E get(int index){ }
   	....
}

使用泛型: 即什么时候确定泛型。
在创建对象的时候确定泛型

例如,ArrayList list = new ArrayList();

此时,变量E的值就是String类型,那么我们的类型就可以理解为:

class ArrayList<String>{ 
     public boolean add(String e){ }

     public String get(int index){  }
     ...
}

定义和使用含有泛型的方法

定义含有泛型的方法:泛型定义在方法的修饰符和返回值之间+。

格式:

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){  }
  • 含有泛型的方法,在调用方法的时候确定泛型的数据类型。传递什么类型的参数,泛型就是什么类型。

  • 含有泛型的静态方法:
    推荐使用类名.方法名调用

含有泛型的接口

定义格式:

修饰符 interface接口名<代表泛型的变量> {  }

例如,

public interface MyGenericInterface<E>{
	public abstract void add(E e);
	
	public abstract E getE();  
}

使用格式: 1、定义实现类实现接口确定泛型的类型 2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

泛型的通配符

当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。

此时只能接收数据,不能往该集合中存储数据。
不能创建对象使用,只能作为方法的参数使用

泛型没有继承概念

举个例子大家理解使用即可:

public static void main(String[] args) {
    Collection<Intger> list1 = new ArrayList<Integer>();
    getElement(list1);
    Collection<String> list2 = new ArrayList<String>();
    getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型

通配符高级使用----受限泛型

泛型的上限

  • 格式类型名称 对象名称
  • 意义只能接收该类型及其子类

泛型的下限

  • 格式类型名称 对象名称
  • 意义只能接收该类型及其父类型

比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类

public static void main(String[] args) {
    Collection<Integer> list1 = new ArrayList<Integer>();
    Collection<String> list2 = new ArrayList<String>();
    Collection<Number> list3 = new ArrayList<Number>();
    Collection<Object> list4 = new ArrayList<Object>();
    
    getElement(list1);
    getElement(list2);//报错
    getElement(list3);
    getElement(list4);//报错
  
    getElement2(list1);//报错
    getElement2(list2);//报错
    getElement2(list3);
    getElement2(list4);
  
}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}

常见数据结构

常用结构有:栈、队列、数组、链表和红黑树。

  • stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。

特点

先进后出 (即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。

  • 压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。

  • 弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。

  • 栈的入口、出口的都是栈的顶端位置。

队列

  • 队列queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。

简单的说,采用该结构的集合,对元素的存取有如下的特点:

  • 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
  • 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。

数组

  • 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。

特点:
查找元素快:通过索引,可以快速访问指定位置的元素

增删元素慢

  • 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
  • **指定索引位置删除元素:**需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

链表

  • 链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表

单向链表

  • 查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素

  • 增删元素快:

    • 增加元素:只需要修改连接下个元素的地址即可。

    • 删除元素:只需要修改连接下个元素的地址即可。

红黑树

  • 二叉树binary tree ,是每个结点不超过2的有序树(tree)
    每个结点上都最多只能有两个子结点。

二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。

红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

红黑树的约束:

  1. 节点可以是红色的或者黑色的

  2. 根节点是黑色的

  3. 叶子节点(特指空节点)是黑色的

  4. 每个红色节点的子节点都是黑色的

  5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

红黑树的特点:

​ 速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍

List集合

java.util.List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。

1.在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的。
3.有索引,在程序中可以通过索引来访问集合中的指定元素。
2.List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。

  • public void add(int index, E element): 将指定的元素,添加到该集合中的指定位置上。
  • public E get(int index):返回集合中指定位置的元素。
  • public E remove(int index): 移除列表中指定位置的元素, 返回的是被移除的元素。
  • public E set(int index, E element):用指定元素替换集合中指定位置的元素,返回值的更新前的元素。

使用迭代器遍历list集合:

Iterator it=list.iterator();
    while(it.hasNext()){
	String s=it.next();
	System.out.println(s);
      
}

操作索引,注意防止索引越界异常;


ArrayList集合

java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList是最常用的集合。

LinkedList集合

java.util.LinkedList集合数据存储的结构是链表结构。方便元素添加、删除的集合。
特点:
1.底层是一个链表结构,查询快,增删满
2.里边包含了大量首位元素操作的方法。

  • public void addFirst(E e):将指定元素插入此列表的开头。
  • public void addLast(E e):将指定元素添加到此列表的结尾。
  • public E getFirst():返回此列表的第一个元素。
  • public E getLast():返回此列表的最后一个元素。
  • public E removeFirst():移除并返回此列表的第一个元素。
  • public E removeLast():移除并返回此列表的最后一个元素。
  • public E pop():从此列表所表示的堆栈处弹出一个元素。
  • public void push(E e):将元素推入此列表所表示的堆栈。
  • public boolean isEmpty():如果列表不包含元素,则返回true。

LinkedList是List的子类,List中的方法LinkedList都是可以使用

##set接口

java.util.Set接口和java.util.List接口一样,同样继承自Collection接口
特点
1.不允许存储重复元素
2.无索引,没有带索引方法,不能使用for循环遍历集合
可使用迭代器 ,和foreach遍历

Set集合有多个子类,
java.util.HashSetextendjava.util.LinkedHashSet

HashSet集合介绍

特点:
1.允许存储重复的元素;
2.没有索引,没有带索引的方法,不可使用普通for循环遍历
3.是一个无序的集合,存储元素和取出元素的顺序不一致
4.底层是一个哈希表结构(查询速度非常快)

有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCodeequals方法。

哈希值: 是一个十进制整数,由系统随机给出(就是对象地址值,是一个逻辑地址,模拟出来得到地址,不是数据实际存储的物理地址)

int hasCode():返回对象的哈希码值

哈希表结构

jdk1.8之后
哈希表是由数组+链表或数组+红黑树

数组结构:把元素进行分组,相同哈希值为一组,链表/红黑树把相同哈希值的元素连接到一起
如果链表的长度超过八位,就会把链表转换为红黑树,提高查询速度。

HasSet存储不相同元素原理!

HasSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一

Linked HashSet集合介绍

特点:底层是一个哈希表(数组+链表/红黑树)+链表:多一条链表(记录元素存储顺序),保证元素有序

可变参数

JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:

修饰符 返回值类型 方法名(参数类型... 形参名){  }

其实这个书写完全等价与

修饰符 返回值类型 方法名(参数类型[] 形参名){  }

使用前提:
当方法的参数列表数据类型已经确定,但是参数的个数不确定,可以使用可变参数。
可变参数原理
可变擦书底层是一个数组,跟据传递参数的个数不同,创建不同长度的数组,来存储这些参数
传递的参数的个数可以是0个或者N个

        int sum = 0;
        for (int a : arr) {
            sum += a;
        }
        return sum;
    }

public static void main(String[] args) {
      
        // 求 这几个元素和 6  7  2 12 2121
        int sum2 = getSum(6, 7, 2, 12, 2121);
        System.out.println(sum2);
    }

可变参数注意事项:
1.一个方法的参数列表只能由一个可变参数
2.如果方法的参数由多个,那么可变参数必须写在参数列表的末尾

Collections

  • java.utils.Collections是集合工具类,用来对集合进行操作。部分方法如下:
  • public static boolean addAll(Collection c, T... elements):往集合中添加一些元素。
  • public static void shuffle(List list) 打乱顺序:打乱集合顺序。
  • public static void sort(List list):将集合中元素按照默认规则排序。
  • public static void sort(List list,Comparator ):将集合中元素按照指定规则排序。
public class CollectionsDemo {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        //原来写法
        //list.add(12);
        //list.add(14);
        //list.add(15);
        //list.add(1000);
        //采用工具类 完成 往集合中添加元素  
        Collections.addAll(list, 5, 222, 12);
        System.out.println(list);
        //排序方法 
        Collections.sort(list);
        System.out.println(list);
    }
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]

void sort(List list):被排序的集合里边存储的元素,必须实comparable,重写就扣中的方法compareTo定义的排序规则。

Comparable接口的排序规则: this-o(参数) 升序排序 o-this 降序排序

void sort(List list,Comparator )

Comparator和compara区别:
comparable:自己(this)和别人(参数)比较,自己需要实现compara接口,重写比较规则comparaTo方法。

Comparator:相当于找一个第三方裁判,比较两个。

Collections.sort(list,newComparator() {
            @Override
            public int compare(String o1, String o2) {
                return o2.charAt(0) - o1.charAt(0);
            }
        });

public int compare(String o1, String o2):比较其两个参数的顺序。

两个对象比较的结果有三种:大于,等于,小于。

如果要按照升序排序,
则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
如果要按照降序排序
则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)

简述Comparable和Comparator两个接口的区别。

Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。

Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。

Map集合

  • Collection中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
  • Map中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
  • Collection中的集合称为单列集合,Map中的集合称为双列集合。
  • 需要注意的是,Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。

java.util.Map集合 Map集合的特点:
1.Map集合是一个双列集合,一个元素包含两个值。一个称为key,一个称为Value
2.Map集合中的元素,Key和Value的数据类型可以相同,亦可以不同。
3.Map集合中的元素,Key是不允许重复的,Value是可以重复的。
4.Map集合中的元素,Key和value是一一对应。

java.util.Map implements Map接口
####HashMap集合的特点:
1.HashMap集合底层是哈希表,查询的速度特点快
JDK1.8之前:数组+单向链表
JDK1.8之后:数据+单向链表/红黑树(链表的长度超过8):提高查询的速度
2.HashMap集合是一个无序的集合,存储元素和取出的速度又可能不一致

java.util.LinkedHashMap extends HashMap集合

LinkedHashMap的特点:

1.LinkedHashMap集合底层是哈希表+链表(可预知的迭代顺序)
2.LinkedHashMap集合一个有序的集合,存储元素和取出元素的顺序是一致的。
  • HashMap:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
  • LinkedHashMap:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。

tips:Map接口中的集合都有两个泛型变量,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量的数据类型可以相同,也可以不同。

Map接口中常用的如下:

  • public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。存储键值对的时候,key不重复,返回值V是null。key重复,会使用新的value替换重复的value,返回被替换的value.

  • public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。k 存在v返回被删除的值。
    key不存在,v返回null。

  • public V get(Object key) 根据指定的键,在Map集合中获取对应的值。key存在返回对应的value值,key不存在,返回null,

  • boolean containsKey(Object key) 判断集合中是否包含指定的键。
    包含返回true,不包含返回false

  • public Set keySet(): 获取Map集合中所有的键,存储到Set集合中。

  • public Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

集合第一种遍历方式,通过键找值的方式

实现步骤:

public Set keySet()`: 实现步骤:
1.使用Map集合中的方法keyset(),把Map集合所有的Key取出来,存储到一个Set集合中。
2.遍历Set集合,获取Map集合中的每一个Key
3.通过Map集合中的方法get(key),找到Value

Set<String> keys = map.keySet();
        // 遍历键集 得到 每一个键
        for (String key : keys) {
          	//key  就是键
            //获取对应值
            String value = map.get(key);
            System.out.println(key+\"的CP是:\"+value);
        }  

Map.Entry:在Map接口中有一个内部接口Entry
作用:当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录键与值(键值对对象,键与值的映射关系)

Set> entrySet(),把Map集合内部的多个Entry对象取出来,存储到一个Set集合中

Map集合的第二种遍历方式,使用Entry对象遍历。
Set>entrySet(),返回此映射中包含的映射关系的Set视图。

实现步骤:

  1. 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中。
  2. 遍历Set集合,获取每一个Entry对象,
  3. 使用Entry对象中的方法getKey()和getValue()获取键与值
Set<Entry<String,String>> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry<String, String> entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+\"     \"+value);
}

java.util.LinkedHashMap extends HashMap

LinkedHashMap
Map接口的哈希表和链表列表实现,具有可预知迭代顺序。

底层原理:
哈希表—+链表(记录元素顺序)

key不允许重复,无序,存取顺序一致。

java.util.Hashtable 集合implements HashMap接口

Hashtable:底层也是哈希表,是一个线程安全的集合,是单线程的集合,速度慢

HashMap:底层是一个哈希表,是一个线程不安全的集合:
,是多线程的集合,速度快。

Hashtable和Vetor集合一样,在Jdk1.2以后被取代,Properties集合是一个唯一和IO流相结合的集合,不能存储null值

JDK新特性:
List接口,Set接口,Map接口:里边增加了一个静态方法of,可以给集合一次性添加多个元素。
StaticListof(E…elements)
使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用

注意:
1.of方法只是用于LIst接口,Set接口,Map接口,不适用于接口实现类。
2.of方法的返回值时一盒不能改变的集合,集合不能在使用add,put方法添加元素,会抛出异常。
3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常-。


异常

程序执行中,出现非正常情况,导致JVM退出。,产生异常时,创建异常对象并抛出一个异常对象,Java处理方式是中断处理。

java.lang.Throwable,其下有两个子类:java.lang.Errorjava.lang.Exception

Error:严重错误Error,无法通过处理的错误,只能事先避免。

  • Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
  • java.lang.Throwable:所有异常超类
  • Exception:编译器异常,写代码时出现
    • RuntimeException:运行期异常,运行过程中出现
    • Error:错误,只能修改代码处理

异常处理流程

异常处理关键字:
1.抛出异常throw,用来抛出一个指定的异常对象
格式: throw new 异常类名(参数);

注意:
1.必须在方法内
2.new 对象必须在exception及其子类对象

3.抛出指定异常必须处理。
若创建的是RuntimeException及其子类对象,不处理,默认交给JVM处理(打印中断)
若创建编译异常则必须处理,要么throws或者try catch

2.Objects非空判断

public static  T requireNonNull(T obj) {
    if (obj == null)
      	throw new NullPointerException();
    return obj;
}

异常处理的第一种方式

交给别人处理

1.throws

运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).最终交给JVM处理 --中断处理

修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{   }	

hrows用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

注意:
1.必须写在方法声明处,
2.声明的异常必须是exception及其子类
3.方法内部如果抛出了多个异常对象,那么后边也必须声明多个异常,若有子父类关系,那么直接声明父类异常

4.调用一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用throws声明抛出,交给方法调用者处理,最终交给JVM
要么 try catch 自己处理

2.捕获异常try…catch

  1. 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
  2. 在方法中使用try-catch的语句块来处理异常。

异常处理的第二种方式

自己处理异常

  • 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

捕获异常语法如下:

try{
     编写可能会出现异常的代码
}catch(异常类型  e:定义一个异常变量,用来接收try抛出的异常对象){
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}

**try:**该代码块中编写可能产生异常的代码。

**catch:**用来进行某种异常的捕获,实现对捕获到的异常进行处理。

注意:try和catch都不能单独使用,必须连用。

1.如果try可能抛出多个异常对象,那么就可以使用多个catch
来处理这些异常对象
2.若try
中产生了异常,那么就会执行catch中异常处理逻辑,执行完则继续执行try catch之后的代码
,若没有产生异常,那么就不i会执行catch中的处理逻辑,执行完try中的代码,继续执行try catch 之后的代码

如何获取异常信息:

Throwable类中定义了一些查看方法:

  • public String getMessage():获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。简短信息

  • public String toString():获取异常的类型和异常描述信息(不用)。详细信息

  • public void printStackTrace():打印异常的跟踪栈信息并输出到控制台。默认调用此方法,信息最全面

包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

finally 代码块

finally:有一些特定的代码无论异常是否发生,都需要执行。

try…catch…finally:自身需要处理异常,最终还得关闭资源。

1不能单独使用
2一般用于资源释放,无论程序是否出现异常,最后都要释放资源

当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。

如果finally中有return语句,永远返回finally中的结果

异常注意事项

  • 多个异常使用捕获又该如何处理呢?

    1. 多个异常分别处理。
    2. 多个异常一次捕获,多次处理。
    3. 多个异常一次捕获一次处理。
    try{
         编写可能会出现异常的代码
    }catch(异常类型A  e){try中出现A类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }catch(异常类型B  e){try中出现B类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }
    

一个try多个catch注意事项:

  • catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。

  • 如果finally有return语句,永远返回finally中的结果,避免该情况.

  • 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出

父类异常什么样,子类异常就什么样

自定义异常

根据自己业务的异常情况来定义异常类.

异常类如何定义:

  1. 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
    必须处理,要么throws,要么try catch
  2. 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException
    抛出运行期异常,无需处理,交给JVM处理,中断处理

格式:public class XXXException extends Exception |RuntimeException{
添加一个空参构造方法
添加一个带异常信息的构造方法
}

// 业务逻辑异常
public class RegisterException extends Exception {
    /**
     * 空参构造
     */
    public RegisterException() {
    }

    /**
     *
     * @param message 表示异常提示
     */
    public RegisterException(String message) {
        super(message);
    }
}

多线程

  • 并发:指两个或多个事件在同一个时间段内发生。

  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程调度:

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

    创建多线程大的第一种方式:

java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
创建Thread类的子类,通过继承Thread类来创建启动多线程的步骤如下:

1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run()方法,设置线程任务。
3.创建Thread类的子类对象
4.调用Thread类中的方法,start方法,开启新的线程,执行run方法

public class Demo01 {
	public static void main(String[] args) {
		//创建自定义线程对象
		MyThread mt = new MyThread(\"新的线程!\");
		//开启新线程
		mt.start();
		//在主方法中执行for循环
		for (int i = 0; i < 10; i++) {
			System.out.println(\"main线程!\"+i);
		}
	}
}

自定义线程类:

public class MyThread extends Thread {
	//定义指定线程名称的构造方法
	public MyThread(String name) {
		//调用父类的String参数的构造方法,指定线程的名称
		super(name);
	}
	/**
	 * 重写run方法,完成该线程执行的逻辑
	 */
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(getName()+\":正在执行!\"+i);
		}
	}
}

Threa类中常用方法:

获取线程名称
public String getName():获取当前线程名称
public static Thread currentThread():返回当前正在执行线程对象的引用。

设置线程名称
1.使用Threa类中的setName(名字)方法,使之与参数name相同。
2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类Threa给子线程起义和名字Thread(String name)分配新的Thread对象。

public static void sleep (long millis):使正在执行的线程以指定毫秒数暂停。

创建多线程大的第二种方式:

实现Runnable接口的实现类,该类实现run方法,分类该类的实例,在创建Thread时作为一个参数来传递并启动。

实现步骤:
1.创建一个runnable接口的实现类
2.在实现类中重写run方法
3.创建一个runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法

两种方法的区别, 实现runnable接口创建多线程程序的好处:
1.避免的单继承的局限性,一个类只能继承一个类,实现runnable接口还可以继承其他类。
2.增强的程序的扩展性,降低了程序的耦合性。实现接口方式,把设置线程任务和开启新线程进行了分离(解耦),
实现类中重写run方法,
创建Thread类对象,调用start放啊,开启新的线程。
尽量使用实现runnable接口的方式

匿名内部类实现线程的创建

匿名:没有名字
内部类:写在其他类内部

匿名内部类,简化代码
把子类继承父类,创建子类对象合一步完成

把实现实现类接口,重写接口的方法,创建实现类对象合一步完成

匿名内部类实现类对象,没有名字

格式: new 父类(){ 重写父类接口方法 };

线程安全问题:
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){ 需要同步操作的代码 }

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。
    注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
    (BLOCKED)。

public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
}}}}

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

使用步骤:
1.把访问共享数据的代码抽取出来,放到一个方法中。
2.在方法中添加synchronized修饰符

格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}

同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

使用同步方法代码:

public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
}
}

静态同步方法:锁对象是谁?不能是this
this是创建对象之后产生的,静态方法优先于对象。
静态方法的锁对象是本类的classshuxing—class文件对象

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。

使用步骤:
1.在成员位置创建一个ReentrantLock implements Lock 接口
2.在可能出现安全问题的代码前调用Lock接口的中的方法lock获取锁
3.在可能出现安全问题的代码前调用Lock接口的中的方法unlock释放锁

public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+\"正在卖:\"+ticket‐‐);
}
lock.unlock();
}
}
}

等待唤醒机制

线程状态

线程状态 导致状态发生条件

  • NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
  • Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
  • Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
  • Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
  • Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep .Object.wait。
  • Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

Timed Waiting(计时等待)

们在run方法中添加了sleep语句,这样就
强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)

  1. 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协
    作关系。
  2. 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程
    中会睡眠
  3. sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
    小提示:sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就
    开始立刻执行。

3 BLOCKED(锁阻塞)一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。

线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。

4 Waiting(无限等待):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。

一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的
Object.notify()方法 或 Object.notifyAll()方法。

void wait():在其他线程调用此对象的notify()方法或notifyAll方法前,导致当前方法等待。

void notify():唤醒此对象监视器上等待的单个的线程。会继续执行wait方法之后的代码。如果有多个等待线程,随机唤醒一个。

void notifyAll():唤醒所有等待线程

注意:
线程代码必须用同步代码块包裹起来,保证等待和唤醒只能有一个在执行。
同步使用的锁对象必须保证唯一 只有锁对象才能调用wait 和notify 方法


public class WaitingTest {
public static Object obj = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (obj){
try {
System.out.println( Thread.currentThread().getName() +\"=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象\");
obj.wait(); //无限等待
//obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + \"=== 从waiting状
态醒来,获取到锁对象,继续执行了\");
}
}
}
},\"等待线程\").start();
new Thread(new Runnable() {
@Override
public void run() {
// while (true){ //每隔3秒 唤醒一次
try {
System.out.println( Thread.currentThread().getName() +\"‐‐‐‐‐ 等待3秒钟\");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println( Thread.currentThread().getName() +\"‐‐‐‐‐ 获取到锁对
象,调用notify方法,释放锁对象\");
obj.notify();
}
}
// }
},\"唤醒线程\").start();
}
}

进入Timed Waiting(计时等待)两种方式:
1.使用sleep方法,在毫秒值结束之后,进入runnab/block状态
2.使用wait方法,在毫秒值之后,若还没有被notify唤醒,就会自动醒来,线程进入到runnable/blocked状态。

线程间通信

多个线程处理同一个资源,但是处理的动作(线程的任务)却不相同。

等待与唤醒机制,线程间通信

注意:
wait方法与notify方法必须要由同一个锁对象调用
wait和notify方法属于Object类的方法,锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait和notify方法必须在同步代码块或同步函数中使用,因为 通过锁对象调用这两个方法

1.3 生产者与消费者问题

等待唤醒机制其实就是经典的“生产者与消费者”的问题。

就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:

包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,
包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态)
,因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。
包子铺线程能否进一步执行则取决于锁的获取情况。

代码演示:

包子资源类:

public class BaoZi {
     String  pier ;
     String  xianer ;
     boolean  flag = false ;//包子资源 是否存在  包子资源状态
}

吃货线程类:

public class ChiHuo extends Thread{
    private BaoZi bz;

    public ChiHuo(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }
    @Override
    public void run() {
        while(true){
            synchronized (bz){
                if(bz.flag == false){//没包子
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(\"吃货正在吃\"+bz.pier+bz.xianer+\"包子\");
                bz.flag = false;
                bz.notify();
            }
        }
    }
}

包子铺线程类:

public class BaoZiPu extends Thread {

    private BaoZi bz;

    public BaoZiPu(String name,BaoZi bz){
        super(name);
        this.bz = bz;
    }

    @Override
    public void run() {
        int count = 0;
        //造包子
        while(true){
            //同步
            synchronized (bz){
                if(bz.flag == true){//包子资源  存在
                    try {

                        bz.wait();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                // 没有包子  造包子
                System.out.println(\"包子铺开始做包子\");
                if(count%2 == 0){
                    // 冰皮  五仁
                    bz.pier = \"冰皮\";
                    bz.xianer = \"五仁\";
                }else{
                    // 薄皮  牛肉大葱
                    bz.pier = \"薄皮\";
                    bz.xianer = \"牛肉大葱\";
                }
                count++;

                bz.flag=true;
                System.out.println(\"包子造好了:\"+bz.pier+bz.xianer);
                System.out.println(\"吃货来吃吧\");
                //唤醒等待线程 (吃货)
                bz.notify();
            }
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        //等待唤醒案例
        BaoZi bz = new BaoZi();

        ChiHuo ch = new ChiHuo(\"吃货\",bz);
        BaoZiPu bzp = new BaoZiPu(\"包子铺\",bz);

        ch.start();
        bzp.start();
    }
}

执行效果:

包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
包子铺开始做包子
包子造好了:薄皮牛肉大葱
吃货来吃吧
吃货正在吃薄皮牛肉大葱包子
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子

线程池

  • **线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
    JDK1.5之后提供java.util.concurrent.Executor:线程池工厂类,用来生成线程池
  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量),创建一个可重用固定线程数的线程池。返回的线程池ExecutorService 实现类对象,我们可以用ExecutorService接口接收
  • java.util.concurrent.ExecutorService:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务。
  • public Future submit(Runnable task):获取线程池中的某一个线程对象,并执行,提交一个runnable任务用于执行。
  • Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。

关闭/销毁线程池方法:

void shutdown():

使用线程池中线程的步骤: 线程池会一直开启,使用完了会自动把线程归还给线程池,线程可以继续使用。

  1. 使用线程池工厂类Executor中提供的方法newFixedThreadPool(int nThreads)创建一个指定线程数量的线程池对象。
  2. 创建一个类实现Runnable接口,重写run方法,设置线程任务。
  3. 调用ExecutorService中的方法submit(Runnable task)传递线程任务(实现类),提交Runnable接口子类对象,开启线程,执行run方法。
  4. 若不用了,关闭线程池(一般不做)。

线程池测试类代码实现:

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        // 创建Runnable实例对象
        MyRunnable r = new MyRunnable();

        //自己创建线程对象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 调用MyRunnable中的run()

        // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
        // 再获取个线程对象,调用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
        // 将使用完的线程又归还到了线程池中
        // 关闭线程池
        //service.shutdown();
    }
}

Lambda表达式

##函数式编程思想
函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

面向对象的思想:

​ 做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:

​ 只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

冗余的Runnable代码

传统写法

当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程。代码如下:

public class Demo01Runnable {
	public static void main(String[] args) {
    	// 匿名内部类
		Runnable task = new Runnable() {
			@Override
			public void run() { // 覆盖重写抽象方法
				System.out.println(\"多线程任务执行!\");
			}
		};
		new Thread(task).start(); // 启动线程
	}
}

Lambda表达式 实现多线程

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println(\"多线程任务执行!\")).start(); // 启动线程
	}
}

Lambda标准格式

Lambda省去面向对象的条条框框,格式由3个部分组成:

  • 一些参数
  • 一个箭头
  • 一段代码

Lambda表达式的标准格式为:

(参数类型 参数名称) -> { 代码语句 }

格式说明:

  • 小括号内的语法与传统方法参数列表一致:接口中抽象方法的参数列表,无参数则留空;多个参数则用逗号分隔。
  • ->是新引入的语法格式,代表指向动作。把参数传递给方法体
  • 大括号内的语法与传统方法体要求基本一致。重写接口的抽象方法的方法体。

使用Lambda标准格式(无参无返回)

传统写法

如果使用传统的代码对Person[]数组进行排序,写法如下:

import java.util.Arrays;
import java.util.Comparator;

public class Demo06Comparator {
    public static void main(String[] args) {
      	// 本来年龄乱序的对象数组
        Person[] array = {
        	new Person(\"古力娜扎\", 19),
        	new Person(\"迪丽热巴\", 18),
       		new Person(\"马尔扎哈\", 20) };

      	// 匿名内部类
        Comparator<Person> comp = new Comparator<Person>() {
            @Override
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

Lambda写法

import java.util.Arrays;

public class Demo07ComparatorLambda {
    public static void main(String[] args) {
        Person[] array = {
          	new Person(\"古力娜扎\", 19),
          	new Person(\"迪丽热巴\", 18),
          	new Person(\"马尔扎哈\", 20) };

        Arrays.sort(array, (Person a, Person b) -> {
          	return a.getAge() - b.getAge();
        });

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

使用Lambda标准格式(有参有返回)

给定一个计算器Calculator接口,内含抽象方法calc可以将两个int数字相加得到和值:

public interface Calculator {
    int calc(int a, int b);
}

在下面的代码中,请使用Lambda的标准格式调用invokeCalc方法,完成120和130的相加计算:

public class Demo08InvokeCalc {
    public static void main(String[] args) {
        // TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß
    }

    private static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println(\"结果是:\" + result);
    }
}

解答

public static void main(String[] args) {
    invokeCalc(120, 130, (int a, int b) -> {
      	return a + b;
    });
}

Lambda省略格式

所以凡是可以根据上下文推导得知的信息,都可以省略。

例如上例还可以使用Lambda的省略写法:

public static void main(String[] args) {
  	invokeCalc(120, 130, (a, b) -> a + b);
}

省略规则

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. ():小括号内参数的类型可以省略;

  2. (): 如果小括号内有且仅有一个参,则小括号可以省略;

  3. {}:如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。({},return,分号)要省略三个一起省略。

  4. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。

  5. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号