赵阳来软帝学习的第二周
day7 - 复习与测试
数组
- 1.数组:用于存储一组相同数据类型的数据结构
- 2.每个数组都有一个length属性
- 3.数组中每个元素都存在一个独一无二的索引,索引从0开始到length-1
- 4.一旦获取的元素超出了索引范围则出现ArrayIndexOutOfBounds
- 5.获取数组中所有元素使用循环遍历
面向对象基础
类(Class)和对象(Object)
类
对一类具备相同特征事物的抽象描述,相同特征体现在属性和行为上,比如 People类具备name,age等属性,同时也具备study,play等行为,所以在java中可以使用如下方式表达
public class People{
private String name;
private int age;
public void study(){
System.out.pritnln("学习java");
}
public void play(){
System.out.println("玩游戏");
}
}
对象
对象是类的一个实例,java中通过new关键字创建一个对象,获得对象后可以通过对象调用类中的属性(成员变量)和行为(方法),对象创建示例:
People p = new People();
构造器
面向对象进阶
封装
继承
多态
抽象类与接口
内部类
异常处理
常用类
网站推荐
- 牛客网
- leetcode
- github/gitee
day8 - java8新增日期类&正则表达式&泛型
Java8新增日期处理类
由于java.util.Date和java.util.Calendar类设计上的缺陷,例如输出格式,月份从0开始等问题,从Java8开始新增了java.time包用于对时间日期进行处理,其中常用的类包含以下几个:
- LocalDate
- LocalTime
- LocaDateTime
- 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是线程非安全实现,因此在多线程并发时,需要为每个线程单独创建该实例。
正则表达式(选学)
正则表达式最早起源于perl语言,内部包含一种特殊的字符串格式能够对特定的字符串内容进行解析,替换,查找等等功能,比如判断一个字符串是否是手机号,会使用如下正则表达式:
- ^1[356789]\d{9}$
正则表达式是独立于语言的第三方技术(跟使用的编程语言无关);目前几乎所有的编程语言都支持正则表达式。
正则表达式是一门十分强大的表达式语言,在很多实际场景中常见,比如:填写表达式验证输入内容的格式(手机号格式,邮箱地址格式),微博的话题,聊天信息的表情提取,敏感词的过滤,网络爬虫;正则表达式的使用场景分为三种:
- 查找
- 替换
- 验证
java中使用正则表达式一下以下几个类
- java.lang.String
- java.util.regex.Pattern
- java.util.regex.Matcher
String类中哪些方法使用到了正则表达式:
- split
- replace
- matches
public class RegexDemo {
public static void main(String[] args) {
System.out.println("15456543212".matches("^1\\d{10}$"));
//任何一个字符串实际都是一个正则表达式
System.out.println("a".matches("a"));
//匹配数字
System.out.println("1".matches("[0-9]"));
System.out.println("1".matches("\\d"));
//匹配非数字
System.out.println("0".matches("\\D"));
System.out.println("%".matches("[^0-9]"));
//验证输入的内容只能是abc中的一个字母?
System.out.println("b".matches("[abc]"));
//验证内容是否是小写的单词
System.out.println("f".matches("[a-z]"));
//验证内容只能是数字和字母
System.out.println("$".matches("[0-9A-Za-z]"));
System.out.println("0".matches("\\w"));
//判断文本内容是否是换行标记
System.out.println("\n".matches("\\n"));
//匹配空白字符(\n \r \t)
System.out.println("\r".matches("\\s"));
System.out.println("lo".matches("lo\\b"));
}
}
注意事项:
java中的所有正则表达式在正常工作之前,都需要先编译,编译的过程通过Pattern类实现
Pattern & Matcher
在日常使用正则表达式时,String中matches方法虽然能够满足部分需求(匹配),但是针对一些特殊需求,比如说,筛选等功能不能实现,因此,jdk中针对正则表达式的使用还提供了两个类:
- Pattern :提供针对正则表达式的编译功能
- Matcher:提供对已经编译过的正则表达式匹配字符串的功能
Pattern和Matcher使用
String s = "asdiu1323479827348917612312312318912314213131231231sajffasdf";
//对正则表达式编译获取匹配模式对象
Pattern p = Pattern.compile("1\\d{10}");
System.out.println(p.pattern());
//使用匹配模式对指定制服穿进行匹配获取匹配器
Matcher m = p.matcher(s);
//将字符串序列跟整个模式匹配
System.out.println(m.matches());
//从匹配器中寻找符合匹配模式的字符串,如果能找到则返回true
while(m.find()) {
//返回匹配的字符串内容
String phone = m.group();
System.out.println(phone);
}
//运行结果:
//13234798273
//17612312312
//18912314213
group
public static void main(String[] args) {
String score="109:89/99-120";
String regex = "((\\d{1,3}):(\\d{1,3}))/((\\d{1,3})-(\\d{1,3}))";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(score);
System.out.println(m.groupCount());
//开始查找
m.find();
//整组匹配
System.out.println(m.group());
System.out.println(m.group(0));
//按照分组匹配
System.out.println(m.group(1));
System.out.println(m.group(2));
System.out.println(m.group(3));
System.out.println(m.group(4));
System.out.println(m.group(5));
System.out.println(m.group(6));
//现有3个球队比分,在一个字符串中表示,现要求从以下比分中别获取每场比分的结果
//并获取每个队伍的分数,分数不能从0开始
// 100:66/89:98/77:65
}
泛型(Generic)
泛型即参数化类型,在JDK1.5之后引入了泛型的概念,参数化类型即将一种数据类型以参数形式传递到类,接口或者方法中,可以将对于数据类型的检查从运行期间提前到编译期,java中的泛型使用分为三种情况:
- 泛型接口
- 泛型类
- 泛型方法
泛型标记命名是可以自定义的,JDK对一些常见的泛型有以下定义:
- T 类型(Type)
- E 元素(Element)
- K 键(Key)
- V 值(Value)
泛型类
在类声明时在后面通过<>
符号指定类型参数,然后在类中可以使用这些类型参数设置动态类型
public class MyCalc<I,D,F> {
public I add1(I i1,I i2) {
return null;
}
public D add2(D d1,D d2) {
return null;
}
public F add3(F f1,F f2) {
return null;
}
public static void main(String[] args) {
//创建对象时指定数据类型
MyCalc<Integer,Double,Float> mc = new MyCalc<>();
//方法调用时需要根据前面确定好的类型传入对应类型的参数
mc.add1(10, 20);
mc.add2(3.4, 4.5);
mc.add3(1.22f, 2.22f);
}
}
泛型接口
在声明接口是设置参数化类型,在实现类对接口实现时动态指定数据类型,从而将数据类型在编译期间提前确定,减少在运行期间的数据类型转换操作
例如:
public interface BaseManager<T> {
boolean add(T obj);
boolean delete(T obj);
boolean update(T obj);
T findById(int id);
ArrayList<T> findAll();
}
对应的不同实现
public class EmpManager implements BaseManager<Emp>{
@Override
public boolean add(Emp obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean delete(Emp obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean update(Emp obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public Emp findById(int id) {
// TODO Auto-generated method stub
return null;
}
@Override
public ArrayList<Emp> findAll() {
// TODO Auto-generated method stub
return null;
}
}
public class DeptManager implements BaseManager<Dept>{
@Override
public boolean add(Dept obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean delete(Dept obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean update(Dept obj) {
// TODO Auto-generated method stub
return false;
}
@Override
public Dept findById(int id) {
// TODO Auto-generated method stub
return null;
}
@Override
public ArrayList<Dept> findAll() {
// TODO Auto-generated method stub
return null;
}
}
泛型方法
泛型方法即在声明方法的时候可以设定参数化类型,然后使得可以通过方法传递任意类型的参数,以及返回任意类型的返回值
public class MethodDemo {
/**
* 泛型方法
* @param <T>
* @param s
* @return
*/
public <T> T query(Class<T> t,String s) {
return null;
}
public static void main(String[] args) {
MethodDemo md = new MethodDemo();
Emp emp = md.query(Emp.class, "");
Dept dept = md.query(Dept.class, "");
String s = md.query(String.class, "");
}
}
上下边界限定super&extends
JVM没有泛型的概念,泛型只存在于编译器编译期间,java实现泛型使用的是类型擦除,在编译期间由编译器识别泛型,并做出处理,到JVM之前或擦除所有的类型,例如:
ArrayList<String> list1 = new ArrayList<>();
list1.add("rose");
list1.add("jack");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(10);
list2.add(100);
System.out.println(list1.getClass() == list2.getClass());//true
System.out.println(list1.getClass());
System.out.println(list2.getClass());
ArrayList<CharSequence> list3 = list; //编译错误
对以上的需求,如果需要成立时:
ArrayList<CharSequence> list3 = list;
泛型中引入统配符的概念:
- ?
- <? extends T>
- <? super T>
具体使用如下:
public class Demo {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
list1.add("rose");
list1.add("jack");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(10);
list2.add(100);
//上界限定:只能取值不能存值(只出不进)
ArrayList<? extends Number> list3 = list2;
// list3.add(10);//编译错误
Number n = list3.get(0);
System.out.println(n);
//下届限定:只能存值不能取值(只进不出)
ArrayList<? super Number> list4 = new ArrayList<>();
list4.add(new Integer(10));
list4.add(new Double(3.4));
Object obj = list4.get(0);
System.out.println(obj);
}
}
练习
- 统计从开始上课以来,到目前为止,一共写了多少行有效代码?有多少行注释?有多少空行?有多少个字符
- 文件递归遍历
- 文件过滤(找所有Java文件)
- 读取每个文件的内容
- 使用正则表达式匹配换行
package homework0710;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 代码统计工具
*
* @author mrchai
*/
public class CodesStatistics {
/** 存储所有java文件所在路径集合 */
private List<String> javaFiles;
private Pattern patternLines;
private Pattern patternWhiteSpaceLines;
private Pattern patternCommentLines;
{
// 匹配行表达式
patternLines = Pattern.compile("\\n");
// 匹配空白行表达式
patternWhiteSpaceLines = Pattern.compile("\\n[\\s| ]*\\n");
// 匹配注释表达式
patternCommentLines = Pattern.compile("(//)|(/\\*)|(\\*)");
}
public CodesStatistics(File baseDir) {
// 容器初始化
javaFiles = new ArrayList<String>();
// 读取Java文件到集合
readJavaFilesToList(baseDir);
// 文件总数
System.out.println("java文件总数:" + javaFiles.size() + "个");
}
/**
* 读取所有Java文件到集合中
*
* @param dir
*/
public void readJavaFilesToList(File dir) {
File[] files = dir.listFiles();
if (files != null) {
for (int i = 0; i < files.length; i++) {
File f = files[i];
if (f.isFile() && isJavaFile(f)) {
// 将文件绝对路径添加到集合
javaFiles.add(f.getAbsolutePath());
} else {
// 对目录递归
readJavaFilesToList(f);
}
}
}
}
/**
* 判断指定文件是否是Java文件
*
* @param f
* @return
*/
public boolean isJavaFile(File f) {
return f.getName().toLowerCase().endsWith(".java");
}
/**
* 匹配所有代码行
*
* @return
*/
public int countLines() {
int count = 0;
for (int i = 0; i < javaFiles.size(); i++) {
String content = FileUtils.readToString(javaFiles.get(i));
Matcher matcher = patternLines.matcher(content);
while (matcher.find()) {
count++;
}
}
return count;
}
/**
* 匹配空白代码行
*
* @return
*/
public int countWhiteSpaceLines() {
int count = 0;
for (int i = 0; i < javaFiles.size(); i++) {
String content = FileUtils.readToString(javaFiles.get(i));
Matcher matcher = patternWhiteSpaceLines.matcher(content);
while (matcher.find()) {
count++;
}
}
return count;
}
/**
* 匹配注释代码行
*
* @return
*/
public int countCommentLines() {
int count = 0;
for (int i = 0; i < javaFiles.size(); i++) {
String content = FileUtils.readToString(javaFiles.get(i));
Matcher matcher = patternCommentLines.matcher(content);
while (matcher.find()) {
count++;
}
}
return count;
}
/**
* 有效代码行
*
* @return
*/
public int countValidLines() {
return countLines() - countWhiteSpaceLines() - countCommentLines();
}
public static void main(String[] args) {
File dir = new File("D:\\项目资料\\教学项目\\java\\j2003");
CodesStatistics cs = new CodesStatistics(dir);
int lines1 = cs.countLines();
System.out.println("总代码行数:" + lines1);
int lines2 = cs.countWhiteSpaceLines();
System.out.println("空白代码行数:" + lines2);
int lines3 = cs.countCommentLines();
System.out.println("注释代码行数:" + lines3);
int lines4 = cs.countValidLines();
System.out.print("有效代码行数:" + lines4);
}
}
day10 - 集合框架(二)
集合排序与Collections类
集合排序(续)
在对集合排序时除了使用java.util.Comparator接口自定义比较规则外,还可以通过java.lang包中提供的Comparable接口,针对需要实现排序的类型对该接口实现之后,并实现内部的compareTo方法可以自定义排序规则,最后通过Collections提供sort方法将需要排序的集合(List)传入即可。
具体使用方式:
-
声明需要具备排序能力实体类,实现Comparable接口
/** * 实现Comparable接口后可以使用Collections.sort()实现自然排序 */ public class Student implements Comparable<Student>{ private int sno; private String name; private int age; private String clz; public Student(int sno, String name, int age, String clz) { super(); this.sno = sno; this.name = name; this.age = age; this.clz = clz; } public int getSno() { return sno; } public void setSno(int sno) { this.sno = sno; } 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 String getClz() { return clz; } public void setClz(String clz) { this.clz = clz; } /** * 实现比较规则 */ @Override public int compareTo(Student s) { //根据年龄排序 return this.age - s.age; } @Override public String toString() { return "Student [sno=" + sno + ", name=" + name + ", age=" + age + ", clz=" + clz + "]"; } }
-
实现排序
public class TestStudent { public static void main(String[] args) { List<Student> list = new ArrayList<>(); list.add(new Student(1,"kobe",44,"1班")); list.add(new Student(4,"wade",22,"5班")); list.add(new Student(10,"jeffry",35,"3班")); list.add(new Student(9,"tom",21,"2班")); list.add(new Student(3,"curry",33,"1班")); list.add(new Student(2,"allen",40,"2班")); list.add(new Student(5,"grant",19,"5班")); //集合中的元素对应类型必须实现Comparable接口 Collections.sort(list); // Arrays.sort(arr) list.forEach(s->System.out.println(s)); } }
执行结果:
Student [sno=5, name=grant, age=19, clz=5班] Student [sno=9, name=tom, age=21, clz=2班] Student [sno=4, name=wade, age=22, clz=5班] Student [sno=3, name=curry, age=33, clz=1班] Student [sno=10, name=jeffry, age=35, clz=3班] Student [sno=2, name=allen, age=40, clz=2班] Student [sno=1, name=kobe, age=44, clz=1班]
Collections
Collections来自于java.util包中的一个实体类,一般用作于对集合进行常规处理,比如排序,查找,交换,集合生成,转换等;Collections接口只提供了静态方法,常见的Collections方法:
public class CollectionsDemo {
// 二叉树:红黑树,b+树,avl树
// 20 45 3 7 6 9 22
// 3 6 7 9 20 22 45
// 6
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//向指定的集合(Collection)中添加元素(动态数组)
Collections.addAll(list, "hello","rundy","softeem","world");
System.out.println(list);
//从一组有序的集合中搜索目标元素的所在位置
int i = Collections.binarySearch(list, "softeem");
System.out.println(i);
// List list2 = Collections.emptyList();
//获取集合中的最大值(按自然排序规则)
String max = Collections.max(list);
System.out.println(max);
//根据提供的排序规则从集合中需要匹配规则的最大值
max = Collections.max(list,(s1,s2)->s1.length()-s2.length());
System.out.println(max);
//反转集合
Collections.reverse(list);
System.out.println(list);
//将集合中元素打乱顺序
Collections.shuffle(list);
System.out.println(list);
//将集合中的元素按照提供的排序比较器进行排序
Collections.sort(list, (s1,s2)->s1.compareTo(s2));
//将当前非线程安全的集合转换为线程安全的实现
list = Collections.synchronizedList(list);
System.out.println(list);
}
}
Collection、Collections、Connection区别?
Collection是JDK1.2之后新增的集合框架的顶层接口
Collections是一个用于对集合进行处理的工具类
Connection是在JDBC中用于操作数据的一个连接接口
基于Pinyin4J实现中文排序
对中文内容排序需要使用到一个开源插件(jar包)-Pinyin4j
- pinyin4j-2.5.0.jar
使用流程
- 将jar文件复制到项目根目录下(推荐创建lib目录,并放到lib目录中)
- 将jar文件添加到构建路径
示例代码:
String s = "软帝";//ruan3di4
//将指定的中文字符转换为字符串数组(中文字符的多种读音的拼音表达形式)
String[] py = PinyinHelper.toHanyuPinyinStringArray(s.charAt(0));
for (String c : py) {
System.out.println(c); //ruan3
}
//声明字符串用于临时存储转换之后汉语拼音字符串
String str = "";
//遍历获取每一个字符
for (int i = 0; i < s.length(); i++) {
String[] info = PinyinHelper.toHanyuPinyinStringArray(s.charAt(i));
//将每个汉字的第一个拼音读音拼接到一起
str += info[0];
}
System.out.println(str);//ruan3di4
中文排序示例:
List<String> names = new ArrayList<>();
names.add("柴老师");
names.add("王老师");
names.add("苍老师");
names.add("老王");
names.add("老李");
names.add("小泽老师");
Collections.sort(names,(s1,s2)->{
String p1 = "";
String p2 = "";
for(int i = 0;i<s1.length();i++) {
p1 += PinyinHelper.toHanyuPinyinStringArray(s1.charAt(i))[0];
}
for(int i = 0;i<s2.length();i++) {
p2 += PinyinHelper.toHanyuPinyinStringArray(s2.charAt(i))[0];
}
return p1.compareTo(p2);
});
System.out.println(names);
运行结果:
[苍老师, 柴老师, 老李, 老王, 王老师, 小泽老师]
Set
Set集合也是从Collection扩展而来的子集合,相比List集合来说,Set集合内部存储的数据不是按照添加顺序存储,内部不允许出现重复的元素(不允许出现e1.equals(e2)),对null也只允许出现一个,对于set集合常用的实现主要有以下:
- HashSet(哈希表-散列表)
- TreeSet(二叉树-红黑树)
- LinkedHashSet
HashSet
HashSet是针对于Set集合的实现,内部基于HashMap的实现,在进行元素存储时,会对每一个存储的元素进行hash(),获取一个哈希地址,通过该hash地址可以找到对象在内存的位置,因此存储方式跟具体得出的hash值有关系,因此存储顺序可能会跟添加顺序不一致。
public class SetDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("helloworld");
list.add("rose");
list.add("helloworld");
list.add("rose");
System.out.println(list);
Set<String> set = new HashSet<String>();
set.add("admin");
set.add("hello");
set.add("lily");
set.add("admin");
System.out.println(set);
Set<User> users = new HashSet<User>();
users.add(new User(1, "admin"));
users.add(new User(2, "softeem"));
users.add(new User(1, "admin"));
System.out.println(users);
//语法糖
for(String s:set) {
System.out.println(s);
// if(s.equals("lily")) {
// set.remove(s);
// }
}
//获取set集合的迭代器
Iterator<String> it = set.iterator();
while(it.hasNext()) {
String s = it.next();
System.out.println(s);
if(s.equals("lily")) {
//对集合迭代时需要删除元素只能通过迭代器自生的remove方法
//否则就会出现ConcurrentModificationException
it.remove();
}
}
System.out.println(set);
//根据提供的Collection对象构建一个HashSet
Set<String> set2 = new HashSet<>(list);
System.out.println(set2);
}
}
TreeSet
TreeSet集合是基于TreeMap的Set集合实现,内部元素的存储根据元素的自然顺序(存储的元素需要实现Comparable接口)排序;由于元素是自然顺序排序的实现,因此需要保证集合内部存储的元素必须都是相同的数据类型。
使用方式:
public class TreeSetDemo {
public static void main(String[] args) {
Set set = new TreeSet();
set.add("hello");
// set.add(100); //程序执行到这一步时会出现ClassCastException
set.add(true);
/*
* TreeSet使用注意事项:
* 1.内部存储的元素必须是相同的数据类型
* 2.元素对应的数据类型应该实现过Comparable接口
*/
Set<User> users = new TreeSet<>();
users.add(new User(3, "rundy"));
users.add(new User(1, "admin"));
users.add(new User(2, "softeem"));
System.out.println(users);
}
}
对于以上的集合Set<User> users = new TreeSet<>();
集合内部存储的都是User类型数据,并且User类需要实现java.lang.Comparable
接口,TreeSet依据此排序实现对元素的存储进行排序;如果多个元素排序结果一致,则TreeSet会去除重复元素,例如:
public class User implements Comparable<User>{
private int id;
private String name;
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + "]";
}
@Override
public int compareTo(User u) {
return this.id-u.id;
}
}
当多个用户的id一致时,TreeSet会根据compareTo方法判定元素为重复,此时会去除重复:
Set<User> users = new TreeSet<>();
users.add(new User(3, "rundy"));
users.add(new User(1, "admin"));
users.add(new User(1, "softeem"));
System.out.println(users);
//运行结果
[User [id=1, name=admin], User [id=3, name=rundy]]
使用Compartor
如果被添加到TreeSet中的元素没有实现Comparable接口也可以,此时只需要在创建TreeSet时通过以下构造器,传入排序比较器对象即可:
- TreeSet(Comparator<? super E> c)
实现如下:
//User无需实现Comparable接口
//构造TreeSet时传入排序比较器
Set<User> set = new TreeSet<>((u1,u2)->u1.getId()-u2.getId());
set.add(new User(3, "rundy"));
set.add(new User(1, "admin"));
set.add(new User(2, "softeem"));
System.out.println(set);
注意事项:
- 由于Set集合存储元素的”无序“(不按照添加顺序存储),因此元素没有索引的概念,在获取元素的时候只能通过迭代器迭代。
- HashSet和TreeSet区别
- HashSet是基于哈希表的实现,元素的存储顺序也是按照哈希地址排序,因此是否重复由哈希值确定
- TreeSet是基于二叉树-红黑树实现,元素的存储顺序根据元素实现的Comparable接口中的comparaTo方法的实现排序,是否重复也是根据排序值判定
- TreeSet使用要求:
- 集合中元素必须是同一种数据类型
- 要么让内部元素实现Comparable接口,要么创建TreeSet时提供排序比较器(Comparator)
List集合和Set集合区别
List集合是一有序的集合,内部元素存储顺序跟添加的一致,List集合允许重复元素;Set反之;
Map(映射)
Map也称之为映射,内部的元素是以键值对为结构存储,可以通过key获取map中的value;Key不允许重复;但是值是可以重复的,Map集合有一些常见的实现:
- HashMap
- TreeMap
- ConcurrentHashMap
在Map接口出现之前,JDK中存在类似键值对结构:
- Dictionary
- Properties
- Hashtable
HashMap
HashMap是Map集合一个实现,内部是基于哈希表的排序实现元素存储的,实现原理是链表+数组(JDK8之后引入红黑树)
public class HashMapDemo {
public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("d", "davied");
map.put("a", "allen");
map.put("c", "allen");
map.put("b", "bob");
System.out.println(map);
Map<String, Object> user = new HashMap<>();
user.put("id", 10);
user.put("name", "狗蛋");
user.put("birth", LocalDate.of(1999, 11, 11));
System.out.println(user);
// 根据键获取值
Object name = user.get("name");
System.out.println(name);
// 如何遍历Map集合
// 获取Map集合键值对的Set集合(Map转换成Set)
// Set<Map.Entry<String, Object>> set = user.entrySet();
// for (Entry<String, Object> e : set) {
// System.out.println(e.getKey()+"-----"+e.getValue());
// }
// 获取map集合的键集
Set<String> keys = user.keySet();
for (String k : keys) {
// 根据键获取值
Object v = user.get(k);
System.out.println(k + "/" + v);
}
//移除指定键对应的元素
user.remove("id");
//修改指定键对应的值
user.replace("name", "隔壁老王");
System.out.println(user);
//获取集合中元素的个数
System.out.println(user.size());
}
}
TreeMap
Treemap的实现是基于二叉排序树中的红黑树实现,所存储的元素要求键必须是同一种数据类型,并且有实现过Comparable接口(或者创建TreeMap时指定排序比较器)
public class TreeMapDemo {
public static void main(String[] args) {
Map<Object, Object> map = new TreeMap<>();
map.put("v", "vaen");
map.put("a", 3.14);
map.put("n", "true");
map.put(new String("cc"), 'c');
System.out.println(map);
Map<Stu, Object> map2 = new TreeMap<Stu, Object>(new Comparator<Stu>{
public int compara(Stu s1,Stu s2){
return s1.getAge() - s2.getAge();
}
});
map2.put(new Stu(10,"孙悟空"), 18000);
map2.put(new Stu(15,"猪八戒"), 28000);
map2.put(new Stu(8,"唐僧"), true);
System.out.println(map2);
}
}
HashMap、Hashtable、TreeMap的区别
- HashMap是基于hash算法的实现,内不通过数组结合链表实现,允许空键值存在,是线程不同步的实现
- TreeMap是基于红黑树的实现,内部元素的存储顺序是由自然顺序对键进行排序之后存储,是线程不同步的实现
- Hashtable是从老式的Dictionary类继承而来,内部的实现原理跟HashMap一样,不允许空键值存在,实现线程同步的实现,运行效率较低
java.util.concurrent.ConcurrentHashMap(并发编程)
day12 - JDK新特性专题
java5协变
协变:JDK1.5新增特性;要求子类重写方法的返回值类型必须跟父类方法的返回值一致,或者是父类返回值的子类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2n80PEJ9-1594644469576)(assets\1594429335200.png)]
Java7Objects类
Objects类是java7中新增的一个针对java对象提供的空指针安全相关操作
public class ObjectsDemo {
public static void main(String[] args) {
String s1 = new String("hello");
String s2 = null;
//在使用equals时,通常将确定不为null的对象放到前面
// System.out.println(s2.equals(s1));
//空指针安全的比较
System.out.println(Objects.equals(s1, s2));
// System.out.println(s2.hashCode());
System.out.println(Objects.hashCode(s2));
System.out.println(s2 == null);
System.out.println(Objects.isNull(s2));
System.out.println(s2 != null);
System.out.println(Objects.nonNull(s2));
// System.out.println(Objects.requireNonNull(s2));
// System.out.println(s2.toString());
System.out.println(Objects.toString(s2));
}
}
Java8函数式接口与Lambda表达式
Java这门语言是面向对象的,因此在执行一些方法时候如果需要传递参数,这些参数也只能是基本类型数据,或者引用类型;而引用类型是以对象形式存在的;但是实际的需求可能是需要将一个方法作为参数传递到其他方法中,在java8之前只能通过匿名内部类的形式进行传递,例如:
public interface Fightable {
void fight(int power);
}
public class TestFightable {
public void attack(Fightable f,int p) {
f.fight(p);
}
public static void main(String[] args){
TestFightable tf = new TestFightable();
tf.attack(new Fightable(){
public void fight(int power){
//具体实现
}
},100);
}
}
由于以上代码的冗余,以及可读性问题,所以在java8中新增函数式接口以及lamda表达式,对以上操作进行简化:
TestFightable tf = new TestFightable();
tf.attack(p->System.out.println("攻击"),100);
从以上的实现来看,代码得到大量简化;但是使用以上表达形式有一些要求:
函数式接口
函数式接口(@FunctionalInterface),所表示的是接口中只能存在一个未实现的方法:
@FunctionalInterface
public interface Calc {
int minus(int a,int b);
}
@FunctionalInterface 用于标注当前接口是一个函数式接口
Lamda表达式
lamda表达式是一种简约的流式编程表达形式(js中称之为箭头函数),语法形式:
() -> {}
- () 表示需要实现的方法参数列表
- -> 指向方法的实现
- {} 方法的具体实现
基本写法
(int a,int b)-> {
int c = a + b;
return c;
}
- 另外编译器也会根据上下文自动推断参数类型,因此以上代码可以继续简化
(a,b) ->{
int c = a + b;
return c;
}
- 如果需要实现的方法内部只有一行代码需要执行,则可以如下表示
(a,b) -> a + b;
- 如果方法只有一个参数时,可以简化为:
a -> System.out.println(a);
lamdab表达式就是对匿名内部类的简化写法
函数式接口以及lambda表达式的存在,打开了Java通向函数式编程的大门!!!
Java8流式API简介
StreamAPI概述
流式编程作为Java 8的亮点之一,是继Java 5
之后对集合的再一次升级,可以说Java 8
几大特性中,Streams API
是作为Java 函数式的主角来设计的,夸张的说,有了Streams API
之后,万物皆可一行代码。
什么是Stream
Stream
被翻译为流,它的工作过程像将一瓶水导入有很多过滤阀的管道一样,水每经过一个过滤阀,便被操作一次,比如过滤,转换等,最后管道的另外一头有一个容器负责接收剩下的水。
示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3yRz4C28-1594644469580)(D:\课程课件\java\java基础&高级\assets\1594400609467.png)]
首先通过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使用通常分为三个步骤:
- 获取Stream
- 中间处理
- 结尾处理
获取Stream
可以用如下方法打开一个 Stream
:
- 使用
Collection
子类的stream()
(串行流)或parallelStream()
- 使用
Arrays.stream()
方法为数组创建一个流 - 使用
Stream.of()
方法创建流 - 使用
Stream.iterate()
方法创建流 - 使用
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})
//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(), "男"));
以上操作表示过滤出所有性别为”男“的学生;
上述代码等效于:
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. 结尾处理
结尾处理即意味着中间处理已完成,等待最终操作,比如收集,统计,迭代等操作。由于结果为最终执行项,因此不可重复调用不同的结尾处理方式,否则会出现异常:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oxC7Q4x4-1594644469583)(assets\1594455882631.png)]
-
迭代
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.结尾处理