我们经常会使用try/catch/finally语句块。当然,return关键字使用也是很平常的事,但是不知道大家有没有注意个这样一个问题。当在try语句块里面使用return语句,在finally里面去修改return所要返回的内容会出现什么情况。首先,我们知道return是结束方法的标志,一旦方法执行到return语句就将返回不再往下执行。其次,我们也知道,finally里面的语句是无论方法怎样执行,最后都要执行finally里面的语句。那么究竟是先执行return还是finally呢?下面通过两个小实验来解决这个问题。
首先看第一个例子
public class TestTryCatch {
public static void main(String[] args){
TestTryCatch test = new TestTryCatch();
System.out.println(test.fun());
}
public int fun(){
int i = 10;
try{
//doing something
return i;
}catch(Exception e){
return i;
}finally{
i = 20;
}
}
}
输出结果:10
OK,很简单的一个例子,创建了一个方法fun,在方法里使用try/catch语句,方法要求返回值类型为int型。在try里面放回i,这个时候是10,但是在finally里面将i值修改为20。我们看到结果是10,好像是return先执行。那么接下来再看另一个例子:
public class TestTryCatch {
public static void main(String[] args){
TestTryCatch test = new TestTryCatch();
System.out.println(test.fun());
}
public StringBuilder fun(){
StringBuilder s = new StringBuilder("Hello");
try{
//doing something
s.append("Word");
return s;
}catch(Exception e){
return s;
}finally{
string.append("finally");
}
}
}
输出结果:HelloWordFinally
看结果似乎有点出乎意料了,因为这次finally里面修改的内容生效了。看代码其实差别不大,只是把返回值类型修改为StringBuilder了。那么这是为什么呢?下面就为大家解释一下其中到底是怎么执行的。
首先,拿第一个例子来说,可以在main方法里实现这样一条语句:int result = test.fun();我们知道这样做是没有问题的,但是大家都知道“=”号赋值是值赋值。但是,方法的存放地址和常量的存放地址是不一样的,方法的存放在代码区的。上面我们把一个方法赋值给一个int型也没有报错。那是因为在声明方法是我们声明了返回值类型。那么编译器就会在代码的最前端预留一段返回值类型的内存。执行return的时候,就会把返回的内容写入到这段内存中。
这样,执行“=”号赋值的时候,就能在内存中匹配到相同的类型。赋值便能成功。
弄清楚上面的道理之后,再来解释最开始提出的问题就容易多了。在执行了return之后,返回的值已经被写入到那段内存中了,finally再修改i的值,只是修改了后面代码段的i值,对返回段内存没有影响。至于第二个例子,再看下面这张图你就会明白。
我们可以看到,当返回值不是基本数据类型的时候,其是指向一段内存的,return将返回段指向一段内存,但是代码段的s依然是指向的同一段内存地址,所以当s修改它指向内存中的值的时候,其实也就修改了返回段指向内存中的值,所以最终的值改变了。
到底返回值变不变可以简单的这么记忆:当finally调用的任何可变API,会修改返回值;当finally调用任何的不可变API,对返回值没有影响。
总结一下:其实return与finally并没有明显的谁强谁弱。在执行时,是return语句先把返回值写入但内存中,然后停下来等待finally语句块执行完,return再执行后面的一段。
很多同学在学习Java的过程中可能会听到一种叫枚举的数据类型,那到底什么是枚举呢?
在Java程序中又是怎么使用枚举的呢?
下面我们就简单的讲解下这个神秘的枚举类型。
什么是枚举?
枚举:枚举其实就是一种数据类型,跟int, char 这种差不多。是JDK1.5以后引入的一种新的数据类型。
它有一定的特点就是:在创建好一个枚举类型后,能够使用的量,只能是enum里面规定的值。
当然在定义枚举时,有要求:枚举中的量必须是不变的,并且是有限的。
什么意思呢?
例如:
一周中的周一(MONDAY), 周二(TUESDAY), 周三(WEDNESDAY), 周四(THURSDAY), 周五(FRIDAY), 周六(SATURDAY), 周末(SUNDAY)。
对于我们来说,一周中的时间,从周一到周末是不会改变的,并且是有限的,就可以使用枚举来展示一周中的数据。
当然除了一周的时间的满足条件以外,在生活中还有很多枚举的存在,如:“春夏秋冬”四季、“上下左右”方向,也可以使用枚举来表示。
那我们在Java程序中如何书写枚举呢?
当然同学们都是有一定Java基础的,那如果我们想要把一周的时间表示出来,在JDK1.5之前,那时没有枚举的话,该怎么做呢?
通过初步分析,我们发现一周中的时间都是不变的,那我们应该使用常量来表示
代码如下:
public class Week{
public static final String MON = "MONDAY";
public static final String TUE = "TUESDAY";
public static final String WED = "WEDNESDAY";
public static final String THU = "THURSDAY";
public static final String FRI = "FRIDAY";
public static final String SAT = "SATURDAY";
public static final String SUN = "SUNDAY";
}
这样定义后,在Java代码中可以直接使用Week.MON的方式来调用Week类中的值。
当然,我们还可以偷个懒,将上述代码写在接口中(因为接口定义的变量默认都是public static final)
最终变成:
public interface IWeekConstants {
String MON = "Mon";
String TUE = "Tue";
String WED = "Wed";
String THU = "Thu";
String FRI = "Fri";
String SAT = "Sat";
String SUN = "Sun";
}
这样确实要简单一点了,那我们用枚举还能更简单吗?
OK,我们用枚举试试:
public enum WeekEnum {
MON, TUE, WED, THU, FRI, SAT, SUN;
}
这真不是一般的简单。
在我们使用枚举时只需要把确定的值一个一个的列举出来即可,因为在使用枚举来表示这些数据时它们就已经确定了值,并不需要担心量的变化。
所以,在某种情况下,使用枚举也是一种不错的选择。
当然,在定义枚举的过程有有一些要求:
1.首先创建枚举类型要使用 enum 关键字–public enum WeekEnum{},类似于类的创建过程。
2.每个枚举量之间用“,”隔开,就像:MON, TUE, WED, THU, FRI, SAT, SUN
当然,最后的SUN后面可以不加“;”,但为了代码整体的展示,加上“;”更能体现书写代码时的好习惯。
OK,到这里,肯定会有人会问:“枚举怎么写得那么简单啊!”
其实枚举仅仅只是看起来写得很简单。
枚举书写简单的原因是:在枚举中,每一个枚举对象在创建时都会默认继承java.lang.Enum这个类。
但由于java.lang.Enum这个类是抽象类,所以不能直接创建其对象。
所以每写一个枚举对象,实际上就创建一个Enum的子类(Enum本身是一个抽象类),而在每次创建枚举的子类时都会自动映射到下述的构造器中创建对象:
protected Enum(String name, int ordinal)
在该构造器中,每个枚举的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。
例如上述的枚举代码中,一共是周一到周末,创建了7个枚举对象,相当于调用了7次
protected Enum(String name, int ordinal)
在这个构造函数中,name 是常量,如:上面所写的MON,TUE等;ordinal是被创建序数(从0开始)。
效果相当于:
public enum WeekEnum {
new Enum<EnumTest>("MON",0);
new Enum<EnumTest>("TUE",1);
new Enum<EnumTest>("WED",2);
...
}
所以说,枚举看起来很简单,但是在执行时依然做了很多准备工作。
那在Java代码中怎么运用呢?
用法一:常量
enum Color{
GREEN, YELLOW, RED
}
上述例子中,GREEN, YELLOW, RED都是常量,这是枚举中常用的方式之一。
我们首先创建了一个枚举类,并且在其中创建3个枚举对象
接下来看看如何得到枚举中的子类对象
public class TrafficLight {
//使用枚举对象和使用静态属性差不多
//首先通过枚举名,再加上.运算符找到对应的常量名,返回的是一个枚举对象
Color color = Color.RED;
//那么color就是一个枚举对象,并对应RED
}
通过上述例子,相信同学们都大致了解如何获得枚举对象了。
上面我们也说了枚举和int、char一样都是数据类型,那能不能在switch中使用呢?
用法二:switch
接下来看看在switch中如何使用枚举
接着上面的代码:
public void change() {
switch (color) {//将枚举对象放入switch参数列表中
case RED:
System.out.println("红色");
break;
case YELLOW:
System.out.println("黄色");
break;
case GREEN:
System.out.println("绿色");
break;
}
}
将枚举对象放入switch参数列表中是JDK1.5以后的新方式,可以把枚举对象看做参数来使用,其对应的就是它所代表的的值。
如上述代码中,Color color = Color.RED;先取出枚举对象,然后赋值给color。
这时color枚举对象对应的就是RED,所以,在case选择过程中,会被case RED选中,进而执行case RED中的语句,最终输出“红色”。
同理如果在创建枚举对象时使用的是Color.YELLOW,则枚举对象对应的就是YELLOW,则会执行对象case YELLOW中的语句输出“黄色”。
注意:在前面也说了,只能使用枚举中已经写好的对象,所以使用其它没有的枚举对象是会出现错误的。
从上述代码中相信大家已经基本了解了如何在代码中来使用我们的枚举对象了,同时,也了解了switch中枚举的新用法。
下面来个有难度的。
之前我们说过,枚举类型在创建时很类似类的创建,那枚举能不能和类一样,在枚举中创建方法呢?
用法三:向枚举中添加新方法
如果打算自定义自己的方法,那么必须在enum实例序列的最后添加一个分号。而且 Java 要求必须先定义 enum 实例。
怎么书写呢?
public enum Color {
//先定义 enum 实例
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);//;不能忘掉
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
// 普通方法
public static String getName(int index) {
for (Color c : Color.values()) {
if (c.getIndex() == index) {
return c.name;
}
}
return null;
}
// get set 方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
从上述代码中,我们发现其中不仅创建了枚举的对象,同时还出现了构造器、成员变量和对应的get、set方法,并且还加上了一个普通的方法。
尤其是RED(“红色”, 1)这种形式的数据。其相当于在创建枚举的RED、GREEN等对象时,使用构造器把构造出来的对象与枚举对象进行了绑定。
相当于枚举类中的对象只有RED、GREEN、BLANK、YELLO,而这些对象分别拥有对应的两个成员变量:name和index。
当然这种定义方式我们很少见,不过一定得知道哦,不然被问到就尴尬了。
那上述的代码,我们能不能还像刚才那样对枚举对象进行使用呢?
通过代码给大家展示一些常用的方法:
public static void main(String[] args) {
//使用Color.values(),可以返回一个Color类型的数组
Color c[] = Color.values();
//既然是个数组,进行遍历
for (Color color : c) {
//使用get、set方法中的get方法,得到c对象中对应的name值。
System.out.println(color.getName());
//使用get、set方法中的get方法,得到c对象中对应的index值。
System.out.println(color.getIndex());
//使用枚举类的方法ordinal(),得到枚举对象的索引值(从0开始)
System.out.println(color.ordinal());
//使用枚举类的方法name(),得到枚举对象的名称
System.out.println(color.name());
//直接输出枚举对象
System.out.println(color);
}
//根据枚举的静态方法,通过字符串找到对应的枚举对象
//如果该对象不存在,则出现java.lang.IllegalArgumentException异常
Color c1 = Color.valueOf("RED");
System.out.println(c1);
}
下面对上述方法进行了简单的归纳
简单介绍在枚举中常用的一些方法:
1.使用Color.values(),可以返回一个Color枚举类型中枚举对象的数组。Color[] c = Color.values();
2.也可以通过枚举类中定义的方法:
首先获得枚举类的对象Color c = Color.RED;
通过枚举类中的方法:c.getName()(这个是get、set方法),得到c对象中对应的”红色”值。
当然也可以通过getIndex()(这个是get、set方法)得到写出的数值。
3.可以使用枚举类的枚举对象子代的方法:
同样首先获得枚举类的对象Color c = Color.RED;
(1)c.ordinal();得到该枚举对象的索引值,当然,索引是从0开始。
(2)c.name();得到对应的对象名称如:RED。
(3)System.out.println(c),直接输出枚举对象,获得的同样是对象名称:RED。
(4)静态方法使用枚举类来调用如:Color.valueOf(“RED”),根据传入的字符串,找到对应的枚举对象。
通过上述内容,相信大家对枚举也有了一定的了解。
既然在枚举中我们可以自定义方法,那能不能像类一样去重写父类中的方法呢?
用法四:覆盖枚举的方法
下面给大家展示一个比较简单的重写例子:
我们在枚举中重写toString()方法(所有类的都会隐式的继承Object类):
public enum Color1 {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color1(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
}
重写了toString()方法后,发现在输出枚举对象时,其输出内容也发生了改变,当然,在枚举中也可以加上main方法直接使用
例如:
public enum Color1 {
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color1(String name, int index) {
this.name = name;
this.index = index;
}
//覆盖方法
@Override
public String toString() {
return this.index+"_"+this.name;
}
public static void main(String[] args) {
Color1 c = Color1.BLANK;
System.out.println(c);
}
}
输出的结果也不再是BLANK,而是“3_白色”。
通过上面的讲解,我们发现,虽然枚举在创建过程中和类不同,但是枚举和类是非常相似的。
下面再给同学们列举一些枚举的其它用法。
用法五:实现接口
这里简单的给大家介绍下枚举如何实现接口。
直接上代码:
首先是接口:
public interface Behaviour {
public void print();
public String getInfo();
}
枚举代码:
public enum Color implements Behaviour{
RED("红色", 1), GREEN("绿色", 2), BLANK("白色", 3), YELLO("黄色", 4);
// 成员变量
private String name;
private int index;
// 构造方法
private Color(String name, int index) {
this.name = name;
this.index = index;
}
//接口方法
@Override
public String getInfo() {
return this.name;
}
//接口方法
@Override
public void print() {
System.out.println(this.index+":"+this.name);
}
}
和我们类实现接口是一样的,必须要求实现接口中的方法,并且还有一点特殊点就是枚举不能只实现接口的方法,还必要书写枚举中的量才可以。
也就是说,必须有RED(“红色”, 1), GREEN(“绿色”, 2), BLANK(“白色”, 3), YELLO(“黄色”, 4);这些枚举的量,否则就会报错误。
提一个小问让同学们想一想:枚举可以实现接口,那枚举可以继承类吗?
这个答案一定是:不可以!(枚举默认java.lang.Enum这个类,而Java只能单继承,所以不可以!)
用法六:使用接口组织枚举
什么是使用接口组织枚举呢?
简单的说就是在接口中创建多个枚举变量,其中枚举同时实现该接口
比如说:
public interface Food {
enum Coffee implements Food{
BLACK_COFFEE,DECAF_COFFEE,LATTE,CAPPUCCINO
}
enum Dessert implements Food{
FRUIT, CAKE, GELATO
}
}
Food接口的作用就是将其所包含的enum(Coffee、Dessert)组合成一个公共类型,可以使用Food对其中的枚举进行管理
那这样我们如何使用枚举中的值呢?
可以使用:
1.Coffee coffee = Food.Coffee.BLACK_COFFEE;
2.Coffee coffee = Coffee.BLACK_COFFEE;
3.Food coffee = Coffee.BLACK_COFFEE;
来创建出枚举的对象,其它的方法没有区别,当然还可以在接口中添加一些抽象方法
比如:
public interface Food {
enum Coffee implements Food {
BLACK_COFFEE, DECAF_COFFEE, LATTE, CAPPUCCINO;
@Override
public void use() {
System.out.println(Coffee.this.getClass());
}
}
enum Dessert implements Food {
FRUIT, CAKE, GELATO;
@Override
public void use() {
System.out.println(Dessert.this.getClass());
}
}
public void use();
}
用法七:关于枚举集合的使用
在Java中有java.util.EnumSet和java.util.EnumMap是两个枚举集合。
EnumSet保证集合中的元素不重复。
EnumMap中的 key是enum类型,而value则可以是任意类型。
简单的给展示下两种枚举集合
EnumSet代码:
enum Color {
RED, GREEN, BLUE;
}
public class EnumSetDemo {
public static void main(String args[]) {
// 声明一个EnumSet对象
EnumSet<Color> esOld = null;
EnumSet<Color> esNew = null;
// 将枚举的全部类型设置到EnumSet对象之中
esOld = EnumSet.noneOf(Color.class);
// 增加内容
esOld.add(Color.RED);
// 增加内容
esOld.add(Color.GREEN);
// 从已有的集合拷贝过来
esNew = EnumSet.copyOf(esOld);
print(esNew);
}
// 专门的输出操作
public static void print(EnumSet<Color> temp) {
// 循环输出EnumSet中的内容
for (Color c : temp) {
System.out.print(c + "、");
}
System.out.println();
}
}
EnumMap代码:
enum Color {
RED, GREEN, BLUE;
}
public class EnumMapDemo {
public static void main(String args[]) {
// 定义Map对象,同时指定类型
Map<Color, String> desc = null;
// 实例化EnumMap对象
desc = new EnumMap<Color, String>(Color.class);
// 添加数据
desc.put(Color.RED, "红色");
desc.put(Color.GREEN, "绿色");
desc.put(Color.BLUE, "蓝色");
// 遍历,并进行输出
for (Color c : Color.values()) {
System.out.println(c.name() + " --> " + desc.get(c));
}
for (Color c : desc.keySet()) {
System.out.print(c.name() + "、");
}
for (String s : desc.values()) {
System.out.print(s + "、");
}
}
}
根据上述举的例子,希望同学们对于枚举集合的使用有个直观的感受。
说了这么多,希望大家对枚举有更深入的了解,其实枚举只是一个Java和其它很多语言中的一种数据类型而已,只是由于其特殊的写法,在程序中运用有所不同。
总结
这里简单的总结一下:
1.由于枚举的出现时间比较晚,在我们的项目中使用的频率是比较低的,但是懂得枚举的使用可以使你的代码更为简洁明了。
2.虽然枚举的使用看起来很简单,但它也有自身的局限性(要求枚举中的数据全是不变的,有限的),所以,在编写程序的过程中应该灵活运用所学的只是,这样才能使自己的程序写得越来越好。
好的代码不是一蹴而就的,需要反复琢磨与思考,懂得合理运用才能事半功倍。