Java 第五周学习总结

一、正则表达式补充&集合入门

正则表达式补充

group

捕获组通过从左到右计算其开始括号进行编号。 例如,在表达式((A)(B( C))中,存在四个这样的组:

  1. ((A)(B( C)))
  2. (A)
  3. (B( C))
  4. ©

使用方式:

String score = "109:95";

String regex = "((\\d{1,3}):(\\d{1,3}))";

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(score);
//搜索整个字符串序列
m.find();

System.out.println(m.group());  //109:95
System.out.println(m.group(0)); //109:95
System.out.println(m.group(1)); //109:95
System.out.println(m.group(2)); //109
System.out.println(m.group(3)); //95

扩展:
//火箭队:湖人队/勇士队:骑士队/热火队:凯尔特人队
score = “95:98/97:99/95:88”;
//分别获取三组比分,以及每一个队伍的得分

//火箭队:湖人队/勇士队:骑士队/热火队:凯尔特人队
String score = "95:98/97:99/95:88";
//分别获取三组比分,以及每一个队伍的得分
String regex = "((\\d{1,3}):(\\d{1,3}))";

Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(score);
while(m.find()) {
    System.out.println(m.group(1));
    System.out.println(m.group(2));
    System.out.println(m.group(3));	
    System.out.println("==========");
}

运行结果:

95:98
95
98
==========
97:99
97
99
==========
95:88
95
88
==========

正则表达式三种模式

贪婪模式(greedy)

贪婪模式即从匹配到的位置开始一直往后依次搜索,并且会回溯

String html = "href=\"//cloud.video.taobao.com/video/1098/aaabbc.swf\" href=\"http://www.tengxun.com/video/aaa.swf\"";
Pattern pMp4 = Pattern.compile("//cloud.video.taobao.com.+.swf");
Matcher mMp4 = pMp4.matcher(html);
while(mMp4.find()) {
    System.out.println(mMp4.group());
}

结果:
//cloud.video.taobao.com/video/1098/aaabbc.swf" href="http://www.tengxun.com/video/aaa.swf

懒汉模式(reluctant)

通过贪婪模式能够发现,表达式会一直往后搜索,以最后一个匹配到的结尾为终止条件,获取的结果跟预期的存在差距,只需要将正则表达式做如下修改即可匹配到我们需要的资源:

//cloud.video.taobao.com.+?.swf

以上的匹配模式称之为懒惰匹配(勉强模式),寻找最短的匹配

结果
//cloud.video.taobao.com/video/1098/aaabbc.swf
//cloud.video.taobao.com/video/aaa.swf

独占模式(possessive)

独占模式跟贪婪模式的区别在于,不会回溯,即一直往后搜索会将后续的所有字符串进行匹配

String s = "aabbbccddcaabbbdd";
String regex = "aa.*+dd";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);
m.find();
System.out.println(m.group());

结果:
Exception in thread "main" java.lang.IllegalStateException: No match found at java.util.regex.Matcher.group(Unknown Source) at java.util.regex.Matcher.group(Unknown Source) at com.softeem.lesson20.regex.ReluctantDemo.main(ReluctantDemo.java:27)

集合入门

在java基础阶段,能存储大量相同数据的容器只有数组,但是是数组存在一个弊端,必须指定数组的容量,而且容量无法改变(即数组的长度一旦定义则无法修改),因此我们学习了ArrayList。在jdk1.2之前java官方就提供了一些集合的结局方案:

  • Enumeration(枚举:对集合迭代接口)
  • Vector(向量)
  • Stack(栈)
  • Hashtable(哈希表)
  • Dictionary(字典
  • Properties(属性表)

以上集合工具在jdk1.2之前就已经存在,但是由于没有一个统一的标准,因此组织混乱,而且也存在部分bug.
从JDK1.2开始java中新增了集合API,用于将所有集合进行统一归纳,形成了两种集合的解决方案:

  • 单例集合
  • 双列集合

单列集合有一个顶层的接口:Collection

双列集合有一个顶层接口:Map

在这里插入图片描述以上结构图为java集合框架组织结构图,所有虚线表示的都是接口或抽象类,实线表示的为针对接口的实现类。java中所有的集合接口以及类都位于java.util包中

Collection

Collection是所有单列集合的顶层接口,在java中存在的有序集合(List)和无需集合(Set)接口都从Collection接口继承,Collection中的常用方法有:

  • public boolean add(E e): 把给定的对象添加到当前集合中 。
  • public boolean addAll(Collection e): 把给定的集合对象添加到当前集合中 。
  • public void clear() :清空集合中所有的元素。
  • public boolean remove(E e): 把给定的对象在当前集合中删除。
  • public boolean contains(E e): 判断当前集合中是否包含给定的对象。
  • public boolean isEmpty(): 判断当前集合是否为空。
  • public int size(): 返回集合中元素的个数。
  • public Object[] toArray(): 把集合中的元素,存储到数组中。
  • public Iterator iterator():获取当前集合的迭代器对象
  • default Stream stream():获取用于进行流式处理的Stream对象(JDK8新增)

由于Collection是一顶层集合接口,因此对于不同类型的集合也存在两个子接口分别进行处理:

  • List:是一个有序的集合,并且允许重复的元素出现
  • Set:是一个无序集合,并且不允许重复元素出现

面试题:
Collection、Collections、Connection有什么区别?
Collection是所有单列集合的顶层接口;Collections是针对集合进行处理的工具类,比如排序,查询,洗牌,逆序等操作;Connection是Java访问数据库技术(JDBC)中的数据库对象的顶层接口

List集合

List接口是一个有序的集合,内部允许重复(e1.equals(e2))的元素出现,并且元素的存储顺序是按照添加顺序存储,因此可以通过元素的索引位置快捷的搜索到目标元素;List接口除了包含Collection中的方法之外,还新增了以下常见方法:

  • public E get(int index):根据元素的索引获取指定位置的元素并返回
  • public ListIterator listIterator(): 获取此集合对应的列表(有序)迭代器
  • public E remove(int index):移除指定位置的元素
  • public List subList(int fromIndex,int toIndex):将集合从指定位置进行截取,截取到目标位置,并将返回的数据形成新子List(假分页)

List接口有几个常见的实现类:

  • ArrayList
  • LinkedList
  • Vector

ArrayList

其中最常用的是java.util.ArrayList;ArrayList内部基于数组+数据拷贝的实现,初始容量是10,当添加的元素位置超出容量时,会在原数组的容量基础上扩充为1.5倍;由于ArrayList是基于数组的实现,因此在进行数据检索时的效率很高,只需要获取到元素的索引就能快速定位到元素的位置,但是由于数组的长度一旦定义,则无法修改,因此在对ArrayList进行元素的添加和删除的时候会导致数组的容量发生变化,需要频繁的创建新数组的对象,因此在进行添加,删除时效率很低;ArrayList适合做查询不适合做修改(查快改慢

ArrayList基本使用:

List list = new ArrayList();
list.add(new String("hello"));
list.add(true);
list.add(100);
list.add('h');

//向指定的位置插入元素
list.add(1,"world");

System.out.println("集合中是否包含指定元素:"+list.contains("hello"));

//替换指定位置的元素
list.set(1, "softeem");

//截取一个子集合(前包后不包)
list= list.subList(2, 5);

System.out.println(list.toString());
//将集合转换为对象数组
Object[] objs = list.toArray();
System.out.println(objs.length);

//清空集合
list.clear();
System.out.println("集合是否为空:"+list.isEmpty());

LinkedList

前面已经了解了ArrayList的实现原理是基于数组结合数组拷贝,但是由于数组的先天性问题:长度一旦定义无法修改,因此ArryList不适合进行频繁的修改(增加,删除)操作;如果需要频繁对容器进行修改操作时,List接口还提供了另一个实现类:LinkedList;

LinkedList是基于双向链表的实现(链表的结构如下),在元素进行增删操作时,只需要修改链表的首尾指针即可轻松实现,因此LinkedList适合用于频繁的修改操作中;但是在进行元素的检索时,只能从链表头,链表尾部依次搜索,查询效率相对较低。(LinkedList改快查慢

在这里插入图片描述
常用方法:
实际开发中对一个集合元素的添加与删除经常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。这些方法我们作为了解即可:

  • 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都是可以使用,这里就不做详细介绍,我们只需要了解LinkedList的特有方法即可。在开发时,LinkedList集合也可以作为堆栈,队列的结构使用。(了解即可)

使用参考:

LinkedList list = new LinkedList();
list.add("rose");
list.add("jack");
list.add("bob");
list.add("tom");
list.add("jarry");
list.add("bob");
list.add(null);

//这里的数值不是索引,表示的链表需要进行节点搜索的次数
System.out.println(list.get(5));

for (Object obj : list) {
    System.out.println("--->"+obj);
}
System.out.println(list.size());

//向集合的头部插入元素
list.addFirst("123");
//向集合的尾部插入元素
list.addLast("789");
System.out.println(list.getFirst()+"获取头部元素");
System.out.println(list.getLast()+"获取尾部元素");

//从头部弹出元素(将元素从集合中删除)
System.out.println(list.pop());
//向集合中加入元素(等同addFirst)
list.push("456");
System.out.println(list);

Vector

Vector是在JDK1.0就已经存在的基于动态数组(数组+数组拷贝)的集合实现,Vector是一个线程安全(关键方法上都使用了synchronized修饰)的集合实现,Vector在容量扩充时,增长为原理啊的2倍(ArrayList是1.5倍);在jdk1.2之后新的集合API出现,Vector被改造为从List集合实现,因此Vector,ArrayList,LinkedList都是源自于List接口

Vector,LinkedList,ArrayList的区别?

  • Vector是老式的集合,内部基于动态数组实现,容量扩充为原来的2倍,Vector是线程安全的实现
  • ArrayList是JDK1.2之后新增的List集合的实现,内部也是基于动态数组实现,容量可扩展为原来的1.5倍,ArrayList是线程不安全的实现(效率高),数据查询较快,修改较慢
  • LinkedList是JDK1.2之后新增的List集合的实现,内部是基于双向链表实现,而是线程不安全的实现,在进行数据修改方面比较快,数据查询较慢

Iterator迭代器

Iterator接口从jdk1.5之后新增的对集合进行快速遍历的接口,内部实现通常是由集合是类自身通过内部类的形式来完成,比如ArrayList:
在这里插入图片描述

常用方法

  • public boolean hashNext():判断迭代器是否存在下一个可以迭代的元素
  • public E next():获取下一个迭代到的元素
  • public void remove():删除当前迭代到的元素

基本使用方法:

List list = new ArrayList();
list.add("lilei");
list.add("lily");
list.add("lucy");
list.add("rose");
list.add("jack");
//获取集合自身的迭代器对象
Iterator it = list.iterator();
while(it.hasNext()) {
    Object obj = it.next();
    it.remove();
}

由于以上语法相对比较复杂,因此迭代器的出现同时也新增一种语法糖技术:forEach

 for(Object obj:list){  
 	 System.out.println(obj);
 }

Listlterator

ListIterator从Iterator实现而来,在Iterator的基础上扩充了一些方法:

  • public boolean hasPreviouse():是否存在上一个可迭代元素
  • public E previouse():获取上一个迭代到的元素
  • public void add(E e):通过迭代器向元素中添加一个元素
  • public void set(E e):通过迭代器,修改当前位置的元素
List list = new ArrayList();
list.add("helloworld");
list.add("jack");
list.add("rose");
list.add("lily");
list.add("lucy");

//获取当前集合的列表迭代器(可以反向迭代,添加,替换元素)
ListIterator it = list.listIterator();
//循环判断是否有下一个可迭代元素
while(it.hasNext()) {
    //取出下一个元素
    System.out.println(it.next());
}
//添加元素
it.add("kobe");
System.out.println("=========");
//循环判断是否有上一个可迭代元素
while(it.hasPrevious()) {
    //取出上一个元素
    System.out.println(it.previous());
}

面试问题:
在对集合元素遍历的同时进行删除或者添加操作时会导致一个异常出现;

在这里插入图片描述
在这里插入图片描述
在获取迭代器时会首先将改值缓存到Iterator对象的成员变量中
在这里插入图片描述
接下来在对集合使用内部的更新方法发操作元素时每次都会重新修改modCount值,但是迭代器中只会缓存第一次赋予的值(除非调用了迭代器自己的remove);因此一旦使用集合方法更新了元素,都将导致迭代器中检查两个值是否一致,如果不一致则抛出异常(ConcurrentModifactionException)
在这里插入图片描述
解决方案

  1. 使用迭代器自身的remove
Iterator it = list.iterator();
while(it.hasNext()) {
    Object obj = it.next();
    it.remove();
}
  1. 在找到满足条件的元素进行操作之后,立马结束循环
for (Object obj : list) {
    if("lucy".equals(obj)) {				
        list.remove(obj);
        //结束循环
        break;
    }
}
  1. 当需要更改的元素不止一个时可以,使用如下方式:
//声明临时数组
List temp = new ArrayList();
for (Object obj : list) {
    if("lucy".equals(obj) || "lily".equals(obj)) {	
        //将符合条件的元素存储到临时集合中
        temp.add(obj);
    }
}
//在循环结束后统一处理
list.removeAll(temp);

二、集合框架

Collection

Collection是从jdk1.2之后新增用于对集合进行操作的工具类,内部的所有方法都是static;Collections中常见的操作有如下:

  • 二分查找
  • 集合的创建
  • 排序
  • 顺序的打乱

集合排序

Collections中用于实现集合排序的方法有如下两个:

  • sort(List list)
  • sort(List list,Comparator c)

深入研究以上两个方法的实现,不难看出集合中元素的排序主要依靠一下两个接口实现:

  • Comparable
  • Comparator

Comparable & Comparator

Comparable接口中提供了一个compareTo方法,该方法需要由排序类进行实现,根据方法内部的实现,Collections中的sort方法会依赖该实现对元素进行排序,使用方式:

public class Student implements Comparable<Student> {

	private int sno;
	private String sname;
	private String sex;
	private Date birth;
	private double score;
	
	//构造器
    //setter/geeter
    //toString

	@Override
	public int compareTo(Student s) {
        //按学号
//		return this.sno - s.sno;
        //按姓名
//		return this.sname.compareTo(s.sname); 
        //按生日
		return this.birth.compareTo(s.birth);
	}

}

测试类:

List list = new ArrayList();
list.add(new Student(6, "softeem", "男", new Date(100,1,10), 79.5));
list.add(new Student(4, "admin", "女", new Date(96,2,5), 89.5));
list.add(new Student(2, "bob", "男", new Date(98,0,10), 59.5));
list.add(new Student(1, "tom", "男", new Date(98,2,11), 66.0));
list.add(new Student(5, "jerry", "女", new Date(95,3,22), 39.5));

//Arrays
//排序(集合中元素必须实现Comparable接口)
Collections.sort(list);

for (Object object : list) {
    System.out.println(object); 
}

运行结果:

Student [sno=5, sname=jerry, sex=, birth=Sat Apr 22 00:00:00 CST 1995, score=39.5]
Student [sno=4, sname=admin, sex=, birth=Tue Mar 05 00:00:00 CST 1996, score=89.5]
Student [sno=2, sname=bob, sex=, birth=Sat Jan 10 00:00:00 CST 1998, score=59.5]
Student [sno=1, sname=tom, sex=, birth=Wed Mar 11 00:00:00 CST 1998, score=66.0]
Student [sno=6, sname=softeem, sex=, birth=Thu Feb 10 00:00:00 CST 2000, score=79.5]

对于以上排序的实现关键在于,需要让排序类实现Comparable接口,若未对接口实现,则运行Collections.sort(list)将会导致以下异常:

Exception in thread "main" java.lang.ClassCastException: com.softeem.lesson21.collections.Student cannot be cast to java.lang.Comparable
	at java.util.ComparableTimSort.countRunAndMakeAscending(Unknown Source)
	at java.util.ComparableTimSort.sort(Unknown Source)
	at java.util.Arrays.sort(Unknown Source)
	at java.util.Arrays.sort(Unknown Source)
	at java.util.ArrayList.sort(Unknown Source)
	at java.util.Collections.sort(Unknown Source)
	at com.softeem.lesson21.collections.ArrayListTest.main(ArrayListTest.java:53)

以上排序方式需要由排序类实现Comparable接口,在Collections接口中还提供了另一种排序实现方式,即Collection.sort(list,comparator)只需要传入集合与对应的排序比较器对象即可:

public class User {

	private int id;
	private String name;
	private Date regTime;
	private int vipLevel;
	
	//构造器
    //setter/getter
    //toString
}

测试类:

List<User> list= new ArrayList<>();
list.add(new User(1105, "softeem", new Date(), 5));
list.add(new User(1109, "rose", new Date(), 4));
list.add(new User(1106, "jack", new Date(), 5));
list.add(new User(1107, "docker", new Date(), 3));
list.add(new User(1108, "admin", new Date(), 1));
list.add(new User(1102, "bobo", new Date(), 2));

//方式一:
Collections.sort(list,new Comparator<User>() {
    @Override
    public int compare(User u1, User u2) {
        return u1.getName().compareTo(u2.getName());
    }
});
//方式二:观察sort方法的源码即可发现还可以以如下方法进行排序
list.sort(new Comparator<User>() {
    @Override
    public int compare(User u1, User u2) {
        return u2.getVipLevel() - u1.getVipLevel();
    }
});

//方式三:
Collections.sort(list, new MyCompartor());

方法三的比较定义:

public class MyCompartor implements Comparator<User>{

	@Override
	public int compare(User u1, User u2) {
		return u1.getId() - u2.getId();
	}

}

扩展:中文排序(Pinyin4j)

通过以上的两个接口实现排序只要选择合适接口实现,并通过调用Collections的sort方法即可完成排序,但是对于一些特殊的需求:比如中文排序,JDK提供的解决方案比较受限,因此需要第三方的技术来实现对于中文的排序;这里可以使用开源的对中文字符处理的框架(插件):Pinyin4j(pinyin for java);这个插件提供了用于将中文汉字转换为汉语拼音的API,具体使用方式分为以下步骤:

  1. 将插件构建到项目中
    pinyin4j-2.5.0.jar
  2. 使用pinyin4j
//将中文字符转换为拼音
String[] s = PinyinHelper.toHanyuPinyinStringArray('中');
for (String string : s) {
    System.out.println(string);
}

//执行结果:
//zhong1
//zhong4

使用前需要导入:
import net.sourceforge.pinyin4j.PinyinHelper;

排序方式

思路:

  1. 将中文字符的每一位获取,并转换为拼音拼接陈新的字符串
  2. 直接使用String类中已实现的comparaTo方法进行比较返回整数值即可
List<String> list = new ArrayList<>();
list.add("王老师"); //wang
list.add("阿老师"); //a
list.add("波老师"); //bo
list.add("柴老师"); //chai
list.add("窦老师"); //dou
list.add("刘老师"); //liu

Collections.sort(list,new Comparator<String>() {
    @Override
    public int compare(String s1, String s2) {
        String name1 = "";
        String name2 = "";
        for(int i = 0;i<s1.length();i++) {
            char c = s1.charAt(i);
            String s = PinyinHelper.toHanyuPinyinStringArray(c)[0];
            name1 += s;
        }
        for(int i = 0;i<s2.length();i++) {
            char c = s2.charAt(i);
            String s = PinyinHelper.toHanyuPinyinStringArray(c)[0];
            name2 += s;
        }
        //将转换成汉语拼音的字符串进行比较
        return name1.compareTo(name2);
    }
});

System.out.println(list);

运行结果:

[阿老师, 波老师, 柴老师, 窦老师, 刘老师, 王老师]

三、Map集合

Set集合

之前我们已经学习过了List集合,List具备以下特点:

  • 有序(存储顺序跟添加顺序一致)
  • 允许重复(可以添加重复数据 e1.equals(e2))
  • 允许空元素

虽然对于大多数应用场景下List集合都非常实用,但是对于一些特殊需求,List集合会暴露明显的问题,比如对集合中的元素去除重复,对于List集合必然就会需要涉及到集合遍历,影响到程序的运行效率;因此对于这种需求Collection接口还提供了另一种子接口:Set

Set也是从jdk1.2开始出现,对比List集合,Set集合特点如下:

  • 元素的存储顺序与添加顺序无关(无序)
  • 内部不允许重复元素

Set由于是一个接口,因此对于该接口,集合框架内部提供了一些常见的实现类:

  • HashSet
  • LinkedHashSet
  • TreeSet

注意事项:
由于Set集合的实现中没有针对数组接口的实现,因此内部的元素也不存在索引

常用方法

  • add(Object obj) 向集合中添加元素
  • addAll(Collection c) 将一个集合添加到当前集合中
  • clear() 清除集合中所有元素
  • isEmpty() 判断集合是否为空集合(size = 0)
  • remove(Object obj) 删除指定元素
  • iterator() 获取当前集合的迭代器对象
  • size() 获取集合中元素个数

HashSet

HashSet是基于哈希表的实现(内部实际就是一个HashMap),内部的元素存储根据调用元素的hashCode方法实现,由于对象的存储基于hashCode算法,因此如果多个对象的的hashCode值是一致的,则集合中只会存储一个(在重写hashCode方法时也必须要同时重写equals

User类:

public class User {

	private int id;
	private String name;
	private String pwd;
 
    //构造器
    //setter/getter
    //toString
    
    @Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + id;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((pwd == null) ? 0 : pwd.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		User other = (User) obj;
		if (id != other.id)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (pwd == null) {
			if (other.pwd != null)
				return false;
		} else if (!pwd.equals(other.pwd))
			return false;
		return true;
	}
}

测试类:

Set set = new HashSet();
set.add(10);
set.add(7);
set.add(9);
set.add(8);
set.add(3);
set.add(5);
set.add(10);
set.add(new User(1,"softeem","123"));
set.add(new User(1,"softeem","123"));

输出结果:

[3, User [id=1, name=softeem, pwd=123], 5, 7, 8, 9, 10]

TreeSet

在前面HashSet中,元素虽然不允许重复,但是由于存储顺序是无需的,在对于一些需要排序的需求场景下,HashSet也暴露了功能不足的问题,因此 ,Set集合中针对该需求,还提供了另一种能够实现排序的Set实现:TreeSet;TreeSet内部实现原理是基于二叉树中的红黑树实现,使用TreeSet的前提:

  • 元素必须是同一种数据类型
  • 元素必须实现过Comparable接口
Set set = new TreeSet();
set.add(18);  
set.add(27);  
set.add(45);  
set.add(8);   
set.add(32); 
set.add(50);  
set.add(10);

System.out.println(set);

结果:

[8, 10, 18, 27, 32, 45, 50]

注意事项:
TreeSet内部对于元素的去除重复根据重写的comparaTo方法来决定,如果多个对象的comparaTo方法返回值是一致的,则集合中只会存储一个:

public class User implements Comparable<User>{

	private int id;
	private String name;
	private String pwd;
	
	
	//构造器
    //getter/setter
    //toString

    /**
    * 实现排序比较规则
    */
	@Override
	public int compareTo(User o) {
		return name.compareTo(o.name); 
	}
	
}

测试:

Set set = new TreeSet();
set.add(new User(1,"softeem","123"));
set.add(new User(2,"softeem","456"));
System.out.println(set);

输出结果:
[User [id=1, name=softeem, pwd=123]]
TreeSet中的元素对应的类如果未实现Comparable接口,则会抛出运行时异常:
java.lang.ClassCastException
因为内部会将元素强制转换为Copparable对象

LinkedHashSet

HashSet是基于hash算法实现的元素的存储,但是由于不同的对象hash值存在差异,因此元素的存储顺序不一定按添加顺序来;在实际开发中如果需要按元素的添加顺序存储,并且要保证元素不重复,因此可以使用由Set集合提供的另一个实现:LinkedHashSet;LinkedHashSet是从HashSet继承而来,内部的实现原理实际是基于LinkedHashMap.

Set set = new LinkedHashSet();
set.add("hello");
set.add(true);
set.add(3.14);
set.add(100);
set.add(new User());

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

结果:

hello
true
3.14
100
User [id=0, name=null, pwd=null]

面试题:
HashSet,TreeSet和LinkedHashSet区别?

  • HashSet是Set集合基于hash表(散列表)的实现元素存储,内部的元素存储顺序跟hash值有关,如果多个对象的hash一致(equals也一致)则集合会认为是重复元素,因此不会加入集合中;HashSet内部实现原理是基于HashMap的。

  • TreeSet是Set集合基于红黑树(二叉排序树中的特殊平衡二叉树),内部元素的存储顺序是根据元素对应类实现的Comparable接口中CompareTo方法进行存储,入股多个对象的comparTo方法返回值一致,则TreeSet会认为是重复元素,因而不会重复存储;TreeSet实际就是基于TreeMap的实现

  • LinkedHashSet是HashSet的子类,内部基于单链表的实现,元素的存储顺序按照添加顺序来存储,是一个有序的set集合,内部实现原理使用了LinkedHashMap。

Map集合

之前所学习到的List集合以及Set集合都是直接从Collection继承而来的单列子集合,另外,java的集合框架中也提供了另一种特殊的集合接口:双列集合:Map;

Map集合是由键值对结构组成,通常由一个唯一键对应一个值,集合中存储的元素键不允许重复,但是值可以重复,Map集合是一个键值对集合的顶层接口,常见的实现类主要有以下:

  • HashMap
  • TreeMap
  • LinkeHashMap
  • ConcurrentHashMap
    虽然Map集合是一个键值对结构,但是实际内部存储的每一个元素都是一个Entry对象,而Entry内部包含两个属性,一个是Key,另一个是Value

Map集合常见方法:

  • clear() 清除Map集合中的所有元素
  • containsKey(Object key) 判断集合中是否包含指定的键
  • containsValue(Object value) 判断集合中是否包含指定的值
  • entrySet() 返回当前Map集合中Entry的Set集合
  • get(Object key) 根据键获取值
  • put(Object k,Object v) 向集合中添加元素(键值对)
  • keySet() 获取键的Set集合
  • remove(Object key) 根据键删除指定的元素
  • size() 返回该Map集合中元素的个数
  • values() 返回Map集合中所有的值集合(Collection)

HashMap

HashMap是Map中最常用的一个实现类,内部实现是基于数组+链表(JDK1.8之前;JDK1.8之后改为使用数组+链表+红黑树实现);元素的存储按照键值对的方式,存储顺序根据键的hashCode(包括equals)计算之后存储

HashMap的初始容量是16,默认的加载因子是0.75(降低hash碰撞的概率),HashMap的扩容方式为原来的2倍;实现原理:

  • 在1.8之前使用的是数组和链表实现,默认情况下通过计算元素的hash值,然后和16取余(实际: hash & (length-1)),再根据计算结果将元素(Map.Entry)存储对应的数组中,如果该位置已经存在元素,则此时引入链表,采用头插法将最新的元素插入到链表头部。
  • 在JDK1.8之后使用数组+链表+红黑树(平衡排序二叉树)实现,红黑树的加入有特定前提
    • 数组的长度必须超过64
    • 链表深度必须大于8
      在这里插入图片描述
      HashMap的时使用:
//创建一个基于HashMap的Map接口实现对象
Map map = new HashMap();
map.put("a", "admin");
map.put("r", "rose");
map.put("k", "kaven");
map.put("m", "mulan");
map.put("n", "novar");
map.put("x", "xiaoming");
//集合中存储重复的键时会覆盖之前存储的元素
map.put("a", "rose");
map.put("y", "rose");
map.put(14, "14");
map.put(30,"300000");
map.put(46,"46");

System.out.println(map);
//根据键获取值
System.out.println(map.get("k"));
System.out.println("a".hashCode() % 16);
System.out.println("r".hashCode() % 16);
System.out.println("k".hashCode() % 16);
System.out.println(("m".hashCode() & 15) + "--" +("m".hashCode() % 16));
System.out.println("n".hashCode() % 16);
System.out.println("x".hashCode() % 16);
System.out.println(new Integer(14).hashCode() % 16);
System.out.println(new Integer(30).hashCode() % 16);
System.out.println(new Integer(46).hashCode() % 16);
//判断map集合中是否包含指定的键
System.out.println(map.containsKey("a"));

//
//判断map集合中是否包含指定的值
System.out.println(map.containsValue("rose"));

//获取Map集合中所有的Entry元素的Set集合
Set<Map.Entry> set = map.entrySet();
for (Entry e : set) {
    System.out.println(e.getKey()+"/"+e.getValue());
}

Map map2 = new HashMap();
map2.put("100", "hellowrold");
map2.put(true, "true");
map2.put(3.14, "314");
map2.put('c', "cccaaa");
map2.put(50, "hellowrold");

//
//获取Map的键集
Set set = map2.keySet();
System.out.println(set);
//根据键移除指定元素
Object obj = map.remove('c');
System.out.println(obj);

System.out.println(map2);

//获取Map的值集
System.out.println(map2.values());

TreeMap

Map集合另外针对于排序的需求还有一个TreeMap的实现类,该类内部基于红黑树(平衡排序二叉树)实现;内部的元素存储顺序,由键对应的类型实现Comparable接口后,通过重写comparaTo方法实现;TreeMap的使用需要满足以下两个条件:

  • key的类型必须一致
  • key对应的类必须实现Comparable接口

TreeMap不允许空键出现

Map map = new TreeMap();
map.put("hello", "helloworld");
map.put("rose", "helloworld");
map.put("jack", "helloworld");
map.put("softeem", "helloworld");
map.put("admin", "helloworld");

System.out.println(map);

对于以上代码,TreeMap会使用键对应的类型中的comparaTo方法来完成对于元素的排序存储
对于复杂类型的key,使用方式:

public class Student implements Comparable<Student>{

	private int sno;
	private String sname;
	private Date birth;
	private double score;
	
    //构造器
    //setter/getter
    //toString

	@Override
	public int compareTo(Student s) {
		return (int)(this.score - s.score);
	}
	
}

测试:

Map map = new TreeMap();
map.put(new Student(104,"孙悟空",new Date(),65), "helloworld");
map.put(new Student(103,"猪八戒",new Date(),75), "helloworld");
map.put(new Student(101,"沙和尚",new Date(),55), "helloworld");
map.put(new Student(105,"唐僧",new Date(),45), "helloworld");
map.put(new Student(106,"小白龙",new Date(),55), "helloworld");
map.put(new Student(109,"白骨精",new Date(),77), "123");

Set set = map.keySet();
for (Object obj : set) {
    System.out.println(obj);
}

结果:

Student [sno=105, sname=唐僧, birth=Wed Nov 25 15:46:24 CST 2020, score=45.0]
Student [sno=101, sname=沙和尚, birth=Wed Nov 25 15:46:24 CST 2020, score=55.0]
Student [sno=104, sname=孙悟空, birth=Wed Nov 25 15:46:24 CST 2020, score=65.0]
Student [sno=103, sname=猪八戒, birth=Wed Nov 25 15:46:24 CST 2020, score=75.0]
Student [sno=109, sname=白骨精, birth=Wed Nov 25 15:46:24 CST 2020, score=77.0]

根据以上结果得知,TreeMap中去除复杂的原则是根据key类型中实现的comparaTo方法来实现,如果该方法返回值为0,则认为比较的两个对象重复,则将参数对象舍弃

Hashtable

Hashtable也是键值对结构的集合解决方案,从jdk1.0之后就已经存在,从老式的集合类java.util.Dictionary继承而来,初始长度是11(HashMap是16),Hashtable是线程安全实现(HashMap是线程不安全的实现);Hashtable不允许空键值出现(HashMap允许)

LinkedHashMap

LinkedHashMap是基于链表的HashMap实现,本身也是从HashMap继承而来,通过链表实现内部元素的存储顺序保持与添加顺序一致。

集合框架总结

  • Iteratable:迭代器
  • Collection:所有单列集合的顶层接口
    • List:有序允许重复元素的集合
      • ArrayList:基于数组的实现
      • LinkeList:基于链表(双向)实现
      • CopyOnWriterArrayList(并发安全的动态数组)
      • Vector:基于数组的实现
    • Set:不允许重复元素,无序的集合
      • HashSet:基于HashMap实现
        • LinkedHashSet:基于LinkedHashMap实现
      • TreeSet:基于TreeMap的实现
      • CopyOnWriterArraySet
  • Map:键值对的结构,内部的元素为Entry对象(Entry由key和value组成)
    • HashMap:1.8之前使用数组+链表(单链表);1.8之后使用数组+链表+红黑树实现
      • LinkedHashMap:按照添加顺序有序存储(数组+链表)
    • TreeMap:基于红黑树的实现
    • Hashtable
  • Comparable:由需要进行排序的类实现
  • Comparator:独立的比较器
  • Collections:对于集合操作的工具类型(内部很多实现是基于Arrays的)
  • Arrays:对于数组操作的工具类型

四、泛型 & JKD8新特性

泛型

在了解泛型前,JDK1.5之前对于集合中元素的存储可以是任意的数据类型,在实际开发中就可能遇到类似以下问题:

List list = new ArrayList();
list.add("hello");
list.add(10);
list.add(true);

for (Object obj : list) {
    String s = (String)obj;
    System.out.println(s.length()); //程序运行出现 java.lang.ClassCastException
}

对于以上代码运行期间会出现类型转换异常,解决方案修改位如下代码

for (Object obj : list) {
    if(obj instanceof String){
        String s = (String)obj;
        System.out.println(s.length());
    }
}

在jdk1.5之前只能使用以上解决方案,但是这样依赖会增加时间的开销。

从JDK1.5之后,Java开始引入了泛型机制,通过使用泛型可以将原本在程序运行时才能检测的异常,提前到编译期;从而减少一些不必要程序运行时间开销:


List<String> list = new ArrayList<String>();
list.add("hello");
list.add(10); // 编译错误 类型不支持
list.add(true); //编译错误 类型不支持

for (String s : list) {
    System.out.println(s.length());
}

泛型即参数化类型,将一种数据类型作为参数传递到指定的位置,作用在于可以将原本在运行时才能提示的异常(ClassCastException)提前到编译期间;语法为使用一对<>内部包裹具体的引用类型

定义泛型时可以使用一些特殊的类型符号表示,常见:

  • T:Type
  • E:Element
  • K:Key
  • V:Value

案例:

List<E>
Comparable<T>
Map<K,V>

使用案例:

List<Integer> list;
Map<String,Object> map;
Comparable<User>

Java中的泛型分为三种使用方式:

  • 泛型类
  • 泛型接口
  • 泛型方法

泛型类

泛型类即在一个普通的类中使用泛型表达式设定泛型参数,在类中可以将泛型作为参数以及返回值

//泛型类
public class Service<T> {

	public void showInfo(T obj) {
		Student s = (Student)obj;
		System.out.println(s.getName());
	}
	
	public void del(T t) {
		System.out.println("删除。。。");
	}
    
	public T getByName(String name) {	
        //反射
		return null;
	}
		
	public static void main(String[] args) {
		
		Teacher t = new Teacher();
		t.setName("李老师");
		
		Student stu = new Student();
		stu.setName("孙悟空");
		
		Service<Student> s = new Service<Student>();
		s.showInfo(stu);
		s.del(stu);
	}
}

泛型的使用可以同时传递多种参数类型,如下:

public class Service2<U,S,T> {

	public void showUser(U u) {
		User user = (User)u;
		System.out.println("用户名:"+user.getName());
	}
	
	public void showStu(S s) {
		Student stu = (Student)s;
		System.out.println("学生名:"+stu.getName());
	}
	
	public void showTeacher(T t) {
		Teacher teacher = (Teacher)t;
		System.out.println("老师名:"+teacher.getName());
	}
	
	public static void main(String[] args) {
		
		Teacher t = new Teacher();
		t.setName("李老师");
		
		Student stu = new Student();
		stu.setName("孙悟空");
		
		User user = new User();
		user.setName("红孩儿");
		//jdk1.7之后泛型的定义只需要在声明时指定即可
		Service2<User,Student,Teacher> s = new Service2<>();
		s.showStu(stu);
		s.showTeacher(t);
		s.showUser(user);
	}
}

常见的泛型类:
java.utli.ArrayList/java.util.LinkedList
java.utli.HashSet/java.util.TreeSet

泛型接口

泛型接口即在声明接口时,指定泛型参数,当实现类对接口实现时,只需要在实现的接口之后指定具体类型,在方法中即可将参数(返回值)类型确定为目标类型,从而避免了强制转换操作。

接口定义:

//泛型接口
public interface IManager<T> {

	public boolean add(T o);
	
	public List<T> findAll();
	
	public boolean delete(int id);
	
	public boolean update(T o);
}

实现类1:

public class UserManager implements IManager<User>{

	@Override
	public boolean add(User o) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public List<User> findAll() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean delete(int id) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean update(User o) {
		// TODO Auto-generated method stub
		return false;
	}

}

实现类2:

public class OfficeManager implements IManager<Office>{

	@Override
	public boolean add(Office o) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public List<Office> findAll() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public boolean delete(int id) {
		// TODO Auto-generated method stub
		return false;
	}

	@Override
	public boolean update(Office o) {
		// TODO Auto-generated method stub
		return false;
	}
	
}

常见接口泛型:
java.util.Collection
java.util.List
java.util.Set
java.util.Map

泛型方法

泛型方法指的是单独定义一个方法,在方法声明时指定泛型参数,此时该泛型参数可用作于当前方法的返回值类型或者参数类型。

public class Test {

	//泛型方法
	public static <T> T get(T t) {
		//反射实现具体操作
		System.out.println(t);
		return t;
	}
	
	public static void main(String[] args) {
		Teacher t = new Teacher();
		t.setName("李老师");
		
		Student stu = new Student();
		stu.setName("孙悟空");
		
		User user = new User();
		user.setName("红孩儿");
		
		Admin admin = new Admin();
		
		Teacher tea = get(t);
		Student s = get(stu);
		User u = get(user);
		Admin a = get(admin);
	}

}

泛型方法在一些开源项目中比较常见:
fastJSON

泛型通配符

在对泛型类型不确定时可以使用通配符的方式表示,泛型通配符包含以下计中:

  • <?>
  • <? extends T>
  • <? super T>
上界限定(extends)

上界弦定即:规定传入集合中的元素必须时目标元素对象或者其子类对象,并且上界限定后,集合中的元素只出不进(不能调用add),一般用于方法的参数接收定义和返回值类型定义。

public class Test {

	//上界限定:只出不进(只能获取元素,不能进行修改)规定传入的集合元素只能是People或者People子类对象集合
	public static void test(List<? extends People> list) {
		for (People p : list) {
			System.out.println(p);
		}
		list.remove(0);
		//不允许新增(任何类型)
//		list.add(new User());
	}
	
	public static void main(String[] args) {
		
		People p = new User();
		
		//java编译器中存在类型擦除机制(编译器不理解泛型)
//		List<People> list = new ArrayList<User>(); // 编译错误
		
		List<?> list = new ArrayList<String>();
		
		//上界限定:规定集中能够存储的元素只能是Object或者Object的子类对象
		List<? extends Object> list2 = new ArrayList<String>();
		
		List<User> users = new ArrayList<User>();
		users.add(new User());
		users.add(new User());
		users.add(new User());
		test(users); 
		
	}
}

如果直接位泛型指定<?>即表示位<? extends Object>

下界限定

下界限定即:限定元素的下边界,存入的元素只能是当前类的对象或者存在继承关系的子类对象

//下界限定:限定传入元素的下边界,存入的元素只能是当前类的对象或者存在继承关系的子类对象
List<? super User> list = new ArrayList<People>();
list.add(new User());
list.add(new Student());
//		list.add(new People());
//		list.add(new Object());
//只进不出(出来的任何元素都只能是Object)
Object obj = list.get(0);
list.remove(0);

五、JDK8新特性

接口默认方法&静态方法

接口默认方法

在JDK8之前,接口中只能有常量以及未实现方法的定义,不允许出现成员变量,和已实现方法;从JDK8之后,Java中新增接口默认方法的定义,即可以在接口中添加已实现的方法;

语法:

default 返回值类型 方法名(【参数列表】){
    //实现体
}

案例:

public interface Fightable {

	void fight();
	
	//兼容解决方案
	default void holder() {
		System.out.println("蓄力");
	}
}

接口默认方法实际是一种兼容性解决方案
如果项目已经处于一个运维期,后续如果需要新增功能,比如在接口中新增方法,这将会导致该接口的实现类也必须新增对接口方法的实现,带来的影响较大,维护起来困难;因此Java新增了接口默认方法,直接在接口对方法进行实现,实现类可以选择性是否对接口方法重写,而不要所有的实现类都必须对方法实现。

静态方法

Java8的接口中除了新增默认方法外,另外还新增了静态方法,即直接在接口使用static关键字修饰一个已实现的方法,
语法:

static 返回值类型 方法名(【参数列表】){
    //实现体
}

案例:

//静态方法
static void showPower() {
    System.out.println("攻击力10000");
}

函数式接口

函数式编程:所谓函数式编程即链式编程思维,类似以下操作:

StringBuffer sb = new StringBuffer();
sb.append(true).append(false).insert(0, "123").append(3.14);

函数式接口即一个接口中只存在一个未实现的方法时,该接口就认为是函数式接口;一般函数式接口可以通过在接口上添加一个特殊的注解来进行函数式接口标记:@FunctionalInterface

//函数式接口声明
@FunctionalInterface
public interface Fightable {

	void fight();
	
	//兼容解决方案
	default void holder() {
		System.out.println("蓄力");
	}
	
	//静态方法
	static void showPower() {
		System.out.println("攻击力10000");
	}
	
}

注意事项:
如果一个接口上使用了@FuncationalInterface声明,则该接口中只能存在一个未实现的方法,否则会出现编译错误。

Lambda表达式

在了解lambda表达式之前,先回顾之前所学习到匿名内部类,如果需要创建一个匿名内部类对象,语法如下:

Fightable f = new Fightable() {
    @Override
    public void fight() {
        System.out.println("战斗--匿名内部类");
    }
};

观察以上代码,可以看出,除了方法内部的实现之外,其余的代码都是固定的模板代码

针对以上的匿名内部类写法,java8新增了lambda表达式,以简化匿名内部类的写法:

Fightable f2 = ()->{
    System.out.println("战斗--拉姆达(lambda:λ)表达式");
};

注意事项:
lambda表达式只适用于函数式接口,不适用于类或者抽象类

lambda表达式语法
(【参数列表】)->{
    //实现体
}
lambda表达式的各种使用方式
  1. 对于包含参数的接口方法
Animal a = (String food)->{
    System.out.println("动物吃"+food);
};
a.eat("肉");
  1. 由于lambda表达式会对需要实现的方法进行参数类型自动推断,因此,在使用时无需指定参数类型,以上程序可以简化为:
Animal a = (f) -> {
    System.out.println("动物吃" + f);
};
a.eat(肉");
  1. 对于包含多个参数的lambda表达式,可以如下表示
Animal a = (n, f) -> {
    System.out.println(n + "吃" + f);
};
a.eat("旺财", "肉");
  1. 如果方法的实现体中只有一行代码需要编写,上述代码可以简化为:
Animal a = (n, f) -> System.out.println(n + "吃" + f);
a.eat("旺财", "肉");

如果被实现的方法有返回类型,则编译器会自动将箭头之后的语句执行之后的结果作为返回值返回,而不需要使用return关键字

StreamAPI

StreamAPI概述

流式编程作为Java 8的亮点之一,是继Java 5之后对集合的再一次升级,可以说Java 8几大特性中,Streams API 是作为Java 函数式的主角来设计的,夸张的说,有了Streams API之后,万物皆可一行代码。

什么是Stream

Stream被翻译为流,它的工作过程像将一瓶水导入有很多过滤阀的管道一样,水每经过一个过滤阀,便被操作一次,比如过滤,转换等,最后管道的另外一头有一个容器负责接收剩下的水。
示意图如下:
在这里插入图片描述

首先通过source产生流,然后依次通过一些中间操作,比如过滤,转换,限制等,最后结束对流的操作。
Stream也可以理解为一个更加高级的迭代器,主要的作用便是遍历其中每一个元素。

为什么需要Stream

Stream作为Java 8的一大亮点,它专门针对集合的各种操作提供各种非常便利,简单,高效的API,Stream API主要是通过Lambda表达式完成,极大的提高了程序的效率和可读性,同时Stram API中自带的并行流使得并发处理集合的门槛再次降低,使用Stream API编程无需多写一行多线程的大门就可以非常方便的写出高性能的并发程序。使用Stream API能够使你的代码更加优雅。

流的另一特点是可无限性,使用Stream,你的数据源可以是无限大的。

没有StreamAPI时集合的处理操作:

public static void main(String[] args) {
		
		List<String> list = new ArrayList<>();
		list.add("jackson");
		list.add("tompson");
		list.add("curry");
		list.add("colleson");
		list.add("wade");
		list.add("kobe");
		list.add("kole");
		
		//需求:从集合中将所有以“son”结尾的字符串筛选前5个出来并存储到新集合中
        // 同时需要对新集合实现排序
		//传统写法:
		List<String> list2 = new ArrayList<String>();
    	int count = 0;
		for (String name : list) {
			if(name.endsWith("son")) {
				System.out.println(name);
				list2.add(name);
                count++;
                if(count == 5){
                    break;
                }
			}
		}
        Collections.sort(list2);
}

通过观察以上代码会发现,需要对集合循环遍历,并根据业务需求进行逻辑处理;我们再来看看使用StreamAPI的实现方式:

public static void main(String[] args) {

    List<String> list = new ArrayList<>();
    list.add("jackson");
    list.add("tompson");
    list.add("curry");
    list.add("colleson");
    list.add("wade");
    list.add("kobe");
    list.add("kole");

    //StreamAPI
    List<String> subList = list.stream()
                                .filter(u->u.endsWith("son"))
                                .limit(5)
                                .collect(Collectors.toList());
}

StreamAPI使用

流式API使用通常分为三个步骤:

  1. 获取Stream
  2. 中间处理
  3. 结尾处理
获取Stream

可以用如下方法打开一个 Stream:

  1. 使用 Collection 子类的 stream()(串行流)或 parallelStream()
  2. 使用 Arrays.stream() 方法为数组创建一个流
  3. 使用 Stream.of() 方法创建流
  4. 使用 Stream.iterate() 方法创建流
  5. 使用 Stream.generate() 方法创建流
中间处理

中间处理即对获取的流进行,筛选,排序,截断,去重等常规处理工作,判断是否为中间处理的方式很简单,只要观察方法返回值是否还是Stream对象即可判断是否为中间处理,另外,中间处理可叠加操作。

  • filter
  • distinct
  • skip
  • limit
  • map
  • flatmap
  • sorted
结尾处理

结尾处理即意味着中间处理已完成,等待最终操作,比如收集,统计,迭代等操作。

  • anyMatch
  • noneMatch
  • allMatch
  • findAny
  • findFirst
  • forEach
  • collect
  • reduce
  • count

使用详解

使用IntStream
public class StreamDemo {

	public static void main(String[] args) {
		
		//1.获取流
		OptionalInt max = Arrays.stream(new int[] {9,8,11,5,6,3,2,0,9,6})
		
          //Stream<People> stream = list.stream()
		//2.中间处理(所有的中间处理返回值都是流本身)
		.filter(i -> i >= 5 && i <= 10) //过滤
		.sorted()	//排序
		.distinct() //去除重复
		.skip(1)  //跳过指定行 参数指的是位置
		.limit(2) //限定显示行数
		
		//3.结尾处理
//		.forEach(i->System.out.println(i));  //输出
//		.count();	//统计
//		.sum();	    //求和
//		.average(); //求平均值
		.max();    //求最大值
		
//		System.out.println(od.getAsDouble());
		System.out.println(max.getAsInt());
	}

}
使用Stream
  • 实体类
public class Student {

	/**学号*/
	private int sno;
	/**姓名*/
	private String name;
	/**生日*/
	private LocalDate birth;
	/**专业*/
	private String major;
	/**学分*/
	private double score;
	/**性别*/
	private String sex;
    
    //无参构造器、满属性构造器
    //setter、getter方法
    //hashcode&equals
    //toString
}
  • 准备数据
static List<Student> list = new ArrayList<Student>() {
    {
        add(new Student(10086, "colleos", LocalDate.of(1998, 8, 11), "软件工程", 89.5, "男"));
        add(new Student(10010, "curry", LocalDate.of(1999, 9, 11), "计算机科学", 58.5, "女"));
        add(new Student(10011, "tom", LocalDate.of(1996, 10, 11), "软件工程", 49.5, "男"));
        add(new Student(10011, "tom", LocalDate.of(1996, 10, 11), "软件工程", 49.5, "男"));
        add(new Student(10032, "james", LocalDate.of(1995, 11, 17), "计算机科学", 79.5, "男"));
        add(new Student(10055, "rose", LocalDate.of(1994, 7, 13), "计算机科学", 89, "女"));
        add(new Student(10076, "jack", LocalDate.of(1998, 5, 21), "计算机科学", 55.5, "男"));
        add(new Student(10098, "tompson", LocalDate.of(1999, 6, 19), "网络工程", 69, "女"));
        add(new Student(10012, "garnett", LocalDate.of(1997, 8, 17), "网络工程", 89.5, "男"));
        add(new Student(10018, "akli", LocalDate.of(1998, 6, 11), "软件工程", 89.5, "女"));
        add(new Student(10015, "lulu", LocalDate.of(1996, 1, 20), "软件工程", 87, "男"));
        add(new Student(10099, "zark", LocalDate.of(1999, 2, 22), "软件工程", 77.5, "男"));
    }
};

创建集合并向集合中添加若干数据

1.获取流

使用流式API首先需要获取stream对象,Collection集合提供了stream方法可用于直接获取Stream对象。

Stream<Student> stream = list.stream();
2. 中间处理

中间处理即对获取的流进行,筛选,排序,截断,去重等常规处理工作,判断是否为中间处理的方式很简单,只要观察方法返回值是否还是Stream对象即可判断是否为中间处理,另外,中间处理可叠加操作。

  • 过滤(filter)
stream = stream.filter(s -> Objects.equals(s.getSex(), "男"));

以上操作表示过滤出所有性别为”男“的学生;
上述diamagnetic等效于:

stream = stream.filter(new Predicate(){
    public boolean test(Student s){
        return Objects.equals(s.getSex,"男");
    }
});

注意事项:
过滤处理可根据需求多次执行

  • 去除重复(distinct)
stream = stream.distinct(); 

去除重复元素:依据集合中元素的equals方法和hashcode方法

  • 排序(sorted)
stream = stream.sorted((s1,s2) -> (int)(s1.getScore()-s2.getScore()))  

以上操作根据学生的学分从低到高排序
代码等效于:

stream = stream.sorted(new Comparator<Student>(){
    public int compare(Student s1,Student s2){
        return (int)(s1.getScore()-s2.getScore());
    }
});
  • 跳过(skip)
stream = stream.skip(2);	//跳过的数据行数
  • 限制数据行数(limit)
stream = stream.limit(5);
3.结尾处理

结尾处理即意味着中间处理已完成,等待最终操作,比如收集,统计,迭代等操作。由于结果为最终执行项,因此不可重复调用不同的结尾处理方式,否则会出现异常:
在这里插入图片描述

  • 迭代
stream.forEach(System.out::println);

在这里插入图片描述

  • 统计
long count = stream.count();

在这里插入图片描述

  • 收集
    收集指的是将中间处理之后满足条件的结果存放到集合并返回
List<Student> listStu = stream.collect(Collectors.toList());
链式编程

对以上Stream的使用可以通过一行代码实现,具体如下:

List<String> names = list.stream()	//1.获取流
    .filter(s -> Objects.equals(s.getSex(), "男"))  //2.中间处理  过滤
    .filter(s -> s.getScore() >= 60) //2.中间处理 过滤
    .distinct() //2.中间处理 去除重复
    .sorted((s1,s2) -> (int)(s1.getScore()-s2.getScore())) //2.中间处理 排序
    .skip(2) //2.中间处理  跳过
    .limit(5) //2.中间处理  限制结果行数
    .map(s->s.getName()) //2.中间处理  映射(获取属性子集)
    .collect(Collectors.toList());  //3.结尾处理

JDK8新特性

StreamAPI补充

Set集合StreamAPI使用

set集合和List集合都是从Collection继承而来的单例集合,因此StreamAPI使用方式是一致的:

Set<String> set = new HashSet<>();
set.add("html");
set.add("css");
set.add("javascript");
set.add("Spring");
set.add("summerSpring");
set.add("springMVC");
set.add("SpringBoot");
set.add("ajax");
set.add("mybatis");
set.add("mybatis");

set.stream()											//1.获取流
    .filter(s->s.toLowerCase().contains("spring"))		//2.中间处理   过滤所有包含"spring"单词的数据(不区分大小写)
    .filter(s->s.length()>6)							//3.中间处理	过滤所有长度超过6位的
    .sorted()											//4.中间处理	使用元素的自然顺序(comparable)排序
    .forEach(System.out::println);						//5.结尾处理	迭代

Map集合的StreamAPI使用

Map<String,Object> map = new HashMap<>();
map.put("html", "HyperText Markup Language");
map.put("css", "Cascading Style Sheets");
map.put("js", "JavaScript");
map.put("bs","Bootstrap");
map.put("ssh","Spring Struts2 Hibernate");
map.put("ssm", "Spring SpringMVC Mybatis");
map.put("xml","Extension markup Language");

//迭代
//map.forEach((k,v)->System.out.println(k+"-->"+v)); 

map.entrySet().stream()
    .filter(e->e.getKey().toLowerCase().contains("s")) //过滤所有包含s的数据
    .sorted((e1,e2)->e1.getValue().toString().length() -  e2.getValue().toString().length()) //按值长度排序
    .filter(e->e.getKey().length()>=3)  //过滤出所有key长度>=3的字符
    .skip(1)			
    .limit(2)
    .map(e->e.getValue()) 			//映射:去除所有值形成新的Stream
    .collect(Collectors.toList()) 	//收集:将新的Stream中的所有元素转换为List集合
    .forEach(System.out::println);  //遍历(迭代)

新增日期类(LocalDate/LocalTime/LocalDateTime)

之前常用类中,已经对于一些常见的日期处理类有所了解:

  • java.util.Date
    • java.sql.Date
    • java.sql.Time
    • java.sql.Timestamp
  • java.util.Calendar
  • java.text.DateFormat/java.text.SimpleDateFormat

以上的日期处理存在一些缺陷,比如月份从0开始,可读性不好,以及日期格式化处理DateFormat是一个线程不安全的工具类,因此在多线程并发操作时需要为每一个线程单独创建实例,否则可能出现共享导致结果不一致的情况。

从java8开始,JDK引进了新的日期类,用于对于日期时间提供更为简洁,以及更为安全操作,新的日期处理类位于java.time包下;主要包含以下类:

  • java.time.LocalDate(日期)
  • java.time.LocalTime(时间)
  • java.time.LocalDateTime(日期时间)
  • java.time.format.DateTimeFormatter(对日期时间格式化工具类)

LocalDate

LocalDate类用于表示一个标准格式日期,通常以yyyy-MM-dd格式显示(如:2020-07-07),LocalDate的使用方法:

//获取当前系统时间所表示的日期对象
LocalDate date = LocalDate.now();
//获取年份
System.out.println(datetime.getYear());

//获取月
System.out.println(date.getMonthValue());//获取月份的整数值1-12
System.out.println(date.getMonth());//获取月份枚举(列举)值

//获取日
System.out.println(date.getDayOfYear());
System.out.println(date.getDayOfMonth());
System.out.println(date.getDayOfWeek());
System.out.println(date.getDayOfWeek().getValue());

//根据指定的日期构建一个LocalDate对象
LocalDate date2 = LocalDate.of(2020,7,8);

LocalTime

LocalTime类用于表示一个标准格式时间,通常以HH:mm:ss.SSS格式显示(如:11:22:33.354),LocalTime的使用方法:

LocalTime time = LocalTime.now();

//获取时
System.out.println(time.getHour());
//获取分
System.out.println(time.getMinute());
//获取秒
System.out.println(time.getSecond());
//获取毫秒
System.out.println(time.toInstant(ZoneOffset.of("+8")).toEpochMilli());

//根据指定的日期构建一个LocalTime对象
LocalTime time = LocalDate.of(12,11,18);

LocalDateTime

LocalDateTime类用于表示一个标准日期时间格式,通常以yyyy-MM-ddTHH:mm:ss.SSS格式显示(如:2020-08-07T18:18:18),LocalDateTime的使用方法:

LocalDateTime time = LocalDateTime.now();

//将LocalDateTime 转换为 LocalDate
LocalDate localDate = time.toLocalDate();
LocalTime localTime = time.toLocalTime();
System.out.println(localDate);
System.out.println(localTime);

//根据指定的日期构建一个LocalDateTime对象
LocalDateTime time = LocalDateTime.of(2020,8,9,19,22,12);

DateTimeFormatter

DateTimeFormatter是jdk8新增java.time包中的一个用于对LocalDate,LocalTime,LocalDateTime进行格式化和解析的解析类,内部体提供一些内置的格式化方式,比如:

  • BASIC_ISO_DATE
  • ISO_DATE
  • ISO_INSTANT
  • ISO_LOCAL_DATE

同时该类还支持自定义的格式化匹配模式,通过以下方法获得:

  • ofPattern(String pattern)

例如:

//获取系统时间:2020-07-08T09:47:37.862
LocalDateTime datetime = LocalDateTime.now();
//需要将以上类型的日期时间转换为自定义格式的字符串
//创建一个格式化解析对象
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒");
//需要使用以上格式对日期时间格式化包含两种方式
//方法1:
String time = fmt.format(datetime);

//方法2:
String time2 = datetime.format(fmt);

//以上两种方式获取的字符串日期格式一致的,均为:
//2020年07月08日 09时47分37秒

String t = "2020年07月08日 09时33分21秒"; // String --> LocalDateTime
//将String类型日期时间解析为LocalDateTime对象
dateTime = LocalDateTime.parse(t, fmt);
System.out.println(dateTime);

System.out.println(LocalDate.parse("20200708101211",DateTimeFormatter.ofPattern("yyyyMMddHHmmss")));   
//		System.out.println(LocalDate.parse("20200708",DateTimeFormatter.BASIC_ISO_DATE));   

注意事项:

与DateFormat和SimpleDateFormat的区别在于,DateTimeFormatter是线程安全的实现,在多线程并发的时候可以让多个线程使用同一当前实例,能保证数据的一致性;但是DateFormat是线程非安全实现,因此在多线程并发时,需要为每个线程单独创建该实例。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值