四、Java SE核心I
4.1 Object类
在Java继承体系中,java.lang.Object类位于顶端(是所有对象的直接或间接父类)。
如果一个类没有写extends关键字声明其父类,则该类默认继承java.lang.Object类。Object类定义了“对象”的基本行为,被子类默认继承。
1)toString方法:返回一个可以表示该对象属性内容的字符串。
MyObject obj=new MyObject();
String info=obj.toString();
System.out.println(info);
A.上例为什么我有toString方法?
因为所有的类都继承自Object,而toString方法是Ojbect定义的,我们直接继承了这个方法。
Object的toString方法帮我们返回一个字符串,这个字符串的格式是固定的:类型@hashcode,这个hashcode是一串数字,在java中叫句柄,或叫地址
(但不是真实的物理地址,是java自己的一套虚拟地址,防止直接操作内存的)。
public String toString(){ //只能用public,重写的方法访问权限要大于等于父类中方法的权限
return "这个是我们自己定义的toString方法的返回值MyObject!";
}
B.上例为什么要重写toString方法?
toString定义的原意是返回能够描述当前这个类的实例的一串文字,我们看一串hashcode没意义,所以几乎是要重写的。
public static void main(String[] args){
//System.out.println(toString());//不行!编译错误!
Point p=new Point(1,2);
System.out.println(p);//输出p对象的toString方法返回值
}
C.上例为何有编译错误?
不能直接使用toString方法,因为该方法不是静态的。
ava语法规定:静态方法中不能直接引用非静态的属性和方法,想引用必需创建对象。
非静态方法中可以直接引用静态属性和方法。
2)equals方法:用于对象的“相等”逻辑。
A.在Object中的定义: public boolean equals(Object obj){
return (this==obj);
}
由此可见,this==obj与直接的==(双等于)效果一样,仅仅是根据对象的地址(句柄,那个hashcode值)来判断对象是否相等。
因此想比较对象与给定对象内容是否一致,则必须重写equals方法。
B.“==”与equals的区别:
用“==”比较对象时,描述的是两个对象是否为同一个对象!根据地址值判断。
而equals方法力图去描述两个对象的内容是否相等,内容相等取决于业务逻辑需要,可以自行定义比较规则。
C.equals方法的重写:如,判断两点是否相等。
public boolean equals(Object obj){//注意参数
//若给定的对象obj的地址和当前对象地址一致,那么他们是同一个对象,equals方法中有大量的内容比较逻辑时,加上这个判断会节省性能的开销!
if(this == obj){
return true;
}
/**
* equals比较前要进行安全验证,确保给定的对象不是null!若obj是null,说明该引用变量没有指向任何对象,那么就不能引用ojb所指象的对象
* (因为对象不存在)的属性和方法!若这么做就会引发NullPointException,空指针异常!
*/
if(obj == null){
return false;
}
//直接将Object转为子类是存在风险的!我们不能保证Object和我们要比较的对象是同一个类型的这会引发ClassCastException!我们称为:类造型异常。
//重写equals时第一件要做的事情就是判断给定的对象是否和当前对象为同一个类型,不是同类型直接返回false,因为不具备可比性!
if(!(obj instanceof Point)){
return false;
}
/**
*不能随便把父类转成子类,因为Object是所有类的父类,任何类型都可以传给它所以不能保证obj传进来的就是相同类型(点类型)
*/
Point p=(Point)obj;
return this.x==p.x && this.y==p.y;//内容比较逻辑定义
}
4.2 String类
是字符串类型,是引用类型,是“不可变”字符串,无线程安全问题。在java.lang.String中。
注意事项:String str =“abc”;和String str=new String(“abc”);的区别!
1) String在设计之初,虚拟机就对他做了特殊的优化,将字符串保存在虚拟机内部的字符串常量池中。
一旦我们要创建一个字符串,虚拟机先去常量池中检查是否创建过这个字符串,如有则直接引用。
String对象因为有了上述的优化,就要保证该对象的内容自创建开始就不能改变!所以对字符串的任何变化都会创建新的对象,而不是影响以前的对象!
2) String的equals方法:
两个字符串进行比较的时候,我们通常使用equals方法进行比较,字符串重写了Object的equals方法,用于比较字符串内容是否一致。
虽然java虚拟机对字符串进行了优化,但是我们不能保证任何时候“==”都成立!
3) 编程习惯:
当一个字符串变量和一个字面量进行比较的时候,用字面量.equals方法去和变量进行比较,即:if("Hello".equals(str))因为这样不会产生空指针异常。
而反过来用,即:if(str.equals("Hello"))则我们不能保证变量不是null,若变量是null,我们在调用其equals方法时会引发空指针异常,导致程序退出。
若都为变量则if(str!=null&&str.equals(str1))也可。
4) String另一个特有的equals方法:
euqalsIgnoreCase,该方法的作用是忽略大小写比较字符串内容,常用环境:验证码。if("hello".equalsIgnoreCase(str))。
5) String的基本方法:
①String toLowerCase():返回字符串的小写形式。如:str.toLowerCase()
②String toUpperCase():返回字符串的大写形式。如:str.toUpperCase()
③String trim():去掉字符串两边的空白(空格\t\n\r),中间的不去。如:str.trim()
④boolean startsWith():判断字符串是否以参数字符串开头。如:str.startsWith("s")
⑤boolean endsWith():判断字符串是否以参数字符串结尾。如:str.endsWith("s")
⑥int length():返回字符串字符序列的长度。如:str.length()
eg:如何让HelloWorld这个字符串以hello开头成立
if(str.toLowerCase().startsWith("hello")){}//有返回值的才能继续 . 先转成小写再判断
6) indexOf方法(检索):位置都是从0开始的。
①int indexOf(String str):在给定的字符串中检索str,返回其第一次出现的位置, 找不到则返回-1。
②int indexOf(String str,int from):在给定的字符串中从from位置开始检索str,返回其第一次出现的位置,找不到则返回-1(包含from位置,from之前的不看)。
eg:查找Think in Java中in后第一个i的位置
index=str.indexOf("in");
index=str.indexOf("i",index+"in".length());
//这里对from参数加in的长度的目的是 从in之后的位置开始查找
③int lastIndexOf(String str):在给定的字符串中检索str,返回其最后一次 出现的 位置,找不到则返回-1(也可认为从右往左找,第一次出现的位置)。
④int lastIndexOf(String str,int from):在给定的字符串中从from位置开始检索str, 返回其最后一次出现的位置,找不到则返回-1(包含from位置,from之后的不看)。
7) charAt方法:char charAt(int index):返回字符串指定位置(index)的字符。
eg:判断是否是回文:上海自来水来自海上
boolean tf=true;
for(int i=0;i<str2.length()/2;i++){
//char first=str2.charAt(i);
//char last=str2.charAt(str2.length()-i-1);
if(str2.charAt(i)!=str2.charAt(str2.length()-i-1)){//优化
tf = false;
break;//已经不是回文了,就没有必要再继续检查了
}
}
8) substring方法(子串):字符串的截取,下标从0开始的。
①String substring(int start,int end):返回下标从start开始(包含)到end结束的字 符串(不包含)。
②String substring(int start):返回下标从start开始(包含)到结尾的字符串。
9) getBytes方法(编码):将字符串转换为相应的字节。
①byte[] getBytes():以当前系统默认的字符串编码集,返回字符串所对应的二进制序列。
如:
byte[] array=str.getBytes();
System.out.println(Arrays.toString(array));
②byte[] getBytes(String charsetName):以指定的字符串编码集,返回字符串所对应 的二进制序列。
这个重载方法需要捕获异常,这里可能引发没有这个编码集的异常, UnsupportedEncodingException,
如:
str="常";
byte[] bs=info.getBytes("UTF-8");
注意事项:
①Windows的默认编码集GBK:英文用1个字节描述,汉字用2个字节描述;
ISO-8859-1欧洲常用编码集:汉字用3个字节描述;
GBK国标;GB2312国标;UTF-8编码集是最常用的:汉字用3个字节描述。
②编码:将数据以特定格式转换为字节;
解码:将字节以特定格式转换为数据。
String(byte[] bytes, String charsetName) :通过使用指定的charset解码指定的byte数组,构造一个新的String。
如:String str=new String(bs,"UTF-8");
10)split方法(拆分):字符串的拆分。
String[] split(String regex):参数regex为正则表达式,以regex所表示的字符串为 分隔符,将字符串拆分成字符串数组。
其中,regex所表示的字符串不被保留,即 不会存到字符串数组中,可理解为被一刀切,消失!
eg:对图片名重新定义,保留图片原来后缀
String name="me.jpg";
String[] nameArray=name.split("\\."); //以正则表达式拆分 .有特殊含义,所以用\\. 转义
System.out.println("数组长度:"+nameArray.length);//如果不用\\.则长度为0
System.out.println(Arrays.toString(nameArray));//任意字符都切一刀,都被切没了
String newName="123497643."+nameArray[1];
System.out.println("新图片名:"+newName);
注意事项:分隔符放前、中都没事,放最后将把无效内容都忽略。
String str="123,456,789,456,,,";
String[] array=str.split(",");//分隔符放前、中都没事,放最后将把无效内容都忽略
System.out.println(Arrays.toString(array));//[123, 456, 789, 456]
11)replace方法:字符串的替换。
String replaceAll(String regex,String replacement):将字符串中匹配正则表达式regex 的字符串替换成replacement。
如:String str1=str.replaceAll("[0-9]+", "chang");
12)String.valueOf()方法:重载的静态方法,用于返回各类型的字符串形式。
String.valueOf(1);//整数,返回字符串1 String.valueOf(2.1);//浮点数,返回字符串1.2
4.3 StringUtils类
针对字符串操作的工具类,提供了一系列静态方法,在Apache阿帕奇Commons-lang包下中,需下载。
StringUtils常用方法:
1)String repeat(String str,int repeat):重复字符串repeat次后返回。
2)String join(Object[] array,String):将一个数组中的元素连接成字符串。
3)String leftPad(String str,int size,char padChar):向左边填充指定字符padChar,以达到指定长度size。
4)String rightPad(String str,int size,char padChar):向右边填充指定字符padChar,以达到指定长度size。
4.4 StringBuilder类
与String对象不同,StringBuilder封装“可变”的字符串,有线程安全问题。
对象创建后,可通过调用方法改变其封装的字符序列。
StringBuilder常用方法:
1)追加字符串:StringBuilder append(String str):
2)插入字符串:StringBuilder insert(int index,String str):插入后,原内容依次后移
3)删除字符串:StringBuilder delete(int start,int end):
4)替换字符串:StringBuilder replace(int start,int end,String str):含头不含尾
5)字符串反转:StringBuilder reverse():
eg:各类操作
StringBuilder builder=new StringBuilder();
builder.append("大家好!") .append("好好学习") .append("天天向上");
System.out.println(builder.toString()); //返回的还是自己:builder,所以可以再.append(String str);
builder.insert(4, "!");
System.out.println(builder.toString());
builder.replace(5,9,"Good Good Study!");
System.out.println(builder.toString());
builder.delete(9, builder.length());
System.out.println(builder.toString());
注意事项:
① 该类用于对某个字符串频繁的编辑操作,使用StringBuilder可以在大规模修改字符串时,不开辟新的字符串对象,从而节约内存资源,
所以,对有着大量操作字符串的逻辑中,不应使用String而应该使用StringBuilder。
② append是有返回值的,返回类型是StringBuilder,而返回的StringBuilder其实就是自己(this),append方法的最后一句是return this;
③ StringBuilder与StringBuffer区别:效果是一样的。
StringBuilder是线程不安全的,效率高,需JDK1.5+。
StringBuffer是线程安全的,效率低,“可变”字符串。
在多线程操作的情况下应使用StringBuffer,因为StringBuffer是线程安全的,他难免要顾及安全问题,而进行必要的安全验证操作。
所以效率上要 比StringBuilder低,根据实际情况选择。
4.5正则表达式
实际开发中,经常需要对字符串数据进行一些复杂的匹配、查找、替换等操作,通过正则表达式,可以方便的实现字符串的复杂操作。
正则表达式是一串特定字符,组成一个“规则字符串”,这个“规则字符串”是描述文本规则的工具,正则表达式就是记录文本规则的代码。
[] |
表示一个字符 |
[abc] |
表示a、b、c中任意一个字符 |
[^abc] |
除了a、b、c的任意一个字符 |
[a-z] |
表示a到z中的任意一个字符 |
[a-zA-Z0-9_] |
表示a到z、A到Z、0到9以及下滑线中的任意一个字符 |
[a-z&&[^bc]] |
表示a到z中除了b、c之外的任意一个字符,&&表示“与”的关系 |
. |
表示任意一个字符 |
\d |
任意一个数字字符,相当于[0-9] |
\D |
任意一个非数字字符,相当于[^0-9] |
\s |
空白字符,相当于[\t\n\f\r\x0B] |
\S |
非空白字符,相当于[^\s] |
\w |
任意一个单词字符,相当于[a-zA-Z0-9_] |
\W |
任意一个非单词字符,相当于[^\w] |
^ |
表示字符串必须以其后面约束的内容开始 |
$ |
表示字符串必须以其前面约束的内容结尾 |
? |
表示前面的内容出现0到1次 |
* |
表示前面的内容出现0到多次 |
+ |
表示前面的内容出现1到多次 |
{n} |
表示前面的字符重复n次 |
{n,} |
表示前面的字符至少重复n次 |
{n,m} |
表示前面的字符至少重复n次,并且小于m次 X>=n && X<m |
注意事项:
① 邮箱格式的正则表达式 @无特殊含义,可直接写,也可[@]
② 使用Java字符串去描述正则表达式的时候,会出现一个冲突,即如何正确描述正则表达式的“.”。
起因:在正则表达式中我们想描述一个“.”,但“.”在正则表达式中有特殊含义,他代表任意字符,所以我们在正则表达式中想描述“.”的愿义就要写成“\.”
但是我们用java字符串去描述正则表达式的时候,因为“.”在java字符串中没有特殊意义,所以java认为我们书写String s="\.";是有语法错误的,
因为“.”不需要转义,这就产生了冲突。
处理:我们实际的目的很简单,就是要让java的字符串描述"\."又因为在java中"\"是有特殊含义的,代表转义字符我们只需要将"\"转义为单纯的斜杠,即可描述"\."了。
所以我们用java描述“\.”的正确写法是String s="\\.";
③ 若正则表达式不书写^或$,正则表达式代表匹配部分内容,都加上则表示全匹配
eg:测试邮箱正则表达式:Pattern的作用是描述正则表达式的格式支持,使用静态方法compile注册正则表达式,生成实例。
String regStr="^[a-zA-Z0-9_]+@[a-zA-Z0-9]+(\\.com|\\.cn|\\.net)+$";
Pattern pattern=Pattern.compile(regStr); //注册正则表达式
String mailStr="chang_2013@chang.com.cn";
Matcher matcher=pattern.matcher(mailStr); //匹配字符串,返回描述匹配结果的Matcher实例
if(matcher.find()){ //通过调用Matcher的find方法得知是否匹配成功
System.out.println("邮箱匹配成功!");
}else{
System.out.println("邮箱格式错误!");
}
4.6 Date类
java.util.Date类用于封装日期及时间信息,一般仅用它显示某个日期,不对他做任何操作处理,要做处理用Calendar类,计算方便。
Date date=new Date();//创建一个Date实例,默认的构造方法创建的日期代表当前系统时间
System.out.println(date);//只要输出的不是类名@hashcode值,就说明它重写过toString()
long time=date.getTime();//查看date内部的毫秒值
date.setTime(time+1000*60*60*24);//设置毫秒数让一个时间Date表示一天后的当前时间
int year=date.getYear();//画横线的方法不建议再使用:1、有千年虫问题。2、不方便计算
4.7 Calendar类
java.util.Calendar类用于封装日历信息,其主作用在于其方法可以对时间分量进行运算。
1)通过Calendar的静态方法获取一个实例该方法会根据当前系统所在地区来自行决定时区,帮我们创建Calendar实例,
这里要注意,实际上根据不同的地区,Calendar有若干个子类实现。
而Calendar本身是抽象类,不能被实例化!我们不需要关心创建的具体实例为哪个子类,我们只需要根据Calendar规定的方法来使用就可以了。
2)日历类所解决的根本问题是简化日期的计算,要想表示某个日期还应该使用Date类描述。
Calendar是可以将其描述的时间转化为Date的,我们只需要调用其getTime()方法就可以获取描述的日期的Date对象了。
3)通过日历类计算时间:为日历类设置时间,日历类设置时间使用通用方法set。
set(int field,int value),field为时间分量,Calendar提供了相应的常量值,value为对应的值。
4)只有月份从0开始:0为1月,以此类推,11为12月,其他时间是正常的从1开始。也可以使用Calendar的常量 calendar.NOVEMBER……等.
5)Calendar.DAY_OF_MONTH 月里边的天---号;
Calendar.DAY_OF_WEEK 星期里的天---星期几
Calendar.DAY_OF_YEAR 年里的天
eg:
Calendar calendar=Calendar.getInstance();//构造出来表示当前时间的日历类
Date now=calendar.getTime();//获取日历所描述的日期
calendar.set(Calendar.YEAR, 2012);//设置日历表示2012年
calendar.set(Calendar.DAY_OF_MONTH,15);//设置日历表示15号
calendar.add(Calendar.DAY_OF_YEAR, 22);//想得到22天以后是哪天
calendar.add(Calendar.DAY_OF_YEAR, -5);//5天以前是哪天
calendar.add(Calendar.MONTH, 1);得到1个月后是哪天
System.out.println(calendar.getTime());
6)获取当前日历表示的日期中的某个时间单位可以使用get方法.
int year=calendar.get(Calendar.YEAR);
int month=calendar.get(Calendar.MONTH);
int day=calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year+"年"+(month+1)+"月"+day+"日");//month要处理
4.8 SimpleDateFormat类
java.text.SimpleDateFormat类,日期转换类,该类的作用是可以很方便的在字符串和日期类之间相互转换。
1)这里我们在字符串与日期类间相互转换是需要一些约束的,
"2012-02-02"这个字符串如何转换为Date对象?Date对象又如何转为字符串?
parse方法用于按照特定格式将表示时间的字符串转化成Date对象。
format方法用于将日期数据(对象)按照指定格式转为字符串。
2)常用格式字符串
字符 |
含义 |
示例 |
y |
年 |
yyyy年-2013年;yy年-13年 |
M |
月 |
MM月-01月;M月-1月 |
d |
日 |
dd日-06日;d日-6日- |
E |
星期 |
E-星期日(Sun) |
a |
AM或PM |
a-下午(PM) |
H |
24小时制 |
a h时-小午12时 HH:mm:ss-12:46:33 hh(a):mm:ss-12(下午):47:48 |
h |
12小时制 |
|
m |
分钟 |
|
s |
秒 |
eg:字符串转成Date对象
//1.创建一个SimpleDateFormat并且告知它要读取的字符串格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
String dateFormat="2013-05-14";//创建一个日期格式字符串
//2.将一个字符串转换为相应的Date对象
Date date=sdf.parse(dateFormat);//要先捕获异常
System.out.println(date);//输出这个Date对象
eg:Date对象转成字符串
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
Date now=new Date();
String nowStr=sdf.format(now);//把日期对象传进去
3)在日期格式中 - 和 空格 无特殊意义,无特殊含义的都将原样输出。
eg:将时间转为特定格式
//将当前系统时间转换为2012/05/14 17:05:22的效果
SimpleDateFormat format1=new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
nowStr=format1.format(now);
System.out.println(nowStr);
4.9 DateFormat类
java.text.DateFormat类(抽象类)是SimpleDateFormat类的父类,用的少,没SimpleDateFormat灵活。
创建用于将Date对象转换为日期格式的字符串的DateFormat,创建DateFormat对象的实例,
使用静态方法getDateInstance(style,aLocale),
style为输出日期格式的样式:DateFormat有对应的常量;
aLocale为输出的地区信息,影响字符串的语言和表现形式。
eg:
Date now=new Date();
DateFormat format=DateFormat.getDateInstance( DateFormat.MEDIUM,Locale.CHINA);
4.10包装类
Java语言的8种基本类型分别对应了8种“包装类”。
每一种包装类都封装了一个对应的基本类型成员变量,同时还提供了针对该数据类型的实用方法。
1)包装类的目的:
用于将基本类型数据当作引用类型看待。
2)包装类的名字:
除了Integer(int),Character(char)外,其余包装类名字都是基本类型名首字母大写。
3)拆、装箱:
Integer i=new Integer(1); //创建一个以对象形式存在的整数1,这种从基本类型转为引用类型的过程称之为“装箱”,反之叫“拆箱”。
4)装箱:方式一:Double d=new Double(2.2);//装箱
方式二:Double d=Double.valueOf(2.2);//基本类型都有valueOf方法
5)拆箱:double num=d.doubleValue();//拆箱
6)包装类使用前提:JDK1.5+
public static void say(Object obj){
System.out.println(obj);
}
int a=1; //基本类型,不是Object子类!
say(a); //在java 1.4版本的时候,这里还是语法错误的!因为int是基本类型,不是Object对象,要自己写8种基本类型对应的方法
7)包装类的使用:实例化一个对象,该对象代表整数1;
Integer的作用是让基本类型int作为一个引用类型去看待。这样就可以参与到面向对象的编程方式了。
由此我们可以将一个int当作一个Object去看待了,也成为了Object的子类。
Integer i=new Integer(a); //装箱,或者写Integer i=new Integer(1);
Integer ii=Integer.valueOf(a);//装箱另一种方式
int num=i.intValue(); //拆箱
say(i); //Integer是Object的子类,可以调用!
8)JDK1.5包装类自动拆装箱(原理):
在编译源程序的时候,编译器会预处理,将未作拆箱和装箱工作的语句自动拆箱和装箱。可通过反编译器发现。
say(Integer.valueOf(a));自动装箱
num=i; //引用类型变量怎么能复制给基本类型呢?
//num=i.intValuse();//自动拆箱
9)包装类的一些常用功能:
将字符串转换为其类型,方法是:parseXXX,XXX代表其类型。
这里要特别注意!一定要保证待转换的字符串描述的确实是或者兼容要转换的数据类型!否则会抛出异常!
String numStr="123";
System.out.println(numStr+1); //1231
int num=Integer.parseInt(numStr);
System.out.println(num+1) //124
long longNum=Long.parseLong(numStr);
System.out.println(longNum); //123
double doubleNum=Double.parseDouble(numStr);
System.out.println(doubleNum);//123.0
10)Integer提供了几个有趣的方法:
将一个整数转换为16进制的形式,并以字符串返回;String hStr=Integer.toHexString(num);
将一个整数转换为2进制的形式,并以字符串返回。 String bStr=Integer.toBinaryString(num);
11)所有包装类都有几个共同的常:获取最大、最小值。
int max=Integer.MAX_VALUE;//int最大值
int min=Integer.MIN_VALUE;//int最小值
System.out.println(Integer.toBinaryString(max));
System.out.println(Integer.toBinaryString(min));
4.11 BigDecimal类
表示精度更高的浮点型,在java.math.BigDecimal包下,该类可以进行更高精度的浮点运算。
需要注意的是,BigDecimal可以描述比Double还要高的精度,所以在转换为基本类型时,可能会丢失精度!
1)BigDecimal的使用:
创建一个BigDecimal实例,可以使用构造方法BigDecimal(String numberFormatString)用字符串描述一个浮点数作为参数传入。
BigDecimal num1=new BigDecimal("3.0");
BigDecimal num2=new BigDecimal("2.9"); //运算结果依然为BigDecimal表示的结果
BigDecimal result=num1.subtract(num2);//num1-num2
System.out.println(result);
float f=result.floatValue();//将输出结果转换为基本类型float
int i=result.intValue(); //将输出结果转换为基本类型int
2)BigDecimal可以作加add、减subtract、乘multiply、除divide等运算:
这里需要注意除法,由于除法存在结果为无限不循环小数,所以对于除法而言,我们要制定取舍模式,否则会一直计算下去,直到报错(内存溢出)。
result=num1.divide(num2,8,BigDecimal.ROUND_HALF_UP); //小数保留8位,舍去方式为四舍五入
4.12 BigInteger类
使用描述更长位数的整数“字符串”,来表示、保存更长位数的整数,在java.math.BigInteger包下。
1)BigInteger的使用:创建BigInteger
BigInteger num=new BigInteger("1"); //num=new BigInteger(1);不可以,没有这样的构造器
num=BigInteger.valueOf(1); //这种方式我们可以将一个整数的基本类型转换为BigInteger的实例
2)理论上:BigInteger存放的整数位数只受内存容量影响。
3)BigInteger同样支持加add、减subtract、乘multiply、除divide等运算。
eg:1-200的阶乘
for(int i=1;i<=200;i++){
num=num.multiply(BigInteger.valueOf(i));
}
System.out.println("结果"+num.toString().length()+"位"); System.out.println(num);
4.13 Collection集合框架
在实际开发中,需要将使用的对象存储于特定数据结构的容器中。而JDK提供了这样的容器——集合框架,集合框架中包含了一系列不同数据结构(线性表、查找表)的实现类。
1)Collection常用方法:
①int size():返回包含对象个数。
②boolean isEmpty():返回是否为空。
③boolean contains(Object o):判断是否包含指定对象。
④void clear():清空集合。
⑤boolean add(E e):向集合中添加对象。
⑥boolean remove(Object o):从集合中删除对象。
⑦boolean addAll(Collection<? extends E > c):另一个集合中的所有元素添加到集合
⑧boolean removeAll(Collection<?> c):删除集合中与另外一个集合中相同的原素
⑨Iterator<E> iterator():返回该集合的对应的迭代器
2)Collection和Collentions的区别
Collection是java.util下的接口,它是各种集合的父接口,继承于它的接口主要有Set 和List;
Collections是个java.util下的类,是针对集合的帮助类,提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
4.14 List集合的实现类ArrayList和LinkedList
List接口是Collection的子接口,用于定义线性表数据结构,元素可重复、有序的;
可以将List理解为存放对象的数组,只不过其元素个数可以动态的增加或减少。
1)List接口的两个常见的实现类:ArrayList和LinkedList,分别用动态数组和链表的方式实现了List接口。
List、ArrayList和LinkedList均处于java.util包下。
2)可以认为ArrayList和LinkedList的方法在逻辑上完全一样,只是在性能上有一定的差别,ArrayList更适合于随机访问,而LinkedList更适合于插入和删除,
在性能要求不是特别苛刻的情形下可以忽略这个差别。
ArrayList LinkedList
3)使用List我们不需要在创建的时候考虑容量集合的容量是根据其所保存的元素决定的换句话说,集合的容量是可以自动扩充的。
4)List的实现类会重写toString方法,依次调用所包含对象的toString方法,返回集合中所包含对象的字符串表现。
5)常用方法:
①add(Object obj):向想集合末尾追加一个新元素,从该方法的参数定义不难看出,集合可以存放任意类型的元素,
但在实际编程中我们发现,几乎不会向集合中存放一种以上的不同类型的元素。
②size()方法:返回当前集合中存放对象的数量。
③clear()方法:用于清空集合。
④isEmpty()方法:用于返回集合是否为空。
List list=new ArrayList();
list.add("One");
list.add("Two");
list.add("Three");
//list.add(1);//不建议这样的操作!尽量不在同一个集合中存放不用类型元素
System.out.println("集合中元素的数量:"+list.size());
System.out.println(list);
//System.out.println(list.toString()); //ArrayList重写了toString()方法返回的字符串是每个元素的toString()返回值的序列
list.clear();//清空
System.out.println("清空后元素的数量:"+list.size());
System.out.println("集合是否为空?:"+list.isEmpty());
⑤contains(Object obj)方法:检查给定对象是否被包含在集合中。
检查规则是将obj对象与集合中每个元素进行equals比较,若比对了所有元素均没有equals为true的情况则返回false。
注意事项:
a.根据情况重写equals:若比较是否是同一个对象,则不需要重写,直接用contains里的equals比较即可。
若重写equals为内容是否相同,则按内容比较,不管是否同一个对象。
b.是否重写元素的equals方法对集合的操作结果有很大的效果不同!
⑥boolean remove(Object obj)方法:
删除一个元素: 不重写equals,不会有元素被删除(因为比较的是对象的地址,都不相同),
重写equals为按内容比较,则删除第一个匹配的就退出,其他即使内容相同也不会被删除。
List list=new ArrayList();//多态的写法 //ArrayList arrayList=new ArrayList();//正常的写法
list.add(new Point(1,2)); //Point point =new Point(1,2); list.add(point);等量代换
list.add(new Point(3,4));
list.add(new Point(5,6));
System.out.println("集合中元素的数量:"+list.size());
System.out.println(list);
Point p=new Point(1,2);//创建一个Point对象
System.out.println("p在集合中存在么?"+list.contains(p)); //不重写为false 重写为true
System.out.println("删前元素:"+list.size());
list.remove(p);//将p对象删除,不重写equals,不会有元素被删除
System.out.println("删后元素:"+list.size());
System.out.println(list);
⑦E remove(int index)方法:
移除此列表中指定位置上的元素。向左移动所有后续元素(将其索引减1)。
因此在做删除操作时集合的大小为动态变化的,为了防止漏删,必须从后往前删!
ArrayList list=new ArrayList();
list.add("java");
list.add("aaa");
list.add("java");
list.add("java");
list.add("bbb");
//从前往后删---相邻的元素删不掉!
for(int i=0;i<list.size();i++){
if( "java".equals(list.get(i))){
list.remove(i);
}
}
//从后往前删---可以删干净!
for(int i=list.size()-1;i>=0;i--){
if("java".equals(list.get(i))){
list.remove(i);
}
}
⑧addAll(Collection c)方法:
允许将c对应的集合中所有元素存入该集合,即并集。
注意,这里的参数为Collection,所以换句话说,任何集合类型都可以将其元素存入其他集合中!
⑨removeAll(Collection c)方法:
删除与另一个集合中相同的元素。它的“相同”逻辑通过equals方法来判断。
⑩retainAll(Collection c)方法:
保留与另一个集合中相同的元素,即交集。它的“相同”逻辑通过equals方法来判断。
list1.addAll(list2);//并集
list1.removeAll(list3);//从list1中删除与list3中相同(equals为true)的元素
list1.retainAll(list2);//保留list1中删除与list2中相同(equals为true)的元素
?Object get(int index)方法:
根据元素下标获取对应位置的元素并返回,这里元素的下标和数组相似。
?Object set(int index,Object newElement)方法:
将index位置的元素修改为newElement修改后会将被修改的元素返回。
因此,可实现将List中第i个和第j个元素交换的功能:list.set ( i , list.set ( j , list.get ( i ) ) ) ;
?add(int index,Object newElement)方法:
使用add的重载方法,我们可以向index指定位置插入newElement,原位置的元素自动向后移动,即所谓的“插队”。
?Object remove(int index)方法:
将集合中下标为index的元素删除,并将被删除的元素返回(不根据equals,根据下标删除元素)。
List list=new ArrayList();
list.add("One");
list.add("Two");
list.add("Three");
//因为get方法是以Object类型返回的元素,所以需要造型,默认泛型Object
String element=(String)list.get(2);//获取第三个元素
System.out.println(element);
for(int i=0;i<list.size();i++){//遍历集合
System.out.println(list.get(i));
}
Object old=list.set(2, "三");
System.out.println("被替换的元素:"+old);
System.out.println(list);
list.add(2, "二"); //在Two与“三”之间插入一个“二”
System.out.println(list);
Object obj=list.remove(1);
System.out.println("被删除的元素:"+obj);
?indexOf(Object obj)方法:用于在集合中检索对象,返回对象第一次出现的下标。
?lastIndexOf(Object obj)方法:用于在集合中检索对象,返回对象最后一次出现的下标。
?Object[] toArray()方法:该方法继承自Collection的方法,该方法会将集合以对象数组的形式返回。
?toArray()的重载方法,T[] toArray(T[] a):可以很方便的让我们转换出实际的数组类型。
如下例,参数new Point[0]的作用是作为返回值数组的类型,所以参数传入的数组不需要有任何长度,因为用不到,就没有必要浪费空间。
Object[] array=list.toArray();//将集合以对象数组的形式返回
for(int i=0;i<array.length;i++){
Point p=(Point)array[i];
System.out.println(p.getX());
}
Point[] array1=(Point[])list.toArray(new Point[0]); //toArray()的重载方法
for(int i=0;i<array1.length;i++){
Point p=array1[i];//不需要每次都强转了
System.out.println(p.getX());
}
?List<E> subList(int fromIndex, int toIndex)方法:获取子集合,但在获取子集后,若对子集合的元素进行修改,则会影响原来的集合。
List<Integer> list=new ArrayList<Integer>();
for(int i=0;i<10;i++){
list.add(i);
}
List<Integer> subList=list.subList(3, 8); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 取子集(3-7)
System.out.println(subList);//[3, 4, 5, 6, 7]
//我们在获取子集后,若对自己元素进行修改,会影响原来的集合
for(int i=0;i<subList.size();i++){
int element=subList.get(i);
element*=10;
subList.set(i, element);
//subList.set(i, subList.get(i)*10);同上三步
}
System.out.println(list);//原集合内容也被修改了
?Comparable接口:针对对象数组或者集合中的元素进行排序时,首选需要确定对象元素的“比较”逻辑(即哪个大哪个小)。
Comparable接口用于表示对象间的大小关系,我们需要实现Comparable接口,并重写compareTo()方法定义比较规则。
public int compareTo(ComparablePoint o){
int r=x*x+y*y;//自身点到原点的距离
int other=o.x*o.x+o.y*o.y;//参数点到原点的距离
return r-other; //返回结果大于0,自身比参数大;小于0,自身比参数小;等于0,自身和参数相等; //equals返回true的时候,comparTo的返回值应该为0
}
21 Collections.sort()方法:
需要集合中的对象实现Comparable接口,从而可以调用其compareTo方法判断对象的大小,否则sort将无法判断。
该方法会依次调用集合中每个元素的compareTo方法,并进行自然排序(从小到大)。
List<ComparablePoint> list=new ArrayList<ComparablePoint>();
list.add(new ComparablePoint(1,5));
list.add(new ComparablePoint(3,4));
list.add(new ComparablePoint(2,2));
System.out.println(list);//输出顺序与存方时一致
Collections.sort(list);
System.out.println(list);
22 Comparator接口:比较器。
一旦Java类实现了Comparable,其比较逻辑就已经确定了,如果希望在排序中的操作按照“临时指定规则”,即自定义比较规则。可以采用Comparator接口回调方式。
Comparator比较器创建步骤:
A.定义一个类并实现Comparator接口。
B.实现接口中的抽象方法compare(E o1,E o2)。
C.实例化这个比较器
D.调用Collections的重载方法:sort(Collection c,Comparator comparator)进行排序。通常使用匿名类方式创建一个实例来定义比较器。
Comparator<ComparablePoint> c=new Comparator<ComparablePoint>(){
public int compare(ComparablePoint o1,ComparablePoint o2){
return o1.getX()-o2.getX();//两个点的X值大的大
}
};
Collections.sort(list, c);
System.out.println(list);
4.15 Iterator迭代器
所有Collection的实现类都实现了iterator方法,该方法返回一个Iterator接口类型的对象,用于实现对集合元素迭代的便利。在java.util包下。
1)Iterator定义有三个方法:
①boolean hasNext()方法:判断指针后面是否有元素。
②E next()方法:指针后移,并返回当前元素。E代表泛型,默认为Object类型。
③void remove()方法:在原集合中删除刚刚返回的元素。
2)对于List集合而言,可以通过基于下标的get方法进行遍历;
而iterator方法是针对Collection接口设计的,所以,所有实现了Collection接口的类,都可以使用Iterator实现迭代遍历。
3)迭代器的使用方式:先问后拿。问:boolean hasNext()该方法询问迭代器当前集合是否还有元素;
拿:E next()该方法会获取当前元素。迭代器的迭代方法是while循环量身定制的。
List list=new ArrayList();
list.add("One");
list.add("#");
Iterator it=list.iterator();
while(it.hasNext()){//集合中是否还有下一个元素
Object element=it.next();//有就将其取出
System.out.println(element);
}
4)迭代器中的删除问题:
在迭代器迭代的过程中,我们不能通过“集合”的增删等操作,来改变该集合的元素数量!否则会引发迭代异常!
若想删除迭代出来的元素,只能通过Iterator。迭代器在使用自己的remove()方法时,可以将刚刚获取的元素从集合中删除,但是不能重复调用两次!
即在不迭代的情况下,不能在一个位置删两次。
while(it.hasNext()){//集合中是否还有下一个元素
String element=(String)it.next();//有就将其取出,next返回值为E(泛型)默认为Object所以需要强转
if("#".equals(element)){
//list.remove(element);不可以!
it.remove();//删除当前位置元素
}
}
4.16泛型
1)泛型是 JDK1.5引入的新特性,泛型的本质是参数化类型。
在类、接口、方法的定义过程中,所操作的数据类型为传入的指定参数类型。
所有的集合类型都带有泛型参数,这样在创建集合时可以指定放入集合中的对象类型。同时,编译器会以此类型进行检查。
2)ArrayList支持泛型,泛型尖括号里的符号可随便些,但通常大写E。
3)迭代器也支持泛型,但是迭代器使用的泛型应该和它所迭代的集合的泛型类型一致!
4)泛型只支持引用类型,不支持基本类型,但可以使用对应的包装类
5)如果泛型不指定类型的话,默认为Object类型。
ArrayList<Point> list=new ArrayList<Point>();
list.add(new Point(1,2));
list.add(new Point(3,4));
//list.add("哈哈");//定义泛型后,只运行Point类型,否则造型异常
--遍历集合 1
for(int i=0;i<list.size();i++){
Point p=/*(Point)也不需要强转造型了*/ list.get(i);
System.out.println(p.getX());
}
--遍历集合 2
Iterator<Point> it=list.iterator(); while(it.hasNext()){
Point p=it.next();//也不需要强转了
System.out.println(p);
}
6)自定义泛型
Point p=new Point(1,2);//只能保存整数
//把Point类的int都改成泛型E,或者也可设置多个泛型Point<E,Z>
Point<Double> p1=new Point<Double>(1.0,2.3); //设置一个泛型
Point<Double,Long> p2=new Point<Double,Long>(2.3,3L);//设置多个泛型
4.17增强型for循环
JDK在1.5版本推出了增强型for循环,可以用于数组和集合的遍历。
注意事项:集合中要有值,否则直接退出(不执行循环)。
1)老循环:自己维护循环次数, 循环体自行维护获取元素的方法。
int[] array=new int[]{1,2,3,4,5,6,7};
for(int i=0;i<array.length;i++){//维护循环次数
int element=array[i];//获取数组元素
System.out.print(element);
}
2)新循环:自动维护循环次数(由遍历的数组或集合的长度决定),自动获取每次迭代的元素。
int[] array=new int[]{1,2,3,4,5,6,7};
for(int element:array){
System.out.print(element);
}
3)新循环执行流程:
遍历数组array中的每个元素,将元素一次赋值给element后进入循环体,直到所有元素均被迭代完毕后退出循环。
注意事项:使用新循环,element的类型应与循环迭代的数组或集合中的元素类型一致!至少要是兼容类型!
4)新循环的内部实现是使用迭代器完成的Iterator。
5)使用新循环遍历集合:
集合若使用新循环,应该为其定义泛型,否则我们只能使用Object作为被接收元素时的类型。
通常情况下,集合都要加泛型,要明确集合中的类型,集合默认是Object。
ArrayList<String> list=new ArrayList<String>();
list.add("张三");
list.add("李四");
list.add("王五");
for(String str:list){//默认是Object自己加泛型String
System.out.println(str);
}
4.18 List高级-数据结构:Queue队列
队列(Queue)是常用的数据结构,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素。
Queue接口:在包java.util.Queue。
1)队列遵循先进先出原则:FIFO(First Input First Output)队列不支持插队,插队是不道德的。
2)JDK中提供了Queue接口,同时使得LinkedList实现了该接口(选择LinkedList实现Queue的原因在于Queue经常要进行插入和删除的操作,而LinkedList在这方面效率较高)。
poll offer
3)常用方法:
①boolean offer(E e):将一个对象添加至队尾,如果添加成功则返回true。
②poll():从队列中取出元素,取得的是最早的offer元素,从队列中取出元素后,该元素会从队列中删除。若方法返回null说明 队列中没有元素了。
③peek():获取队首的元素(不删除该元素!)
eg:队列相关操作
Queue<String> queue=new LinkedList<String>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println(queue);//[A, B, C]
System.out.println("队首:"+queue.peek());//获取队首元素,但不令其出队
String element=null;
while((element=queue.poll()) != null){
System.out.println(element);
}
4.19 List高级-数据结构:Deque栈
栈(Deque)是常用的数据结构,是Queue队列的子接口,因此LinkedList也实现了Deque接口。
栈将双端队列限制为只能从一端入队和出队,对栈而言即是入栈和出栈。
子弹夹就是一种栈结构。在包java.util.Deque下。
1)栈遵循先进后出的原则:FILO(First Input Last Output)。
2)常用方法:
①push:压入,向栈中存入数据。
②pop:弹出,从栈中取出数据。
③peek:获取栈顶位置的元素,但不取出。
注意事项:我们在使用pop获取栈顶元素之前,应现使用peek方法获取该元素,确定该元素不为null的情况下才应该将该元素从栈中弹出”,
否则若栈中没有元素后,我们调用pop会抛出异常“NoSuchElementException”。
eg:栈相关操作
Deque<Character> deque=new LinkedList<Character>();
for(int i=0;i<5;i++){
deque.push((char)(‘A‘+i));
}
System.out.println(deque);
//注意使用peek判断栈顶是否有元素
while(deque.peek()!=null){
System.out.print(deque.pop()+" ");
}
4.20 Set集合的实现类HashSet
Set是无序,用于存储不重复的对象集合。在Set集合中存储的对象中,不存在两个对象equals比较为true的情况。
1)HashSet和TreeSet是Set集合的两个常见的实现类,分别用hash表和排序二叉树的方式实现了Set集合。HashSet是使用散列算法实现Set的。
2)Set集合没有get(int index)方法,我们不能像使用List那样,根据下标获取元素。想获取元素需要使用Iterator。
3)向集合添加元素也使用add方法,但是add方法不是向集合末尾追加元素,因为无序。
4)宏观上讲:元素的顺序和存放顺序是不同的,但是在内容不变的前提下,存放顺序是相同的,但在我们使用的时候,要当作是无序的使用。
Set<String> set=new HashSet<String>();//多态 也可HashSet<String> set=new HashSet<String>();
set.add("One");
set.add("Two");
set.add("Three");
// 遍历Set 1
Iterator<String> it=set.iterator();
while(it.hasNext()){
String element=it.next();
System.out.print(element+" ");
}
// 遍历Set 2
for(String element:set){ //新循环遍历Set集合
System.out.print(element+" ");
}
5)hashCode对HashSet的影响:若我们不重写hashCode,那么使用的就是Object提供的,而该方法是返回地址(句柄)!
换句话说,就是不同的对象,hashCode不同。
6)对于重写了equals方法的对象,强烈要求重写继承自Object类的hashCode方法的,因为重写hashCode方法与否会对集合操作有影响!
7)重写hashCode方法需要注意两点:
①与equals方法的一致性,即equals比较返回为true的对象其hashCode方法返回值应该相同。
②hashCode返回的数值应该符合hash算法要求,如果有很多对象的hashCode方法返回值都相同,则会大大降低hash表的效率。
一般情况下,可以使用IDE(如Eclipse)提供的工具自动生成hashCode方法。
8)boolean contains(Object o)方法:查看对象是否在set中被包含。
下例虽然有新创建的对象,但是通过散列算法找到了位置后,和里面存放的元素进行equals比较为true,所以依然认为是被包含的(重写equals了时)。
Set<Point> set=new HashSet<Point>();
set.add(new Point(1,2));
set.add(new Point(3,4));
System.out.println(set.contains(new Point(1,2)));
9)HashCode方法和equals方法都重写时对hashSet的影响:
将两个对象同时放入HashSet集合,发现存在,不再放入(不重复集)。
当我们重写了Point的equals方法和hashCode方法后,我们发现虽然p1和p2是两个对象,但是当我们将他们同时放入集合时,p2对象并没有被添加进集合。
因为p1在放入后,p2放入时根据p2的hashCode计算的位置相同,且p2与该位置的p1的equals比较为true, hashSet认为该对象已经存在,所以拒绝将p2存入集合。
Set<Point> set=new HashSet<Point>();
Point p1=new Point(1,2);
Point p2=new Point(1,2);
System.out.println("两者是否同一对象:"+(p1==p2));
System.out.println("两者内容是否一样:"+p1.equals(p2));
System.out.println("两者HashCode是否一样:"+ (p1.hashCode()==p2.hashCode()));
set.add(p1);
set.add(p2);
System.out.println("hashset集合的元素数"+set.size());
for(Point p:set){
System.out.println(p);
}
10)不重写hashCode方法,但是重写了equals方法对hashSet的影响:
两个对象都可以放入HashStet集合中,因为两个对象具有不用的hashCode值,那么当他们在放入集合时,通过hashCode值进行的散列算法结果就不同。
那么他们会被放入集合的不同位置,位置不相同,HashSet则认为它们不同,所以他们可以全部被放入集合。
11)重写了hashCode方法,但是不重写equals方法对hashSet的影响:
在hashCode相同的情况下,在存放元素时,他们会在相同的位置,hashSet会在相同位置上将后放入的对象与该位置其他对象一次进行equals比较,
若不相同,则将其存入在同一个位置存入若干元素,这些元素会被放入一个链表中。
由此可以看出,我们应该尽量使得多种类的不同对象的hashcode值不同,这样才可以提高HashSet在检索元素时的效率,否则可能检索效率还不如List。
12)结论:不同对象存放时,不会保存hashCode相同并且equals相同的对象,缺一不可。否则HashSet不认为他们是重复对象。
4.21 Map集合的实现类HashMap
Map接口定义的集合又称为查找表,用于存储所谓“Key-Value”键值对。
Key可以看成是Value的索引。而往往Key是Value的一部分内容。
1)Key不可以重复,但所保存的Value可以重复。
2)根据内部结构的不同,Map接口有多种实现类,其中常用的有内部为hash表实现的HashMap和内部为排序二叉树实现的TreeMap。
同样这样的数据结构在存放数据时,也不建议存放两种以上的数据类型,所以,通常我们在使用Map时也要使用泛型约束存储内容的类型。
3)创建Map时使用泛型,这里要约束两个类型,一个是key的类型,一个是value的类型。
4)基本原理图:
5)HashMap集合中常用的方法:
①V put(K Key,V value):将元素以Key-Value的形式放入map。若重复保存相同的key时,实际的操作是替换Key所对应的value值。
②V get(Object key):返回key所对应的value值。如果不存在则返回null。
③boolean containsKey(Object Key):判断集合中是否包含指定的Key。
④boolean containsValue(Object value):判断集合中是否包含指定的Value。
6)若给定的key在map中不存在则返回null,所以,
原则上在从map中获取元素时要先判断是否有该元素,之后再使用,避免空指针异常的出现。
Map在获取元素时非常有针对性,集合想获取元素需要遍历集合内容,而Map不需要,你只要给他特定的key就可以获取该元素。
Map<String,Point> map=new HashMap<String,Point>();
map.put("1,2", new Point(1,2));
map.put("3,4", new Point(3,4));
Point p=map.get("1,2");
System.out.println("x="+p.getX()+",y="+p.getY());
map.put("1,2", new Point(5,6));//会替换之前的
p=map.get("1,2");
System.out.println("x="+p.getX()+",y="+p.getY());
p=map.get("haha");
System.out.println("x="+p.getX()+",y="+p.getY());//会报空指异常
eg:统计每个数字出现的次数。
步骤:①将字符串str根据“,”拆分。
②创建map。
③循环拆分后的字符串数组。
④将每一个数字作为key在map中检查是否包含。
⑤包含则对value值累加1。
⑥不包含则使用该数字作为key,value为1存入map。
String str="123,456,789,456,789,225,698,759,456";
String[] array=str.split(",");
Map<String,Integer> map=new HashMap<String,Integer>();
for(String number:array){
if(map.containsKey(number)){
int sum=map.get(number);//将原来统计的数字取出
sum++; //对统计数字加1
map.put(number, sum); //放回
map.put(number, map.get(number)+1);等同上三部
}else{
map.put(number, 1);//第一次出现value为1
}
}
System.out.println(map);//HashMap也重写了toString()
7)计算机中有这么一句话:越灵活的程序性能越差,顾及的多了。
8)遍历HashMap方式一:
获取所有的key并根据key获取value从而达到遍历的效果(即迭代Key)。
keySet()方法:是HashMap获取所有key的方法,该方法可以获取保存在map下所有的key并以Set集合的形式返回。
Map<String,Point> map=new HashMap<String,Point>();
map.put("1,2", new Point(1,2));
map.put("2,3", new Point(2,3));
map.put("3,4", new Point(3,4));
map.put("4,5", new Point(4,5));
/** 因为key在HashMap的泛型中规定了类型为String,所以返回的Set中的元素也是String,为了更好的使用,我们在定义Set类型变量时也应该加上泛型 */
Set<String> keyset=map.keySet();
for(String key:keyset){
Point p=map.get(key);//根据key获取value
System.out.println(key+":"+p.getX()+","+p.getY());
}
for(Iterator<String> it=keyset.iterator() ; it.hasNext() ; ){//普通for循环
String key=it.next(); Point p=map.get(key);
System.out.println(key+":"+p.getX()+","+p.getY());
}
9)LinkedHashMap:用法和HashMap相同,内部维护着一个链表,可以使其存放元素时的顺序与迭代时一致。
10)Entry类,遍历HashMap方式二:以“键值对”的形式迭代。
Map支持另一个方法entrySet():该方法返回一个Set集合,里面的元素是map中的每一组键值对,Map以Entry类的实例来描述每一个键值对。
其有两个方法:getKey()获取key值;getValue()获取value值。
Entry也需要泛型的约束,其约束的泛型应该和Map相同!Entry所在位置:java.util.Map.Entry。
Map<String,Point> map=new LinkedHashMap<String,Point>();
map.put("1,2", new Point(1,2));
map.put("2,3", new Point(2,3));
map.put("3,4", new Point(3,4));
map.put("4,5", new Point(4,5)); //泛型套泛型
Set<Entry<String,Point>> entrySet=map.entrySet();//Set的泛型不会变,就是Entry
for(Entry<String,Point> entry:entrySet){
String key=entry.getKey();//获取key
Point p=entry.getValue();//获取value
System.out.println(key+","+p.getX()+","+p.getY());
}
11)List、Map、Set三个接口存储元素时各有什么特点:
①List:是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。
用户能够使用索引(元素在List中的位置,类似于数组下标)来访问List中的元素,这类似于Java的数组。
②Set:是一种不包含重复的元素的Collection,即任意的两个元素e1和e2都有e1.equals(e2)=false,Set最多有一个null元素。
③Map:请注意,Map没有继承Collection接口,Map提供key到value的映射。
4.22单例模式和模版方法模式
设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
1)使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。简单的说:设计模式是经典问题的模式化解决方法。
2)经典设计模式分为三种类型,共23类。
创建模型式:单例模式、工厂模式等
结构型模式:装饰模式、代理模式等
行为型模式:模版方法模式、迭代器模式等
3)单例设计模式:
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它。任何情况下,该类只能创建一个实例!
4)单例设计模式创建步骤:
①定义一个私有的静态的当前类型的属性。
②私有化构造方法。
③定义一个静态的可以获取当前类实例的方法。这个方法中我们可以判断是否创建过实例,创建过就直接返回,从而达到单例的效果。
private static DemoSingleton obj; //或private static DemoSingleton obj=new DemoSingleton();
private DemoSingleton(){ }
public static DemoSingleton getInstance(){
if(obj==null){
obj= new DemoSingleton();
}
return obj;
}
5)模版方法模式:意图:定义一个操作中的算法过程的框架,而将一些步骤延迟到子类中实现。
类似于定义接口或抽象类,子类去实现抽象方法。