目录
前言
Math
System
Runtime
BigInteger
BigDecimal
Object
时间类Api
数据包装类
Arrays
正则表达式
lambda表达式
集合进阶
java 大一的时候学的java基础,后来摆烂都忘了大部分 所以此笔记用于复习 在java中常用到的api 帮助我们解决·需求解决
math是java核心包lang下的类无需引入
常用方法:
Math.abs(-12)
System.out.println(Math.ceil(120.2));//121
System.out.println(Math.ceil(120.8));//121
System.out.println(Math.ceil(-12.2));//输出-12.0 不是-13
System.out.println(Math.floor(-13.2));//-14
System.out.println(Math.floor(13.2));//13
public static int max(int a, int b) {
return (a >= b) ? a : b;
}
底层逻辑类似
public static int min(int a, int b) {
return (a <= b) ? a : b;
}
-获取a的次幂 pow
System.out.println("5的2此方"+Math.pow(5,2));
System.out.println("5的-2此方"+Math.pow(5,-2));//1/5^2
System.out.println("5的1/2此方"+Math.pow(5,0.5));//更号5 但是不建议这样
System.out.println("------------------------");
System.out.println("跟更号4"+Math.sqrt(4));//开平方
System.out.println("8开立方根"+Math.cbrt(8));//立方根
System.out.println("0~1之间的随机数值"+Math.random());//[0,1.0)
System.out.println("0~100之间的随机整数值"+Math.floor(Math.random()*100));//[0,100)+1 去掉小数值+1 才是 【0,100】
该类也是java核心包中的无需引入
-停止当jvm的进程静态方法exit
System.exit(0);//0正常停止jvm虚拟机 非0异常停止
时间搓知识:时间搓 从1970 1月1日 0:0:0(时间原点)到现在的毫秒 我国在东八区
有8小时时差,所以是在标准时间搓的基础上加8小时时间毫秒值
System.out.println("当前时间搓:"+System.currentTimeMillis())
ps:该方法是获取当前虚拟机所在系统的时间搓,是在标准时间搓上已经获得计算的
int[] arr1 = new int[10];//空数组
int[] arr2={1,2,3,4,5,6,7,8,9};
/**ps:
* srcpos :数据源索引
* destpos :目标数组索引
* 最后一个参数是拷贝多长
* 细节:1.如果都是基本数据类型 ,俩者的数据类型必须相同
* 2.如果数据源素组和目标数组都是引用类型,那么子类可以赋值给父类 因为应用数据类型拷贝的是数据的内存地址 赋值也是
* person person1 = new person( "John",15);
* person person2 = new person();
* person2=person1;
* person2.age=20;
* System.out.println(person1.age); 此时输出person1的age属性值是20 相同的内存地址 修改了其他引用类使用的值也会发生改变
*/
System.arraycopy(arr2,0,arr1,0,arr2.length);
for (int i = 0; i < arr1.length; i++) {
System.out.println(arr1[i] );//原素组长度为10 比数据源长 所以长出的一位是10
}
Lang包下的java运行环境类
Runtime runtime = Runtime.getRuntime();
runtime.exit(0);
System.exit(0)方法关闭虚拟机的底层就是运行环境关闭
public static void exit(int status) {
Runtime.getRuntime().exit(status);
}
System.out.println("当先环境的运行线程数量:"+runtime.availableProcessors());
System.out.println("可以从当先操作系统中获取最多"+runtime.maxMemory()/Math.pow(1024,2)+"个mb内存");
System.out.println("当前内存消耗:"+runtime.totalMemory()/1024/1024+"MB")
System.out.println("当前虚拟机剩余空间大小:"+runtime.freeMemory()/1024/1024+"mb");//说明当前程序消耗内存4mb 消耗内存=totalmemory-freememory
try {
Runtime.getRuntime().exec("notepad");//打开系统自带记事本
} catch (IOException e) {
e.printStackTrace();
}
java中的整数 byte 1字节 short 2字 int4字节 long 8b 1b=8bit,所以long可以有64位二进制位存储数字 第一位符号位,但是当long的范围也不够用时 就可以使用 bitInteger上限接近无限
// 实际开发中用于初始化随机数
BigInteger bigInterger = new BigInteger(4, new Random());// Random对象 范围是 【0,2】 , 这里是实例化一个 (pram1的parm2幂此方)-1 范围的随机数 这里是0~15的随机数
2.第二种构造方法 制定初始化数值
ps :bit Innteger是整数初始化的时候这个字符串参数只能写整数
BigInteger b2 = new BigInteger("944444444555555555555555555555555555555555555555555551");//这里范围已经超过long了
3.通过静态方法Valueof实例化
BigInteger b3 = BigInteger.valueOf(99999);//参数的取值是根据long对象的范围决定的
该方法有一小细节:
当初始化值在 -16 ~16 时 采用类似单例模式 只会初始化一个对象 只是赋予引用地址(类似spring中的单列模式)
BigInteger b21 = BigInteger.valueOf(13);
BigInteger b22 = BigInteger.valueOf(13);
System.out.println(b21==b22);
结果:
一般建议使用第二个构造方法实例化 ,范围大无局限
BigInteger bigInterger = new BigInteger(4, new Random())
BigInteger radd = bigInterger.add(BigInteger.valueOf(5))
System.out.println(b21.subtract(BigInteger.valueOf(5)))
BigInteger multiply = b21.multiply(BigInteger.valueOf(5));
System.out.println(multiply);
BigInteger divide = b21.divide(BigInteger.valueOf(5));
System.out.println(divide);
BigInteger[] x = b21.divideAndRemainder(BigInteger.valueOf(5));
System.out.println("商是:"+x[0]+"余数"+x[1]);
BigInteger pow = b2.pow(2);
BigInteger moremax = b2.max(b21);
BigInteger min = b2.min(BigInteger.valueOf(15));
BigInteger bigIntegerdemo = new BigInteger("55555");
int i = bigIntegerdemo.intValue();
long l = bigIntegerdemo.longValue();
double d= bigIntegerdemo.doubleValue();
System.out.println("int:"+i);
System.out.println("long:"+l);
System.out.println("double:"+d);
运行结果:
和BigInteger一样都是为了解决数据类型的范围或者精度缺陷而使用的数据类,有着相同的特点,创建后不可改变,且运算对象是返回一个崭新的数据,构造方法和操作方法都大体相同
当使用小数进行加减乘除时候 计算机转换为二进制时候会出现精度损失 所以才需要该api
用于高精度的运算,比如基金,银行等高数据精度运算
BigDecimal b = new BigDecimal(0.01);
System.out.println(b);
BigDecimal b2 = new BigDecimal("0.00001");//精度过高时候会采用16精
System.out.println(b2);
3.通过静态方法实列化对象
BigDecimal b3 = BigDecimal.valueOf(10.00005);
System.out.println(b3);
System.out.println("加法:"+b2.add(BigDecimal.valueOf(12.5)));
System.out.println("减法:"+b2.subtract(BigDecimal.valueOf(12.5)));
System.out.println("乘法:"+b2.multiply(BigDecimal.valueOf(12.5)));
System.out.println("除法(只取余商):"+b2.divide(BigDecimal.valueOf(12.5)));
System.out.println("除法商:"+b2.divideAndRemainder(BigDecimal.valueOf(12.5))[0]+":::除法余:"+b2.divideAndRemainder(BigDecimal.valueOf(12.5))[1]);//和BigInteger一样返回是个数组
/**
* @param1 被除数
* @param2 保留小数点后几位
* @param3 表示小数点精度最后一位四舍五入规则up表示原理数轴0的方向进1 down反之 ,ceil向正方向进1,floor表示精度向负方向进1,half_up 数学中的标准的=四舍五入 ,
*/
System.out.println("保留小数点三位除法:"+b4.divide(BigDecimal.valueOf(3),3,BigDecimal.ROUND_UP));
System.out.println(b2.max(BigDecimal.valueOf(12.5)));
System.out.println(b2.min(BigDecimal.valueOf(12.5)));
Object是所有类的超类 其中有几个常用方法
源码:
输出类名和hash值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
实际开发中我们重写该方法输出成员属性:
@Override
public String toString() {
return "student{" +
"score=" + score +
'}';
}
public boolean equals(Object obj) {
return (this == obj);
}
// 对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。
//引用对象内存申请在堆中 地址在栈 地址指向内存
//equals方法实际就是==比较俩个对象的地址
student s1 = new student();
s1.age=4;s1.name="test";s1.score=79;
student s2 = new student();
s2.age=4;s2.name="test";s2.score=79;
//俩个实例化对象地址不同 false
System.out.println("调用equals方法比较"+s1.equals(s2));//ideal重写写后可以比较属性 true euals本来比较的是引用地址
User Originuser = new User("user1", "{等级:200,攻击力:100,防御:20}", 1656,Signs);
try {
User clone2 = (User)Originuser.clone();
System.out.println("原来对象u1"+Originuser);
System.out.println("克隆对象u2"+clone2);
System.out.println(Originuser==clone2);//克隆的是属性 内存地址不通
//PS:细节这种克隆为浅克隆当对象中有引用数据时候 clone的是内存地址 基本数据类型 拷贝数据值·引用类型拷贝地址值
clone2.Signs[8]=999;
System.out.println("这种克隆方式是浅克隆,引用对象克隆的是地址,所以克隆对象修改了引用属性,原对象也会改变"+Originuser.Signs[8]);
System.out.println(Originuser.Signs==clone2.Signs);//clone方法浅克隆的弊端 无法独立操作
// 深克隆会将所有属性都是进行数值克隆 引用对象也是 而Object的clone是浅克隆 深clone需要自己改写
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
时间类在Jdk8做了 次更新 ,所以分二段讲解
Date now=new Date();//空参数 表示当前时间
System.out.println("当前时间"+now);
Date currentTime = new Date(2008,5,30,11,32);
源码
@Deprecated
public Date(int year, int month, int date, int hrs, int min) {
this(year, month, date, hrs, min, 0);
}
构造参数为0L时候 就是时间原点
System.out.println("时间原点"+new Date(0L));//设置0L就是时间原点
MM表示月 HH表示24的小时
System.out.println("当前时间:"+new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒 EE").format(new Date()))
官方文档:
2.用于解析文本时间 (要求格式符号必须对得上)
//作用2解析字符串时间
try {
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2013-07-24 23:11:11");//解析的时间又恢复原始的时间格式
System.out.println("解析的时间"+date);
} catch (ParseException e) {
e.printStackTrace();
}
System.out.println("==========================演示日历类 ==============");
Calendar calendar = Calendar.getInstance();//抽象类只能通过抽象类获取
System.out.println("当前是几月?"+calendar.get(Calendar.MONTH)+1);//根据系统所在的时区域不同获取日历对象 月:0-11 周日到-周六:1-7 周日是第一天
System.out.println(calendar.get(Calendar.DAY_OF_MONTH));
System.out.println("当前是本周"+(calendar.get(Calendar.DAY_OF_WEEK)-1)//0纪元 1年 2月 3一年的几周 4一月的第几周
);
);
该类的核心方法是 get 获取日历的各项数据 ,参数封装在了Calendar自身中
值得注意的是:月份是0-11,而在国外 周日才是一周的开始 所以 周日对应代码1 周一到周日:234561
获取时区
ZoneId zoneId = ZoneId.systemDefault();//当前系统时区域
Set<String> availableZoneIds = ZoneId.getAvailableZoneIds();//获取所有时区
//获取指定时区
ZoneId of = ZoneId.of("America/Antigua");
Instant instant = Instant.now();//2.1获取当前时间搓 标准到时间原点的时间搓 需要+8小时
System.out.println("当前的标准时间搓:"+instant);
2.atZone (指定时区的时间搓)
ZonedDateTime time = instant.atZone(ZoneId.of("Asia/Aqtau"));
System.out.println("制定的硅谷时间 搓为:"+time);
3.is 进行时间前后判断
Instant now1 = Instant.ofEpochMilli(0L);//该方法是获取实例化时间
Instant now2 = Instant.ofEpochMilli(1000L);//时间圆点+1s
boolean r = now1.isBefore(now2);
System.out.println("now1是否在now2之前:"+r);
System.out.println("now2是否在now1之后:"+now2.isAfter(now1));
4.时间搓进行运算
Instant currenttime = Instant.now();
Instant minuscurrenttime = currenttime.minusMillis(20000L);
System.out.println("当前时间错"+currenttime+'\n'+"减少时间20s后"+minuscurrenttime);
//.ZoneFateTime 带时区的时间类
ZonedDateTime now3 = ZonedDateTime.now();
System.out.println("获取带时区的时间对象"+now3);
ZonedDateTime zonedDateTime = now3.withMonth(1);//3.1with 修改某部分为固定数值
ZonedDateTime zonedDateTime1 = now3.withYear(2035);
System.out.println("修改后的年份"+zonedDateTime1);
/**
* DateTimeFormatter 用于时间的格式化和解析
*/
//1.ofPattern
//获取时间对象
ZonedDateTime nowdate = Instant.now().atZone(ZoneId.of("Asia/Shanghai"));//指定时区 什么地方的应用对应时区不同
//解析/格式化
String date = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss").format(ZonedDateTime.now()); //用法和simpleDateFormatter一样
// String date = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(nowdate); //用法和simpleDateFormatter一样
System.out.println("格式化的时间"+date);
是不是感觉特别繁琐 ,但是相对于之前增加的is方法可以进行对时间的判断
System.out.println("当前系统所在年份日期"+LocalDate.now());
System.out.println("申明指定时间"+LocalDate.of(2005,5,13));
3.localTime 当前分秒 使用场景秒杀活动纳秒级别
System.out.println("当前系统所时分秒"+LocalTime.now());
4.LocalDateTime 年月时分秒都详细包含
System.out.println("当前系统详细时间:"+LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 :HH时mm分ss秒")))
上面三个日历类都可以进行minus,plus,isxx,get等操作
时间比较工具类
1.Duration 时间间隔 s 纳秒
Duration duration = Duration.between(LocalTime.now(), LocalTime.of(5, 25, 29));
System.out.println(duration);
System.out.println("间隔了多少小时:"+duration.toHours());//砖换间隔
2.Period 年月日 时间单位
Period period = Period.between(LocalDate.now(), LocalDate.of(2005, 11, 29));//参数2减去参数1
System.out.println("间隔多少年{}"+period.getYears());
/3.ChronoUnit所有时间单位的间隔
通过静态方法选择时间单位
ChronoUnit.HOURS.between();
ChronoUnit.SECONDS.between();
可以这样使用因为底层使用到了枚举
mysql中的timestamp类型和java中的localdatetime刚好对应
Integer i1 = Integer.valueOf("127");
Integer i2 = Integer.valueOf("127");
System.out.println(i1==i2);
Integer i3 = Integer.valueOf("128");
Integer i4 = Integer.valueOf("128");
System.out.println(i3==i4);
//创建新空间 地址肯定不同
Integer integer = new Integer("127");
Integer integer2 = new Integer("127");
System.out.println(integer==integer2);
可以通过==进行比较内存地址发现,其中new申请内存空间的方式实例化,肯定地址不同 而静态方法,127为什么俩个对象地址相同呢
查看源码
发现范围在IntegerCache(缓存)的高地范围之间是直接返回 缓存
high被设计成127,这样在-128 到127这个常用区间运算,数据无须重复申请内存.
int i3 = i2.intValue()+ i1.intValue();
因为引用对象无法直接运算 ,只能转为基本类型运算,这样代码量过于冗杂,所以jdk5以后实现了自动装箱(包装)和自动拆箱(数据转换),可以理解包装类就是数据本身了,大大便利了开发
ArrayList<Integer> arrayList1=new ArrayList<>();
Integer a=8;//可以直接申明数据
arrayList1.add(1);//默认int 和Integer是一种数据处理方式
arrayList1.add(a);
arrayList1.add(Integer.valueOf("2"));
arrayList1.add(3);
Integer x = arrayList1.get(0);
int y = arrayList1.get(0);//可以转换
System.out.println(x);
System.out.println(y);
ps:之前练习用到Scanner的next方法,说一下nest方法遇到空格和回车停止输入,nextline只有回车才停止输入,所以建议Scanner工具默认nextline方法控制台输入
属于util包下的工具类,简化数组开发,以下是常用方法
Integer[] array = {2,45,44,34,6,78};
System.out.println("默认升序排序:"+Arrays.toString(array));
Arrays.sort(array); //默认升序排序
也可以选择重载的方法自己书写排序规则() ,第二个参数是函数式接口 使用匿名内部类方式实例化对象,然后重写函数
/**
*重写的 Comparator(比较器) 中的compare(比较方法)
* @param1 a1表示无序序列中的便利的每一个参数 表示要插入的数据
* @param2 a2表示已经排好的有序序列中的便利的每一个参数 表示已经经过排序的数据
* @return 返回值值是负数 a1放在当a2之前,反之之后
*/
Arrays.sort(array, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;//降序排序
}
});
验证并且输出o1,o2
Integer[] array = {2,45,44,34,6,78};
Arrays.sort(array, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("未排序:"+o1);
System.out.println("排序:"+o2);
return o1-o2;//升序排序 也就是 sort参数的默认排序
}
});
负应在位置插入点-1什么意思呢?代码演示如下
System.out.println("原数组"+Arrays.toString(array));
int i = Arrays.binarySearch(array, 10);
System.out.println("二分查找,该元素10应该在在索引"+i+"的位置");
返回值-3 为什么呢?因为采用二分查找,10如果存在应该在6和34之间,也就是索引2 ,变为负数-1,所以返回-2-1=-3.
Integer[] copy = Arrays.copyOf(array, 8);//参数1 元素组,参数2 拷贝后长度
System.out.println(Arrays.toString(copy));//跟拷贝长度有关 1.范围=原长度全部拷贝 2.大于全部范围则拷贝的数组补齐默认值(null)
/**
* 细节该方法拷贝数组范围[) 俗记 包头不包尾
*/
Integer[] ofRange = Arrays.copyOfRange(array, 0, 5);
System.out.println("拷贝结果:"+Arrays.toString(ofRange));
System.out.println("====================fill 数组填充================================");
Arrays.fill(array,0,2,100);//表示填充0-2部分 填充值100 也是包头不包尾巴
Arrays.fill(array,1000);//全填充
List<Integer> x = Arrays.asList(array);
当java中判断一个字符串是否满足我们需要的需求,我们会采用正则表达式(regex)。
比如判断一个字符串是否为QQ号,就可以使用字符串的matches方法进行判断
满足规则返回true 否则返回false
String qq="2613867717";
//1.使用正则表达式 限制字符串格式
System.out.println(qq.matches("[1-9]\\d{5,19}"));
首先在做练习之前先了解转义字符
\(此处是打了俩个反斜杠,Markdown语法也会识别转义字符,单个反斜杠不可可见),在转义字符后边的字母会改变原本的字符意思,\n(换行),\t(空格)等…以上都是打了俩个反斜杠才表示一个反斜杠(速记双反斜杠才是单反斜杆的作用,单反斜杠本身就被识别转义)
比如:输出反斜杠
System.out.println("\\");
[1-9]\d{16}(\d|x|X)
这里表示的就是 1-9其中一个数字开头,跟16个数字,在拼接任意数字或者X或x,如果不加()
限定范围 则代表整个|符号前和|符号后进行或运算.
4.一般预定义字符和数量词混用
比如\d{5,10} 表示字符串必须满足5个以上,10个数字已下的规则
String regex="/^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/";
这样肯定报错,/^ 和$/ 是正则表达式的开始和结束符号 ,JAVA自动底层编译 ,在讲单个反斜杠\,改成双斜杠;
//我这里选择的是一代身份证 anyrule有三代身份证 可以改成三代身份证自行验证
String regex="[1-9]\\d{7}(?:0\\d|10|11|12)(?:0[1-9]|[1-2][\\d]|30|31)\\d{3}";
lambda表达式的作用一般是用于简化匿名内部类做参数的书写
原来需要匿名内部类实现
Arrays.sort(ar, new Comparator<Integer>() {
//匿名内部类 无需特意实列化一个对象重写需要的方法
// Lambda表达式就是对匿名内部类的简化 无需new对象 在逐个改写 直接(参数)->{方法题}
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
});
lambda表达式优化后:
俩种:当重写的方法体只有一行代码时候,可以省略return 和方法体{},以及;
Arrays.sort(ar, (Integer o1, Integer o2) -> { return o1 - o2;});
Arrays.sort(ar, ( o1, o2) -> o2 - o1 );
参数由原来的匿名内部类,转变为(参数)->{ }的格式,因为使用匿名内部类的目的式为了实列化接口的方法,lambda表达式跳过面向对象的过程,可以直使用参数
//只需要 专注于方法的改写 ,无需在关注接口及实现类本身
(Integer o1, Integer o2) -> {
return o1 - o2;
}
1.lambda 表达式可以用来简化匿名内部类的书写
2.Lambda表达式只能简化函数式接口的匿名内部类写法
3.函数式接口:
仅有一个抽象方法的接口叫函数式接口,接口上可添加@FunctionalInterface
4.当方法体只有一行代码时,可以省略 return ,;,{},且必须同时省略
Arrays.sort(ar, (o1, o2) -> { return o1 - o2;});
简化为:Arrays.sort(ar, ( o1, o2) -> o2 - o1 );
5.参数类型可以省略,当参数只有一个时,()也可以省略
比如这里我们自定义函数式接口
public static void mehod1(Swim s) { //参数是实例化该函数式接口
s.swimming();
}
interface Swim{
public abstract void swimming();
}
调用 :
mehod1(()-> System.out.println("lambda式输出自定义接口"));
得益于函数式接口只有一个抽象方法,所以lambda表达式简化的就是申请接口内存空间,选择重写方法的过程只有是重写抽象方法只有一个,所以参数类型也可以省略(相对应),lambda表达式基本格式在形参需要函数式接口的地方无需再实例化(参数)->{方法体}
集合体系庞大
总体大类可以分为单列集合List,Set和双列集合Map
集合大类虽然繁多,单都是继承了顶级接口根据数据结构不同改写的子类,应该按数据接口记忆即可
在使用集合之前需要了解使用泛型
泛型是jdk5 中引入的特性 用于在编译阶段(java 的泛型其实是个伪泛型 , 只在编译时期有效)约束操作数据,并且进行检查
格式:<数据类型>
只支持引用类型
ArrayList<Integer> List = new ArrayList<>();//泛型约束
如果指明泛型,那么集合可以存储任意类型
ArrayList arrayList = new ArrayList();//不约束 可以存储任意数值 等同于 ArrayList
arrayList.add(45);
arrayList.add("我真的帅啊");
arrayList.add(3.14);
arrayList.add('G');
System.out.println(arrayList);
因为取出来的数据是Object,这样集合里储存的数据本身的特点无法发挥,如果要使用某个引用类型特有特性IObject多态的弊端
没办法调用集合中自子类内有的数据 比如 Integer.length,Integer.valueOf(o.toString)
Integer的转toHexString(),就算强转 由于原素对象类型不同个数多
很麻烦,或者自定义数据类型无法使用其特定方法,所以需要约束.
1.泛型类 不确定类中某个变量的时候
格式:修饰符 class 类名{},<>里面的字母任意,代表不确定的类
比如mapper,list 源码
在比如自定义泛型类
/**
* 这样就可以自定义仁和引用类型作用变量
* @param 记录数据类型
*/
class girl<e>{
private Object[] o=new Object[10];
private int size=0;
public Boolean add(e element){
o[size]=element;
size++;return true;
}
public <t> t get1(t index){
return index;
}
public <t> void addAll(ArrayList<t> list,t ...e){
for (int i = 0; i < e.length; i++) {
list.add(e[i]);
}
}
public e get(Integer index){
return (e)o[index];
}
}
public static <t> t get1(t index){
return index;
}
girl girl = get1(new girl<>());
3.泛型接口
/**
* 使用泛型接口的方法
*1.实现类给给出具体限制
* class mylist implements List{}
* 2.实现类 继续延续泛型 等创建对象时候决定
* public class ArrayList extends AbstractList
* implements List, RandomAccess, Cloneable, java.io.Serializable
*/
class mylist implements List<String>{
@Override
public int size() {
return 0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean contains(Object o) {
return false;
}
@Override
public Iterator<String> iterator() {
return null;
}
@Override
public Object[] toArray() {
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a) {
return null;
}
@Override
public boolean add(String s) {
return false;
}
@Override
public boolean remove(Object o) {
return false;
}
@Override
public boolean containsAll(Collection<?> c) {
return false;
}
@Override
public boolean addAll(Collection<? extends String> c) {
return false;
}
@Override
public boolean addAll(int index, Collection<? extends String> c) {
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
return false;
}
@Override
public boolean retainAll(Collection<?> c) {
return false;
}
@Override
public void clear() {
}
4.(重点)通配符
/**
*1. 泛型不具备继承性 泛型限定的申明类型 只能传入该类型 以及子类也
* 2.泛型通配符 ? 也表示一个不确定类型的变量
* 进行类型的限定
* ? extends E 表示可以传递e和e所有的子类
* ? super E 表示可以传递e和e所有的父类
* 应用场景 如果定义类,方法,接口的时候,如果类型不确定,就可以使用定义泛型类,方法,接口
* 应用场景 如果定义类,方法,接口的时候,如果类型不确定,但是数据范围是一个继承体系,就可以使用定义泛型通配符
*
*/
代码实现:
设计了一个方法只允许animal和子类传递
public static void method(ArrayList<? extends animal> list){
};// 此时表示限定的范围必须是E以及所有子类
cat是animal的子类
method(new ArrayList<cat>());
method(new ArrayList<animal>());
1.数据可以继承,泛型不行
public static void me2(Collection<animal> animalArrayList){
System.out.println("参数是一个annimal的泛型集合,那么集合就只能传递这个泛型的集合,传递其他,哪怕子类约束的泛型不能·");
/**
* 泛型比作具有型号限制,集合接口,类比作容器外貌,限制的什么型号就是什么型号 没有继承关系 ,装对应型号的东西,和什么样的外貌无关
* 数据具有继承关系 所以容器本身可以选择不同的具有继承关系的容器,但是储存的数据,和容器本身可以继承
* 限定的数据比作内容,可以装和内容相似的子类
*/
}
要求什么类型的泛型集合,就只能是什么类型的泛型集合,但是数据可以继承,只要是同泛型,该集合的子类也可以满足,和集合类型,数据类型无关,只和泛型本身有关
泛型不可继承,所以传入该方法只能是这个泛型,数据集合可以继承,所以集合子类也可以
me2(new HashSet<animal>());
me2(new ArrayList<animal>());
数据可以继承,所以泛型约定数据的子类也可以存入容器
ArrayList<cat> lihuacats = new ArrayList<>();
lihuacat lihuacat = new lihuacat();
farmacat farmacat = new farmacat();
lihuacats.add(lihuacat);
lihuacats.add(farmacat);// 泛型不能继承 但是数据可以 泛型定义的数据类型,集合可以添加该类以及子类类型
2.泛型同集合一样,只能储存引用类型
所有单列集合的祖宗接口 ,所有方法都可以被单列集合继承使用,其子接口
List集合具有:索引,可重复,有序储存特点
set集合具有:无索引,不可重复,无序储存的特点
因为collection是单列集合顶级接口,所以因为共性,储存也是无序
//因为是个接口 我们无法直接实例化对象,只能实例化子类 所以这里采用多态形式
Collection<Integer> collection=new ArrayList<Integer>();
boolean b = collection.add(15);
System.out.println("由于数据结构无索引,无法get只能直接输出"+collection)
System.out.println("clear 清空集合");
collection.clear();
System.out.println("执行clear后"+collection);
清空后只剩下个[]空集合
boolean remove = collection.remove(123)
/**
* 底层是根据equals 方法进行比较,只能比较引用对象
* 如果是自定义对象需要重写equals方法比较属性值
*/
System.out.println("该15元素是否存在于集合中 :"+collection.contains(15));
System.out.println("该123元素是否存在于集合中 :"+collection.contains(123));
boolean empty = collection.isEmpty();
Iterator<Integer> iterator = collection.iterator();//2.1获取迭代器
Iterator<Integer> iterator = collection.iterator();//2.1获取迭代器
//判断迭代器当前(初始)位置是否有元素 类似0索引
while(iterator.hasNext()){ //iterator.next();获取集合当前元素的值 迭代器并且指向下一个元素
//如果初始位置有元素 开始迭代遍历
Integer integer = iterator.next(); //遍历完后 这个迭代器的位置已经移动到集 合末尾 并且不会复位 需要再次遍历只能在获取一个迭代器
System.out.println("遍历结果::"+integer);
}
使用细节:
1.迭代器床架后默认是在0索引,因为next方法索引移动到下一位置,所以当指针到最后一位后,不会复原,如果需要再次便利,只能再次使用集合创建迭代器
2.在便利过程中,不能使用集合的数据操作方法对集合进行操作,会报异常
原因是便利的核心方法中有一个类似乐观锁机制来记录修改数据次数,在子类LinkedList原码可以提现(checkForComodifiction就是检查次数的方法)
3.便利的时候不要在方法体中使用来此next,当最后一个数据已经被返回,但是第二个next方法继续获取指针数据,由于此时指向的地址已经跳转到末尾+1,所以抛出异常
4.如果真的需要在便利过程中删除数据,只能使用iterator的封装修改方法
for (Integer i: collection) {//泛型装的内容:集合 底层就是迭代器 适用于所有单列集合包括数组
i=5555;
System.out.println(i);
}
System.out.println("即使逐个改变i 原集合不会改变"+collection);
细节:增强for 是将集合/数组元素 逐个赋值 给第三方变量,所以改变第三方变量的值 原集合数值不变
collection.forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
}
});
collection.forEach(i->System.out.print(i));
Collection的子接口,在父接口的方法上,融合自己的索引特性,方法轻微改变
相较于父接口,多了索引于是添加便可以根据索引添加.默认方法是添加到末尾,但是可以添加到指定位置原索引进行后移
List<String> list = new ArrayList<>();//顶级接口通过创建子类实现
list.add(0,"我是第一位");
list.add("我是第二位");
list.add(2,"我是第三位");
list.add(3,"我是四位");
list.add( 2 ,"添加到那个索引 原索引的位置以此向后移");
System.out.println(list);
子接口特性重载以后,根据索引删除 返回删除得值
System.out.println("通过索引返回删除的数据 ::"+list.remove(2));
细节:当集合存储Integer时候,只传递一个数字4,会删除元素4?还是索引4,因为自动装箱的存在,在这里会很模糊,实际是按照传递的索引处理,java会选择形参和实参相同的方法,如果手动装箱数字4,才会特意去删除这个元素
返回元素内容,参数按照索引处理:
Integer remove1 = list2.remove(4);
手动装箱后,返回Boolean值,参数按照元素处理
Integer integer = Integer.valueOf(4);//手动装箱
//此时 形参和实参相同才是按照元素删除
boolean b = list2.remove(integer);
String s = list.set(0, "哈哈哈我被修改咯,返回修改前的元素");
因为List多了索引特性相较于原来的三种增加了以下
for(int i=0;i<listdemo1.size();i++){
System.out.println("fori::"+listdemo1.get(i));
}
ListIterator<String> iterator1 = listdemo1.listIterator();//迭代器iterator的子接口】
while (iterator1.hasNext()) {
String next = iterator1.next();
if ("bbb".equals(next)) {
iterator1.add("qqq");
}
System.out.println("列表迭代器正向便利::"+ next);
}
//1.继承的迭代器便利
ArrayList<String> listdemo1 = new ArrayList<>();
listdemo1.add("aaa");
listdemo1.add("bbb");
listdemo1.add("ccc");
listdemo1.add("ddd");
Iterator<String> iterator = listdemo1.iterator();
while (iterator.hasNext()) {
System.out.println("迭代器便利::"+iterator.next());
}
//2.增强for便利
for(String S:listdemo1){
System.out.println("for便利::"+s);
}
//3.lambda
listdemo1.forEach(i-> System.out.println( "lambda(匿名内部类)::"+i));
之前演示集合接口,是通过多态的方式申明Arraylist,Array数组,如名字一般ArrayList是实现了基于动态数组的数据结构,是List最基本的实现类.
查看源码:
空参构造就是创建了一个空数组
数组长度默认是10
private static final int DEFAULT_CAPACITY = 10;
底层添加元素:在执行前会进行素组自增
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
`
这里特意说明,在使用iteractor(迭代器便利),for(底层也是迭代器)便利时,不能使用数组的方法进行修改数组,是因为底层有类似乐观锁的机制;
//源码在执行便利的时候会调用checkForComodification()检查集合修改次数和创建迭代器时候是否一致
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
LinkedList底层是使用链表方式数据结构的集合,由于数据存储结构的特点,LinkedList结合了双链表的特点,储存不在是数组而是链表,以结点为单位存储数据
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
除了拥有一般链表增加是默认集合尾部添加
由于双链表对头尾增删方便的特点,还增加了以下方法
ist1.addLast("最后一位置添加");
list1.addFirst("第一位添加");
list1.removeFirst();
list1.removeLast();
特点
1.无序
2.无索引
3.不重复
因为Set是顶级接口继承Collection,所以方法无任何改变,以下代码实现
/**
* 系列顶级接口set继承 Collection 所以方法都一样
*/
//实例化
Set<String> sets = new HashSet<>();//接口无法实例化,通过多态 子类的实例化来创建对象
//1. add 注意和list collection的区别 不可重复 add会返回false
sets.add("a");
boolean r = sets.add("a");
System.out.println("第二次添加结果::"+r);
sets.add("b");
sets.add("c");
//2. remove
sets.remove("a");
//3. addAll
ArrayList<String> list = new ArrayList<>();
list.add("listA");
list.add("listB");
list.add("listC");
sets.addAll(list);
//将有序集合 添加到无序集合中输出发现数据无序排序验真了无序性
System.out.println(sets);
//4.便利
//4.1迭代器
Iterator<String> it = sets.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println("便利的数据::"+s);
}
//4.2 增强for
for (String set : sets) {
System.out.println("sets :: "+ set);
}
//4.3lambda
sets.forEach(s-> System.out.println("lambda :: "+s));
hashset作为set接口最基础的实现类特点和顶级接口一致,由于底层结合hash算法,更能解析set的特点
/**
* 特点 :无序 不重复,无索引 故此 和顶级接口 方法特点一至于 是set的 基础实现 主要眼界源码
* 哈希值::对象的整数表现形式
* 1.如果没有重写hashcode,不同对象打印的哈希值是不同的,都是地址转换为整数 (原生Object的hash方法)
* 2.重写hashcode,不同对象只要属性值相同,计算的哈希值就是一样的
* 3.小部分情况会出现地址不同,哈希值一样的情况(hash碰撞)
* hash值是set底层判断是否重复的一个标准,底层会调用equals方法
* 如果set里面存储自定义对象 一定要重写equals,hashcode,JAVA其他类·重写好了
*/
底层(jdk8后一样的元素直接不计算,而是挂在相同数据的位置)
1通过元素的地址计算而成的hash值来决定存储位置,所以无法通过索引获取
2.每个数据的hash值不同,无法保证有序
3.底层储存时候会调用equals方法比较,所以无重复
源码验证:
1.添加时先通过内置map存放数据,作为map的key,保证了其无重复性
2.我们熟知的map kv结构,key无重复,底层多次调用equals比较
4.Hash和linkedset储存自定义类必须改写Hahcode和equals
由于底层根据equals去重,hashcode决定存储地址,在实际开发中,存储基本数据的包装类,之所以做到了按照数据去重,而不是按照对象内存地址(如果是对象内存地址每个对象都不能不同,但数据大小一样肯定不符合要求),是因为java底层改写了equals,和hashcode方法,根据数据大小值计算hashcode和进行比较.
在hashset的基础上,底层又使用了双链表结构存储数据的顺序, 使得数据具有无索引,不重复,有序的特点 .
/**
* 特点 :有序 不重复,无索引
* 有序 :链表方式存储和读取 一致
* 不重复:底层的equals和hashcode就行判断
* 无索引:没有索引
* 原理 :在底层数据结构hash表的,每个元素多了双链表 机制记录储存顺序 ,不等于索引顺序
*/
LinkedHashSet<Student> linkedset = new LinkedHashSet<>();
System.out.println(linkedset.add(s1));
System.out.println(linkedset.add(s2));
System.out.println(linkedset.add(s3));
System.out.println(linkedset.add(s4));
System.out.println(linkedset);
// 虽然有序 但是 如果没有顺序读取要求 默认还是hashset ,减少底层运算时间
结果确实是按照存入顺序进行保存的
底层:
源码:
其实也可以发现set和map底层相似,至少set的源码依赖map
特点原理:
无重复:底层会调用equals方法比较
有序:底层加入了链表记录数据录入顺序
无索引:数据底层根据hash算法存储,而不是根据地址连续存
treeSet 底层融入了红黑树(是一种类似二叉排序树的数据结构)数据结构储存 ,所以具有排序功能 ,增删改查性能良好.
TreeSet特点
1.底层数据结构加入了红黑树,数据可以排序
2.无重复
3.无索引
无重复和无索引和hash,equals无关,而是tree数结构具有排序算法
System.out.println("使用案列 保存整数 并且排序");
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.add(3);
treeSet.add(2);
treeSet.add(4);
treeSet.add(1);
System.out.println(treeSet);//实现排序
输出结果
当存储基本数据类型时,根据大小升序,如果是自定义类型呢
所以treeset可以改变排序规则
1.储存的数据实现排序接口
TreeSet<Student> Studentreesets = new TreeSet<>();
Student ss1 = new Student("Jack", 21, 2002);
Student ss2 = new Student("jerry", 19, 2012);
Student ss3 = new Student("mark", 26, 2005);
Studentreesets.add(ss1);
Studentreesets.add(ss2);
Studentreesets.add(ss3);
for (Student student : Studentreesets) {
System.out.println(student);
}
定义对象实现比较接口 Comparable
class Student implements Comparable<Student>{//1.不知道比较规则参数时候 该类也要定义位置变量泛型 2. 如果已经明确就可以直接指明泛型
private String name;
private int age;
private int Id;
public Student() {
}
public Student(String name, int age, int id) {
this.name = name;
this.age = age;
Id = id;
}
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 int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
/**
* compreto 方法 指定排序规则
* @param o
* @return
*/
@Override
public int compareTo(Student o) {
//这里演示根据年龄升序排序 this本类代表要存入的元素·,o代表已经在红黑树的元素 一开始 是红黑树的根节点 一开始传入的数据成为根节点
//return 负数,认为要存入的比较小放左边
//return 正数,认为要存入的比较大放右边
//return 0,一样大舍弃
/**
* 和Arrays中的sort方法一样的算法 this=o1表示未排序的,这里传入的o等同于sort第二个参数02表示已经排序好了
* 速记: 升序算法= 未排序的元素-已经排序好的元素
* 反之降序=排序好-未排序
*/
System.out.println("已经排序好::"+o.name);
System.out.println("本对象::"+this.name);
return this.getAge() - o.getAge();
}
}
重点是实现了接口的抽象方法定义规则
return 负数,认为要存入的比较小放左边
return 正数,认为要存入的比较大放右边
return 0,一样大舍弃
和Arrays.sort方法算法类似
升序:未排序的元素-已经排序的参数
降序:排序好的-未排序
/**
* compreto 方法 指定排序规则
* @param o
* @return
*/
@Override
public int compareTo(Student o) {
//这里演示根据年龄升序排序 this本类代表要存入的元素·,o代表已经在红黑树的元素 一开始 是红黑树的根节点 一开始传入的数据成为根节点
//return 负数,认为要存入的比较小放左边
//return 正数,认为要存入的比较大放右边
//return 0,一样大舍弃
/**
* 和Arrays中的sort方法一样的算法 this=o1表示未排序的,这里传入的o等同于sort第二个参数02表示已经排序好了
* 速记: 升序算法= 未排序的元素-已经排序好的元素
* 反之降序=排序好-未排序
*/
System.out.println("已经排序好::"+o.name);
System.out.println("本对象::"+this.name);
return this.getAge() - o.getAge();
}
虽然这里可以让Treeset可以保存引用对象时候排序,但是如果需要改写对基本数据类型包装类排序就只能使用第二个方法排序了(不能改写jdk源码).
演示 排序规则是根据字符串对象长度升序排序,一样长就根据ASCII表升序
System.out.println("第二种方法是通过在建立集合的时候创建 比较器对象指定规则,有参构造");
TreeSet<String> students1 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
//o1 未排序
//o2 已经排序好
int i = o1.length() - o2.length();
return i==0?o1.compareTo(o2):i;
}
});
总结: