Java异常处理+常用类+泛型
异常处理
程序在执行过程中会出现各种异常,例如一个水果超市里面只卖苹果和香蕉,但是客户的需求是买牛肉,这时就需要处理这个异常。一般来讲我们可以通过一个简单的操作来处理,比如:
String[] products = new String[]{"apple" , "banana"};boolean findProduct(String s){ for( int i = 0 ; i < products.length ; i ++ ){ if( products[i].equals(s) )return true ; } return false ;}void handle(){ if( !findProduct("beef") ){ System.out.println("没有这个商品"); // 处理异常 }}不过,在Java中,本身就有专门处理异常的东西。使用Java专门的异常处理,可以防止程序在异常情况下持续执行,不论从可读性还是逻辑上都要优于上面的代码。Java异常处理的逻辑就是:抛出异常,捕获异常并进行处理,给出反馈。
捕获
异常的捕获通过catch语句实现,格式如下:
try{ //可能出现异常的代码}catch (异常的类型){ //解决方法}可能出现异常的代码就是我们可能要处理的部分,如上面说到的购买物品时的方法。异常的类型是指我们try语句中遇到问题时所抛出的异常对象的类型。catch中的解决方法一般就是反馈问题等。例如在Integer类的方法parseInt中,如果我们给patseInt的参数并非数字字符,那么就无法将其转成整型,这就是一个异常,此时parseInt会抛出一个NumberFormatException类型的异常,我们可以通过对这个异常进行处理(捕获这个异常,并给出解决方法)来提示用户传参错误。就像这样:
public class Main { public static void main(String[] args){ try{ int number = Integer.parseInt("abcd");//abcd是非数字字符,不能转成数字,此时产生异常 //这里parseInt会抛出NumberFormatException类型的异常: //throw new NumberFormatException(); //这里的throw语句已经在parseInt中设计好,不需要我们专门再写 }catch (NumberFormatException e){ System.out.println("非数字字符不能转成数字哦"); } }
}输出:
非数字字符不能转成数字哦
抛出
抛出异常通过throw关键字抛出一个异常对象实体来实现,上面代码中的:
throw new NumberFormatException();就是一个例子,它抛出了一个NumberFormatException异常类的实体,这个异常类是已经定义好的,我们也可以抛出自己定义的异常类的实体,不过需要注意的是,抛出的异常必须是实体,而且在异常抛出后,try语句块后面的代码不会再执行 ,紧接着执行catch语句块,这也防止了上文说到的“程序在异常情况下持续执行”,以免预料之外的错误。
自定义异常类
结合捕获和抛出,我们可以针对不同的异常情况,自己定义异常并进行处理(这里说的是自己定义异常,可不是自己制造异常哦)。所有的自定义的异常类都需要是Exception的子类,也就是说,我们在定义异常类时,需要继承Exception父类:
public class 异常名称 extends Exception{……}将可能出现异常的方法用throws和异常类名绑定,当方法体中检测出异常时,就抛出对应的异常对象实体,如果不绑定,则需要通过try-catch语句抛出异常,否则无法抛出对应异常的实体:
void exampleMethod() throws 异常名称{……}注意:通过throws和异常类绑定的方法才可以使用throw抛出异常,这样的方法需要放在try中,只有try内可以抛出异常。之后再通过catch语句捕获到对应类型的异常(捕获到对应异常类型的实体,参数是该实体的引用):
catch(异常名称 变量名){……}就可以灵活处理各种潜在的异常。就拿上面说到的水果超市购物为例:
//ProductTypeException异常类:public class ProductTypeException extends Exception{ private String message = "没有这个商品"; public void warn(){ System.out.println(message); }}//FruitShop类:public class FruitShop { String[] products = new String[]{"apple" , "banana"}; boolean findProduct(String s){ for( int i = 0 ; i < products.length ; i ++ ){ if( products[i].equals(s) )return true ; } return false ; // 处理异常 } void purchase(String s) throws ProductTypeException{ //和异常类绑定的方法 if( !findProduct(s) ){ throw new ProductTypeException(); //方法内抛出对应异常实体 } else { //…… } } void purchaseWithoutThrows(String s){ if( !findProduct(s) ){ try { throw new ProductTypeException(); //通过try抛出异常实体 }catch (ProductTypeException e){ e.warn(); } } else{ //…… } }}//Main类:public class Main { public static void main(String[] args){ FruitShop fruitShop = new FruitShop();
try{ fruitShop.purchase("beef"); }catch (ProductTypeException e){ e.warn(); }
fruitShop.purchaseWithoutThrows("beef"); }}输出:
没有这个商品
没有这个商品
finally
try-catch语句除了try和catch,还可以有一个子语句——finally{……},格式如下:
try{ //……}catch(异常类名 变量){ //……}finally{ //……}不论try中是否抛出异常,finally语句都会执行,但是有下面两种特殊情况:
try-catch语句内执行return语句后,finally仍会执行
如果try-catch中执行了程序退出代码System.exit(0),则finally不会执行
那finally语句有什么用呢?在某些情况,不论异常是否出现,有些语句一定要执行,就拿上面的水果超市举例,不管客户购买时是否出现异常,最后都要说一句“欢迎下次光临”(假设出现异常以后客户就不再买了),那么在我们处理完异常后就可以使用finally语句勇敢地对客户说出心中想说的那句话:
try{ fruitShop.purchase("beef");}catch (ProductTypeException e){ e.warn();}finally { System.out.println("欢迎下次光临");}其余代码同上。执行完这个try-catch后就会输出:
没有这个商品
欢迎下次光临
String
绝大多数语言中,字符串都是避不开的话题。C/C++中,我们用字符数组来处理字符串,也搭配了一系列的字符处理函数,而且C++还提供了string容器。而在Java中则又是一种全新的方式来专门处理字符串。首先就是常见的字符数组,但是Java的字符数组只是正儿八经的数组,它不支持直接被字符串赋值或初始化,只能一个字符一个字符地赋值:
char[] ch = new char[]{'o','h'};就和其他类型的数组的操作方式一模一样。可见,Java的字符数组处理字符串貌似不是很方便。所以关于字符串,真正的主角是String。
众所周知,String是引用数据类型,是一个封装好的类。
创建方法
String常用的构造方法(先不看构造方法体)有:
String(String s)
String str = new String(s);创建一个指向s实体的对象变量str,即:将s的引用赋给新变量str
String(char[] c)
String str = new String(c);将c内的所有字符拼接成字符串,并让新变量str指向该字符串实体(也就是将c复制一份赋给新变量str)
String(char[] c,int startIndex,int count)
String str = new String(c,startIndex,count);过程同上,不过是把c中下标从startIndex开始后面的count个字符拼接成字符串,并让新变量str指向该实体
此外,就是所有人都心心念念的字符串直接赋值的事情,String是支持的!例如
String str = "It's supported!";比较
String是引用数据类型,当比较两个String类时,如果像这样:
String s1 = new String("hello");String s2 = new String("hello");System.out.println(s1 == s2);如果单纯的用==运算符去比,这时输出的是false,也就是说,s1 != s2,因为String是引用数据类型,通过==运算符只是比较的s1和s2两个变量本身的值是否相等,也就是比的他们所存放的引用值是否相等,也就是比的他们是否指向同一个对象实体。Java的new运算符顾名思义,就是开辟新天体,即使s1和s2的内容一样,它们也都是通过开辟新天体得到的,此时这两个新天地是完全不同的两个地方,也就是它们指向的是两个实体。
虽然这样很合逻辑,但是很反人类,因为我们大多数时候只关心两个字符串内容,并不关心它们的引用值,这时想要比较s1和s2两个String变量的内容,则需要通过其中一个String对象调用equals(String s)方法来进行比较:
System.out.println( s1.equals(s2) );这时输出true。
(注意是equals,不是equal哦,因为equals方法的调用者是一个字符串,是第三人称,谓语动词要变第三人称单数 [狗头] )
那么再考虑一种情况:
String s1 = "oi";String s2 = "oi";System.out.println(s1 == s2);System.out.println(s1.equals(s2))先说结果:
true
true
这涉及到Java对于常量和变量的处理:
对于变量:变量存放在动态区。new就是在动态区中开辟新天地的,换言之,new创建出来的是变量,即使创建两个内容相同的东西,它们也在不同的内存块,引用值也是不同的。
对于常量:常量在常量池中。常量本身是不可变,每次创建出来常量以后,不会再创建新的内容相同的常量。对于构建出相同的字符串常量,它们是同一个东西,引用值也是相同的。
对于上面的情况,“oi”是在常量池中构建出来的常量,它具有一个引用值(假如是22FF),执行玩s1=“oi”后,在执行s2=“oi”时,由于常量池中已经有了“oi”常量,此时并不会创建新的“oi”常量,而是直接把现有的“oi”常量的引用值22FF赋给s2,所以s1和s2的引用值相等,内容也相等,它们指向的是同一个实体,这一点与new构建出来的变量不同。
拼接
Java的字符串允许+运算:把+后面的字符串接到前面的字符串上。下面是一个简单的栗子:
String str1 = "hel";String str2 = "lo";String string1 = str1 + str2 ;String string2 = str1 + str2 ;System.out.println( string1 == string2 );System.out.println( string1.equals(string2) );此时string1和string2的内容就是“hello”,那string1和string2是否==呢?在执行拼接操作时:
+左右两端都是常量:那么拼接后的新字符串也是按照常量进行构造,存放在常量池。
+左右两端不全是常量:那么拼接后的字符串按变量处理,存放在动态区。
上面的str1和str2存放着常量的引用,但它们本身是变量,此时str1+str2就是 变量+变量 ,此时产生的新字符串是一个动态区的变量,每次产生一个变量都会在动态区中开辟一块新内存,所以两次str1+str2产生的是两个不同的对象实体(不过它们内容相同)。上面程序输出:
false
true
常用方法
length
String对象调用length方法返回字符串的有效字符的个数,这句话是不是似曾相识,它和C/C++里的strlen函数的功能相同。用法如下:
String exampleStr = "Java太香啦";System.out.println(exampleStr.length());System.out.println("芜湖".length());输出:
7
2
equals
用于比较两个字符串内容是否相同,很常用。上面说到的==一般不怎么用,通常我们只关注两个字符串的内容,并不关注它们指向的是不是同一个实体。equals的用法就是String对象调用equals(String s)方法和参数s字符串比较内容是否相同,上文已经有一些相关的例子。
starsWith和endsWith
原型是public boolean starsWith(String s),endsWith同理。顾名思义,它们用来比较某个String对象是否以参数s开始/结束:
String exampleStr = "abcdefg";System.out.println(exampleStr.startsWith("abc"));System.out.println(exampleStr.startsWith("bc"));System.out.println(exampleStr.endsWith("efg"));System.out.println(exampleStr.endsWith("oi"));输出:
true
false
true
false
这两个方法是被重载过的方法,还有一种用法的原型是:public boolean startsWith(String s , int offset),意思是从offset的位置开始的字符串是否以s开头(即判断从offset开始到最后这个字串的前缀是否是s),注意是从下标为offset的位置开始,而不是第offset个位置,下标从0开始。endsWith没有这种重载,毕竟它比的是字符串的后缀,就没有从哪里开头这一说了。用法也很简单:
String exampleStr = "abcdefg";System.out.println(exampleStr.startsWith("abc"));System.out.println( exampleStr.startsWith("bcd",1) );System.out.println( exampleStr.startsWith("cde",3) );输出:
true
true
false
compareTo
原型是:public int compareTo(String s),这个方法用来比较两个字符串的字典序大小,如果调用者大于参数s,返回正数;小于返回负数;相等返回0。和C/C++的strcmp功能相同,用法如下:
String s1 = "abcdefg";String s2 = "bcdae";System.out.println(s1.compareTo(s2));System.out.println(s1.compareTo(s1));System.out.println(s2.compareTo(s1));输出:
-1
0
1
有时在比较字母时,我们只关心是否是同一个字母,并不关心大小写是否相同,此时就可以用compareToIgnoreCase(String s)方法:
String s1 = "ABC";String s2 = "abc";System.out.println(s1.compareTo(s2));System.out.println(s1.compareToIgnoreCase(s2));输出:
-32
0
contains
原型是:public boolean contains(String s),用来判断参数s是否是调用者的子串,注意是子串,而不是子序列。这里需要小区分一下:
子串:在原串中截取一部分下来,子串在原串中一定是连续的一部分,例如abcde的子串有abc,bcde,bcd等。
子序列:在原串中按先后顺序取一些字符再合并到一起,子序列在原串中不一定连续,如abcde的子序列有ace,bde等,只要保证先后顺序不变就行。
contains的用法如下:
String s1 = "abcde";System.out.println(s1.contains("bcd"));System.out.println(s1.contains("bde"));//bde是子序列,但不是子串输出:
true
false
其他
其他的String的方法还有如:
int indexOf(String s):返回s子串在调用者中第一次出现的下标,如果调用者不含有s,则返回-1
int lastIndexOf(String s):用法同上,区别在于它返回的是最后一次出现的下标
String substring(int startpoint):返回调用者从下标startpoint开始到最后的子串
String substring(int start , int end):上面方法的重载,返回调用者下标在[start , end),即从start开始到end-1的子串
String trim():返回调用者去掉前后空格后的子串
还有各种基本类型转成字符串的方法,如int转成String的:static String valueOf(int n)类方法等。
在其他类型中还有一些与String相关的方法,例如将String转成对应数据的方法:
Integer中的parseInt(String s),还有其他对应基本类型的方法等。
包装类
众所周知,Java有八种基本数据类型,而Java的核心思想是面向对象,这一点在基本数据类型上貌似体现的并不是很明显,而且对基本数据类型进行一些复杂的处理也很麻烦。Java对每种基本数据类型都提供了对应的包装类:Boolean, Byte, Short, Integer, Long, Float, Double, Character。这些包装类相当于基本数据类型的plus版本,它们都是引用数据类型,里面有很多实用方便的方法,使得我们在对基本数据类型处理时方便很多。由于这些包装类的各种方法实现的功能都大差不差,都是对对应的基本类型进行某种操作,所以就暂时拿Integer举例进行详细说明。
创建
一种是通过调用Integer的valueOf类方法,这个方法会返回一个Integer实例:
Integer x = Integer.valueOf(3) ;另一种是最方便的直接赋值:
Integer x = 3 ;常用方法
compareTo
int compareTo(Integer anotherInteger),和String的compareTo相同调用者大于参数时返回正数,等于返回0,小于返回负数。
public class Main { public static void main(String[] args){ Integer x = 3 ; System.out.println(x.compareTo(2)); System.out.println(x.compareTo(3)); System.out.println(x.compareTo(4)); }}输出:
1
0
-1
equals
用于判断两个Integer内容是否相等,和String相同,这里不再赘述。
进制转换
各种包装类中,都有和String相互转换的方法,例如String转成Integer的parseInt(String s)方法,和Integer转成String的toString(Integer x)方法,而且这两种都是Interger的类方法,也都是被重载过的方法。那么和字符串的转换和我们的主题:进制转换有什么关联呢?
在存储数据时,当我们定义一个基本类型int x = 3 ; 此时在x所在的内存中存放的并非3本身,而是3对应的二进制数;在我们输入数据时,通过键盘输入的数字也并非数字本身,而是以字符串的形式读入到计算机。计算机的世界里只有0和1,但是我们在输出x时呈现给我们的时十进制数3本身,这是因为在输出时系统默认将二进制格式化为十进制进行显示。有了这个原理,我们就可以让系统将二进制格式化成我们想要的进制进行输出,而这一点正是可以通过将数据和字符串的转换的方法来实现。
数字字符串转成十进制
通过parseInt(String s , int x),按x进制进行解析,将数字字符串s转成对应数值,也就是说,我们给定的数字字符是x进制的,最后的返回值是十进制数据,但是存在内存中的永远都是二进制哦。
public class Main { public static void main(String[] args){ Integer x = Integer.parseInt("F" , 16); System.out.println(x); }}输出:
15
如果我们给定的数字字符串和对应进制不匹配,则会抛出NumberFormatException异常,就像我们上面在异常处理中说到的一样。parseInt方法的x默认是十进制。
将十进制转成对应进制
通过toString(int number , int x)方法,将十进制数number转成x进制数对应的字符串表示,最终返回值是一个String,例如:
public class Main { public static void main(String[] args){ System.out.println(Integer.toString(15,16)); }}输出:
f
有没有发现我们介绍的进制转换只是针对十进制和其他进制转换的,这是因为计算机的默认格式化是十进制,数据的存放形式是二进制,如果我们要进行八进制转十六进制,无非也就是在计算机的格式化上做文章,也就是和十进制打交道,最后转成的对应进制只是显示的问题而已,而原来存放的数据还是它自己,并没有变成所谓的“对应进制”,就像下面这样:
public class Main { public static void main(String[] args){ Integer x = 15 ; System.out.println(Integer.toString(x,16)); System.out.println(x); }}输出:
f
15
虽然看起来是把x转成了十六进制,但只是在显示层面的一种转换而已,最后在计算机内存的还是那个二进制,最后一行输出x时还是按照默认十进制进行格式化输出,x并没有任何变化,所以我们刚刚说到的“八进制转成十六进制”的事情,可以说是不存在的事情,我们最后要打交道的还是针对x的默认格式化(十进制),计算机要面对的还是x的二进制,途中不涉及八进制,最后结果只是十六进制的一种呈现。
StringBuilder
我们在进行字符串拼接时,经常需要创建一个新的对象实体,然后计算引用再重新赋给变量,这样子效率比较低,StringBuilder类就解决了这个问题。
创建
StringBuilder常用的创建方法有两种,一种是在构造方法中传入一个int型参数,来指定数组的初始大小;另一种是直接传入一个字符串,此时StringBuilder类里面的内容就是这个字符串:
StringBuilder s1 = new StringBuilder(1000); //传入初始大小StringBuilder s2 = new StringBuilder("hello"); //传入字符串内容字符串拼接
StringBuilder的一个主要解决的就是字符串的拼接问题,它会预分配缓存区,这样在拼接时不需要再创建新的对象实体。StringBuilder的拼接通过append方法来实现。用法如下:
public class AboutStringBuilder { public static void main(String[] args){ StringBuilder str = new StringBuilder("I'm "); String s = "Builder"; str.append("String"); str.append(s); System.out.println(str); }}输出:
I’m StringBuilder
可见,StringBuilder的append方法也是被重载过的,它既可以接收StringBuiler类型的参数,也可以接收String类型的参数,并且将它们拼接到原有字符串的后面。而且StringBuilder的append可以累加,就像这样:
public class AboutStringBuilder { public static void main(String[] args){ StringBuilder str = new StringBuilder("I'm "); String s = "Builder"; str.append("String").append(s); //进行累加 System.out.println(str); }}那么这个是怎么实现的呢,通过StringBuilder的源码:

我们可以发现,每个append方法的返回值都是this,即它本身,因此实现了累加。 利用这个特性,我们可以写一个累加器,可以累加也可以累乘等:
public class Adder { private long sum = 0; Adder accumulate(int n){ this.sum += n ; return this; } Adder multiply(int n){ this.sum *= n ; return this; } long getSum(){ return this.sum; }}
public class MainClass { public static void main(String[] args){ Adder adder = new Adder(); adder.accumulate(3).accumulate(5).multiply(2); System.out.println(adder.getSum());
Adder otherAdder = new Adder(); otherAdder.accumulate(1).multiply(1).multiply(2).multiply(3); //3的阶乘 System.out.println(otherAdder.getSum()); }}输出:
16
6
转成String
虽然StringBuilder和String的用法都很像,但是StringBuilder终究不是String,有些时候我们需要把StringBuilder转成String对象,再调用String的相关方法进行某些操作,比如判断某个StringBuilder对象的内容是否包含Builder子串,我们可以这样:
public class AboutStringBuilder { public static void main(String[] args){ StringBuilder str = new StringBuilder("I'm "); String s = "Builder"; str.append("String"); str.append(s); String string = str.toString(); //转成字符串 System.out.println(string.contains("Builder")); //调用String的contains方法 }}输出:
true
BigDecimal
在程序设计中,经常会处理一些大到离谱的数字,而Java的math包中的BigDecimal类,它为浮点数的高精度处理提供了很多现成的方法。
创建
BigDecimal的创建方法由很多,可以把不同类型的数据放到构造方法的参数中,将其转成BigDecimal类型的数据。下面是用整型、String、和double型创建BigDecimal对象的栗子:
BigDecimal bd1 = new BigDecimal(120);BigDecimal bd2 = new BigDecimal("12.5");BigDecimal bd3 = BigDecimal.valueOf(25.5);常用方法
既然是math包中的类,自然少不了各种数学上的运算的方法:
BigDecimal add(BigDecimal another):加
BigDecimal subtract(BigDecimal another):减
BigDecimal multiply(BigDecimal another):乘
BigDecimal divide(BigDecimal another):除,如果商是无限小数,则会抛出异常
BigDecimal divide(BigInteger other , RoundingMode mode):上面divide方法的重载,后面的RoundingMode类可以对小数进行取舍,如最常用的四舍五入
BigDecimal divide(BigInteger other , int scale , RoundingMode mode):同样是上述方法的重载,scale用于设定保留几位小数。
int compareTo(BigDecimal other):比较两个BigDecimal的大小,返回值和String的compareTo相同。
下面是一些简单的加减乘除的例子:
import java.math.BigDecimal;import java.math.RoundingMode;
public class AboutBigDecimal { public static void main(String[] args){ BigDecimal bd1 = new BigDecimal("10.2"); BigDecimal bd2 = BigDecimal.valueOf(3.3); System.out.println(bd1.add(bd2)); System.out.println(bd1.subtract(bd2)); System.out.println(bd1.multiply(bd2)); System.out.println(bd1.divide(bd2,5, RoundingMode.HALF_UP)); //保留5位小数,四舍五入 System.out.println(bd1.compareTo(bd2)); }}输出:
13.5
6.9
33.66
3.09091
1
泛型简介
泛型,顾名思义,并不是某种具体的类型。它是一种数据集合的模板(框架),在我们对一些数据集合的处理上,针对不同的数据类型,因为它们类型不同,可能出现类型转换上的问题,所以我们一般只对同类型的集合进行处理,但是对于不同类型可能某些方法是相同的。听力来有点绕,举个栗子,假如我们有Integer和Double两种数据的集合,这两种集合都需要写一个增添元素的add方法,这时对于两种集合都要写类似的代码进行add,如果再有一个String集合,就又需要写一次add,而且Java有数不清的不同种的类,那么这些集合都要写一次add,而且还有自己定义的各种类……。这显然是很离谱的,而泛型编程就解决了这种问题,它提供了一套模板,不同的数据类型都可以用这套模板来打造集合,这时就不需要再反复的编写add等方法,可以直接用模板的方法。
自定义泛型
泛型的定义的语法是class 类名<类型>,这里的类型不可以是基本数据类型,也不是特定的类型,毕竟是泛型嘛,规定了类型就不能叫泛型了。这里的类型只是一个标识符,任何合法的标识符都可以放在这里,未来这里将接收不同的引用类型,在泛型的类体中可以使用这个标识符。定义方法如下:
class Example<T> {……}这里的T可以接收任何引用类型,但是需要注意一点,T也是Object类的子类,可以调用Object类的方法。假如每个类都需要一个speak方法来输出一句话,这时就可以调用这个模板:
public class Example<T> { public void speak(){ System.out.println("oi"); }}然后创建各种对应的类型的对象,再调用speak方法:
public class Main { public static void main(String[] args){ Example<String> e1 = new Example<>();//接收String类 e1.speak(); Example<Integer> e2 = new Example<>();//接收Integer类 e2.speak(); Example<Student> e3 = new Example<>();//接收自定义的Student类 e3.speak(); Example<Exception> e4 = new Example<>();//甚至是一个异常类 e4.speak(); }}输出:
oi
oi
oi
oi
还可以定义泛型的集合,来存放多个数据:
public class Example<T> { Object[] array = new Object[100] ; //集合最大时100个元素 private Integer total = 0 ; void add(T e){ array[total] = e ; total ++ ; } int length(){ return total ; } void output(){ for( int i = 0 ; i < total ; i ++ ){ System.out.print(array[i] + " "); } System.out.println(); }}
public class Main { public static void main(String[] args){ Example<String> es = new Example<>(); es.add("hello"); es.add("world"); es.add("!"); System.out.println(es.length()); es.output(); }}输出:
3
hello world !
Java也有一些现成的泛型:list
List
List是列表,是一种泛型类的容器,它实现了collection接口,用来存放数据集合,它可以通过ArrayList或LinkedList或Vector实现。通常我们用ArrayList这个可变长的数组来实现:
import java.util.ArrayList;import java.util.List;
public class Main { public static void main(String[] args){ List<Integer> li = new ArrayList<>() }}list的常用方法有:
size():返回当前 list 的长度
add(ClassName val):在当前 list 尾部插入一个元素,这里的ClassName可以时任意类
add(int index, ClassName e):上面方法的重载,在当前 list 指定位置插入一个元素
get(int index):返回当前 list 中第 index 位置的值,若越界则抛出异常
set(int index, ClassName e):修改当前 list 中第 index 位置的值
还记得开头的那个水果超市嘛,咱们再拿它举个栗子:
import java.util.ArrayList;import java.util.List;
public class Product { String name ; Double price ; Product(){} Product(String name , Double price){ this.name = name ; this.price = price ; }}
public class FruitShop { List<Product> list = new ArrayList<>(); void addProduct(Product product){ //增添商品 list.add(product); } int returnTypes(){ //返回一共有多少种商品 return list.size(); } Product getNthGoods(int index){ //获取第n个商品 return list.get(index); } void setNthGoods(int index , Product product){ //修改第n个商品的信息 list.set(index , product); }} //感觉只是给list的各种方法改了个名……
public class Main { public static void main(String[] args){ FruitShop fruitShop = new FruitShop(); fruitShop.addProduct(new Product("apple" , 3.14)); fruitShop.addProduct(new Product("banana" , 5.20)); System.out.println("共有" + fruitShop.returnTypes() + "种商品"); System.out.println("第1个水果是:" + fruitShop.getNthGoods(0).name + "价格:" + fruitShop.getNthGoods(0).price ); fruitShop.setNthGoods(0 , new Product("applePlus" , 10.0 )); System.out.println("修改后:"); System.out.println("第1个水果是:" + fruitShop.getNthGoods(0).name + "价格:" + fruitShop.getNthGoods(0).price ); System.out.println("第1个水果是:" + fruitShop.getNthGoods(1).name + "价格:" + fruitShop.getNthGoods(1).price ); }}输出:
共有2种商品
第1个水果是价格:3.14
修改后:
第1个水果是价格:10.0
第1个水果是价格:5.2
Set
集合,它是保证内部元素不唯一的容器,可以通过HashSet、LinkedHashSet或TreeSet来实现,后两者是有序的,LinkedHashSet按插入顺序排序,TreeSet默认按照升序排列。我们拿有序的TreeSet来举例,既保证有序,有保证唯一,它的底层应该是红黑树,是不是似曾相识,C++的set容器和这里的TreeSet很像。下面是一个简单的栗子:
import java.util.*;
public class Main { public static void main(String[] args){ Set<Integer> s1 = new HashSet<>(); // 无序 Set<Integer> s2 = new LinkedHashSet<>(); // 按插入顺序排序 Set<Integer> s = new TreeSet<>(); // 有序,默认升序 //下面拿TreeSet的实现举例 s.add(10); s.add(2); s.add(5); s.add(20); s.add(18); //此时s内:2 5 10 18 20 for( Iterator<Integer> it = s.iterator() ; it.hasNext() ; ){ System.out.println(it.next()); } // 输出s }}输出:
2
5
10
18
20
既然是有序,那就可以自定义排序规则,对于我们自定义的排序规则,有两种方法可以让Set遵循这种规则:第一种是自然排序,在元素类型内部定义好比较规则(就像C++在结构体内重载operator<一样);第二种是用比较器进行比较(就像C++的set<int , cmp>一样)。这里我们暂时先用自然排序方法。
第一种的自定义排序,需要让元素的类去实现Comparable接口(Comparable
以一个很经典的问题为例:假如我们有一个学生类,学生有姓名有成绩,按照成绩从大到小排序:
import java.util.*;
public class Student implements Comparable<Student>{ String name = null ; Integer grade = null ; Student(){} Student(String name , Integer grade){ this.name = name ; this.grade = grade ; } @Override public int compareTo(Student student) { if(this.grade != student.grade) return student.grade - this.grade ; //成绩高的放在前面 else return this.name.compareTo(student.name); // 姓名小的放在前面 } //此处必须写else一行,否则对于成绩相等的同学,会被认为是同一个元素}
public class Main { public static void main(String[] args){ Set<Student> s = new TreeSet<>(); s.add(new Student("zhangsan",99)); s.add(new Student("lisi",100)); s.add(new Student("wangwu",98)); s.add(new Student("zhaoliu",99)); for( Student student : s ){ System.out.println(student.name + " " + student.grade); } }}输出:
lisi 100
zhangsan 99
zhaoliu 99
wangwu 98
注意:compareTo不止是排序规则,也是判定元素是否相同的标准!上面代码如果不写else一行,那么zhaoliu会被认为和zhangsan是同一个东西,不会被放到s中。
Set的常用方法有:
size():返回 Set 的元素个数
add(ClassName val):在Set种插入一个元素
contains(ClassName val):判断 this 中是否有元素 val
remove(ClassName val):删除元素 val
addAll(Collection e):将一个容器里的所有元素添加进 this
retainAll(Collection e):将 this 改为两个容器内相同的元素
removeAll(Collection e):将 this 中与 e 相同的元素删除
再举个栗子,还是那个水果店,我们把水果按名字排序摆放,再进行一些操作:
import java.util.*;
public class Product implements Comparable<Product>{ String name ; Double price ; Product(){} Product(String name , Double price){ this.name = name ; this.price = price ; }
@Override public int compareTo(Product product) { return this.name.compareTo(product.name); } //将名称按字典序排序}
public class FruitShop { Set<Product> set = new TreeSet<>(); void addProduct(Product product){ set.add(product); } int returnTypes(){ return set.size(); } boolean contains(Product product){ return set.contains(product); }}
public class Main { public static void main(String[] args){ FruitShop fruitShop = new FruitShop(); fruitShop.addProduct(new Product("apple" , 3.14)); fruitShop.addProduct(new Product("banana" , 5.20)); System.out.println("初始"); for(Product product : fruitShop.set){ System.out.println(product.name + " " + product.price); }
Set<Product> trunk = new TreeSet<>(); //进货 trunk.add(new Product("mango",5.8)); trunk.add(new Product("pear",4.5)); fruitShop.set.addAll(trunk); System.out.println("进货后"); for(Product product : fruitShop.set){ System.out.println(product.name + " " + product.price); }
Set<Product> buyer = new TreeSet<>(); //大买家 buyer.add(new Product("banana" , 5.20)); buyer.add(new Product("pear",4.5)); //买光所有的banana和pear fruitShop.set.removeAll(buyer); System.out.println("购买后"); for(Product product : fruitShop.set){ System.out.println(product.name + " " + product.price); } }}输出:
初始
apple 3.14
banana 5.2
进货后
apple 3.14
banana 5.2
mango 5.8
pear 4.5
购买后
apple 3.14
mango 5.8
Map
映射,和C++的map类似,每个元素是key和value的键值对,可以通过HashMap、LinkedHashMap或TreeMap,与Set类似,HashMap无序,LinkedHashMap按插入顺序排列,TreeMap默认按照key升序排列。
它的常用方法有:
put(ClassName key, ClassName value):插入一个元素
size():返回 map 的长度
containsKey(ClassName val):判断 map 中是否有元素 key 为 val
get(ClassName key):将 map 中对应的 key 的 value 返回
keySet():将 map 中所有元素的 key 作为集合返回
接下来是不是该我们的水果店上场了呢,一个商品有名称有价格,就可以看出是名称到价格的一种映射,这不拿它举栗子拿谁举呢:
import java.util.*;
public class Main { public static void main(String[] args){ // 我们还是以默认按key升序的TreeMap举例 Map<String , Double> fruitShop = new TreeMap<>(); fruitShop.put("apple" , 3.14); fruitShop.put("banana" , 5.20); System.out.println("店里有:"); for( String key : fruitShop.keySet() ){ System.out.println(key + " " + fruitShop.get(key)); } System.out.println("一共" + fruitShop.size() + "种水果");
if(fruitShop.containsKey("mango")) System.out.println("有mango"); else System.out.println("没有mango");
if(fruitShop.containsValue(3.14)) System.out.println("有3.14元的水果"); else System.out.println("没有3.14元的水果"); }}输出:
店里有:
apple 3.14
banana 5.2
一共2种水果
没有mango
有3.14元的水果
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!
部分内容可能已过时