Java基础三
五、常见算法
1.排序算法
1.1冒泡排序
冒泡排序
- 每次从数组中找出最大值放在数组的后面去。
实现冒泡排序的关键步骤分析
- 确定总共需要做几轮:数组的长度-1.
- 每轮比较几次:
- 当前位置大于后一个位置则交换数据
示例:
Test1
package com.itheima.algorithm;
//目标:掌握冒泡排序的编写
import java.util.Arrays;
public class Test1 {
public static void main(String[] args) {
//1.准备一个数组
int[] arr={ 9,2,53,16,2,23};
//2.定义一个循环控制排几轮
for (int i = 0; i < arr.length-1; i++) {
//3.定义一个循环控制每轮比较几次
for(int j=0;j<arr.length-i-1;j++){
//当前位置元素是否大于后一个位置的元素,大则交换
if(arr[j]>arr[j+1]){
int tmp=arr[j+1];
arr[j+1]=arr[j];
arr[j]=tmp;
}
}
}
System.out.println(Arrays.toString(arr));
}
}
1.2选择排序
选择排序
-
每轮选择当前位置,开始找出后面的较小值与该位置交换
选择排序的关键 -
确定总共需要选择几轮:数组的长度-1.
-
控制每轮从以前位置为基准,与后面元素选择几次.
示例:
Test2
package com.itheima.algorithm;
import java.util.Arrays;
//目标:掌握选择排序
public class Test2 {
public static void main(String[] args) {
//1.准备好一个数组
int[] arr={5,1,3,2};
//2.控制选择几轮
for (int i = 0; i < arr.length-1; i++) {
int minIndex=i;
//控制每轮选择几次
for(int j=i+1;j<arr.length;j++){
if(arr[minIndex]>arr[j]){
minIndex=j;
}
}
//决定是否交换
if(i!=minIndex){
int tmp=arr[i];
arr[i]=arr[minIndex];
arr[minIndex]=tmp;
}
}
System.out.println(Arrays.toString(arr));
}
}
2.搜索算法
2.1二分查找
二分查找
示例:
package com.itheima.algorithm;
import java.util.Arrays;
//目标:掌握二分查找
public class Test3 {
public static void main(String[] args) {
//1.准备好一个数组
int[] arr={7,23,79,81,183,127,131,147};
System.out.println(binarySearch(arr, 1237));
System.out.println(Arrays.binarySearch(arr, 81));
}
public static int binarySearch(int[] arr,int data){
//1、定义两个变量,一个站在左边位置,一个站在右边位置
int left=0;
int right=arr.length-1;
//2.定义一个循环控制折半
while(left<=right){
//3.每次折半,都算出中间位置处的索引
int middle=(left+right)/2;
//4、判断当前要找的元素值,与中间位置处的元素值的大小情况.
if(data < arr[middle]) {
//往左边找,截止位置(右边位置)=中间位置-1
right = middle - 1;
}else if(data >arr[middle]) {
//往右边找,起始位置(左边位置)=中间位置+1
left = middle + 1;
}else{
//中间位置处的元素值,正好等于我们要找的元素值
return middle;
}
}
return -1;//数组中没有这个元素
}
}
六、正则表达式
1.概述、初体验
正则表达式
- 就是由一些特定的字符组成,代表的是一个规则。
***作用一:***用来校验数据格式是否合法
***作用二:***在一段文本中查找满足要求的内容
示例:
Test1
package com.itheima.d2_regex;
//目标:体验一下使用正则表达式来校验数据格式的合法性.
//需求:校验0Q号码是否正确,要求全部是数字,长度是(6-20)之间,不能以0开头.
public class Test1 {
public static void main(String[] args) {
System.out.println(checkQQ(null));//false
System.out.println(checkQQ("21304501"));//true
System.out.println(checkQQ("012312213"));//false
System.out.println(checkQQ("a12312213"));//false
System.out.println(checkQQ("213"));//false
System.out.println("---------------------------------");
System.out.println(checkQQ1(null));//false
System.out.println(checkQQ1("21304501"));//true
System.out.println(checkQQ1("012312213"));//false
System.out.println(checkQQ1("a12312213"));//false
System.out.println(checkQQ1("213"));//false
}
public static boolean checkQQ1(String qq){
return qq!=null && qq.matches("[1-9]\\d{5,19}");
}
public static boolean checkQQ(String qq){
//1.判断qq号码是否为null
if(qq==null||qq.startsWith("0")||qq.length()<6||qq.length()>20){
return false;
}
//2、qq至少是不是null,不是以0开头的,满足6-20之间的长度,
//判断qq号码中是否都是数字.
for(int i=0;i<qq.length();i++){
//根据索引提取当前位置处的字符
char ch=qq.charAt(i);
//记住ch记住的字符,如果不是数字,qq号码不合法
if(ch<'0'||ch>'9'){
return false;
}
}
//3.说明qq号码合法
return true;
}
}
2.书写规则
String提供了一个匹配正则表达式的方法
示例
Test2
package com.itheima.d2_regex;
//掌握正则表达式书写规则
public class Test2 {
public static void main(String[] args) {
//1、字符类(只能匹配单个字符)
System.out.println("a".matches("[abc]"));//[abc]只能匹配a、b、c
System.out.println("e".matches("[abcd]"));//false
System.out.println("d".matches("[abc]"));//[^abc]不能是abc
System.out.println("a".matches("[abc]"));//false
System.out.println("b".matches("[a-zA-Z]"));//[a-zA-Z]只能是a-zA-Z的字符
System.out.println("2".matches("[a-zA-Z]"));//false
System.out.println("k".matches("[a-z&&[^bc]]"));//:a到z,除了b和c
System.out.println("b".matches("[a-z&&[Abc]]"));//false
System.out.println("ab".matches("[a-zA-Z0-9]"));//false注意:以上带[内容]的规则都只能用于匹配单个字符
//2、预定义字符(只能匹配单个字符). \d \D \s \S \w \W
System.out.println("徐".matches("."));//.匹配任意字符
System.out.println("徐徐".matches("."));//false
//在Java中.\是有特殊用途的,例如特殊字符 \n \t
System.out.println("1".matches("\\d"));//true
System.out.println("12".matches("\\d"));//false
System.out.println(" ".matches( "\\s"));//\s:代表一个空白字符
System.out.println("a".matches( "\\s"));//false
System.out.println("a".matches( "\\S"));// \S:代表一个非空白字符
System.out.println(" ".matches( "\\S"));//false
System.out.println("a".matches( "\\w"));// \w:[a-zA-Z_0-9]
System.out.println("_".matches( "\\w"));//true
System.out.println("".matches( "\\w"));//false
System.out.println("徐".matches( "\\W"));//[\w]不能是a-zA-Z_0-9
System.out.println("a".matches( "\\W"));//false
System.out.println("23232".matches( "\\d"));//false注意:以上预定义字符都只能匹配单个字符.
//3.数量词:? * + {n} {n,} {n,m}
System.out.println("a".matches( "\\w?"));//?代表次或1次
System.out.println("".matches( "\\w?"));//true
System.out.println("abc".matches( "\\w?"));//false
System.out.println("abc12".matches( "\\w*"));//*代表0次或多次
System.out.println("".matches( "\\w*"));//true
System.out.println("abc12".matches( "\\w*"));//false
System.out.println("abc12".matches( "\\w+"));//+代表1次或多次
System.out.println("".matches( "\\w+"));//false
System.out.println("abc12".matches( "\\w+"));//false
System.out.println("a3c".matches( "\\w{3}"));//(3}代表要正好是n次
System.out.println("abcd".matches( "\\w{3}"));//false
System.out.println("abcd".matches( "\\w{3,}"));//{3,}代表是>=3次
System.out.println("ab".matches( "\\w{3,}"));//false
System.out.println("abcde".matches( "\\w{3,}"));//false
System.out.println("abc232d".matches( "\\w{3,9}"));//{3,9}代表是大于等于3次,小于等于9次
//4、其他几个常用的符号:(?i)忽略大小写、或:|、 分组:()
System.out.println("abc".matches( "(?i)abc"));//true,整个abc忽略大小写
System.out.println("ABC".matches( "(?i)abc"));//true
System.out.println("aBc".matches( "a((?i)b)c"));//true.//只有B忽略大小写
System.out.println("ABc".matches( "a((?i)b)c"));//false
//需求1:要求要么是3个小写字母,要么是3个数字.
System.out.println("123".matches("\\d{3}|[a-z]{3}"));
System.out.println("abc".matches("\\d{3}| [a-z]3}"));
System.out.println("aAc".matches("\\d{3}|[a-z]{3}"));
//需求2:必须是"我爱"开头,中间可以是至少一个"编程",最后至少是1个"666"
System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));
System.out.println("我爱编程编程6666666".matches("我爱(编程)+(666)+"));//false,7个6
}
}
3.用于校验数据格式
示例:
Test3
package com.itheima.d2_regex;
import java.util.Scanner;
//目标:校验用户输入的电话、邮箱、时间是否合法
public class Test3 {
public static void main(String[] args) {
//checkPhone();
while(true)
checkEmail();
}
public static void checkPhone() {
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入您的手机号码(手机|座机):");
String phone = sc.nextLine();
if (phone.matches("(1[3-9]\\d{9})|0\\d{2,7}-? \\d{4,19}")) {
System.out.println("您输入的号码格式正确~~~");
break;
} else {
System.out.println("您输入的号码格式不正确~~~");
break;
}
}
}
public static void checkEmail(){
Scanner sc=new Scanner(System.in);
while (true) {
System.out.println("请输入您的邮箱:");
String email =sc.nextLine();
// \\. 第一个杠告诉后一个\只是一个\ ,第二个\告诉.只是一个点
if(email .matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")){
System.out.println("您输入的邮箱格式正确~~~");
break;
}else{
System.out.println("您输入的邮箱格式不正确~~~");
break;
}
}
}
}
4.用于查找消息
示例
Test4
package com.itheima.d2_regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*目标:掌握使用正则表达式查找内容.
*/
public class Test4 {
public static void main(String[] args) {
method1();
}
//需求1:从以下内容中爬取出,手机,邮箱,座机、400电话等信息.
public static void method1() {
String data = "来黑马程序员学习Java,\n" +
"电话:1866668888,18699997777\n" +
"或者联系邮箱:boniv0 itcast.cn,\n" +
"座机电话:01036517895,010-98951256\n" +
"邮箱:bozai@itcast.cn,\n" +
"邮箱:d1ei00090163.com,\n" +
"热线电话:400-618-9090,400-618-4000,4006184000,4006189090";
//1.定义爬取规则
String regex = "((1[3-9]\\d{9})|0\\d{2,7}-?\\d{4,19})" + "|(\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2})"
+ "|(400-?\\d{3,7}-?\\d{3,7})";
//2.把正则表达式封装成一个Pattern对象
Pattern pattern = Pattern.compile(regex);
//3.通过pattern对象去获取查找内容的匹配器对象
Matcher matcher = pattern.matcher(data);
//4.定义一个循环开始爬取信息。
while (matcher.find()) {
String rs = matcher.group();
System.out.println(rs);
}
}
}
5.用于搜索替换、内容分割
正则表达式用于搜索替换、分割内容,需要结合String提供的如下方法完成:
示例:
Test5
package com.itheima.d2_regex;
import java.util.Arrays;
//目标:掌握使用正则表达式做搜索替换、内容分割
public class Test5 {
public static void main(String[] args) {
//1、public String replaceAll(String regex,String newstr):按照正则表达式匹配的内容进行替换
//需求1:请把 古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴,中间的非中文字符替换成"-"
String s1="古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
System.out.println(s1.replaceAll("\\w+", "-"));
//需求2(拓展):某语音系统,收到一个口吃的人说的"我我我喜欢编编编编编编编编编编编编程程程!",需要优化成"我喜欢编程!"。
String s2="我我喜欢编编编编编编编编编编编编程程程!";
System.out.println(s2.replaceAll("(.)\\1+", "$1"));
/*
* (.)一组:.匹配任意字符
* \\1:为这个组声明一个组好:1号
* +:声明必须是重复的字
* $1可以取到第一组代表的那个重复的字
* */
//2、pubuic String split(String regex):按照正则表达式匹配的内容进行分割字符串,反回一个字符串数组。
//需求1:请把 古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴,中的人名获取出来。
String s3="古力娜扎ai8888迪丽热巴999aa5566马尔扎哈fbbfsfs42425卡尔扎巴";
String[] names=s3.split("\\w+");
System.out.println(Arrays.toString(names));
}
}
七、异常
1 认识异常
什么是异常
- 异常就是代表程序出现的问题
异常的体系
Error: 代表的系统级别错误(属于严重问题),也就是说系统一旦出现问题,sun公司会把这些问题封装成Error对象给出来。
说白了,Error是给sun公司自己用的,不是给我们程序员用的,因此我们开发人员不用管它。
Exception: 叫异常,它代表的才是我们程序可能出现的问题,所以,我们程序员通常会用Exception以及它的孩子来封装程序出现的问题。
- 运行时异常: RuntimeException及其子类,编译阶段不会出现错误提醒,运行时出现的异常(如:数组索引越界异常)
- 编译时异常: 编译阶段就会出现错误提醒的.(如:日期解析异常)
示例:
Test1
package com.itheima.d3_exception;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
//目标:认识异常
public class Test1 {
public static void main(String[] args) throws ParseException {
//Integer.valueOf("abc");
/* 运行时异常
int[] arr={11,22,33};
System.out.println(arr[5]);
*/
/* 编译时时异常
第一种解决方法
try {
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2018-11-11 10:24");
System.out.println(d);
} catch (ParseException e) {
throw new RuntimeException(e);
}
*/
//第二种解决方法 throws ParseException
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2018-11-11 10:24");
System.out.println(d);
}
}
2. 自定义异常
- Jva无法为这个世界上全部的问题都提供异常类来代表,如果企业自己的某种问题,想通过异常来表示,以便用异常来管理该问题,那就需要自己来定义异常类了。
自定义异常的种类
示例:
AgeIllegalException
package com.itheima.d3_exception;
//1、必须让这个类继承自Exception,才能成为一个编译时异常类.
public class AgeIllegalException extends Exception{
public AgeIllegalException() {
}
public AgeIllegalException(String message) {
super(message);
}
}
AgeIllegalRuntimeException
package com.itheima.d3_exception;
//1、必须让这个类继承自RuntimeException,才能成为一个运行时异常类.
public class AgeIllegalRuntimeException extends RuntimeException{
public AgeIllegalRuntimeException() {
}
public AgeIllegalRuntimeException(String message) {
super(message);
}
}
Test2
package com.itheima.d3_exception;
//目标:掌握自定义异常,以及异常的作用
public class Test2 {
public static void main(String[] args) {
//需求:保存一个合法的年龄
try {
saveAge(160);
System.out.println("saveAge底层执行成功");
} catch (Exception e) {
e.printStackTrace();
System.out.println("saveAge底层出现了Bug");
}
try {
saveAge2(256);
System.out.println("saveAge2底层执行成功");
} catch (AgeIllegalException e) {
e.printStackTrace();
System.out.println("saveAge2底层执行失败");
}
}
public static void saveAge(int age){
if(age>0&&age<150){
System.out.println("年龄被成功保存:"+age);
}else {
//用一个异常对象封装这个问题
//throw 跑出去这个异常对象
throw new AgeIllegalRuntimeException("/age is illegal,your age is"+age);
}
}
public static void saveAge2(int age)throws AgeIllegalException{
if(age>0&&age<150){
System.out.println("年龄被成功保存:"+age);
}else {
//用一个异常对象封装这个问题
//throw 跑出去这个异常对象
//throws 用在方法上,抛出方法内部的异常
throw new AgeIllegalException("/age is illegal,your age is"+age);
}
}
}
3. 异常的处理
开发中对于异常的处理
抛出异常(throws)
- 在方法上使用throws关键字,可以将方法内部出现的异常抛出去给调用者处理。
捕获异常(try…catch)
- 直接捕获程序出现的异常。
示例:
Test3
package com.itheima.d3_exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
//目标:掌握异常的处理方式:记录异常并响应合适的信息给用户
public class Test3 {
public static void main(String[] args) {
try {
test1();
} catch (FileNotFoundException e) {
System.out.println("您要找的文件不存在");
e.printStackTrace();//打印这个异常对象的信息,记录下来
} catch (ParseException e) {
System.out.println("您要解析的时间有问题");
e.printStackTrace();//打印这个异常对象的信息,记录下来
}
}
public static void test1() throws FileNotFoundException, ParseException {
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2018-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws FileNotFoundException {
//读取文件的
InputStream is= new FileInputStream("D:/sdjfkl.png");
}
}
Test3_2
package com.itheima.d3_exception;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
//写法优化
public class Test3_2 {
public static void main(String[] args) {
try {
test1();
} catch (Exception e) {
System.out.println("您当前操作有问题");
e.printStackTrace();//打印这个异常对象的信息,记录下来
}
}
public static void test1() throws Exception{
SimpleDateFormat sdf= new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date d=sdf.parse("2018-11-11 10:24:11");
System.out.println(d);
test2();
}
public static void test2() throws Exception {
//读取文件的
InputStream is= new FileInputStream("D:/sdjfkl.png");
}
}
Test4
package com.itheima.d3_exception;
import java.util.Scanner;
//目标:捕获异常,尝试重新修复
public class Test4 {
public static void main(String[] args) {
//需求:调用一个方法,让用户输入一个合适的价格返回为止.
while (true) {
try {
System.out.println(getMoney());
} catch (Exception e) {
System.out.println("请您输入合法的数字!!");
}
}
}
public static double getMoney(){
Scanner sc=new Scanner(System.in);
while (true){
System.out.println("请您输入合适的价格:");
double money=sc.nextDouble();
if(money>=0){
return money;
}else {
System.out.println("您输入的价格是不合适的");
}
}
}
}
八、集合框架
1.集合框架(一)
1.1集合体系概述
集合体系结构
- Collection代表单列集合,每个元素(数据)只包含一个值.
- Map代表双列集合,每个元素包含两个值(键值对).
Collection集合体系
Collection集合特点
-
Lst系列集合:添加的元素是有序、可重复、有索引.
- ArrayList、LinekdList:有序、可重复、有索引.
-
Set系列集合:添加的元素是无序、不重复、无索引.
- HashSet:无序、不重复、无索引;
- LinkedHashSet:有序、不重复、无索引.
- TreeSet:按照大小默认升序排序、不重复、无索引.
示例:
Test1
package com.itheima_d1_collection;
import java.util.ArrayList;
import java.util.HashSet;
//目标:认识Collection体系的特点.
public class Test1 {
public static void main(String[] args) {
//简单确认一下Collection集合的特点
ArrayList<String> list=new ArrayList<>();//有序 可重复 有索引
list.add("java1");
list.add("java2");
list.add("java3");
list.add("java1");
System.out.println(list);
HashSet<String> set=new HashSet<>();//无序,不重复,无索引
set.add("java1");
set.add("java2");
set.add("java3");
set.add("java1");
System.out.println(set);
}
}
1.2Collection常用方法
为啥要先学Collection的常用方法?
- Collection是单列集合的祖宗,它规定的方法(功能)是全部单列集合都会继承的.
Collection的常见方法如下:
示例:
Test2API
package com.itheima_d1_collection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
public class Test2API {
public static void main(String[] args) {
Collection<String> c=new ArrayList<>();
//1.public boolean add(E e):添加元素,添加成功返回true.
c.add("java1");
c.add("java1");
c.add("java2");
c.add("java2");
c.add("java3");
System.out.println(c);
//2.public void clear():清空集合的元素.
// c.clear();
//System.out.println(c);
//3.public boolean isEmpty():判断集合是否为空是空返回true,反之.
System.out.println(c.isEmpty());//false
//4.public int size():获取集合的大小.
System.out.println(c.size());
//5.public boolean contains(Object obj):判断集合中是否包含某个元素.
System.out.println(c.contains("java1"));//true
System.out.println(c.contains("Java1"));//false 区别大小写
//6.public boolean remove(Ee):删除某个元素:如果有多个重复元素默认删除前面的第一个!
System.out.println(c.remove("java1"));
System.out.println(c);//[java1, java2, java2, java3]
//7.public Object[]toArray():把集合转换成数组
Object[]arr=c.toArray();
System.out.println(Arrays.toString(arr));//[java1, java2, java2, java3]
//把集合转成指定类型的数组(要保证是同一种类型)
String[] arr2=c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(arr2));//[java1, java2, java2, java3]
System.out.println("------------------------------------------");
//把一个集合的全部数据拷贝加入到另一个集合中去。
Collection<String> c1=new ArrayList<>();
c1.add("java1");
c1.add("java2");
c1.add("java3");
Collection<String> c2=new ArrayList<>();
c2.add("java4");
c2.add("java5");
c1.addAll(c2);//就是把c2集合的全部数据倒入到c1集合中去。
System.out.println(c1);//[java1, java2, java3, java4, java5]
System.out.println(c2);//[java4, java5]
}
}
1.3Collection的遍历方式
1.3.1迭代器
迭代器概述
- 选代器是用来遍历集合的专用方式(数组没有选代器),在Java中迭代器的代表是Iterator。
Collection集合获取迭代器的方法
Collection集合获取迭代器的方法
迭代器执行流程
next():取出来同步移到下一个位置
开始:(迭代器创建完后默认指向第一个元素)
遍历了第一个元素后
遍历完最后一个元素后
示例:
CollectionDemo1
package com.itheima_d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//目标:Collection集合的遍历方式一:使用迭代器Iterator追历
public class CollectionDemo01 {
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c);
//c=[赵敏,小昭,素素,灭绝]
//使用迭代器遍历集合
//1、从集合对象中获取迭代器对象。
Iterator<String> it = c.iterator();
/* System.out.println(it.next());//赵敏
System.out.println(it.next());//小昭
System.out.println(it.next());//素素
System.out.println(it.next());//灭绝
*/
// System.out.println(it.next());越界,报错
//2.使用循环结合迭代器遍历,next():取出来同时移到下一位
while(it.hasNext()){
String ele =it.next();
System.out.println(ele);
}
}
}
1.3.2增强for循环
增强for循环
-
增强for可以用来遍历集合或者数组
-
增强for遍历集合,本质就是迭代器遍历集合的简化写法.
示例:
CollectionDemo2
package com.itheima_d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
//目标:Collection集合的遍历方式二:增强for
public class CollectionDemo02 {
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c);
//c=[赵敏,小昭,素素,灭绝]
//增强for遍历集合或数组
for (String i:c){
System.out.println(i);
}
String[] names={"张三","李四","王五"};
for (String name:names){
System.out.println(name);
}
}
}
1.3.3Lambda表达式
Lambda表达式遍历集合
- 得益于JDK8开始的新技术Lambda表达式,提供了一种更简单、更直接的方式来遍历集合.
需要使用Collection的如下方法来完成
示例:
CollectionDemo3
package com.itheima_d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.function.Consumer;
//目标:Collection集合的遍历方式三:JDK8开始新增的Lambdab表达式
public class CollectionDemo03 {
public static void main(String[] args) {
Collection<String> c =new ArrayList<>();
c.add("赵敏");
c.add("小昭");
c.add("素素");
c.add("灭绝");
System.out.println(c);
//c=[赵敏,小昭,素素,灭绝]
//default void forEach(Consumer<? super T> action):结合Lambda表达式遍历集合
c.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//不断简化
c.forEach((String s) ->{
System.out.println(s);
});
c.forEach( s ->{
System.out.println(s);
});
//out是对象,println是引用,可以用方法引用简化
c.forEach(System.out::println);
}
}
案例
集合存储对象的原理
存的是地址
示例:
CollectionTest4
package com.itheima_d2_collection_traverse;
import java.util.ArrayList;
import java.util.Collection;
/* 目 标:完成电影信息的展示。
new Movie("《肖生克的救赎》”,9.7,"罗宾斯")
new Movie("《霸王别姬》",9.6," "张国荣、张丰毅"
new Movie("《阿甘正传》",9.5, "汤姆.汉克斯")
*/
public class CollectionTest04 {
public static void main(String[] args) {
//1.创建一个集合负责存储多个电影对象
//集合存对象,存的是对象的地址
Collection<Movie> movies=new ArrayList<>();
movies.add(new Movie("《肖生克的救赎》",9.7,"罗宾斯"));
movies.add(new Movie("《霸王别姬》",9.6, "张国荣、张丰毅"));
movies.add(new Movie("《阿甘正传》",9.5, "汤姆.汉克斯"));
System.out.println(movies);
for (Movie movie : movies) {
System.out.println("电影名:"+movie.getName());
System.out.println("评分"+movie.getScore());
System.out.println("主演"+movie.getActor());
System.out.println("--------------------------------");
}
}
}
Movie
package com.itheima_d2_collection_traverse;
public class Movie {
private String name;
private double score;
@Override
public String toString() {
return "Movie{" +
"name='" + name + '\'' +
", score=" + score +
", actor='" + actor + '\'' +
'}';
}
public Movie() {
}
public Movie(String name, double score, String actor) {
this.name = name;
this.score = score;
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
private String actor;
}
1.4List集合
1.4.1特点、特有方法
List集合特点
List集合的特有方法
- List集合因为支持索引,所以多了很多与索引相关的方法,当然,Collection的功能List也都继承了.
示例
package com.itheima.d3_collection_list;
import java.util.ArrayList;
import java.util.List;
//目标:掌握List系列集合的特点,以及其提供的特有方法
public class Test1 {
public static void main(String[] args) {
//1.创建一个ArrayList集合付象(有序、可重复、有索引)
List<String> list=new ArrayList<>();//一行经典代吗
list.add("蜘蛛精");
list.add("至尊宝");
list.add("猪八戒");
list.add("牛夫人");
System.out.println(list);//[蜘蛛精, 至尊宝, 猪八戒, 牛夫人]
//2.public void add(int index,E element):在某个索引位置插入元素.
list.add(2,"紫霞仙子");
System.out.println(list);//[蜘蛛精, 至尊宝, 紫霞仙子, 猪八戒, 牛夫人]
//3.public E remove(int index.):根据索引别除元素,返回被删别除元素
System.out.println(list.remove(2));//紫霞仙子
System.out.println(list);//[蜘蛛精, 至尊宝, 猪八戒, 牛夫人]
//4.public E get(int index):返回集合中指定位置的元素.
System.out.println(list.get(3));//牛夫人
//5.public E set(int index,E element.):修改索引位置处的元素,修改成功后,会返回原来的数据
System.out.println(list.set(3, "牛魔王"));//牛夫人
System.out.println(list);//[蜘蛛精, 至尊宝, 猪八戒, 牛魔王]
}
}
1.4.2遍历方式
List集合支持的遍历方式
- for循环(因为List集合有索引)
- 迭代器
- 增强for循环
- Lambda:表达式
示例
Test2
package com.itheima.d3_collection_list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test2 {
public static void main(String[] args) {
List<String> list =new ArrayList<>();
list.add("糖宝宝");
list.add("蜘蛛精");
list.add("至尊宝");
//(1)for循环
for(int i=0;i<list.size();i++){
//i=0 1 2
String s=list.get(i);
System.out.println(s);
}
//(2)迭代器
Iterator<String> it=list.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
//(3)增强for循环(foreach遍历)
for (String s : list) {
System.out.println(s);
}
//(4)JDK1.8开始之后的Lambda表达式
list.forEach(s -> {
System.out.println(s);
});
}
}
1.4.3ArrayList集合的底层原理
1.ArrayList集合的底层原埋
- 基于数组实现的
- 查询速度快(注意:是根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同.
- 删除效率低:可能需要把后面很多的数据进行前移.
- 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容.
2.ArrayList集合的适用场景
- ArrayList适合:根据索引查询数据,比如根据随机索引取数据(高效)!或者数据量不是很大时!
- ArrayList不话合:数据量大的同时,又要频繁进行增删操作!
1.4.4LinkedList集合的底层原理
LinkedList集合的底层原理
- 基于双链表实现的
- 什么是链表?有什么特点
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址.
- 链表的特点1:查询慢,无论查询哪个数据都要从头开始找.
- 链表的特点2:链表增删相对快
- 链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址.
- 双向链表
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的.
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的.
- 什么是链表?有什么特点
- 特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的.
LinkedList新增了:很多首尾操作的特有方法.
LinkedList的应用场景之一:可以用来设计队列
LinkedList的应用场景之一:可以用来设计栈
示例
Test3
package com.itheima.d3_collection_list;
import java.util.LinkedList;
//目标:学握LinkedList集合的使用.
public class Test3 {
public static void main(String[] args) {
//1.创建一个队列
LinkedList<String> queue=new LinkedList<>();
queue.addLast("第1号人");
queue.addLast("第2号人");
queue.addLast("第3号人");
queue.addLast("第4号人");
System.out.println(queue);//[第1号人, 第2号人, 第3号人, 第4号人]
//出队
System.out.println(queue.removeFirst());//第1号人
System.out.println(queue.removeFirst());//第2号人
System.out.println(queue.removeFirst()); //第3号人
System.out.println(queue);//[第4号人]
System.out.println("------------------------------------------");
//2.创建一个栈对象
LinkedList<String> stack=new LinkedList<>();
//压栈(push)
/* stack.addFirst("第1颗子弹");
stack.addFirst("第2颗子弹");
stack.addFirst("第3颗子弹");
stack.addFirst("第4颗子弹");
stack.addFirst("第5颗子弹");*/
stack.push("第1颗子弹");
stack.push("第2颗子弹");
stack.push("第3颗子弹");
stack.push("第4颗子弹");
stack.push("第5颗子弹");
System.out.println(stack);//[第5颗子弹, 第4颗子弹, 第3颗子弹, 第2颗子弹, 第1颗子弹]
//出栈
/* System.out.println(stack.removeFirst());//第5颗子弹
System.out.println(stack.removeFirst());//第4颗子弹*/
System.out.println(stack.pop());//第5颗子弹
System.out.println(stack.pop());//第4颗子弹
System.out.println(stack);//[第3颗子弹, 第2颗子弹, 第1颗子弹]
}
}
1.5Set集合
1.5.1特点
Set集合特点
示例:
Test1
package com.itheima.d4_collection_set;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.TreeSet;
//目标:整体了解一下Set系列集合的特点.
public class Test1 {
public static void main(String[] args) {
//1.创建一个Set集合的对象
Set<Integer> set=new HashSet<>();//创建一个HashSet的集合对象
//无序,不重复,无索引
set.add(666);
set.add(666);
set.add(555);
set.add(555);
set.add(888);
set.add(888);
System.out.println(set);//[888, 666, 555],只无序一次,每次运行都一样
Set<Integer> set1=new LinkedHashSet<>();//创建一个LinkedHashSet的集合对象
//有序,不重复,无索引
set1.add(666);
set1.add(666);
set1.add(555);
set1.add(555);
set1.add(888);
set1.add(888);
System.out.println(set1);//[666, 555, 888]
Set<Integer> set2=new TreeSet<>();//创建一个TreeSet的集合对象
//排序(默认升序),不重复,无索引
set2.add(666);
set2.add(666);
set2.add(555);
set2.add(555);
set2.add(888);
set2.add(888);
System.out.println(set2);//[555, 666, 888]
}
}
1.5.2HashSets集合的底层原理
哈希值
- 就是一个int类型的数值,Java中每个对象都有一个哈希值.
- Java中的所有对象,都可以调用Object类提供的hashCode方法,返回该对象自己的哈希值.
public int hashCode()
:返回对象的哈希码值
对象哈希值的特点
- 同一个对象多次调用nashCode()方法返回的哈希值是相同的.
- 不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞).
int(-21亿多~21亿多),45亿个对象就不够用了
示例:
Student
package com.itheima.d4_collection_set;
import java.util.Objects;
public class Student implements Comparable<Student>
{
private String name;
private int age;
private double height;
//this是调用者,o是被比较的,这种会丢对象
@Override
public int compareTo(Student o) {
//如果认为左边对象大于右边对象,返回正整数
return this.age-o.age;//按年龄升序,前面减后面
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
//两个对象内容一样就返回true
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}
//两个对象内容一样就返回true
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student() {
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
Test2
package com.itheima.d4_collection_set;
/*目标:了解一下哈希值.
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值.
public int hashCode():返回对象的哈希值.
同一个对象多次调用hashCode()方法返回的哈希值是相同的,
不同的对象,它们的哈希值一股不相同,但也有可能会相同(哈希碰撞).
*/
public class Test2 {
public static void main(String[] args) {
Student s1=new Student("张三",25,180.5);
Student s2=new Student("李四",22,182.5);
System.out.println(s1.hashCode());//990368553
System.out.println(s1.hashCode());//990368553
System.out.println(s2.hashCode());//1096979270
// 哈希碰撞
String str1=new String("abc");
String str2=new String("acD");
System.out.println(str1.hashCode());//96354
System.out.println(str2.hashCode());//96354
}
}
HashSet:集合的底层原理
- 基于哈希表实现.
- 哈希表是一种增删改查数据,性能都较好的数据结构.
哈希表
- JDK8之前,哈希表=数组+链表
- JDK8开始,哈希表=数组+链表+红黑树
JDK8之前HashSet:集合的底层原理,基于哈希表:数组+链表
加载因子:元素个数/容量=加载因子时,就扩容
JDK8开始HashSet集合的底层原理,基于哈希表:数组+链表+红黑树
数据结构-树
深入理解HashSet集合去重复的机制.
示例:
Student见上文
Test3
package com.itheima.d4_collection_set;
import java.util.HashSet;
import java.util.Set;
//目标:自定义的类型的对象,比如两个内容一样的学生对象,如何让HashSet集合能够去重复!
//重写hashcode与equals
public class Test3 {
public static void main(String[] args) {
Set<Student> students=new HashSet<>();
Student s1=new Student("至尊宝",33,176);
Student s2=new Student("蜘蛛精",22,162.5);
Student s3=new Student("蜘蛛精",22,162.5);
Student s4=new Student("牛魔王",48,169.9);
students.add(s1);
students.add(s2);
students.add(s3);
students.add(s4);
System.out.println(s2.hashCode());//-281178460
System.out.println(s3.hashCode());//-281178460
System.out.println(students);
}
}
1.5.3LinkedHashSet集合的底层原理
LinkedHashSet底层原理
- 依然是基于哈希表(数组、链表、红黑树)实现的.
- 但是,它的每个元素都额外的多了一个双链表的机制记录它前后元素的位置.
1.5.4TreeSet集合
- 特点:不重复、无索引、可排序(默认升序排序,按照元素的大小,由小到大排序)
- 底层是基于红黑树实现的排序。
注意:
- 对于数值类型:Integer,Double,默认按照数值本身的大小进行升序排序.
- 对于字符串类型:默认按照首字符的编号升序排序.
-对于自定义类型如Student对象,TreeSet默认是无法直接排序的.
自定义排序规则
- TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则.
方式一 - 让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则.
方式二 - 通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则.
注意:如果类本身有实现Comparable:接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序.
示例:
Student见上文
Test4
package com.itheima.d4_collection_set;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
//目标:掌握TreeSet集合的使用.
public class Test4 {
public static void main(String[] args) {
Set<Integer> set1 = new TreeSet<>();
set1.add(6);
set1.add(5);
set1.add(5);
set1.add(7);
System.out.println(set1);//[5, 6, 7]
//就近选择自己自带的比较器对象进行排序
//比较器不会丢对象
/* Set<Student> students =new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
//需求:按照身高升序
return Double.compare(o1.getHeight(),o2.getHeight() );
}
});*/
//简化
Set<Student> students =new TreeSet<>((o1,o2)-> Double.compare(o1.getHeight(),o2.getHeight()));
students.add(new Student("蜘蛛精",23,168.3));
students.add(new Student("紫霞",22,163.8));
students.add(new Student("至尊宝",26,175.8));
students.add(new Student("牛魔王",22,185.8));
System.out.println(students);
//
// [Student{name='紫霞', age=22, height=163.8}, Student{name='蜘蛛精', age=23, height=168.3},
// Student{name='至尊宝', age=26, height=175.8}] 牛魔王不存在了,因为认为age一样就是相同的
}
}
1.5.5Collection集合的使用总结
1.5.6注意事项:集合的并发修改异常问题
集合的并发修改异常
- 使用迭代器遍历集合时,又同时在删除集合中的数据,程序就会出现并发修改异常的错误.
- 由于增强fo循环遍历集合就是迭代器遍历集合的简化写法,因此,使用增强fo循环遍历集合,又在同时删除集合中的数据时,程序也会出现并发修改异常的错误
怎么保证遍历集合同时删除数据时不出bug?
- 使用迭代器遍历集合,但用迭代器自己的删除方法删除数据即可.
- 如果能用fo循环遍历时:可以倒着遍历并删除;或者从前往后遍历,但删除元素后做i- -操作.
示例:
Test1
package com.itheima.d5_collection_exception;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
//目标:理解集合的并发修改异常问题,并解决.
public class Test1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("王麻子");
list.add("小李子");
list.add("李爱花");
list.add("张全蛋");
list.add("晓李");
list.add("李玉刚");
System.out.println(list);
//([王麻子,小李子,李爱花,张全蛋,晓李,李玉刚]
//需求:找出集合中全部带"李"的名字,并从集合中刚除.
/* Iterator<String> it = list.iterator();
while (it.hasNext()){
String name=it.next();
if(name.contains("李")){
list.remove(name);
}
}
System.out.println(list);
报错
*/
/* //使用for循坏遍历集合并删除集合中带李字的名字
//[王麻子,小李子,李爱花,张全蛋,晓李,李玉刚]
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
if (name.contains("李")) {
list.remove(name);
}
}
System.out.println(list);//[王麻子, 李爱花, 张全蛋, 李玉刚]没删完*/
//解决方法
for(int i=0;i<list.size();i++){
String name=list.get(i);
if(name.contains("李")){
list.remove(name);
i--;
}
}
System.out.println(list);//[王麻子, 张全蛋]
//倒着删除也可以
Iterator<String> it = list.iterator();
while (it.hasNext()){
String name=it.next();
if(name.contains("李")){
// list.remove(name);//并发修改异常的错误
it.remove();//刚除达代器当前遍历到的数据,每删除一个数据后,相当于也在底层做了i--
}
}
System.out.println(list);//[王麻子, 张全蛋]
//增强for循环遍历集合并删除数据,没办法解决问题
}
}
2.集合框架(二)
2.1Collection的其他相关知识
2.1.1前置知识:可变参数
可变参数
- 就是一种特殊形参,定义在方法、构造器的形参列表里,格式是:数据类型…参数名称;
可变参数的特点和好处
- 特点:可以不传数据给它;可以传一个或者同时传多个数据给它;也可以传一个数组给它.
- 好处:常常用来灵活的接收数据.
可变参数的注意事项:
- 可变参数在方法内部就是一个数组.
- 一个形参列表中可变参数只能有一个
- 可变参数必须放在形参列表的最后面
示例:
Test1
package com.itheima.d6_parameter;
import java.lang.reflect.Array;
import java.util.Arrays;
//认识可变参数,掌握其作用
public class Test {
public static void main(String[] args) {
//特点
test(); //不传数据
test(10);//传输一个数据给它
test(10,20,30,40);//传输多个数据给它
test(new int[]{10,20,30,40});
}
//注意事项1:一个形参列表中,只能有一个可变参数
//注意事项2:可变参数必须放在形参列表的最后面(int age,int...nums)
public static void test(int...nums){
//可变参数在方法内部,本质就是一个数组
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
System.out.println("-----------------------------------");
}
}
2.1.2Collections
Collections
- 是一个用来操作集合的工具类
Collections:提供的常用静态方法
Collections只能支持对List集合进行排序
排序方式1:
注意:本方法可以直接对自定义类型的List集合排序,但自定义类型必须实现了Comparable接口,指定了比较规则才可以.
排序方式2:
示例:
package com.itheima.d7_collections;
//目标:掌握Collections集合工具类的使用.
import com.itheima.d4_collection_set.Student;
import java.util.*;
public class Test{
public static void main(String[]args) {
//1、public static <T> boolean addAll(Collection<? super T> c,T...elements):为集合批量添加数据
List<String> names=new ArrayList<>();
Collections.addAll(names,"张三","李四","王五","张麻子");//addAll支持为一切Collection类集合批量添加数据
System.out.println(names);
//2、public static void shuffle(List<?>List):打乱List集合中的元素顺序.
Collections.shuffle(names);
System.out.println(names);
//3、public static<T>void sort(List<T>List):对List集合中的元素进行升序排序.
List<Integer> list=new ArrayList<>();
list.add(3);
list.add(5);
list.add(2);
Collections.sort(list);
System.out.println(list);
//若对未知比较规则的类比较,会报错,必须让对象的类实现比较规则或者使用比较器
//4、public static<T>void sort(List<T>List,Comparator<?super T>c):对List集合中元素,按照比较器对象指定的规则进行比较
}
}
2.1.3案例-斗地主
示例:
Card
package com.itheima.d8_collection_test;
public class Card {
private String number;
private String color;
//每张牌是由大小的
private int size;
public Card() {
}
public Card(String number, String color, int size) {
this.number = number;
this.color = color;
this.size = size;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
@Override
public String toString() {
return
color +number ;
}
}
GameDemo
package com.itheima.d8_collection_test;
/* 目标:斗地主游戏的案例开发。
业务需求分析:
业务:总共有54张牌。
点数:"3","4","5","6","7","8","9","10","J","Q","K","A","2"
花色:"◆","♣","♥","♠"
大小王:"",""
点数分别要组合4种花色,大小王各一张.
斗地主:发出51张牌,剩下3张作为底牌.
*/
public class GameDemo {
public static void main(String[] args) {
//1.牌类
//2.房间
Room m=new Room();
//3.启动游戏
m.start();
}
}
Room
package com.itheima.d8_collection_test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Room {
//必须有一张牌
private List<Card> allCards = new ArrayList<>();
public Room(){
//1.做出54张牌,存入到集合allCards;
//a.点数:个数确定,类型确定
String[] numbers={"3","4","5","6","7","8","9","10","J","Q","K","A","2"};
//b.花色:个数确定,类型确定
String[] colors={"◆","♣","♥","♠"};
int size=0;//表示每张牌的大小
//c.遍历点数,再遍历花色,组织牌
for (String number : numbers) {
size++;
//number="3"
for (String color:colors){
Card c=new Card(number,color,size);
allCards.add(c);
}
}
//单独存入大小王
Card c1=new Card("","小🃏",++size);
Card c2=new Card("","大🃏",++size);
Collections.addAll(allCards,c1,c2);
System.out.println("新牌"+allCards);
}
//游戏启动方法
public void start() {
//1.洗牌:allCards
Collections.shuffle(allCards);
System.out.println("洗牌后"+allCards);
//2.发牌,首先肯定要定义 三个玩家
List<Card> A=new ArrayList<>();
List<Card> B=new ArrayList<>();
List<Card> C=new ArrayList<>();
//正式发牌给三个玩家,依次发出51张牌剩余3张作为底牌
for (int i = 0; i < allCards.size()-3; i++) {
Card c=allCards.get(i);
if (i%3==0){
A.add(c);
}else if (i%3==1){
B.add(c);
}else {
C.add(c);
}
}
//3.对3个玩家的牌进行排序
sortCards(A);
sortCards(B);
sortCards(C);
//看牌
System.out.println("A:"+A);
System.out.println("B:"+B);
System.out.println("C:"+C);
List<Card> lastThreeCards=allCards.subList(allCards.size()-3,allCards.size());//51,52,53
System.out.println("底牌:"+lastThreeCards);
A.addAll(lastThreeCards);
sortCards(A);
System.out.println("A抢到地主后:"+A);
}
/**
* 集中进行排序
* @param cards
*/
private void sortCards(List<Card> cards) {
Collections.sort(cards, new Comparator<Card>() {
@Override
public int compare(Card o1, Card o2) {
return o2.getSize()-o1.getSize();
}
});
}
}
2.2Map集合
2.2.1概述
认识Map集合
- Map集合称为双列集合,格式:{key1=value1,key2=value2,key3=value3,…},一次需要存一对数据做为一个元素.
- Map集合的每个元素"key=value"称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做**“键值对集合”**
- Map集合的所有键是不允许重复的,但值可以重复,键和值是一 一对应的,每一个键只能找到自己对应的值
Map集合体系
Map集合体系的特点
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的 - HashMap(由键决定特点):无序、不重复、无索引;(用的最多)
- LinkedHashMap(由键决定特点):由键决定的特点:有序、不重复、无索引.
- TreeMap(由键决定特点):按照大小默认升序排序、不重复、无索引.
示例:
Test1
package com.itheima.d9_map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.TreeMap;
//掌握Map集合的特点
public class Test1 {
public static void main(String[] args) {
Map<String,Integer> map=new HashMap<>();//经典代码,按照键 无序,不重复,无索引
map.put("手表",100);
map.put("手表",200);//后面重复的数据会覆盖前面的数据(键)
map.put("手机",2);
map.put("Java",2);
map.put(null,null);
System.out.println(map);//{null=null, 手表=200, Java=2, 手机=2}
Map<String,Integer> map1=new LinkedHashMap<>();//有序,不重复,无索引
map1.put("手表",100);
map1.put("手表",200);//后面重复的数据会覆盖前面的数据(键)
map1.put("手机",2);
map1.put("Java",2);
map1.put(null,null);
System.out.println(map1);//{手表=200, 手机=2, Java=2, null=null}
Map<Integer,String> map2 =new TreeMap<>();//排序,不重复,无索引
map2.put(12,"Java");
map2.put(14,"Python");
map2.put(20,"Mysql");
map2.put(17,"Redis");
System.out.println(map2);//{12=Java, 14=Python, 17=Redis, 20=Mysql}
}
}
2.2.2常用方法
为什么要先学习Map的常用方法?
-Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的.
Map的常用方法如下:
示例:
package com.itheima.d9_map;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test2 {
public static void main(String[] args) {
//1.添加元素:无序,不重复,无索引.
Map<String,Integer> map =new HashMap<>();
map.put("手表",100);
map.put("手表",220);
map.put("手机",2);
map.put("Java",2);
map.put(null,null);
System.out.println(map);
//map={null=null,手表=220,Java=2,手机=2}
//2.public int size():获取集合的大小
System.out.println(map.size());//4
//3、public void clear():清空集合
// map.clear();
//System.out.println(map);
//4.public boolean isEmpty():判断集合是否为空,为空返回true,反之!
System.out.println(map.isEmpty());
//5.public V get(Object key):根据键获取对应值
int v1=map.get("手表");
System.out.println(v1);
System.out.println(map.get("手机"));//2
System.out.println(map.get("张三"));//不存在,返回null
//6.public V remove(Object key):根据键删除整个元素(删除键会返回键的值):
System.out.println(map.remove("手表"));
//7.public boolean containsKey(0bject key.):判断是否包含某个键,包含返回true,反之
System.out.println(map.containsKey("手表"));//false
System.out.println(map.containsKey("手机"));//true
System.out.println(map.containsKey("Java"));//true
System.out.println(map.containsKey("java"));//false
//8.public boolean containsValue(Object value.):判断是否包含某个值.
System.out.println(map.containsValue(2));//true
System.out.println(map.containsValue("2"));//false
//9.public Set<K>keySet():获取Map集合的全部键.
Set <String > keys=map.keySet();
System.out.println(keys);
//10.public Collection<V>valves();获取Map集合的全部值.
Collection<Integer> values=map.values();
System.out.println(values);
//11.把其他州ap集合的数据例入到自己集合中来.(拓展)
Map<String,Integer>map1 =new HashMap<>();
map1.put("java1",10);
map1.put("java2",20);
Map<String,Integer>map2 =new HashMap<>();
map2.put("java3",10);
map2.put("java2",222);
map1.putAll(map2);//map2集合元素加入到map1,重复的会覆盖
System.out.println(map1);//{java3=10, java2=222, java1=10}
System.out.println(map2);//{java3=10, java2=222}
}
}
2.2.3遍历方式
Map集合的遍历方式
方式一
方式二
方式三
示例:
Test1
package com.itheima.d10_map_traverse;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
//目标:掌握Map集合的遍历方式1:键找值
public class Test1 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map=new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",168.5);
map.put("紫霞",165.0);
map.put("至尊宝",183.6);
System.out.println(map);
//map={蜘蛛精=168.5, 至尊宝=183.6, 紫霞=165.0}
//1.获取Map集合的全部键
Set<String> keys=map.keySet();
System.out.println(keys);//[蜘蛛精, 至尊宝, 紫霞]
//2.遍历所有的键,根据键获取其对应的值
for (String key : keys) {
//根据键获取对应的值
double value =map.get(key);
System.out.println(key+"===>"+value);
}
}
}
Test2
package com.itheima.d10_map_traverse;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Test2 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map=new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",168.5);
map.put("紫霞",165.0);
map.put("至尊宝",183.6);
System.out.println(map);
//map={蜘蛛精=168.5, 至尊宝=183.6, 紫霞=165.0}
//entries =[(蜘蛛精=168.5), (至尊宝=183.6), (紫霞=165.0)]
//1.调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
String key=entry.getKey();
double value=entry.getValue();
System.out.println(key+"===>"+value);
}
}
}
Test3
package com.itheima.d10_map_traverse;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class Test3 {
public static void main(String[] args) {
//准备一个Map集合
Map<String,Double> map=new HashMap<>();
map.put("蜘蛛精",162.5);
map.put("蜘蛛精",168.5);
map.put("紫霞",165.0);
map.put("至尊宝",183.6);
System.out.println(map);
//map={蜘蛛精=168.5, 至尊宝=183.6, 紫霞=165.0}
map.forEach((k,v)->{
System.out.println(k+"===>"+v);
});
//原理:
/* map.forEach(new BiConsumer<String, Double>() {
@Override
public void accept(String s, Double a) {
System.out.println(s+"===>"+a);
}
});
*/
}
}
2.2.4案例
示例:
Demo
package com.itheima.d10_map_traverse;
import javax.print.DocFlavor;
import java.util.*;
//目标:完成Map集合的案例,统计投票人数
public class Demo {
public static void main(String[] args) {
//1.把80个学生选择的景点数据拿到程序中来
List<String> data=new ArrayList<>();
String[] selects={"A","B","C","D"};
Random r=new Random();
for (int i = 1; i <=80 ; i++) {
int index= r.nextInt(4);//0 1 2 3
data.add(selects[index]);
}
System.out.println(data);
//2.开始统计每个景点的投票人数
//准备一个Map集合用于统计最终的结果
Map<String,Integer> result=new HashMap<>();
//3.开始遍历80个经典数据
for (String s : data) {
//问问Map集合中是否存在该景点
if(result.containsKey(s)){
//已存在,加一
result.put(s,result.get(s)+1);
}else {
//第一次统计,存入“景点=1”
result.put(s,1);
}
}
System.out.println(result);
}
}
2.2.5HashMap
HashMap集合的底层原理
- HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的.
- HashMap集合是一种增删改查数据,性能都较好的集合,但是它是无序,不能重复,没有索引支持的(由键决定特点)
- HashMap的键依赖hashCode方法和1equals方法保证键的唯一
- 如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap:集合就能认为是重复的.
实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已.
哈希表 - JDK8之前,哈希表=数组+链表
- JDK8开始,哈希表=数组+链表+红黑树
- 哈希表是一种增删改查数据,性能都较好的数据结构.
示例
Student
package com.itheima.d11_map_impl;
import java.util.Objects;
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
public Student() {
}
//this o,会去重
@Override
public int compareTo(Student o) {
return this.age-o.age;//年龄升序
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
Test1HashMap
package com.itheima.d11_map_impl;
import java.util.HashMap;
import java.util.Map;
//目标:掌握Map集合下的实现类:HashMap集合的底层原理.
public class Test1HashMap {
public static void main(String[] args) {
Map<Student,String> map =new HashMap<>();
map.put(new Student("蜘蛛精",25, 168.5),"盘丝洞");
map.put(new Student("蜘蛛精",25,168.5),"水帘洞");
map.put(new Student("至尊宝",23,163.5),"水帘洞");
map.put(new Student("牛魔王",28,183.5),"牛头山");
System.out.println(map);
}
}
2.2.6LinkedHashMap
LinkedHashMap集合的原理
- 底层数据结构依然是基于哈希表实现的,只是每个键值对元素又额外的多了一个双链表的机制记录元素顺序**(保证有序).**
实际上:原来学习的LinkedHashSet:集合的底层原理就是LinkedHashMap
示例:
Test2LinkedHashMap
package com.itheima.d11_map_impl;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
//目标:掌握LinkedHashMap的底层原理.
public class Test2LinkedHashMap {
public static void main(String[] args) {
//Map<String,Integer> map new HashMap<>();//按照键无序,不重复,无索引.
Map<String,Integer>map=new LinkedHashMap<>();//按照键有序,不重复,无索引.
map.put("手表",100);
map.put("手表",220);
map.put("手机",2);
map.put("Java",2);
map.put(null,null);
System.out.println(map);
}
}
2.2.7TreeMap
- 特点:不重复、无索引、可排序(按照键的大小默认升序排序,只能对键排序)
- 原理:TreeMap跟TreeSet:集合的底层原理是一样的,都是基于红黑树实现的排序.
TreeMap集合同样也支持两种方式来指定排序规则
- 让类实现Comparable:接口,重写比较规则.
- TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则.
示例
Test3TreeMap
package com.itheima.d11_map_impl;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
public class Test3TreeMap {
public static void main(String[] args) {
//优先用比较器
/* Map<Student,String> map =new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o2.getHeight(), o1.getHeight());
}
});*/
Map<Student,String> map =new TreeMap<>((o1,o2)-> Double.compare(o2.getHeight(), o1.getHeight()));
map.put(new Student("蜘蛛精",25, 168.5),"盘丝洞");
map.put(new Student("蜘蛛精",25,168.5),"水帘洞");
map.put(new Student("至尊宝",23,163.5),"水帘洞");
map.put(new Student("牛魔王",28,183.5),"牛头山");
System.out.println(map);
}
}
2.2.8补充知识:集合的嵌套
案例
示例
Test
package com.itheima.d12_collection_nesting;
import java.util.*;
/*目标:理解集合的嵌套.
江苏省="南京市","扬州市","苏州市","无锡市","常州市"
湖北省="武汉市","孝感市","十堰市","宜昌市","鄂州市"
河北省="石家庄市","唐山市","邢台市","保定市","张家口市"
*/
public class Test {
public static void main(String[] args) {
//1、定义一个Map集合存储全部的省份信息,和其对应的城市信息.
Map<String, List<String>> map =new HashMap<>();
List<String> cities=new ArrayList<>();
Collections.addAll(cities,"南京市","扬州市","苏州市","无锡市","常州市");
map.put("江苏省",cities);
List<String> cities1=new ArrayList<>();
Collections.addAll(cities1,"武汉市","孝感市","十堰市","宜昌市","鄂州市");
map.put("湖北省",cities1);
List<String> cities2=new ArrayList<>();
Collections.addAll(cities2,"石家庄市","唐山市","邢台市","保定市","张家口市");
map.put("河北省",cities2);
System.out.println(map);
List<String> city=map.get("湖北省");
for (String s : city) {
System.out.println(city);
}
map.forEach((p,c)->{
System.out.println(p+"===>"+c);
});
}
}
2.3JDK8新特性:Stream
2.3.1认识Stream
什么是Stream
- 也叫Stream流,是Jdk8开始新增的一套API(java.util.stream.*),可以用于操作集合或者数组的数据.
- 优势:Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好.
案例
Stream流的使用步骤
示例:
Test1
package com.itheima.d13_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
//目标:初步体验Stream流的方便与快捷
public class Test1 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三丰", "张无忌", "周芷若", "赵傲", "张强");
System.out.println(names);
//names=[张三丰,张无忌,周花若,赵敏,张强]
//找出姓张,且是3个字的名字,存入到一个新集合中去.
List<String> list1= new ArrayList<>();
for (String name : names) {
if (name.startsWith("张") && name.length() == 3) {
list1.add(name);
}
}
System.out.println(list1);
//开始进行使用Stream流来解决这个问题
List<String> list2=names.stream().filter(s->s.startsWith("张"))
.filter(a->a.length()==3).collect(Collectors.toList());
System.out.println(list2);
}
}
2.3.2常用方法
1.获取Stream流
-
获取集合的Stream流,
-
获取数组的Stream流
2.Stream流常见的中间方法 -
中间方法指的是调用完成后会返回新的Stream流,可以继续使用(支持链式编程).
3.Stream流常见的终结方法 -
终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了.
-
收集Stream流:就是把Streami流操作后的结果转回到集合或者数组中去返回.
-
Stream流:方便操作集合/数组的手段;集合/数组:才是开发中的目的.
示例:
Student
package com.itheima.d13_stream;
import java.util.Objects;
public class Student {
private String name;
private int age;
private double height;
public Student() {
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", height=" + height +
'}';
}
public Student(String name, int age, double height) {
this.name = name;
this.age = age;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}
Test2
package com.itheima.d13_stream;
import java.util.*;
import java.util.stream.Stream;
import static javax.swing.UIManager.put;
public class Test2 {
public static void main(String[] args) {
//1、如何获取List集合的Stream流?
List<String> names =new ArrayList<>();
Collections.addAll(names,"张三丰","张无忌","周芷若","赵敏","张强");
Stream<String> stream = names.stream();
//2、如何获取Set集合的Stream流?
Set<String> set =new HashSet<>();
Collections.addAll(set,"刘德华","张曼玉","蜘蛛精","马德","德玛西亚");
Stream<String> stream1 = set.stream();
stream1.filter(s->s.contains("德")).forEach(s-> System.out.println(s));
//3、如阿获取Map集合的Stream流?
Map<String,Double> map =new HashMap<>();
map.put("古力娜扎",172.3);
map.put("迪丽热巴",168.3);
map.put("马尔扎哈",166.3);
map.put("卡尔扎巴",168.3);
//方法一 只能处理键
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();
//方法二 只能处理值
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
//方法三,处理键值
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e->e.getKey().contains("巴"))
.forEach(e-> System.out.println(e.getKey()+"===>"+e.getValue()));
//4、如阿获取数组的Stream流?
String[]names2={"张翠山","东方不败","唐大山","独孤求败"};
Stream<String> s2 = Arrays.stream(names2);
Stream<String> s3 = Stream.of(names2);
}
}
Test3
package com.itheima.d13_stream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
//目标:掌挥Stream流提供的常见中间方法,
public class Test3 {
public static void main(String[] args) {
List<Double> scores = new ArrayList<>();
Collections.addAll(scores,88.5,100.0,60.0,99.0,9.5,99.6,25.0);
//需求1:找出成绩大于等于60分的数据,并升序后,再输出.
scores.stream().filter(s->s>=60).sorted().forEach(s-> System.out.println(s));
System.out.println("-----------------------------------");
List<Student>students = new ArrayList<>();
Student s1=new Student("蜘蛛精",26,172.5);
Student s2=new Student("蜘蛛精",26, 172.5);
Student s3=new Student("紫霞",23, 167.6);
Student s4=new Student("白晶晶", 25,169.0);
Student s5=new Student("牛魔王",35, 183.3);
Student s6=new Student("牛夫人",34, 168.5);
Collections.addAll(students,s1,s2,s3,s4,s5,s6);
//需求2:找出年龄大于等于23,且年龄小于等于30岁的学生,并按照年龄降序输出
students.stream().filter(s->s.getAge()>=23&&s.getAge()<=30)
.sorted((o1, o2) -> o2.getAge()- o1.getAge())
.forEach(s-> System.out.println(s));
System.out.println("-----------------------------------");
//需求3:取出身高最高的前3名学生,并输出.
students.stream().sorted(((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight())))
.limit(3).forEach(System.out::println);
System.out.println("-----------------------------------");
//需求4:取出身高倒数的2名学生,并输出.
students.stream().sorted(((o1, o2) -> Double.compare(o2.getHeight(), o1.getHeight())))
.skip(students.size()-2).forEach(System.out::println);
//需求5:找出身高超过168的学生叫什么名字,要求去除重复的名字,再输出.
students.stream().filter(s->s.getHeight()>168).map(s->s.getName())
.distinct().forEach(System.out::println);
//distinct去重复,自定义类型的对象(希望内容一样就重复),重写hashCode,equals
students.stream().filter(s->s.getHeight()>168)
.distinct().forEach(System.out::println);
Stream<String> st1 = Stream.of("张三", "李四");
Stream<String> st2= Stream.of("张三2", "李四2","王五");
Stream<String> allSt = Stream.concat(st1, st2);
allSt.forEach(System.out::println);
}
}
Test4
package com.itheima.d13_stream;
import java.util.*;
import java.util.stream.Collectors;
//目标:了解Stream终结方法
public class Test4 {
public static void main(String[] args) {
List<Student> students = new ArrayList<>();
Student s1=new Student("蜘蛛精",26, 172.5);
Student s2=new Student("蜘蛛精",26, 172.5);
Student s3=new Student("紫霞",23, 167.6);
Student s4=new Student("白晶晶",25, 169.0);
Student s5=new Student("牛魔王", 35, 183.3);
Student s6=new Student("牛夫人", 34, 168.5);
Collections.addAll(students,s1,s2,s3,s4,s5,s6);
//需求1:请计算出身高超过168的学生有几人.
long size=students.stream().filter(s->s.getHeight()>168 ).count();
System.out.println(size);
//需求2:请找出身高最高的学生对象,并输出.
Student s=students.stream().max((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(s);
//需求3:请找出身高最矮的学生对象,并输出.
Student ss=students.stream().min((o1, o2) -> Double.compare(o1.getHeight(), o2.getHeight())).get();
System.out.println(ss);
//需求4:请找出身高超过170的学生对象,并放到一个新集合中去返回
//重点:流只能收集一次,不能把这个代码提前:students.stream().filter(a->a.getHeight()>170)
List<Student> students1=students.stream().filter(a->a.getHeight()>170).collect(Collectors.toList());
System.out.println(students1);
Set<Student> students2=students.stream().filter(a->a.getHeight()>170).collect(Collectors.toSet());
System.out.println(students2);
//需求5:请找出身高超过170的学生对象,并把学生对象的名子和身高,存入到一个州a即集合返回.
Map<String, Double> map=students.stream().filter(a->a.getHeight()>170)
.distinct().collect(Collectors.toMap(a->a.getName(), a->a.getHeight()));
System.out.println(map);
// Object[] arr=students.stream().filter(a->a.getHeight()>170).toArray();
Student[] arr=students.stream().filter(a->a.getHeight()>170).toArray(b->new Student[b]);
System.out.println(Arrays.toString(arr));
}
}