文章目录
- 前言
- 一、常用类
-
- 1.Object类常用方法
-
- toString方法
- equals方法
- fianlize()方法
- 2. String类
-
- String字符串的储存原理
- 内存图分析
- String常用的构造方法
- String常用方法
- 3. StringBuilder/StringBuffer类
- 4. 基本类型包装类
-
- 简介
- 包装类类的常用方法(以Integer为例)
- 5. JDK1.7日期类
- 6. 枚举类
- 二、异常
-
- 1. 概念
- 2. 异常类继承层次
- 3. 异常的处理
- 4. 异常的常用方法
-
- fnally子句
- 5. 自定义异常
-
- 重写后的方法不能抛出更多的异常可以更少—解释
- 三、集合
- 1. 集合特征
- 2. Collection接口
-
- 接口常用方法
- contains方法
- 3. 迭代器
-
- 迭代器遍历集合注意事项
- 4. List接口常用方法
-
- 实现类分析
- set接口的两个实现类
- LinkedHashSet
- 5. 泛型
-
- 泛型在集合中的使用
-
- 自动类型推断
- 泛型详解
-
- 泛型本质
- 泛型的好处
- 泛型的使用
-
- 泛型类
- 泛型接口
- 泛型方法
- 泛型通配符
- 泛型的实现原理
- 6. foreach
- 7. Map接口
-
- Map接口中的常用方法
- Map集合的遍历
- Map集合中的Entry内部类
- Map实现类
-
- HashMap集合
- Properties类
- TreeMap类
-
- TreeMap中自定义的类无法自动排序
- put方法源码
- 自定义类自动排序的两种方案
前言
接上篇Java基础,这里详细介绍了Java进阶的部分内容,包含字符串相关类,异常,集合,泛型等
一、常用类
1.Object类常用方法
toString方法
//源代码
public String toString(){return this.getClss().getName()+"@"+Interger.toSHextring(hashCode());
}
- 默认实现结果:类名@对象的虚拟内存地址转换为十六进制的形式
- 该方法的目的:同过调用这个方法可以将一个"Java对象"转换成"字符串表示形式",SU公司在开发建议所有的子类都去重写toString()方法,将其化为简洁的,详实的,易懂的。
- Object(老祖宗类)为任意类的父类
示例:
注意:引用数据类型输出引用的时候会自动调用toString()方法
System.out.println(s1);等效于System.out.println(s1.toString())
equals方法
//源代码
public boolean equals(Object obj){return (this == obj);
}
- “= =”比假的是式子两边存储空间中存的数据是否相等
- 两边为基本数据类型时,比较数据本身
eg:int a=1,b=2;
“a= =b” 比较的是1= =2 - 两边为引用数据类型的话则是比较引用中存储的地址是否相同
- 两边为基本数据类型时,比较数据本身
- 默认实现结果:利用"= ="比较两个对象是否相等
- 该方法的目的:以后编程中都要通过equals方法来判断两个引用是否相等(比较基本数据类型用“= =”,比较引用数类型只能用equals方法)
- 注意:因为Object类中提供的方法是利用"=="来比较的(所以默认的equals方法有限,当使用时,就需要重写,比较对象是否相等,比较的一定是对象中的内容,因为new的对象内存地址肯定不会相等,也没有比较的意义
案例:上述例子中比较对不同Mytime对象进行比较,若年月日相同认为比较结果相同
注意:idea中ALT+INS可自动生成toString及equals方法
- String类重写了equals方法以及toString方法(不仅仅是String,Java中几乎所有的类都重写了equals方法以及toString方法)
- String是lang包中的一个类:String s1 = “abc”等效于 String s1 = new String(),详情见帮助文档
String重写了equals方法和toString方法用于比较或输出字符串对象
fianlize()方法
//源代码
protected void fianlaize() throwable{}
- 该方法只有一个方法体,且无代码,不需要手动调用,垃圾回收器负责调用
- fianlize()方法实际上是SUN公司为java程序员准备的一个时机,垃圾销毁时机(与静态代码块类似,区别是静态代码块与类加载时期执行,并且只执行一次),如果希望在对象销毁时执行一段代码的话,这段代码就需要写到finalize()方法中。
示例
2. String类
String字符串的储存原理
- Java中双引号括起来了的都是String对象,储存堆中的“字符串常量池”中,不可改变(因为实际开发过程中字符串的使用太够频繁,所以SUN公司将其放到了字符串常量池中)常量池中的常量可以复用。
- 对应String常量优化机制:如果都是常量进行运算在编译期间JVM就会给将其组合在一起。
示例
package day8.string;
/*jvm: 有常量优化机制如果都是常量进行相运算在编译期间就会给我们合在一起,运行的时候其实 s3 就优化为了 "abc" 和 s1一样都是常量赋值, 则会复用如果有变量参与运算, 则他不会在编译期间合并, 运行期间组装在一起,凡是改变字符串内容的都是在底层采用new的方法创建的新字符串*/
public class Dem01 {public static void main(String[] args) {String s1 = "abc";String s2 = "abc";String s3 = "ab"+"c";String s4 = "ab";String s5 = s4+"c";String s6 = new String("abc");System.out.println(s1==s2);//trueSystem.out.println(s1==s6);//falseSystem.out.println(s1==s3);//trueSystem.out.println(s1==s5);//false//注意:虽然传入的为s1的地址但是參與運算后生成了一個新的字符串,//將新字符串的地址給了參數s1,原变量内容s1並未改變method(s1);System.out.println(s1);}private static void method(String s1) {s1+="aaa";//变量参与拼接调用的是StringBuilder的append方法和tostring方法System.out.println(s1);}
}
内存图分析
示例1
- 面试题:String s = new String(“xyz”); 产生几个对象?
答案:一个或两个。- 首先new运算符一定会在堆中开辟空间,然后根据构造方法传入的字符串参数去常量池中检索,如果常量池中原来没有 "xyz“,则会在常量池中创建该字符串对象,并把引用给new出来的对象 。
- 如果原来的常量池中存在"xyz"时,则直接把常量池中的字符串对像引用放到new出来的对象中。
- 注意: JDK7之后字符串和封装类常量池放到了堆中,下图常量池位置画错了
示例2
注意: 字符串和包装类常量池JDK7之后放到了堆中
String常用的构造方法
补充: new String(byte[] b,String s) 将b数组按照s编码转换为字符串
String常用方法
//String中的常用方法public class StringText3 {public static void main(String[] args) {/*1.(掌握) public char charAt(int index)返回指定索引处的char值(主意数组的索引由0开始)*/char c = "中国人".charAt(1);System.out.println(c); //国/*2.(了解)public int compareTo(String anotherString)按字典顺序比较两个字符串,前后一致返回值为0,前小后大为负数,前大后小为正数*/int result1 = "abc".compareTo("abc");System.out.println(result1); //0int result2 = "abcd".compareTo("abce");System.out.println(result2); //-1int result3 = "abce".compareTo("abcd");System.out.println(result3); //1//字符串比较的时候先比较第一个字母和后面字符串第一个字母比较,//比较出来便停止,否则比较第二个,以此类推System.out.println("xyz".compareTo("yxz")); //-1/*3.(掌握) public boolean contains(CharSequence s)判断前面的字符串是否包含后面的子字符串*/System.out.println("HelloWorld.java".contains(".java")); //trueSystem.out.println("http://www.baidu.com".contains("https://"));//false/*4.(掌握) public boolean endsWith(String suffix)判断当前字符串是否以某个字符串结尾。*/System.out.println("test.txt".endsWith(".java")); //falseSystem.out.println("test.text".endsWith(".text")); //trueSystem.out.println("fdsajk1fhdkjlsahjklfdss".endsWith("ss")); //true/*5.(掌握)public boolean equals(Object anObject)比较两个字符串内容是否相同 “==”比价引用地址是否相同*/String s1 = "Hello",s2 = "Hello";String s3 = new String("Hello");System.out.println(s1.equals(s2)); //trueSystem.out.println(s1.equals(s3)); //true/*6.(掌握)public boolean equalsIgnoreCase(String anotherString)判断两个字符串是否相等并且忽略大小写*/System.out.println("abc".equalsIgnoreCase("ABC")); //true//*7.(掌握) public byte[] getBytes()将字符串对象转换成字节数组//public byte[] getBytes(String s) s为码表 将字符串对象按照指定编码转换成字节数组 解码byte[] bytes = "abcdefg".getBytes();for (int i=0;i<bytes.length;i++){System.out.print(bytes[i]+" "); //97 98 99 100 101 102 103}System.out.println();/*8.(掌握)public int indexof(String str)查找指定字符串在当前字符串中第一次出现的位置(索引---0开始)*/System.out.println("afybeyhjavaaefds".indexOf("aa")); //10//9.(掌握) public boolean isEmpty()判断某个字符串是否为空String s4 = "a";System.out.println(s4.isEmpty()); //false/*10.(掌握)public int length()判断字符串长度 判断数组长度用的为length,判断字符串长度用的length()方法*/System.out.println("abc".length()); //3/*11.(掌握) public int lastIndexOf(String str)查找指定字符串在当前字符串中出现的位置(索引---0开始)*/System.out.println("ababababab".lastIndexOf("ab")); //7/*12.(掌握)public String replace(CharSequece target,CharSequence replacement)替换 CharSequence为String的父类接口*/String s5 = "http://www.baidu.com".replace("baidu","百度");System.out.println(s5); //http://www.百度.com//13.(掌握)public String[] split(String regex) 拆分字符串/*注意事项:1.切割点(regex)不保留2.切割点不要在首尾,尾部空白切割点不计数,首部全计数eg:String s1 = ",,,1,2,3";String s2 = "1,2,3,,,";String s3 = ",,,,,,";Strng[] s11 = s1.sp;it(",") 长度为5Strng[] s22 = s2.sp;it(",") 长度为3Strng[] s33 = s3.sp;it(",") 长度为03.该方法为正则方法,在正则表达式中‘.’代表匹配全部字符,如果想用‘.’切割需要通过\转义,即字符串对象.split("\.)*/String s6 = "2023-2-26";String[] ymd = s6.split("-");for(int i = 0;i<ymd.length;i++) {System.out.print(ymd[i]+" "); //2023 2 26}System.out.println();/*14.(掌握) public boolean startsWith(String str)判断字符串是否是以指定字符串开始的*/System.out.println("http://www.baidu.com".startsWith("http:")); //trueSystem.out.println("http://www.baidu.com".startsWith("https:"));//false/*15.(掌握) public String substring(int beginIndex)返回一个子字符串,以指定索引处的字符开头到此字符串的末尾。*/System.out.println("http://www.baidu.com".substring(7));//www.baidu.com/*16.(掌握) public String substring(int beginIndex,int endIndex)返回一个字符串子字符串,从索引beginIndex开始到索引endIndex-1处*/System.out.println("http://www.baidu.com".substring(7,11));//www.//17.(掌握)public char[] toCharArray()将字符串转换成char数组char[] chars = "我是中国人".toCharArray();for (int i=0;i<chars.length;i++){System.out.print(chars[i]+" "); //我 在 中 国 人}System.out.println();//18.(掌握) public String toLowerCase()字符串转换成小写System.out.println("aBfDFG".toLowerCase()); //abfdfg//19.(掌握) public String toUpperCase()字符串转换成大写System.out.println("aBfDFG".toUpperCase()); //ABFDFG//20.(掌握) public String trim()去除字符串前后空白System.out.println(" dfasedf ".trim()); //abfdfg/*21.(掌握) public static String valueOf(Object obj) String类中唯一的静态方法将非字符串转化为字符串,调用对象的toString方法public static String valueOf(引用数据类型 参数名)public static String valueOf(基本数据类型 参数名)public static String valueOf(char[] c)public static String valueOf(char[] c, int start, int last)*/String s7 = String.valueOf(3.14);String s8 = String.valueOf('a');System.out.println(s7+" "+s8); //3.14 a}/*22. (掌握) boolean matches(String regex) 判断字符串是否符合指定正则表达式*/
}
3. StringBuilder/StringBuffer类
1.Java中字符串是不可变得,每一次拼接都会产生新的字符串这样占用的大量的方法区内存造成空间浪费eg:String s = "abc";s += "hello"; 这两行代码编在方法区字符串常量中创建了三个对象:"abc""hello""abchello"2.进行大量字符串的拼接的时候使用JDK中自带的:java.lang.StringBuffer和java.lang.StringBiulder
String类与StringBuffer的底层储存都是byte数组,区别是String是由final修饰的也就是不可变3.StringBuffer构造方法
StringBuffer() 构造一个不带字符串的字符串缓冲区,初始容量为16
StringBuffer(String str) 构造一个包含指定字符串的字符串缓冲区,初始容量为16(str<=16)或str.length()+16(str>16)
StringBuffer(int capacity) 构造一个字符串缓冲区,初始化长度为capacity4. StringBuilder/Buffer都提供了 public StringBuffer/Buffer append(String str)方法
来对字符串进行拼接 原理是数组的扩容(把原来数组复制到一个容量更大得到数组
中),并释放掉原来的数组5.StringBuilder与StringBuffer的区别:
StringBuffer中的方法中都有synchronized关键字修饰,表示StringBuffer在
多线程环境运行下是安全的
StringBuilder中的方法没有synchronized关键字修饰,表示StringBuilder在
多线程的环境运行下是不安全的public class StringBufferText1 {public static void main(String[] args) {StringBuffer stringBuffer = new StringBuffer();//public StringBuffer append(String s) 拼接字符串stringBuffer.append("a");stringBuffer.append("ddd");stringBuffer.append("adasf");System.out.println(stringBuffer);//adddadasf//拼接并没有产生String对象,在输出的时候默认调用了toString将其转换为String对象//所以常量词中只有adddadasf一个字符串存在//public StringBuffer reverse() 反转字符串//public String toString() 返回字符串对象}
}
4. 基本类型包装类
简介
-
作用:java中为8种数据类型准备备了8中包装类型,均为引用数据类型其父类是Object,以应对八中基本类型的不够用的情况。
-
拆箱和装箱1.5之后支持自动拆装,所以基本类型装了,拆装了解即可, 基本数据类型包装类的唯一用途就只剩下String转基本数据类型了(其对应方法都为静态)
//包装类用于解决类似以下需求
public class Text3_1 {
// 入口
public static void main(String[] args){
// 需求:调用doSome()方法传一个整形参数
// 但doSome()方法参数为object。因此doSome()方法无法接收基本数据类型
//,此时就需要用到包装类—可传一个数字对应的包装类引用
Myint myint =new Myint(10);
doSome(myint);
}
public static void doSome(Object obj){
System.out.println(obj);
}
}
//包装类
class Myint{
private int a;
public Myint(){}
public Myint(int a) {
this.a = a;
}
}1.八中基本数据类型对应的包装类名
基本上数据类型 包装类型
byte java.lang.Byte(父类Number)
short java.lang.Short(父类Number)
int java.lang.Integer(父类Number)
long java.lang.Long(父类Number)
float java.lang.Float(父类Number)
double java.lang.Double(父类Number)
boolean java.lang.Boolean(父类Object)
char java.lang.Character(父类Object)-
以上八中包装类,重点以java.lang.Integer为代表进行学习,其他类类似
Integer 的构造方法(都过时了)
Integer(int value) int类型转换为Integer引用类型
Integer(String s) String类型转换为Integer引用类型 -
八中包装类其中六个都是数字对应的包装类,他们的父类都是Number(抽象类)子类 重写了其全部抽象方法
Number类的方法: 拆箱操作(1.5后不需要程序员直接写了)
byte byteValue() 以byte形式返回指定数字的值
abstract double doubleValue() 以double形式返回指定数字的值
abstract float floatValue() 以float形式返回指定数字的值
abstract int intValue() 以int形式返回指定数字的
abstract long longValue() 以long形式返回指定数字的值。
short shortValue() 以short返回指定数字的值
这些方法八中包装类及子类均有,负责拆箱 -
java5之后支持自动装箱和制动拆箱机制(上述2.3不需要自己写了);
以Integer为例
使用Integer.valueOf()方法实现装箱
使用Integer.intValue()方法实现拆箱
Eg:
Integer i = 123;//自动装箱
int j = s;//自动拆箱
//方法,查文档
public class Text3_2 {
public static void main(String[] args) {
// 123这个基本数据类型,由构造方法的包装达成了基本数据类型到引用数
//据类型的转换
// 基本数据类型–(转换为)->引用数据类型(装箱)
Integer integer = new Integer(123);//过时了
float f = integer.floatValue();
// 引用数据类型–(转换为)->基本数据类型(拆箱)
System.out.println(f);//123.0
// 引用数据类型–(转换为)->基本数据类型(拆箱)
int retValue = integer.intValue();
System.out.println(retValue);//123
// 通过访问包装类的常量,来获取最大值和最小值
System.out.println(“int的最大值:”+Integer.MAX_VALUE);//int的最大值:2147483647
System.out.println(“int的最小值:”+Integer.MIN_VALUE);//int的最小值:-2147483648
System.out.println(“int的最大值:”+Byte.MAX_VALUE);//int的最大值:127
System.out.println(“int的最大值:”+Byte.MIN_VALUE);//int的最大值:-128Integer s = 900; System.out.println(s + 1);//901 /* Integer i = 100; int k = i;//自动拆箱 相当于编译器自动作以下的语法编译: Integer i = new Integer(100); int k = i.intValue();
*/
// 基本数据类型--(自动转换为)->引用数据类型(自动装箱)Float a = 12.3F;// 引用数据类型--(自动转换为)->基本数据类型(自动拆箱)float b = a+1;System.out.println(b);//13.3// 基本数据类型--(自动转换为)->引用数据类型(自动装箱)Character character = 98;// 引用数据类型--(自动转换为)->基本数据类型(自动拆箱)char c = character;System.out.println(c);//b }
}
-
Java中为了提高程序的执行效率,将-128到127之间的所有包装类对象提前创建好放到了堆中的“整数形常量池”中,使这个区间的数据时不需要new了(自动装/拆箱同样是通过new),直接从整形常量池当中取出即可。
示例
ineger x = 127;
Integer y = 127;
System.out.println(x==y)//true
包装类类的常用方法(以Integer为例)
public class IntegerText3_3 {public static void main(String[] args) {//6.1 int intValue() 返回Integer为int类型同里该类中还有float floatValue() / double doubleValue()等等Integer x = new Integer(520);int y = x.intValue();System.out.println(y);//520//6.2 /*(String->基本数据类型)重点方法 ;static int parseInt(String s)静态方法 传参String,返回int数字字符串转基本数据类型网页上输入的数字 实际上是字符串,后台数据库中需要求存数字时java程序就需要将字符串转换成基本数据类型该方法的参数只能是数字字符串*/int retValue = Integer.parseInt("123");// int retValue = Integer.parseInt("中文");// 非数字字符串 报错NumberFormatException---数字格式化异常System.out.println(retValue+100);//223/*(照葫芦画瓢)static double parseDouble(String s)---Double类static float parseFloat(String s)---Float类static long parseLong(String s)---Long类*///6.3 //static Integer valueOf(int/String a)将int/或String类型数据转换成Integer类型 基本数据类型/String类型转基本数据类型包装类Integer s = Integer.valueOf(retValue);/*同样Byte,Short,Long类中都有该静态方法供使用*///6.4 //String int Integer之间互相转换int a = 123;String b = "456";Integer c = 789;String a1 = String.valueOf(a);//int-->String//或者---String a1 = a + ""; 利用+运算符Integer a2 = Integer.valueOf(a);//int-->Integer//或者---Integer a2 = a; 自动装箱int b1 = Integer.parseInt(b);//String-->int(借助Integer)Integer b2 = Integer.valueOf(b);//String-->Integerint c1 = c.intValue();//Integer-->int//或者---int c1 = c; 自动拆箱String c2 = String.valueOf(c);//Integer-->String/*其他类型转换成String---String.valueOf()方法int/String转换包装类--基本类型包装类名.valueOf()方法String转int----借助包装类的静态方法-parseXxxint与Integer的转换直接利用自动装/拆箱机制*/}
5. JDK1.7日期类
常用日期类:java.util.Date //日期的获取
java.text.SimplDateFormat // 日期格式化package 常用类.日期的处理;
import java.text.ParseException;//异常
import java.text.SimpleDateFormat;
import java.util.Date;
public class DateTest1 {public static void main(String[] args) throws ParseException {//1. 获取系统当前时间(精确到毫秒的系统当前时间),直接调用无参构造Date nowTime = new Date();System.out.println(nowTime);//Fri Mar 03 11:04:01 CST 2023 该类重写了toString方法//2. Date--->String(格式化)//日期格式化 将日期类型Date,安照指定格式转换:Date--转换成具有一//定格式的日期字符串-->String/*SimpleDateFormat于java.text包下负责日期格式化yyyy 年(4位)MM 月(2位)dd 日HH 时mm 分ss 秒SSS 毫秒(毫秒三位,最高999.1000毫秒为1秒)注意:在日期格式中,除了y M d H m s S这些字符外,其他字符可以随意组合*/
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy/MM/dd");//调用format方法将日期转换成对应格式的字符串String s1 = sdf.format(nowTime);String s2 = sdf1.format(nowTime);System.out.println(s1+" "+s2);//2023-03-03 11:22:53 368 2023/03/03//3. String--->DateString time = "2008-08-08 08:08:08 888";
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:sss SSS");//格式与字符串格式保持一致Date dateTime = sdf2.parse(time);System.out.println(dateTime);//Fri Aug 08 08:08:08 CST 2008//4. 获取自1970年1月1日 00:00:00 000到当前时间的总毫秒数---System的静态//方法currentTimeMillis()long nowTimeMillis = System.currentTimeMillis();System.out.println(nowTimeMillis);//1677814356431//统计方法执行的时长 结束减开始long begin = System.currentTimeMillis();print();long end = System.currentTimeMillis();System.out.println("耗费时长"+(end-begin)+"毫秒");//耗费时长6毫秒//5. new Date(long l) 指定毫秒构建对象即1970-01-01 00:00:000开始l毫//秒后的时间Date time1 = new Date(1);
System.out.println(time1);//Thu Jan 01 08:00:00 CST 1970 (当前所在地是北京)//获取昨天此时的时间Date time2 = new Date(System.currentTimeMillis()-24*60*60*1000);String s = sdf.format(time2);System.out.println(s);//2023-03-02 14:17:19 471}public static void print(){for (int i=0;i<=100;i++){System.out.println("i = "+i);}}Date date = new Date();System.out.println(date);//6. 获原点点到当前的毫秒 public long getTime()long time = date.getTime();time += 1000*60*60*24;//7. 设置时间 时间(原点到当前的时间)date.setTime(time);System.out.println(date); //第二天的当前时间
}
6. 枚举类
/*
Java 枚举(enum) 由public修饰 用于作为选项使用
Java 枚举是一个特殊的类,使用 enum 关键字来定义,各个枚举项使用逗号 , 来分割。
枚举项通过类名.访问的形式访问
例如定义一个颜色的枚举类。
*/
enum { RED, GREEN, BLUE;
}
/*
以上枚举类 Color 颜色常量有 RED, GREEN, BLUE,分别表示红色,绿色,蓝色。
使用实例:
*/
enum Color{RED, GREEN, BLUE;
}public class Test{// 执行输出结果public static void main(String[] args){switch(Color color){case RED ->{System.out.println("红色")}case GREEN ->{System.out.println("绿色")}case BLUE ->{System.out.println("蓝色")}}}
}
/*枚举作为选项, 不会出现其他非法的选项, 而且这些选项由于是对象, 不能直接做
算数运算, 从而避免非法选项导致的困扰*/
- 枚举类的本质是类,采用了多例设计模式,每一个枚举项都是一个枚举类对象
- 多例模式及对应枚举类
- 枚举类的本质还是类可以拥有类的所有属性,枚举项的本质为枚举对象,默认调用了无参构造(构造方法必须私有,不写默认提供私有无参构造,枚举项默认被static final修饰,见上图)
- 枚举注意点:
- 枚举类用于做选项
二、异常
1. 概念
-
异常指程序中发生的不正常情况的现象,语法错误不是异常,Java语言提供的异常的处理方式(java将异常打印输出到控制台—异常信息有JVM打印,供程序员查看,便于对程序进行修改,增强了程序的健壮性)
-
经典异常:
- 空指针异常:NullPointerException
- 类型转换异常:ClassCastException
- 数组下标越界异常:ArrayIndexOutOfBoundsException
- 数字格式化异常:NumberFormatException
-
异常以对象的形式存在
package 异常类;
public class ExceptionText1 {
public static void main(String[] args) {
NumberFormatException nfe = new NumberFormatException(“数字格式化异常”);
System.out.println(nfe);
//java.lang.NumberFormatException: 数字格式化异常//这里可以发现,该异常类或其父类重写了toString()方法int a = 1;int b = 0;int c = a/b;//异常是以类和对象的形式存在的,当程序执行的这里的时候会new异常对//象:new Arithme//ticException("/by zero");并且将new的对象打印到控制台System.out.println(c);/*控制台打印出:Exception in thread "main" java.lang.ArithmeticException: / byzeroat 异常类.ExceptionText1.main(ExceptionText1.java:7)*/ }
}
2. 异常类继承层次
- 所有的异常类都直接或间接地继承于java.lang.Throwable类
- Error和Exception
- Throwable有两个直接子类:Error和Exception。
- Error Error:是程序无法恢复的严重错误,程序员根本无能为力,只能让程序终止。例如:JVM内部错误、内存溢出和资源耗尽等严重情况。
- Exception:是程序可以恢复的异常,它是程序员所能掌控的。例如:除零异常、空指针访问、网络连接中断和读取不存在的文件等。
- 受检查异常和运行时异常(都是在运行阶段发生的因为只有在运行阶段才能创建异常对象)
- Exception类可以分为:受检查异常和运行时异常。
- 受检查异常:受检查异常是除RuntimeException以外的异常类。它们的共同特点是:编译器会检查这类异常是否进行了处理,即要么捕获(try-catch语句),要么抛出(通常在方法后声明 throws),否则会发生编译错误。
- 运行时异常:运行时异常是继承RuntimeException类的直接或间接子类运行时异常往往是程序员所犯错误导致的,健壮的程序不应该发生运行时常。它们的共同特点是:编译器不检查这类异常是否进 行了处理,也就是对于这类异常不捕获也不抛出,程序也可以编译通过。由于没有进行异常理一旦运行时异常发生就会导致程序的终止,这是用户不希望看到的。
3. 异常的处理
- 在声明方法的位置上,使用throws关键字,抛给上一级【(谁调用抛给谁)抛给上一级后上一级同样有两种处理方式。但注意,如果异常一直上抛,最终会抛给main方法,main方法继续上抛,抛给JVM,JVM则会终止程序的执行】 上报的终极目标也是让上级捕捉—解决问题
- 使用try…catch语句进行异常捕捉
Eg:(编译时异常必须要要进行处理,否则编译无法通过,而运行时异常则并无要求)
throws方式throws上抛异常时,必须抛出该异常或其父类throws运行同时抛出多个异常,异常之间用逗号隔开。throw上抛后,异常处后续代码不在执行
try-catch方式catch后面的小括号中形参所指引的对象为自动创建的异常对象。以该程序为例ClassNotFoundException c等效于ClassNotFoundException c = new ClassNotFoundException() 参数类型为具体异常类或其父类(多态)catch可以写多个但必须遵循由小到大
eg:try{(有继承关系的话) (同级别同样可以写多个)}catch(具体异常类型或其父类型 a){}catch(a的直接父类或间接父类 b){}catch(b的直接父类或间接父类 c){}try中出现异常后直接转跳至catch中执行后续代码,try中异常处后续代码不在执行 但try - catch语句后续代码不受影响,所以出异常后不需要执行的代码也要放到try中
Java8的新特性:try{int a = 100/0;}catch( ArithmeticException | NullPointerException c){System.out.println("数字格式化异常?空指针异常?");}
4. 异常的常用方法
/*
异常对象的两个常用方法:
String msg = e.getMessage(); 获取异常简单的信息,其实就是异常构造方法上的
String参数
e.printStackTrace(); 打印异常堆栈信息
*/
public class EXceptionText4 {public static void main(String[] args){//以下只是new了一个异常对象,并未用关键字throws抛出。JVM会认为这//是一个普通类不会终止程序NullPointerException e = new NullPointerException("空指针异常---啊啊啊"); //获取异常简单信息String s = e.getMessage();System.out.println(s);//空指针异常---啊啊啊//打印异常追踪信息e.printStackTrace();/*java.lang.NullPointerException: 空指针异常---啊啊啊at 异常类.EXceptionText4.main(EXceptionText4.java:10)*/ }
}
fnally子句
package 异常类;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;/*
关于try-catch中的fianll子句:1.finally必须和try一起出现,不能单独编写,且无论try语块中是否出现异常finally子句中的代码都一定会执行(最后执行)2.finally语句块中完成资源的释放/关闭,finally中代码比较有保障
*/public class ExceptionText5 {public static void main(String[] args) {FileInputStream fis = null;try{//创建输入流对象(该对象的构造方法上抛了FileNotFoundException异常)fis = new FileInputStream("D:\我们\5ad81dc343d5e73f.jpg");//开始读取文件String s = null;s.toString();//这里一定会出现空指针异常
//流使用完必须关闭,但这个位置编写关闭代码可能无法执行(上述代码出现异常)//fis.close();}catch(FileNotFoundException e){e.printStackTrace();/*java.lang.NullPointerException: Cannot invoke "String.toString()" because "s" is null at 异常类.ExceptionText5.main(ExceptionText5.java:20)*/}catch (NullPointerException e){e.printStackTrace();}finally{System.out.println("此中代码一定会执行");//此中代码一定会执行if (fis != null ) {//可能发送编译时异常需要处理try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}
}//退出JVM后fianlly不在执行/*
强调finally中的语句一定会执行
*/
public static void main(String[] args){try{System.out.println("try...");//try...//return语句执行后一定会结束该方法return;}finally{System.out.println("finally...");//fianlly...}//此行代码无法执行System.out.println("测试");
}
/*
此代码执行过程为:
先执行try中的 System.out.println("try...")
后执行finally中的 System.out.println("finally...")
最后执行 return 结束方法
*/
public static void main(String[] args){try{System.out.println("try...");//try...//退出JVMSystem.exit(0);//此处finally将无法再执行}finally{System.out.println("finally...");}
}
5. 自定义异常
所以,定义异常类只需要继承异常的总父类,提供构造方法以此访问父类构造,初始化父类特征即可
-
异常类的自定义:
- 第一步:编写一个:类继承Exception(编译时异常)或者RuntimeException(运行时异常)
- 第二步:提供两个构造方法,一个无参,一个带String参数的
-
异常的的手动抛出:throw 异常对象
//手动抛出运行时异常对象—运行时异常没必要进行预处理
public void push(int val) {
if (isFull()) {
throw new RuntimeException(“此栈已满”);
}
}
//手动抛出编译时异常—必须进行预处理throws上抛
public void push(int val) throws Exception{
if (isFull()) {
throw new Exception(“此栈已满”);
}
}
//或者try - catch
public void push(int val) {
if (isFull()) {
try{
throw new Exception(“此栈已满”);
}catch(Exception e){
e.printStackTrace();
}
}
//自设定异常也是如此
重写后的方法不能抛出更多的异常可以更少—解释
public class Aniaml{public void doSome(){}public void doOther() throws Exception{}
}
class Cat extends Aniaml{//编译报错public void doSome() throws Exception{}//编译通过public void doOther() throws Exception{}//编译通过public void doOther() throws RuntimeException{}//编译通过public void doOther(){}
}
三、集合
非常重要(实际开发中几乎离不开集合) 所有的集合类和集合接口都在java.util包下
- 集合实际上是一个容器,用来容纳其他类型的数据,数组就是一个集合
- 集合不能直接储存基本数据类型也不能直接储存java对象,集合中储存的都是java对象的内存地址(或者说集合中储存的是引用)
- 在java中每一个不同的集合,底层会会对应一个不同的数据结构,向不同的集合中存储元素就相当于将数据放到了不同的数据结构中。(java已经将写好了常用的集合,知道怎么用就okl)
集合继承结构图(部分常用集合)
1. 集合特征
ArrayList:底层是数组。
LinkedList:底层是双向链表。
Vector:底层是数组,线程安全的,效率较低,使用较少。
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。
HashMap:底层是哈希表。
Hashtable:底层也是哈希表,只不过线程安全的,效率较低,使用较少。
Properties:是线程安全的,并且key和value只能存储字符串String。
TreeMap:底层是二叉树。TreeMap集合的key可以自动按照大小顺序排序。
LinkenHashSet:HashSet的子类,有序不能重复无索引
LinkenHashMap:HashMap的子类,有序不能重复无索引List集合存储元素的特点:有序可重复有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。可重复:存进去1,可以再存储一个1.Set(Map)集合存储元素的特点:无序不可重复无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。不可重复:存进去1,不能再存储1了。SortedSet(SortedMap)集合存储元素特点:首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。不可重复:存进去1,不能再存储1了。可排序:可以按照大小顺序排列。 Map集合的key,就是一个Set集合。
往Set集合中放数据,实际上放到了Map集合的key部分。
2. Collection接口
接口常用方法
package 集合;
/*
关于java.util.Collection接口中的常用方法1.Collection存放元素类型问题没用使用"泛型"之前,Collection中可以存储Object的所用类型。使用“泛型”之后,Collection中只能储存某个具体类型。但注意:集合中只能储存java对象的内存地址,不能储存基本数据类型也不能储存Java对象2.Collection中的常用方法boolean add(E e) 向集合尾部添加元素int size() 返回此集合中的元素数void clear() 清空集合中所有元素boolean contains(Object o) 判断当前集合中是否包含元素oboolean remove(Object o) 从此集合中移除指定元素(当集合中存在多个该元素时只删除第一个)注意:该方法删除后后续元素前移,会导致索引变化boolean isEmpty() 判断集合是否为空Object[] toArray() 返回包含此集合中所有元素的数组int indexof(E e) 范围元素第一次出现的索引void forEach(Consumer<T> c) 参数为接口需要实现的方法为void accept(T o),遍历集合,取出元素执行重写方法(底层还是迭代器)
*/
import jdk.jfr.StackTrace;
import java.util.ArrayList;
import java.util.Collection;
public class CollectionText1 {public static void main(String[] args) {//创建一个集合对象//Collection c = new Collection();接口是抽象的无法实例化//多态Collection c = new ArrayList();//测试Collection接口中add方法c.add(123);//自动装箱---放进去的是Integer的引用c.add(3.14);c.add(new Object());c.add(new String());c.add(false);//自动装箱---放入的是Boolean的引用//测试Collection接口中size方法System.out.println("集合中元素的个数:"+c.size());//集合中元素的个数:5//测试Collection接口中clear方法c.clear();System.out.println("集合中元素的个数:"+c.size());//集合中元素的个数:0c.add("hello");c.add("world");c.add("浩克");c.add("绿巨人");//测试Collection接口中contains方法System.out.println(c.contains("浩克"));//true//测试Collection接口中remove方法c.remove("浩克");System.out.println(c.contains("浩克"));//false//测试Collection接口中remove方法System.out.println(c.isEmpty());//falseSystem.out.println("-------------------------");Object[] obj = c.toArray();for (int i = 0; i<c.size();i++){System.out.println(obj[i]);// hello world 绿巨人}}
}
contains方法
- remove方法与contains方法和indexOf方法底层都调用了equals方法
- 总结:Collection接口中的remove方法和contains方法和indexOf方法底层均使用了equals方法,所以放入集合中的对象必须重写equals方法(contains底层是indexOf)
- 构造方法,私有变量的get set方法,toSting方法equals方法—这些方法的编写和重写是程序员的基本素养
3. 迭代器
package 集合;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
/*关于集合遍历/迭代专题 重点
*/
public class CollectionText2 {public static void main(String[] args) {// 注意:遍历/迭代方式,是所有Collection通用的一种方式,Map集合//中不能使用//创建对象Collection c = new ArrayList();//添加元素c.add("abc");c.add("def");c.add(100);c.add(new Object());//对集合中的元素进行遍历/迭代/*Iterator对象中的两个方法:boolean hasNext() 如果仍有元素可以迭代,则返回true.Object next() 返回迭代的 下一个元素———(就是先将迭代器前进一位,之后返回当前迭代器指向的对象)*/
//第一步:获取集合对象的迭代器对象Iterator---调用Collection中的iterator方法Iterator it = c.iterator();//调用迭代器类的的 hasNext()方法与next()方法进行判断和取值while(it.hasNext()){Object obj = it.next();System.out.println(obj);//abc def 100 java.lang.Object@776ec8df}}
}
原理图:
注意: 集合调用interator方法获取的是集合当前状态的迭代器对象每当集合结构发生改变时都需要重新获取迭代器对象,否则会发生异常
迭代器遍历集合注意事项
/*获取迭代器对象时,相当于对当前的集合状态拍了一张快照,而底层会不断的将
快照与集合实时状态进行比较,不相同时会报错所以当集合状态改变时(添加删除等
等),迭代器对象必须重写获取*///删除元素
//1. Collection集合接口中的remove方法可以实现集合元素的删除
//(底层用的equals方法)
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
//获取当前集合的迭代器对象
Iterator it1 = c.iterator();
c.remove(2);
//此时集合状态发生迭代器对象必须重新获取
Iterator it2 = c.iterator();
//2. 迭代器对象还有一个remove方法,实现快照和集合对应元素的同时删除
//---因此不需要在重新获取迭代器对象
it2.remove();//移除当前迭代的元素(指针指向的元素next方法移动指针)
4. List接口常用方法
因为Set的集合是无序的,因此所有带下标的方法都是List特有的
package 集合.List接口常用方法;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/*0.List接口继承Collection接口拥有其全部方法1.Lsit集合储存元素的特点:有序可重复有序:List接口中的元素由下标从0开始以1递增可重复:允许集合内有相同元素2.List接口的特有方法:(下面学习中E先当成Object)void add(int index, E element) 向列表指定位索添加元素后续元素依次后移(下标0开始) 使用较少效率低E set(int index,E element) 修改指定索引的元素(返回别修改的元素)E get(int index) 根据索引获取元素int indexOf(Object o) 获取对象第一次出现处的索引int lastindexOf(Object o) 获取对象最后一次出现处的索引E remove(int index) 删除指定索引处的元素,后续元素前移(返回被删除的元素)E remove(E Element) 删除指定元素 底层调用的equals方法*/public class ListText1 {public static void main(String[] args) {//创建List集合对象List list = new ArrayList();//添加元素list.add("A");list.add("B");list.add("C");// List中的add方法list.add(2,"KING");//迭代Iterator iterator = list.iterator();while(iterator.hasNext()){System.out.println(iterator.next());// A B KING C}// List中的get方法Object fistobj = list.get(2);System.out.println(fistobj );//KING//由于List中拥有get方法,所以集合的遍历有了新方式for (int i = 0;i<list.size();i++){System.out.println(list.get(i));// A B KING C}// List中的indexOf方法System.out.println(list.indexOf("KING"));//2// List中的lastindexOf方法System.out.println(list.indexOf("KING"));//2// List中的remove方法list.remove(2);System.out.println(list.size());// List中的set方法list.set(2,"x");System.out.println(list.get(2));// X}
}
实现类分析
-
ArrayList集合
-
ArrayList集合的底层是一个Object[ ] 数组
-
默认初始量为10 (底层先创建了一个长度为0的数组,当添加第一个元素时候初始容量10)
-
构造方法:
-
new ArrayList(); // 构建了一个初始长度为10的集合
-
new ArrayList(int initialCapacity); 构建了一个初始长度为initialCapacity的集合
-
ArrayList集合的扩容:
- 扩容量为原来的1.5倍 ArrayList的底层是数组,所以扩容效率比较低,使用的时候尽量给一个合理的初始化
-
ArrayList集合(数组)的优缺点:
- 优点:检索速度高 (数组每个元素占用空间大小相同,内存地址是连续的,知道首元素地址及下标,则通过数学表达式计算出元素的内存地址,不需要一个一个的检索,所以效率高)
- 缺点:随机增删元素效率低,无法存储大数据量 (很难找到一块非常大的连续内存空间)
- 注意,向数组末尾添加元素(不涉及扩容的情况下),效率并不受影响 (数组每个元素占用空间大小相同,内存地址是连续的,知道首元素地址及下标,则通过数学表达式计算出元素的内存地址不需要一个一个的检索,所以效率高)
- ArrayList是使用最多的集合
-
-
LinkedList集合
- 初始容量为0,每次添加元素时,加入一个节点
- LinkedList集合的底层是双向链表
- LinkedLIst集合(链表)的优缺点
- LinkedList底层是链表结构, 由于链表是散列结构, 采用的地址相互记住的模式, 增删操作, 不会签到扩容和迁移数据的问题, 只需要擦除修改临近节点的地址就可能完成相互记住, 而且底层是双链表结构, 维护了头尾节点, 所以对首尾的操作效率是非常高, 但是如果对中间元素操作效率是比较低的, 尤其是查询, 每次查询都需要通过首尾节点才能查询, 效率比较低 (相比较集合链表无法通过数学表达式来计算元素内存地址,因此每一次检索都是从头节点开始遍历,所以效率较低)
- LinkedList集合内存模型(linkedList接口对象中的元素是是以一个个(Node)节点的形式存储的,而对象中只存储了列表的头节点和尾节点)
set接口的两个实现类
package 集合.Set接口;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeSet;
public class Test {public static void main(String[] args) {Set<String> set1 = new HashSet();/*特点:1.无序不可重复,(没有下标)无序:储存时的顺序和取出时的顺序不同2.放入HashSet集合中的元素实际上是放到HashMap元素中了*/set1.add("hello");set1.add("hello1");set1.add("hello1");set1.add("hello2");set1.add("hello3");set1.add("hello3");for (String s:set1){System.out.print(s+" "); //hello1 hello2 hello hello3}Set<String> set2 = new TreeSet();/*特点:1.无序不可重复,(没有下标),但是存储的元素可以自动按照大小排列2.放入TreeSet集合中的元素实际上是放到TreeMap元素中了*/set2.add("D");set2.add("A");set2.add("B");set2.add("X");set2.add("Z");set2.add("Y");for (String s:set2){System.out.print(s+" "); //A B D X Y Z}}
}
LinkedHashSet
LinkedHashSet类是HashSet的子类。LinkedHashSet它底层采用的是也是哈希表结构,只不过额外新增了一个双向链表来维护元素的存取顺序。如下下图所示:
5. 泛型
JDK5.0之后退出的新特性:泛型---泛型的好处是在编译的时候检查类型安全,
并且所有的强制转换都是自动和隐式的,提高代码的重用率。(泛型这种机制是给编译器看的,运行阶段没用)E - Element (在集合中使用,因为集合中存放的是元素)T - Type(Java 类)K - Key(键)V - Value(值)N - Number(数值类型)? - 表示不确定的 java 类型
泛型在集合中的使用
package 集合.泛型;/*集合中使用泛型的好处1.使用泛型后集合中存储的元素统一了2.从集合中去出的元素类型是泛型指定内容缺点导致集合中储存的元素缺乏多样性(但实际开发中使用集合大多数时需储存的数据元素是统一的)*/import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;public class GenericTest1 {public static void main(String[] args){/* 不使用泛型机制List list = new ArrayList();//准备对象Cat cat = new Cat();Bird bird = new Bird();//将对象放入集合list.add(cat);list.add(bird);//遍历集合,取出每一个动物,让它moveIterator iterator = list.iterator();while(iterator.hasNext()){Object object = iterator.next();//next()方法取出的为Object 而Object中没用move 需要下转if(object instanceof Animal){((Animal)object).move();}}*/// 使用泛型机制---指定了集合中可存储的数据类型(原理参考自定义泛型和源码)List<Animal> list = new ArrayList<Animal>();Cat cat = new Cat();Bird bird = new Bird();list.add(cat);list.add(bird);//list.add("sdfg");与指定类型不一致,编译报错//获取迭代器对象 此处加了泛型表示迭代器是Animal类型Iterator<Animal> iterator = list.iterator();while(iterator.hasNext()){//使用泛型后每一次迭代的对象都为Animal,此处就不在需要强制类型转换了iterator.next().move();}}
}class Animal{public void move(){System.out.println("动物在移动");}
}
class Cat extends Animal{public void catchMouse(){System.out.println("猫不抓老鼠啊");}
}
class Bird extends Animal{public void fly(){System.out.println("鸟儿在飞翔");}
}
自动类型推断
List<Animal> myList = new ArrayList<>();
//ArrayList<这里的类型会自动推断>,且<>可以省略
泛型详解
泛型在java中有很重要的地位,无论是开源框架还是JDK源码都能看到它。毫不夸张的说,泛型是通用设计上必不可少的元素,所以真正理解与正确使用泛型,是一门必修课。
泛型本质
- Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
- 泛型的本质是参数化类型,即给类型指定一个参数,然后在使用时再指定此参数具体的值,那样这个类型就可以在使用时决定了。这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型的好处
-
泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
-
保证了类型的安全性。
- 在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。
- 比如:
- 没有泛型的情况下使用集合:
- 比如:
publicstaticvoidnoGeneric(){
ArrayListnames=newArrayList();
names.add(“mikechen的互联网架构”);
names.add(123); //编译正常
}- 有泛型的情况下使用集合:
public static void useGeneric() {
ArrayList names = new ArrayList<>();
names.add(“mikechen的互联网架构”);
names.add(123); //编译不通过
} - 在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。
-
有了泛型后,定义好的集合names在编译的时候add(123)就会编译不通过。相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。
-
消除强制转换
- 泛型的一个附带好处是,消除源代码中的许多强制类型转换,这使得代码更加可读,并且减少了出错机会。
- 举例:
- 以下没有泛型的代码段需要强制转换:
List list = new ArrayList();
list.add(“hello”);
String s = (String) list.get(0); - 当重写为使用泛型时,代码不需要强制转换:
List list = new ArrayList();
list.add(“hello”);
String s = list.get(0); // no cast
- 以下没有泛型的代码段需要强制转换:
-
避免了不必要的装箱、拆箱操作,提高程序的性能
- 在非泛型编程中,将筒单类型作为Object传递时会引起Boxing(装箱)和Unboxing(拆箱)操作,这两个过程都是具有很大开销的。引入泛型后,就不必进行Boxing和Unboxing操作了,所以运行效率相对较高,特别在对集合操作非常频繁的系统中,这个特点带来的性能提升更加明显。
- 泛型变量固定了类型,使用的时候就已经知道是值类型还是引用类型,避免了不必要的装箱、拆箱操作。
object a=1;//由于是object类型,会自动进行装箱操作。
int b=(int)a;//强制转换,拆箱操作。这样一去一来,当次数多了以后会影响程序的运行效率。- 使用泛型之后
public static T GetValue(T a){
return a;
}
public static void Main(){
int b=GetValue(1);//使用这个方法的时候已经指定了类型是int,所以不会有装箱和拆箱的操作。
} -
提高了代码的重用性
泛型的使用
泛型有三种使用方式,分别为:泛型类、泛型接口和泛型方法。
泛型类
- 把泛型定义在类上 在创建对象的时候指定类的类型
-
定义格式:
public class 类名 <泛型类型1,…> { } -
注意事项:泛型类型必须是引用类型(非基本数据类型)
-
定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔:
- public class GenericClass<ab,a,c> {}
-
当然,这个后面的参数类型也是有规范的,不能像上面一样随意,通常类型参数都使用大写的单个字母表示:
-
示例代码:
// 泛型类
public class GenericClass {
private T value;
public GenericClass(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
// 测试类:
GenericClass name = new GenericClass<>(“mikechen的互联网架构”);
System.out.println(name.getValue());
GenericClass number = new GenericClass<>(123);
System.out.println(number.getValue());
泛型接口
- 泛型接口:把泛型定义在接口上
-
定义格式:
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {} -
注意要点:
- 方法声明中定义的形参只能在该方法里使用,而接口、类声明中定义的类型形参则可以在整个接口、类中使用。当调用fun()方法时,根据传入的实际对象,编译器就会判断出类型形参T所代表的实际类型。
public interface GenericInterface {
void show(T value);}
}
public class StringShowImpl implements GenericInterface {
@Override
public void show(String value) {
System.out.println(value);
}}
public class NumberShowImpl implements GenericInterface {
@Overrid
public void show(Integer value) {
System.out.println(value);
}}// 注意:使用泛型的时候,前后定义的泛型类型必须保持一致,否则会出现编译异常:
GenericInterface genericInterface = new NumberShowImpl();//编译异常
// 或者干脆不指定类型,那么 new 什么类型都是可以的:
GenericInterface g1 = new NumberShowImpl();
GenericInterface g2 = new StringShowImpl();
泛型方法
- 泛型方法,是在调用方法的时候指明泛型的具体类型 。多用于静态方法用于限定参数类型(不需要创建对象)
-
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }/*
@return T 返回值为T类型
说明:
1)public 与 返回值中间非常重要,可以理解为声明此方法为泛型方法。
2)只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。*
3)表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。如果不写这会默认为泛型列的类型T
4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E等形式的参数常用于表示泛型。
*/
public T genercMethod(T t){
System.out.println(t.getClass());
System.out.pintln(t);
return t;
}
public static void main(String[] args) {
//这里的泛型跟下面调用的泛型方法可以不一样。
GenericsClassDemo genericString = new GenericsClassDemo(“helloGeneric”);
//传入的是String类型,返回的也是String类型
String str = genericString.genercMethod(“hello”);
//传入的是Integer类型,返回的也是Integer类型
Integer i = genericString.genercMethod(123);
}/*
输出结果
class java.lang.String
hello
class java.lang.Integer
123
*/
泛型通配符
Java泛型的通配符是用于解决泛型之间引用传递问题的特殊语法, 主要有以下三类:
- // 表示类型参数可以是任何类型
- public class Apple<>{}
- // 表示类型参数必须是A或者是A的子类
- public class Apple{}
- // 表示类型参数必须是A或者是A的超类型
- public class Apple{}
- 无边界的通配符(Unbounded Wildcards), 就是<>, 比如List<>无边界的通配符的主要作用就是让泛型能够接受未知类型的数据.
- 固定上边界的通配符(Upper Bounded Wildcards),采用< extends E>的形式使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。要声明使用该类通配符, 采用< extends E>的形式, 这里的E就是该泛型的上边界。
- 注意: 这里虽然用的是extends关键字, 却不仅限于继承了父类E的子类, 也可以代指显现了接口E的类
- 固定下边界的通配符(Lower Bounded Wildcards),采用< super E>的形式使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据.。要声明使用该类通配符, 采用< super E>的形式, 这里的E就是该泛型的下边界.。
- 注意: 你可以为一个泛型指定上边界或下边界, 但是不能同时指定上下边界。
泛型的实现原理
- 泛型本质是将数据类型参数化,它通过擦除的方式来实现,即编译器会在编译期间「擦除」泛型语法并相应的做出一些类型转换动作。
- 例如:
public class Caculate {
private T num;
} - 上述代码定义了一个泛型类,定义了一个属性成员,该成员的类型是一个泛型类型,这个 T 具体是什么类型,并不知道知道,它只是用于限定类型的。
- 反编译一下这个 Caculate 类:
public class Caculate{
public Caculate(){}
private Object num;
} - 发现编译器擦除 Caculate 类后面的两个尖括号,并且将 num 的类型定义为 Object 类型。
- 但并不是所有的泛型类型都以 Object 进行擦除呢大部分情况下,泛型类型都会以 Object 进行替换,但是当使用到了extends和super语法的有界类型
- 如:
public class Caculate {
private T num;
}
- 如:
- 这种情况的泛型类型,num 会被替换为 String 而不再是 Object。这是一个类型限定的语法,它限定 T 是 String 或者 String 的子类,也就是你构建 Caculate 实例的时候只能限定 T 为 String 或者 String 的子类,所以无论你限定 T 为什么类型,String 都是父类,不会出现类型不匹配的问题,于是可以使用 String 进行类型擦除。
- 例如:
- 实际上编译器会正常的将使用泛型的地方编译并进行类型擦除,然后返回实例。但是除此之外的是,如果构建泛型实例时使用了泛型语法,那么编译器将标记该实例并关注该实例后续所有方法的调用,每次调用前都进行安全检查,非指定类型的方法都不能调用成功。
- 实际上编译器不仅关注一个泛型方法的调用,它还会为某些返回值为限定的泛型类型的方法进行强制类型转换,由于类型擦除,返回值为泛型类型的方法都会擦除成 Object 类型,当这些方法被调用后,编译器会额外插入一行 checkcast 指令用于强制类型转换,这一个过程就叫做『泛型翻译』。
6. foreach
/*
JDK5.0之后推出了一个新的特性:增强for循环也叫做foreach增强for循环---不在需要使用下标遍历了语法:for(集合/数组的数据类型 变量名 : 集合名/数组名){System.out.println(变量名);}原理:for循环底层也是迭代器;Collections<> extends Iterable<>;
*/
package 集合.泛型;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class 增强for循环 {public static void main(String[] args) {// 数组的遍历int[] arr = {1, 2, 3, 4, 5, 6};//普通for循环遍历for (int i = 0;i<arr.length;i++){System.out.print(arr[i]);}System.out.println("
------------");//增强for循环遍历for (int a : arr){System.out.print(a);}System.out.println("
------------");// 遍历集合List<String> list = new ArrayList<>();list.add("3");list.add("6");list.add("9");list.add("13");//迭代Iterator<String> iterator = list.iterator();while(iterator.hasNext()){System.out.print(iterator.next());}System.out.println("
------------");//普通for循环(list集合有下标)for(int i = 0;i<list.size();i++){System.out.print(list.get(i));}System.out.println("
------------");//增强for循环for (String s : list){System.out.print(s);}}
}
7. Map接口
Map接口中的常用方法
package 集合.Map接口;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;/*java.util.Map接口常用方法1.Map和Collection没有继承关系2.Map接口以key和value的方式存储数据:键值对key和value都是引用数据类型3.Map接口中常用的方法V put(K key,V value) 向Map集合中添加键值对 如果key重复返回旧valueV get(K key) 通过key获取valuevoid clear() 清空集合boolean containsKey(Object key) 判断Map中是否包含某个keyboolean containValue(Object value) 判断Map中是否包含某个valueboolean isEmpty() 判断Map集合中元素个数是否为0Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)Collection<V> values() 获取Map集合中所有的value,返回一个CollectionV remove(K key) 通过key删除键值对 返回被删除的元素的值int size() 获取Map集合中键值对的个数void forEach(Consumer<T> c) 参数是BiConsumer<K, V> 需要实现的方法为void accept(K k, V v),遍历集合,取出元素执行重写方法(底层还是迭代器)Set<Map.Entry<K,V>> entrySet() 将Map集合转换成Set集合eg: 现在有一个Map集合,如下所示:map1集合对象key value------------------1 zhangsan2 lisi3 wangwuSet set = map1.entrySet();set集合1=zhangsan2=lisi3=wangwu(Set中以key=value的形式转换成一个值了 元素类型为Map.Entry<K,V>---Map中的静态内部类)
*/public class MapTest1 {public static void main(String[] args){//创建Map集合对象Map<Integer,String> map = new HashMap<Integer,String>();//向集合中添加键值对 --- V put(K key,V value);map.put(1,"zhangsan");map.put(2,"lisi");map.put(3,"wangwu");map.put(4,"zhaoliu");//获取所有value --- Collection<V> values();Collection<String> values = map.values();for (String s:values){System.out.print(" "+s); // zhangsan lisi wangwu zhaoliulisi}// 通过key获取value --- V get(Object key);String value = map.get(2);System.out.println(value); //lisi// 获取键值的数量 --- int size();System.out.println(map.size()); //4// 通过key删除key-value --- V remove(Object key);map.remove(2);/* contains的方法底层调用的为equals方法进行比较的,自定义类的时候需要重写equals方法 */// 判断是否包含某个key --- boolean containsKey(K key);System.out.println(map.containsKey(2)); //false// 判断是否包含某个value --- boolean containsValue(V value)System.out.println(map.containsValue("wangwu")); //trueSystem.out.println(map.containsValue(new String("wangwu"))); //true// 清空集合map.clear();System.out.println(map.size()); //0// 判断集合是否为空System.out.println(map.isEmpty()); //true}
}
Map集合的遍历
package 集合.Map接口;
//Map集合的遍历
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapTest2 {public static void main(String[] args){// 第一种方式:获取所有的key,通过key来遍历valueMap<Integer,String> map1 = new HashMap<>();map1.put(1,"zhangsna");map1.put(2,"lijun");map1.put(3,"wangwu");map1.put(4,"zhaoliu");//获取Map集合全部key --- Set<K> keySet(); 实际上Map的key部分就是Set数组Set<Integer> set1 = map1.keySet();//foreachfor (Integer i:set1){//通过key获取valueSystem.out.println(i+"="+map1.get(i));}System.out.println("-------------");// 迭代器Iterator<Integer> it = set1.iterator();while (it.hasNext()){//获取set集合的元素,也就是Map的keyInteger i = it.next();//通过key获取valueString s = map1.get(i);System.out.println(i+"="+s);}System.out.println("-------------");// 第二种方式:把Map集合直接转换成Set集合进行遍历 Set<Map.Entry<K,V>> entrySet() 其元素类型实际为Entry(实际上为一个单向链表)Set<Map.Entry<Integer, String>> set2 = map1.entrySet();//foreachfor (Map.Entry<Integer,String> i:set2){System.out.println(i);}System.out.println("-------------");//迭代器Iterator<Map.Entry<Integer, String>> iterator = set2.iterator();while (iterator.hasNext()){Map.Entry<Integer,String> node = iterator.next();//使用Map.Entry接口的实现类Entry中的 getKey和getValue方法获得其对应值;Integer key = node.getKey();String value = node.getValue();System.out.println(key+"="+value);}// 第二种方式:通过forEach Map集合和CollectionforEach方法是不一样的//Map集合的forEach参数是BiConsumer<K, V> 需要实现的方法为public void accept(K k, V v)//Collection集合的forEach参数是Consumer<T> 需要实现的方法为public void accept(T o)map1.forEach((i,s)-> System.out.println(i+","+s));//等效hashMap.forEach(new BiConsumer<Integer, String>() {@Overridepublic void accept(Integer i, String s) {System.out.println(i+","+s);}});}
}
Map集合中的Entry内部类
- Java的entry是一个静态内部类,实现Map.Entry< K ,V> 这个接口,通过entry类可以构成一个单向链表。
- java中Map及Map.Entry
- Map是java中的接口,Map.Entry是Map的一个内部接口。
- Map提供了一些常用方法,如keySet()、entrySet()等方法。
- keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。
- Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry<K,V>。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。
- Entry类的实现源码
- java中Map及Map.Entry
Map实现类
HashMap集合
package 集合.Map接口;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/*HashMap集合:1.HashMap集合底层是哈希表数据结构哈希表是一个数组和单向列表的结合,数组:查询效率高,随机增删效率低单向列表:随机增删效率高,查询效率低哈希表将以上两者结合到一起,充分发挥了两者各种的优点(但同样对应效率都没有单独某一个高)2.HashMap集合的底层源码(简化版)public class HashMap{//HashMap底层实际上就是一个一维数组,数组中的每个元素都是一个单向链表Node<K,V>[] table;//静态内部类HashMap.Nodestatic class Node<K,V>{final int hash;//哈希值(key的hashCode()方法的执行结果。hash值通过哈希算法,可以转换成数组的下标)final K key;//存储到Map集合中的keyV value;//存储的Map集合中的valueNode<K,V> next;//下个节点的内存地址}}3.重点方法map.put(K,V);v = get(K);重点内容,其原理必须掌握4.HashMap集合的key部分特点:无序:因为不一定挂到哪个单向链表上,所以输出时与输入的顺序会有所不同不可重复:equals和HashCode方法来保证HashMap集合的key不可重复,如果key重复了value会覆盖放在HashMp集合中的元素其实就是放到HashSet中了,所有HashSet集合中的元素也需要同时重写hashCode()和equals()方法5.哈希表HashMap使用不当时无法发挥性能!1.假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成论文纯单向链表2.假设将所有的hashCode()方法返回值都设定为不一样的,则回导致底层哈希表变成了一个数组以上两种情况都称为散列分布不均匀6.HashMap集合的默认初始化容量是16,默认加载因子是0.75(即当底层数组容量达到75%的时候数组开始扩容)重点:HashMap集合初始化容量必须是2的倍数(官方也是这样推荐的),目的是为了达到散列分布均匀,提高集合存取效率*/
public class MapTest3 {public static void main(String[] args){//测试HsahMap集合key部分元素特点//Integer是key,它的hashCode和equals都重写了Map<Integer,String> map = new HashMap<>();map.put(1111,"zhangsan");map.put(6666,"lisi");map.put(7777,"wangwu");map.put(2222,"zhaoliu");map.put(2222,"king");//key重复的时候value会自动覆盖System.out.println(map.size());//遍历Map集合Set<Map.Entry<Integer,String>> set = map.entrySet();for (Map.Entry<Integer,String> i:set){//验证结果:Map集合中的key部分无序不可重复 --- key相同value被覆盖掉了System.out.println(i);//7777=wangwu 1111=zhangsan 6666=lisi 2222=king}}
}
在比较equals的同时也比较了hash值,两者都相同或都不同才代两个元素表相同或不同。但是同时重写两个方法,对象会根据属性计算哈希值,所以此情况下equals相同则哈希值一定相同。由于索引算法原因,哈希值不同也可能放到同一个单链表上,只就是哈希碰撞,但是由于重写了equals方法,所以相同属性的对象不可能发生哈希碰撞,所以不会重复
package 集合.Map接口;
import java.util.HashSet;
import java.util.Set;
/*1.向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后看需要再调用equals方法put(k,v) --- k.hashCode()方法返回哈希值,经过哈希算法转换成数组下标如果数组下标为null equals不需要执行get(k) --- k.hashCode()方法返回哈希值,经过哈希算法转换成数组下标如果单向链表上只有一个元素 equals不需要执行2.注意:如果一个类的equals 方法重写了,那么hashCode方法必须要重写,并且equals方法返回如果是true,hashCode()方法的值必须一样。3.equals和hashCode方法均用idea工具生成,但注意要同时生成4.也就是放在HashMap key部分和放在HashSet中的元素需要同时重写equals和hashCode方法---借用idea工具5.如果两个元素的haah相同,这两个元素一定是在同一个单向链表上,如果两个元素的hash值不同也有可能放到同一单向链表上----这种情况叫“哈希碰撞”*/
public class MapTest4 {public static void main(String[] args){Student s1 = new Student("zhangsan");Student s2 = new Student("zhangsan");s1.equals(s2);//true//未从写hashCode方法 --- 调用的Object类的hashCode方法System.out.println(s1.hashCode());//1324119927 重写hashCode之后(-1432604525)System.out.println(s2.hashCode());//990368553 重写hashCode之后(-1432604525)Set<Student> set = new HashSet<>();set.add(s1);set.add(s2);//虽然Student类中重写了equals,但是并为重写hashCode,而put(k,v)与get(k)方法执行时均先调用hashCode方法System.out.println(set.size());//2 重写hashCode之后为1}
}
class Student{private String name;public Student(){}public Student(String name) {this.name = name;}public void setName(String name){this.name = name;}public String getName() {return name;}@Overridepublic boolean equals(Object obj) {if (obj == null || !(obj instanceof Student)) return false;if (obj == this) return true;return this.name.equals(((Student) obj).name);}/*@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;return name.equals(student.name);}@Overridepublic int hashCode() {return Objects.hash(name);}*/
}
Properties类
package 集合.Map接口;
/*目前只需要掌握Properties属性对象的相关方法即可 --- 存和取两个方法Properties是一个Map集合,继承Hashtable并且在继承的时候指定了Hashtable的泛型为ObjectPropoerties提供了setProperty,getProperty方法来保证存和取的key和value都是String类型。Properties被称为属性对象 是线程安全的。*/
import java.util.Properties;
public class PropertiesTest1 {public static void main(String[] args){Properties pro = new Properties();//两个方法 非重点方法//1.存 Objiect setProperty(String key,String value) --- 调用Hashtable的put方法pro.setProperty("a","124");pro.setProperty("b","456");//2.取 String getProperty(String key) 调用get方法System.out.println(pro.getProperty("b"));//456//提供了与IO流结合的方法 这个才是他存在的意思}
}
TreeMap类
TreeMap中自定义的类无法自动排序
package 集合.Map接口;
/*.1.TreeSet集合底层实际上是一个Map2.TreeMap集合底层是一个二叉树3.放到TreeSet中的元素,等同于放到TreeMap集合key部分4.TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小自动排序5.对于自定义内容TreeSet无法排序,因为未指对象之间的比较规则*/
import java.util.TreeSet;
public class TreeSetTest1 {public static void main(String[] args){//TreeSet集合中得元素可制动排序TreeSet<String> ts1 = new TreeSet<>();ts1.add("zhangsan");ts1.add("lisi");ts1.add("wangwu");ts1.add("zhaoliu");for (String s:ts1){System.out.println(s);//lisi wangwu zhangsan zhaoliu}//TreeSet无法对自设定类型进行排序---未指定比较规则添加元素时压根不知道怎么比较大小TreeSet<Person> ts2 = new TreeSet<>();Person per1 = new Person(123);Person per2 = new Person(456);ts2.add(per1);ts2.add(per2);System.out.println(ts2);/*报错Exception in thread "main" java.lang.ClassCastException: class 集合.Map接口.Person cannot be cast to class java.lang.Comparable (集合.Map接口.Person is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')at java.base/java.util.TreeMap.compare(TreeMap.java:1569)at java.base/java.util.TreeMap.addEntryToEmptyMap(TreeMap.java:776)at java.base/java.util.TreeMap.put(TreeMap.java:785)at java.base/java.util.TreeMap.put(TreeMap.java:534)at java.base/java.util.TreeSet.add(TreeSet.java:255)at 集合.Map接口.TreeSetTest1.main(TreeSetTest1.java:28)*/}
}
//Personl
class Person{private int age;public Person (){}public Person(int i) {this.age = i;}public void setAge(int age){this.age = age;}public int getAge(){return age;}public String toString(){return "Person[age="+age+"]";}
}
put方法源码
TreeSet的add方法调用的还是Map集合的put方法
自定义类自动排序的两种方案
/*TreeSet/TreeMap排序问题1.排序是在添加集合成员的时候进行的,TreeSet的add方法调用的是TreeMap的put方法2.由put源码可知使用无参创建TreeSet/TreeMap对象时,调用put(add)方法会将当前类型key强制转换成Comparable对象然后调用调用compareTo方法与当前集合中key进行比较,所以自设定类实现自动排序的方案一就是实现Comparable接口。3.同样有源码可知当使用带参构造传入一个比较器时,可以使用比较器的compare方法进行比较所以方案二就是创建比较器对象*/
public class TreeMapTest1{public static void main(String args[]){//以TreeSet为例Set<Vip> map = new TreeSet<>();//方案一Set<Vip> map = new TreeSet<>(new BiJiaoQi());//方案二map.add(new Vip("abc",12));map.add(new Vip("abd",12));map.add(new Vip("abc",13));for (Vip S:map){System.out.println(S);}}
}/*方案一:实现Comparable接口(需要重写其抽象方法)public interface Comparable<T> {public int compareTo(T o);}注意:以后尽量用泛型*/
class Vip implements Comparable<Vip>{public Vip(){}public Vip(String name, int age) {this.name = name;this.age = age;}String name;int age;/*重写compareTo方法,在方法中编写比较逻辑比较的是Map集合的key部分返回值<0再左子树上找(也就是参数小于this),>0在右子树上找右边=0 value部分会被覆盖掉*/ //注意:这个方法中参数是目前的树中的key ,调用这个方法的key(this)是需要加入的keypublic int compareTo(Vip v) {//按照年龄排序,年龄相同按字典顺序比较名字if (this.age > v.age) {//this是需要加入的kreturn 1;}else if (this.age<v.age){return -1;}else {return this.name.compareTo(v.name);}}@Overridepublic String toString() {return "Vip{" +"name='" + name + ''' +", age=" + age +'}';}
}/*方案二:创建比较器类 即创建一个实现Comparator接口的类(需要重写其抽象方法)public interface Comparator<T> {public int compare(T o,T x);}*/
class Vip{public Vip(){}public Vip(String name, int age) {this.name = name;this.age = age;}String name;int age;@Overridepublic String toString() {return "Vip{" +"name='" + name + ''' +", age=" + age +'}';}
}
class BiJiaoQi implements Comparator<Vip>{//重写compare方法,在方法中编写比较逻辑//注意:这个方法中的第一个参数为需要加入的keypublic int compare(Vip v,Vip i) {//v为需要加入的k//按照年龄排序,年龄相同按字典顺序比较名字if (v.age > i.age) {return 1;} else if (v.age < i.age) {return -1;} else {return v.name.compareTo(i.name);}}
}
- 注意:TreeMap集合是根据Key值来进行排序的,当自定义类为Map集合中的Key时才需要实现Comparable接口或Comparator接口
- list集合中的成员需要重写equals方法remove和contains会用到
- set集合中HashSet/HashMap中的成员需要重写equals,hashCode来保证元素唯一性
- TreeSet/TreeMap中的成员需要实现Comparable接口并重写compareTo方法来保证元素唯一性
- 所以构造方法,get,set,equals,toString都重写就完事了