飞道的博客

Java基础加强重温_04:日期时间类()、System类()、BigDecimal类()、StringBuilder类()、包装类()、泛型()、Collection集合()

623人阅读  评论(0)

摘要

Java基础加强重温_04:
日期时间类【时间类Date、格式转换类DateFormat、日历类Calendar(与Date互转)】、
System类(exit、currentTimeMillis、数组复制arraycopy)、
BigDecimal类(加减乘除解决精度丢失)、
StringBuilder类(字符串拼接)、
包装类(基本类型与包装类,装箱拆箱,自动装箱拆箱)、
泛型【泛型类、泛型方法、泛型接口、泛型通配符(向上限定,向下限定)】、
Collection集合(单列集合,List,set,常用功能)

一、常用API–日期时间类

1、Date类

java.util.Date类表示特定的瞬间(年月日时分秒),精确到毫秒。

面试题

月份的表示是以0-11代表1-12月。即0代表1月,1代表2月,…

标准基准时间(称为“时代”)即1970年1月1日00:00:00 GMT

今年为什么是2020,因为选了个起点时间,2020年前,耶稣诞生。
Date也是选了起点,UNIX系统诞生的时间。MAC,IOS就是基于UNIX改的,LINUX也是UNIX的免费版,ANDROID基于LINUX。UNIX系统诞生约1969,所以找了 1970年1月1日 0时0分0秒作为语言时间相关类开始的时间

Date精确到毫秒。Date有参构造方法中有个long变量,表示从标准基准时间开始到现在累计的毫秒数。

秒与毫秒换算

1s=1000ms=10^3ms ,秒:SECOND,毫秒:MILLISECOND

Date类构造方法

public Date() :分配一个Date对象并对其进行初始化,表示当前的时间(年月日时分秒,格式为系统默认格式)
public Date(long date) :分配一个Date对象并将其初始化,表示从标准基准时间起,过去指定毫秒数后的时间(年月日时分秒,格式为系统默认格式)。

简单来说:
使用无参构造,可以自动设置当前系统时间的毫秒时刻。

  • 即new出来的date对象默认拿到的是当前时间(年月日时分秒);

指定long类型的构造参数,可以自定义毫秒时刻。

  • 即参数传入毫秒数,new出来的date对象拿到的是从1970年1月1日00:00:00(标准基准时间,称为“时代”),经过了这个毫秒数后的时间(年月日时分秒)。
import java.util.Date;
public class Demo01Date {
	public static void main(String[] args) {
		// 创建日期对象,把当前的时间日期转换成日期对象
		System.out.println(new Date()); // Tue Jan 16 14:37:35 CST 2020
		
		// 创建日期对象,把当前的毫秒值转成日期对象
		System.out.println(new Date(0L)); // Thu Jan 01 08:00:00 CST 1970
	}
}

在使用println方法时,会自动调用Date类中的toString方法。Date类已经对Object类中的toString方法进行了覆盖重写,所以结果为指定格式的字符串。

Date类普通方法:

long getTime​() 返回自1970年1月1日以来,到当前Date对象日期时间累计过去的毫秒数。
void setTime​(long time) 修改Date对象的时间,设置为1970年1月1日00:00:00 的基础上加上参数指定毫秒数,与 new Date(long time)效果差不多

//访问格式
Date类对象.getTime();
Date类对象.setTime(long time);

代码示例

public class TestDate {
    public static void main(String[] args) {
        Date date = new Date();
        System.out.println(date); // Fri Apr 03 01:00:22 CST 2020
        
        long time = date.getTime();
        System.out.println(time); //1585846822673
        
        date.setTime(0); 
        System.out.println(date); //Thu Jan 01 08:00:00 CST 1970 (基准标准时间,中国是UTC+8八区,中国本地时间要在以上标准时间上面加8小时。)
                
        date.setTime(3600000);
        System.out.println(date); // Thu Jan 01 09:00:00 CST 1970
    }
}

2、DateFormat类(SimpleDateFormat)

java.text.DateFormat 是日期/时间格式化子类的抽象类,我们通过这个类可以帮我们完成日期和文本之间的转换,也就是可以在Date对象与String对象之间进行来回转换。

DateFormat类的作用:

Date对象与String的互转

  • 1、格式化:把Date对象转为更好看的String
  • 2、解析:把表达日期String解析回Date对象

为什么要DateFormat类:

Date转String

Date主要表示1970年到某个时间的毫秒值(date对象),如果输出给用户看,用户是看不太懂的:

  • “Thu Oct 25 21:41:33 CST 2018”

用户实际上比较喜欢(String对象)

  • “2018年10月25日 21时41分33秒”

String转Date

因为前端传给后台往往是String,所以要转回Date对象

DateFormat类创建对象(通过子类SimpleDateFormat)

由于DateFormat为抽象类,不能直接使用,所以需要通过它的常用的子类 java.text.SimpleDateFormat创建,SimpleDateFormat就是为了达到DateFormat的效果而存在。这个类需要一个模式(格式)来指定格式化或解析的标准。

public SimpleDateFormat(String pattern) :用给定的模式和默认语言环境的日期格式符号构造SimpleDateFormat。

//参数pattern是一个字符串,结合格式规则,代表日期时间的自定义格式。

给定模式

理解:给定如何格式化Date对象的模式,以年月日时分秒区分:“2020年3月30日11时30分20秒”。或以/,:区分:“2020/3/30 11:30:20”

默认语言环境的日期格式符号(格式规则)

  • 年:yyyy
  • 月:MM
  • 日:dd
  • 时:HH
  • 分:mm
  • 秒:ss

理解:
格式化时,这些符号会提取Date对象里面时间的数字,转换成自定义格式。
解析时,这些符号会提取字符串里面对应位置的数字,转换成系统默认格式
20190928101801: yyyyMMddHHmmss

更详细的格式规则,可以参考SimpleDateFormat类的API文档。

DateFormat类常用方法

public String format(Date date) :将Date对象格式化为字符串。
public Date parse(String source) :将字符串解析为Date对象。

3、DateFormat类案例

需求1:

将当前时间的Date对象 转成 “2018年9月15日 19时18分19秒”

public class Demo02 {
    public static void main(String[] args) throws ParseException {
        //SimpleDateFormat继承了DateFormat,所以format方法
        //final修饰的方法无法被重写,所以被继承 这是错的
       
        //需求:将 当前时间的Date对象 转成 "2018年09月15日 19时18分19秒"
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 ^.^ HH时mm分ss秒");
        Date date = new Date();
        String formatDate = sdf.format(date);
        System.out.println("formatDate = " + formatDate); //2019/9/28 上午9:53
    }
}

需求2

将字符串 “2007-12-26 10:13:31” 转成 Date对象

public class Demo02 {
    public static void main(String[] args) throws ParseException {
        //需求:将字符串 "2007-12-26 10:13:31" 转成 Date对象
        SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd");
        String dateString = "2007-12-26 10:13:31";
        
        //解析
        //光标移动到红色波浪线->alt+enter->选第一个,自动导包
        Date date2 = sdf2.parse(dateString);
        System.out.println("date2 = " + date2);
    }
}

3、Calendar类

Date类的缺点: 对日期的加减比较繁琐,获取日期的 属性(年,月,日…)很多方法已经过期。

java.util.Calendar 是日历类,替换掉了许多Date的方法。该类将所有可能用到的时间信息封装为静态成员变量,方便获取。日历类就是方便获取各个时间属性的。

理解:Calendar对象里面存储的数据就是Date对象转换为String对象的那些年月日时分秒,如:xxxx年xx月xx日xx时xx分xx秒

Calendar的作用:简化日期的操作,获取属性

获取Calendar对象,getInstance方法

Calendar为抽象类,不能直接创建对象,而是通过子类GregorianCalendar的静态方法getInstance创建。

  • 在该方法中通过多态方式,一系列的初始化,最后获得并返回Calendar对象。

Calendar的子类静态方法

public static Calendar getInstance() :使用默认时区和语言环境获得一个日历

获取Calendar对象

 Calendar cal = Calendar.getInstance();

Calendar类常用方法

int get​(int field)  返回给定日历属性/字段的值。

public void set(int field, int value) :将给定的日历字段设置为给定值。

void add​(int field, int amount)  根据日历的规则,将指定的时间量添加或减去给定的日历字段。

与Date实现转换

Date getTime(): 日历对象获取Date对象。理解:将Calendar对象当前的年月日时分秒数据转换成Date对象

setTime(Date date): 日历对象通过Date对象设置时间。理解:将Date对象的年月日时分秒数据替换到当前的Calendar对象

Calendar中的属性/字段

Calendar类中提供很多成员常量,代表给定的日历字段

4、Calendar类案例

Calendar、Date对象互转

	//抽取方法:选中需要提取的代码,快捷键:ctrl+alt+m 起个方法名 回车
    private static void test04() {
        //Date对象转Calendar对象。将Date的年月日时分秒数据替换到当前的Calendar对象
        //setTime(Date date): 日历对象通过Date对象设置时间
        Calendar cal = Calendar.getInstance();
        Date date = new Date(-1000);//1970 08:00:00
        cal.setTime(date);  //理解:将当前时间设置进日历即将Date对象转换成Calendar
        System.out.println("cal = " + cal);
       
        //Calendar对象转Date对象。将Calendar对象当前的年月日时分秒数据转换成Date对象
        //Date getTime(): 日历对象获取Date对象
        Date date2 = cal.getTime();
        System.out.println("date2 = " + date2);
    }

获取日历对象属性/字段(年月日时分秒)

    private static void test02() {
        //Calendar怎么获取对象 public static Calendar getInstance()
        Calendar cal = Calendar.getInstance();
        System.out.println(cal);
        
        //1代表年
        int year = cal.get(Calendar.YEAR);
        System.out.println("year = " + year);
        
        //因为月份从0开始
        int month = cal.get(Calendar.MONTH)+1;
        System.out.println("month = " + month);

        int dayOfMonth = cal.get(Calendar.DAY_OF_MONTH);
        System.out.println("dayOfMonth = " + dayOfMonth);

        int hourOfDay = cal.get(Calendar.HOUR_OF_DAY);
        System.out.println("hourOfDay = " + hourOfDay);

        //星期日=1   星期一=2 星期二=3....星期六=7
        //需要汉化,只能自己定义个方法改造
        int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
        System.out.println("dayOfWeek = " + dayOfWeek);
    }

面试题

  • 星期的开始:西方为周日(1),周一(2),…;中国为周一(1),周二(2),…。
  • 在Calendar类中,月份的表示是以0-11代表1-12月。即0代表1月,1代表2月…
  • 日期是有大小关系的,时间靠后,时间越大。

设置、加减日历对象字段/属性

add,set方法都可以实现日历对象加减
add:正数添加,负数减少
set:直接指定

	//add方式,正负加减
	private static void test03() {
        //void add​(int field, int amount) 根据日历的规则,将指定的时间量添加或减去给定的日历字段。
        Calendar cal = Calendar.getInstance(); //2020年3月30日13时
        
        //1年后
        cal.add(Calendar.YEAR,1);
        System.out.println("1年后:"+cal); //2021

        //8小时后
        cal.add(Calendar.HOUR,8); //21时
        System.out.println("1年8小时候后"+cal);
        
        //负数就是减
		//100年前
		cal.add(Calendar.YEAR,-100); //1921
		
		//10小时前
        cal.add(Calendar.HOUR,8); //21时
	
    }

	//set方法:直接指定
	private static void test03() {
        //void add​(int field, int amount) 根据日历的规则,将指定的时间量添加或减去给定的日历字段。
        Calendar cal = Calendar.getInstance(); //2020年3月30日13时

		//设置年份为2030年
		cal.set(Calendar.YEAR, 2030); //2030
		//将年份修改为2000年
		cal.set(Calendar.YEAR,2000); //2000
	}

6、日期练习案例

需求:
计算你出生了多少天?

分析:
出生了多少天 = 当前时间-生日那天
new Date() - SimpleDateFormat.parse(“1995-07-01”)
转化成毫秒相减得到是相差的毫秒数,需要转为为天数

实现步骤:
1、获取当前时间 : Date begin = new Date();
2、通过SimpleDateFormat把String转为Date,称为birthday
3、long distance = begin.getTime() - birthday.getTime() distance:差距
4、int days = distance / 每天的毫秒数

代码示例:

public class Demo04 {
    public static void main(String[] args) throws ParseException {
        //1、获取当前时间 : Date begin = new Date();
        Date begin = new Date();


        //2、通过SimpleDateFormat把String转为Date,称为birthday
        String birthDayString = "1949-10-01";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date birthDay = sdf.parse(birthDayString);

        //3、long distance = begin.getTime() - birthday.getTime() distance:差距
        long distance = begin.getTime() - birthDay.getTime(); //差距多少毫秒

        //4、int days = distance / 每天的毫秒数
        double days = (double)distance/(24L*60*60*1000);
        //思考如果换成年呢?
        System.out.println(days);
    }
}

总结:
1、Calendar能获取属性、能时间加减,但是在计算时间差距的问题不好用
2、SimpleDateFormat方便快速地定义一个特殊时间
3、算时间差距需要转化为long类型来计算

二、常用API类–System类

java.lang.System 类中提供了大量的静态方法,可以获取与系统相关的信息或系统级操作。

System类的特性:final修饰,隐藏构造方法,里面的方法都是静态方法

System类方法

在System类的API文档中,常用的方法有:

  • void exit(int status)
    退出当前虚拟机,该参数作为状态代码; 按照惯例,非零状态码表示异常终止。
System.exit(0); //0正常退出,非0参数异常退出
  • public static long currentTimeMillis()
    返回当前时间(以毫秒为单位)。当前系统时间与1970年01月01日00:00点之间的毫秒差值
import java.util.Date;
public class SystemDemo {
	public static void main(String[] args) {
		//获取当前时间毫秒值
		System.out.println(System.currentTimeMillis()); // 1516090531144
		
		//计算程序运行时间
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			System.out.println(i);
		}
    	long end = System.currentTimeMillis();
    	System.out.println("共耗时毫秒:" + (end - start));
	}
}
  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
    带精确位置的数组复制,将指定源数组中的数组从指定位置复制到目标数组的指定位置,数组的拷贝动作是系统级的,性能很高。【重要方法】
    参数说明:
    src: 源数组,要被复制的数组
    srcPos: 源数组的第几个开始复制
    dest:目标数组,要复制到哪个数组
    destPos:从目标数组的第几个开始存储数据
    length: 要复制源数组几个元素

arraycopy为什么重要:因为ArrayList底层就用了它,把5个元素数组变成6个元素数组称之为 数组扩容。

arraycopy方法案例

private static void test03() {
        //源数组
        int[] src = {11,22,33,44,55};
        
        //增加66。定义比src数组多一个位置的数组dest
        int[] dest = new int[src.length+1];
       
        //3、static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length):
        //带精确位置的数组复制,将指定源数组中的数组从指定位置复制到目标数组的指定位置。
        //src: 源数组,要被复制的数组
        //srcPos: 源数组的第几个开始复制
        //dest:目标数组,要复制到哪个数组
        //destPos:从目标数组的第几个开始存储数据
        //length: 要复制几个元素
        
        System.arraycopy(src,0,dest,0,src.length); //[11,22,33,44,55,0]
        dest[5] = 66;
        
        //Arrays.toString(数组) 可以把数组打印出来
        System.out.println(Arrays.toString(dest));
    }

如果dest数组长度不够怎么办? java.lang.ArrayIndexOutOfBoundsException:数组下标超出范围错误

System.out的out是什么?

打开System.class这个类,里面会有一句 public final static PrintStream out = null;说明out是System类里面的一个属性(字段),但是PrintStream类的一个对象,并且是static的。因此out不需要实例化,类名.变量名就能直接使用:System.out

三、常用API类–BigDecimal类

float和double类型是为了在广域数值范围上提供较为精确的快速近似计算而精心设计的。然而,它们没有提供完全精确的结果,所以不应该被用于要求精确结果的场合。但是,商业计算往往要求结果精确,BigDecimal类的加减乘除能无丢失精度地表达数字。

BigDecimal好处:

解决double精度丢失的问题,能表达超过long的数

【Java之BigDecimal详解】https://www.cnblogs.com/zhangyinhua/p/11545305.html

构造方法:

BigDecimal​(String val) //将字符串形式的数字转换成BigDecimal

普通方法:

BigDecimal add​(BigDecimal augend) : 加
BigDecimal subtract​(BigDecimal subtrahend) : 减
BigDecimal multiply​(BigDecimal multiplicand) : 乘
BigDecimal divide​(BigDecimal divisor): 除

使用格式:
BigDecimal 返回值 = BigDecimal对象.add(另一个BigDecimal对象)

BigDecimal案例

public class Demo06 {
    public static void main(String[] args) {
        //计算机模拟小数有精度损失的,有时候 0.799999999999999 有时候 0.800000000001
        //某些场景挺严重的:航天,银行
        double d = 0.72+0.08; //0.80
        double d2 = 0.80;
        System.out.println(d); //0.7999999999999999
        System.out.println(d2); //0.8
        System.out.println(d==d2);

        double d3 = 0.79999999999999999;
        System.out.println("d3="+d3);

        //使用BigDecimal来解决上面的问题 BigDecimal​(String val)
        BigDecimal b1 = new BigDecimal("0.7999999999999999999999999999999999999999");
        System.out.println(b1);

        BigDecimal b72 = new BigDecimal("0.72");
        BigDecimal b08 = new BigDecimal("0.08");
        //加法
        BigDecimal b80 = b72.add(b08);

        //解决了double精度损失的问题
        System.out.println("b80 = " + b80);

        //减法
        BigDecimal b64 = b72.subtract(b08);
        System.out.println("b64 = " + b64);

        //乘法
        BigDecimal bmul = b72.multiply(b08);
        System.out.println("bmul = " + bmul);

        //除法
        BigDecimal bdiv = b72.divide(b08);
        System.out.println("bdiv = " + bdiv);

        BigDecimal b02 = new BigDecimal("2");
        BigDecimal b03 = new BigDecimal("3");
        // 1/3 = 0.333333333333333333333333333....
        //java.lang.ArithmeticException 程序不能无限地模拟小数
        //public BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
        //scale:精确的位数
        //roundingMode:最后一位怎么处理,四舍五入
        BigDecimal b0333 = b02.divide(b03,3, RoundingMode.HALF_DOWN);
        System.out.println("b0333 = " + b0333);
    }
}

四、常用API类–StringBuilder类

回顾StringBuilder

StringBuilder类是一个可变的字符序列,可以理解为可以改变长度的字符/字符串。每次操作都是对自身对象进行操作,而不是生成新的对象,其所占空间会随着内容的增加而扩充。

字符串拼接问题

String类的对象内容不可改变,所以每当进行字符串拼接时,总是会在内存中创建一个新的对象。在API中对String类有这样的描述:字符串是常量,它们的值在创建后不能被更改。

public class StringDemo {
	public static void main(String[] args) {
		String s = "Hello";
		s += "World";
		System.out.println(s);
	}
}

这段代码总共产生了三个字符串,即 “Hello” 、 “World” 和 “HelloWorld” 。引用变量s首先指向 Hello 对象,最终指向拼接出来的新字符串对象,即 HelloWord 。

//String的拼接符 + 底层是每次创建StringBuilder对象,再用append方法拼接起来

//全部+ 和append()效率一样
String s1 = "a"+"b"+"c";
String s2 = new StringBuilder("a").append("b").append("c");

// +=
String s1 = "a";
s1+="b";
s1+="c";
//直接用StringBuilder更优秀
String s2 = new StringBuilder("a").append("b").append("c");

StringBuilder类已经覆盖重写了Object当中的toString方法。

五、包装类

我们之前学过基本数据类型有8种.基本数据类型效率高,但是功能及其有限,只能做加减乘除运算。
为了对基本数据类型进行更多的操作(创建对象使用,因为对象可以做更多的功能),Java为每种基本数据类型提供了对应的类(包装类)

基本类型与包装类之间的规律:

首字母变大写,除了int -> Integer,char->Character

1、 装箱与拆箱

基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“:

  • 装箱:基本类型转换为对应的包装类对象。
  • 拆箱:包装类对象转换为对应的基本类型。
装箱:int => Integer: Integer.valueOf(int x)  包装类.valueOf(基本类型)
拆箱:Integer => int: Integer对象.intValue()   包装类对象.基本类型Value()

用Integer与 int为例

基本数值 ----> 包装类对象

//法1:使用构造函数函数转换
Integer i = new Integer(4);

//法2:使用包装类中的valueOf方法转换
Integer ii = Integer.valueOf(4);

包装对象 ----> 基本数值

//包装类对象调用对应的方法:Integer,intValue(); Double,doubleValue();...
int num = i.intValue();

2、自动装箱与自动拆箱

从Java 5(JDK 1.5)开始,基本类型与包装类的装箱、拆箱动作可以自动完成。
理解:即省略了new 包装类、包装类对象.基本类型Value()的代码

  • 自动装箱
    包装类 变量 = 基本类型值;

  • 自动拆箱
    基本类型 变量 = 包装类对象;

  • 自动装箱和自动拆箱的好处
    简化写法,装箱和拆箱的代码基本见不到

Integer i = 4;//自动装箱。等于Integer i = Integer.valueOf(4);

i = i + 5; 
//等号右边:将i对象转成基本数值(自动拆箱),等于i.intValue() + 5; //9
//加法运算完成后,再次装箱,把基本数值转成对象。 //Integer i = 9

3、基本类型与字符串之间的转换

基本类型转换为String

基本类型转换String有三种方式

  • 用+号(最简单,用的最多):
    基本类型直接与双引号"“相连接即可,如:34+” ";
  • 用String类的valueOf方法,参数传入数字/基本类型的变量
String 变量名 = String.valueOf(数字/变量)

String转换成基本类型

包装类.parse基本类型(首字母大写)

除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型:

public static byte parseByte(String s) :将字符串参数转换为对应的byte基本类型。
public static short parseShort(String s) :将字符串参数转换为对应的short基本类型。
public static int parseInt(String s) :将字符串参数转换为对应的int基本类型。
public static long parseLong(String s) :将字符串参数转换为对应的long基本类型。
public static float parseFloat(String s) :将字符串参数转换为对应的float基本类型。
public static double parseDouble(String s) :将字符串参数转换为对应的double基本类型。
public static boolean parseBoolean(String s) :将字符串参数转换为对应的boolean基本类型。

String转Character

String str = "testString";
//将字符串转成字符数组
char[] charArray = str.toCharArray();
//字符数组再转成字符类数组
Character[] charObjectArray = ArrayUtils.toObject(charArray);

https://cloud.tencent.com/developer/ask/48466

Java中char和String的相互转换
https://www.cnblogs.com/rrttp/p/7922202.html

基本类型转字符串案例

需求1:

"5"和5实现互转

public class Demo071 {
    public static void main(String[] args) {
        //1、int=>String,
        int x = 5;
        //法1:x +"" 这种最常用
        String str1 = x + "";
        //法2: String.valueOf()
        String str2 = String.valueOf(x);

        double d = 3.14;
        String dString = d+"";

        //2、String=>int
        //java.lang.NumberFormatException:数字格式异常
        String five = "5";
        //借助包装类
        int intFive = Integer.parseInt(five);
        System.out.println("intFive = " + intFive);

        String pi = "3.14";
        double piDouble = Double.parseDouble(pi);
        System.out.println("piDouble = " + piDouble);

		//Boolean转字符串
        //不区分大小写,老老实实写true 和 false
        boolean flag1 = Boolean.parseBoolean("FALSE");
        System.out.println("flag1 = " + flag1);

    }
}

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出 java.lang.NumberFormatException
异常。

六、泛型

泛型:可以在类或方法中预支地使用未知的类型。

  • 一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。

集合中是可以存放任意对象的,只要把对象存储集合后,那么这时他们都会被提升成Object类型。当我们在取出每一个对象,并且进行相应的操作,这时必须采用类型转换。示例代码:

public class GenericDemo {
	public static void main(String[] args) {
		Collection coll = new ArrayList();
		coll.add("abc");
		coll.add("itcast");
		coll.add(5);//由于集合没有做任何限定,任何类型都可以给其中存放
		Iterator it = coll.iterator();
		while(it.hasNext()){
			//需要打印每个字符串的长度,就要把迭代出来的对象转成String类型
			String str = (String) it.next();
			System.out.println(str.length());
		}
	}
}

由于集合中什么类型的元素都可以存储。导致取出时强转引发运行时 ClassCastException。

Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此
在JDK5之后,新增了泛型(Generic)语法,让你在设计API时可以指定类或方法支持泛型,这样我们使用API的时候也变得更为简洁,并得到了编译时期的语法检查。

泛型的好处:

  • 能限制ArrayList只能放入一类东西,取出来的时候无需强转,导致报java.lang.ClassCastException
  • 将运行时期的ClassCastException,转移到了编译时期变成了编译失败。(类型不对编译失败,等于提前保错)
  • 避免了类型强转的麻烦。

1、定义和使用含有泛型的类

定义格式:

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

//通俗
class 类名<E/T> {
	//所有除了静态能使用数据类型的地方,都加上E/T

	E 成员变量;

	public E 方法名(E 参数) {
		E 局部变量;
	}

	public 类名(){
		E 局部变量;
	}
}

使用泛型类

创建对象的时候确定泛型的数据类型

泛型类案例

例如,API中的ArrayList集合:

class ArrayList<E>{
	public boolean add(E e){ }
	
	public E get(int index){ }
	....
}

创建泛型类对象的时候确定泛型为String

//确定集合存储String类型
ArrayList<String> list = new ArrayList<String>();

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

class ArrayList<String>{
	public boolean add(String e){ }
	public String get(int index){ }
	...
}

再例如,创建泛型类对象的时候确定泛型为Integer

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

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

class ArrayList<Integer> {
	public boolean add(Integer e) { }
	
	public Integer get(int index) { }
	...
}

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

定义格式:

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

//通俗。定义泛型方法,方法名前面两个泛型: <T> T
<T> T 方法名(T t) {
	T t2 = null;
	return t2;
}

注意:方法名前面两个T,<T> T

使用泛型方法

调用方法时,确定泛型的类型

泛型方法案例

定义泛型方法

public class MyGenericMethod {
	public <MVP> void show(MVP mvp) {
		System.out.println(mvp.getClass());
	}
	
	public <MVP> MVP show2(MVP mvp) { 
		return mvp;
	}
}

调用方法时,确定泛型的类型。
泛型方法无法直接传入数据类型,只能根据传入的参数的类型,确定泛型的类型

public class GenericMethodDemo {
	public static void main(String[] args) {
		// 创建对象
		MyGenericMethod mm = new MyGenericMethod();
		// 演示看方法提示
		mm.show("aaa");
		mm.show(123);
		mm.show(12.45);
	}
}

3、定义和使用含有泛型的接口

泛型接口:就是在泛型中使用接口

定义格式

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

//通俗
interface 接口名<泛型> {

}

泛型接口的使用

第一种,在实现泛型接口的时候确定泛型的数据类型
第二种,在实现泛型的时候不要确定,并且实现类与接口声明一样的泛型

泛型接口案例

定义泛型接口

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

使用泛型接口

1、实现接口时确定泛型接口的类型

public class MyImp1 implements MyGenericInterface<String> {
	@Override
	public void add(String e) {
		// 省略...
	}
	
	@Override
		public String getE() {
		return null;
	}
}

此时,泛型E的值就是String类型。

2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型

public class MyImp2<E> implements MyGenericInterface<E> {
	@Override
	public void add(E e) {
		// 省略...
	}
	
	@Override
	public E getE() {
		return null;
	}
}

确定泛型:

public class GenericInterface {
	public static void main(String[] args) {
		MyImp2<String> my = new MyImp2<String>();
		my.add("aa");
	}
}

4、泛型通配符

泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用?,?表示未知通配符。

代码示例

//?代表可以接收任意类型
public static void getElement(Collection<?> coll){
}

public static void main(String[] args) {
	Collection<Intger> list1 = new ArrayList<Integer>();
	getElement(list1);
	
	Collection<String> list2 = new ArrayList<String>();
	getElement(list2);
}

// 泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的

理解:
即创建对象时泛型类型被确定为多个类型时,?通配符可以接受确定后的不同类型。
E/T通配符创建对象被确定泛型类型时,只能接受确定后的泛型类型。

泛型通配符的高级使用

JAVA的泛型中可以指定一个泛型的上限和下限。

泛型的向上限定:

只能接收该类型及其子类

定义格式:

类型名称 <? extends 类 > 对象名称

泛型的向下限定:

只能接收该类型及其父类型

定义格式:

类型名称 <? super 类 > 对象名称

小结:

<? extends A>:表示泛型的类型只能是A类或者A类的子类。 上限,最高到A类。
<? super A>: 表示泛型的类型只能是A类或者A类的父类。  下限,最低到A类。

泛型通配符上下限案例

需求:有个宠物店,可以接收 用户带来的 多只宠物(猫、狗)

讲解:
    有这么一个关系
       Object
         |
        Pet
       /   \
     Dog   Cat

     场景:有个宠物店,可以接收 用户带来的 多只宠物(猫、狗)

     向上限定:? extends 类, 类的本身或者子类的泛型都可以放入
     向下限定:? super 类,类的本身及其父类的泛型都可以放入

小结:
    泛型类作为参数的时候
    1、向上限定:ArrayList<? extends Pet>,意味着只能传入 ArrayList<Pet> ArrayList<Dog>...
    2、向下限定:ArrayList<? super Dog>,只能传入传入ArrayList<Dog> ArrayList<Pet>...

拓展:除了通配符能用上限限定,所有的泛型都能用

代码实现

public class Demo122 {

    //逻辑要求:只能放入宠物。向上限定
    public static void clean(ArrayList<? extends Pet> pets) {
        //清洗动物
        for (int i = 0; i < pets.size(); i++) {
            Pet pet = pets.get(i);
            pet.jiao();
        }
    }

    public static void main(String[] args) {
        ArrayList<Dog> dogs = new ArrayList<>();
        dogs.add(new Dog());
        dogs.add(new Dog());
        clean(dogs);

        ArrayList<Cat> cats = new ArrayList<>();
        clean(cats);

        ArrayList<Pet> pets = new ArrayList<>();
        clean(pets);

        //?通配符不能限定只能放入某些类。
        //?通配符的向上,向下限定可以限定只能放入某些类
//        ArrayList<String> strs = new ArrayList<>();
//        clean(strs);

        System.out.println("——————————————————————————————————————————————");

        ArrayList<Object> objs = new ArrayList<>();
        beauty(dogs);
        beauty(pets);
        beauty(objs);
//        beauty(cats);
    }


    //beauty:美容。向下限定
    public static void beauty(ArrayList<? super Dog> pets) {
        //清洗动物
        for (int i = 0; i < pets.size(); i++) {
            Object object = pets.get(i);
            System.out.println(object);
        }
    }
}

七、Collection集合

回顾ArrayList集合

集合:集合是java中提供的一种容器,可以用来存储多个数据。

单个数据使用变量存储
多个数据使用数组、集合存储

ArrayList和数组的区别

  • 相同点:都能存储多个数据,都能使用下标获取数据
  • 不同点:数组的长度是固定的,ArrayList是无限长的
  • 数组能放基本类型和对象,ArrayList只能放对象

集合和数组的区别

  • 数组的长度是固定的。集合的长度是可变的(底层数组扩容)。
  • 数组中存储的是同一类型的元素,集合可以存储任意类型数据。
  • 集合存储的都是引用数据类型。如果想存储基本类型数据需要存储对应的包装类型

1、Collection接口

Collection是单列集合类的根接口,用于存储一系列符合某种规则的元素。Collection就是集合
双列集合:Map集合(key-value)

集合常用类的继承体系

图示为常用集合,还有其他集合

特点

List接口特点:元素有序、元素可重复。(数组)
Set接口特点:元素不可重复。

2、Collection常用功能

Collection是所有单列集合的父接口,在Collection中定义了单列集合(List和Set)通用的一些方法,这些方法可用于操作所有的单列集合。

public boolean add(E e) : 把给定的对象添加到当前集合中 。

public void clear() :清空集合中所有的元素。

public boolean remove(E e) : 把给定的对象在当前集合中删除。

public boolean contains(Object obj) : 判断当前集合中是否包含给定的对象。

public boolean isEmpty() : 判断当前集合是否为空。

public int size() : 返回集合中元素的个数。

public Object[] toArray() : 把集合中的元素,存储到数组中

有关Collection中的其他方法可以自行查看API学习。

3、Collection集合案例

public class Demo14 {
    public static void main(String[] args) {
        Collection<String> list = new ArrayList<>();
        
        //1.boolean add​(E e) 往集合中添加一个元素
        list.add("黄晓明 & AngelaBaby");
        list.add("景  甜");
        list.add("郭采洁");
        list.add("杨  幂");
        System.out.println("add完:"+list);
        
        //2.void clear​() 从此集合中删除所有元素
        list.clear();
        System.out.println("clear完:"+list);
        System.out.println("isEmpty = " + list.isEmpty());
        
        //3.boolean contains​(Object o) 判断集合中是否包含指定的元素
        list.add("张家辉");
        list.add("任达华");
        list.add("刘青云");
        list.add("古天乐");
        //isHas 有没有
        boolean isHas = list.contains("古天乐");
        System.out.println("isHas = " + isHas);

        //4.boolean isEmpty​() 判断集合是否为空(没有元素),如果为空返回true
        //Empty:空
        boolean isEmpty = list.isEmpty();
        System.out.println("isEmpty = " + isEmpty);


        //5.boolean remove​(Object o) 删除集合中的指定元素,如果删除返回true
        System.out.println("list.remove(渣渣辉) = " + list.remove("渣渣辉"));
        System.out.println("list.remove(张家辉) = " + list.remove("张家辉"));

        //6.int size​() 返回此集合中的元素数
        int size = list.size();
        System.out.println("size = " + size);

        //7.Object[] toArray​() 将集合转成数组
        Object[] objs = list.toArray();

        //泛型方法的应用: <T> T[] toArray(T[] a) 
        //String[] toArray(String[] a);
        //剖析数据流向
        String[] names = new String[list.size()];
        //返回值作用不大,传入的数组已经被添加值
        String[] names2 = list.toArray(names);
        System.out.println("names = " + Arrays.toString(names));
        System.out.println("names2 = " + Arrays.toString(names2));

        //8.get(int i) 属于List接口的方法
    }

}

ArrayList和LinkList的区别

https://www.jianshu.com/p/69cb91f504ab

相同点

1、ArrayList和LinkList都实现了以下接口List(支持泛型)、Cloneable(支持克隆)、Serializable(支持序列化)
2、ArrayList和LinkList都不是线程安全的
3、ArrayList和LinkList都能够用来存放各种数据类型的对象
4、

不同点

1、ArrayList继承了AbstractList类,LinkList继承了AbstractSequentialList类,AbstractSequentialLis类继承了AbstractList类
2、ArrayList内部是由数组是实现的,而LinkList内部是由循环双向链表实现的。
3、LinkList需要更多的内存空间,因为它除了要存储数据之外,还需要存储该节点的前后节点信息,而ArrayList索引处就是存的数据


转载:https://blog.csdn.net/qq_45942076/article/details/105193953
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场