声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(原文链接为http://blog.csdn.net/bettarwang/article/details/26744661),看代码和提问、讨论都更方便。

Java中final的作用主要表如今三方面:修饰变量、修饰方法和修饰类。以下就从这两个方面来解说final的作用。在文末从final及类的设计安全性出发,论述了Java中String为何要被设计成不可变类。

1.final修饰变量

final修饰变量时与C++中的const作用类似,即假设是基本类型变量,则表示其值不能改变;假设是引用类型变量,则表示其一旦初始化,就不能再指向别的对象,可是注意它指向的对象本身的值能够改变(事实上这一点也跟C++中的常指针非常像)。看以下一个样例即知。

import java.util.*;

class Apple
{
private float weight;
public Apple(float weight)
{
setWeight(weight);
}
public void setWeight(float weight)
{
this.weight=weight;
}
public void print()
{
System.out.println("Weight is "+String.valueOf(weight));
}
public void printHashCode()
{
System.out.println("HashCode:"+String.valueOf(hashCode()));
}
} public class FinalSample
{
public static void main(String[]args)
{
final int a=10;
//对于final修饰的基本类型变量,一旦初始化后就不能再被改动
//a=20;
final Apple apple=new Apple(300f);
apple.print();
apple.printHashCode();
//对于final修饰的引用类型变量,仅仅是其指向不能变,可是其指向的对象本身能够改变。
apple.setWeight(350f);
apple.print();
apple.printHashCode();
}
}

输出结果例如以下图所看到的:

显然,final修饰的基本类型变量a是不能被改变;由输出的哈希码不变可知apple始终指向同一个对象,可是它指向的这个对象的成员weight却发生了改变。

也正是因为final对于引用类型变量“仅仅能保证指向不变,却不能保证指向的对象本身不变”,所以在构造类时要特别小心,否则非常easy被黑客利用。看以下一个样例即知。

import java.util.*;

class AppleTag
{
private float weight;
private float size;
public AppleTag(float weight,float size)
{
setWeight(weight);
setSize(size);
}
public void setWeight(float weight)
{
this.weight=weight;
}
public float getWeight()
{
return weight;
}
public void setSize(float size)
{
this.size=size;
}
public float getSize()
{
return size;
}
}
public class Apple
{
private final AppleTag appleTag;
public Apple(AppleTag appleTag)
{
this.appleTag=appleTag;
}
public AppleTag getAppleTag()
{
return appleTag;
}
public void print()
{
System.out.println("Weight:"+String.valueOf(appleTag.getWeight())+
" Size:"+String.valueOf(appleTag.getSize()));
} public static void main(String[]args)
{
AppleTag appleTag=new AppleTag(300f,10.3f);
Apple apple=new Apple(appleTag);
apple.print(); appleTag.setWeight(13000f);
apple.print(); AppleTag tag=apple.getAppleTag();
tag.setSize(100.3f);
apple.print();
} }

输出结果例如以下图所看到的:



类的设计者本意是想使appleTag一旦被初始化即不被改动,而且刻意不提供set函数以防止其被篡改。但实际上类的使用者却能够从两个地方改动appleTag从而达到改变apple的目的,当中一个地方是利用构造器中的实參进行改动,还有一个就是利用getAppleTag()函数的返回值对其进行改动。

显然,本例是因为类的设计不当而释放出过大的权限,使类的使用者将apple的重量和大小改动成了极不合理的值,从而得到错误的结果。那要怎样避免这样的错误呢?

非常easy,就是让final变量与外界充分隔离开,如本例中使类的使用者能获取对应的值,可是无法获取appleTag,代码例如以下所看到的:

import java.util.*;

class AppleTag
{
private float weight;
private float size;
public AppleTag(float weight,float size)
{
setWeight(weight);
setSize(size);
}
public void setWeight(float weight)
{
this.weight=weight;
}
public float getWeight()
{
return weight;
}
public void setSize(float size)
{
this.size=size;
}
public float getSize()
{
return size;
}
}
public class Apple
{
private final AppleTag appleTag;
public Apple(AppleTag appleTag)
{
this.appleTag=new AppleTag(appleTag.getWeight(),appleTag.getSize());
}
public AppleTag getAppleTag()
{
return new AppleTag(appleTag.getWeight(),appleTag.getSize());
}
public void print()
{
System.out.println("Weight:"+String.valueOf(appleTag.getWeight())+
" Size:"+String.valueOf(appleTag.getSize()));
} public static void main(String[]args)
{
AppleTag appleTag=new AppleTag(300f,10.3f);
Apple apple=new Apple(appleTag);
apple.print(); appleTag.setWeight(13000f);
apple.print(); AppleTag tag=apple.getAppleTag();
tag.setSize(100.3f);
apple.print();
} }<span style="color:#ff0000;"> </span>

程序输出结果例如以下:



显然,虽然此处类的使用者仍然尝试越权去改动appleTag,却没有获得成功,原因就在于:在接收时,在类的内部新建了一个对象并让appleTag指向它;在输出时,使用者获取的是新建的对象,保证了使用者即能够获得对应的值,又无法利用获取的对象来改动appleTag。所以apple始终未被改变,三次输出结果同样。

在我的博客《深刻理解Java中的String、StringBuffer和StringBuilder的差别》一文中已经说过String的对象是不可变的,因而在使用String时即使是直接返回引用也不用操心使用者篡改对象的问题,例如以下例:

import java.util.*;
class Apple
{
final String type;
public Apple(String type)
{
this.type=type;
}
public String getType()
{
return type;
}
public void print()
{
System.out.println("Type is "+type);
}
} public class AppleTest
{
public static void main(String[]args)
{
String appleType=new String("Red Apple");
Apple apple=new Apple(appleType);
apple.print(); appleType="Green Apple";
apple.print(); String type=apple.getType();
type="Yellow Apple";
apple.print();
}
}

输出结果例如以下所看到的:



显然,与前两个样例类似,类的使用者也尝试用相似的方法去改动type从而达到改动apple的目的,可是输出结果表明这样的尝试没有成功。原因就在于String的对象不可变,在外面的改动实际上是让appleType及type分别指向了不同的对象,而用于初始化的String对象始终没有改变,当然apple中的type也就不改变了。

再回过头来,我们发现以前让我们非常不爽的String(在深刻理解Java中的String、StringBuffer和StringBuilder的差别一文中讲到过,因为String对象不可变从而导致即使传引用也无法使对象的值改变),事实上是经过Java设计者精心设计的,试想一下,假设String对象可变的话,在我们寻常的程序编写中将会带来极大的安全隐患,而假设想杜绝这样的隐患,则每次在使用String时都要经过精密考虑,程序也会变得更复杂,而偏偏String是被大量使用的(实际上无论哪种编程语言,字符串及其处理都占有相当大的比重),显然会给我们日常的程序编写带来极大的不便。

另外一个值得注意的地方就是数组变量名事实上也是一个引用,所以final修饰数组时,数组成员依旧能够被改变。

深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因的更多相关文章

  1. 为什么Java中的String是设计成不可变的?(Why String is immutable in java)

    There are many reasons due to the string class has been made immutable in Java. These reasons in vie ...

  2. 深刻理解Java中的String、StringBuffer和StringBuilder的差别

    声明:本博客为原创博客,未经同意.不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(链接为http://blog.csdn.net/bettarwang/article/detai ...

  3. 深刻理解Java中形參与实參,引用与对象的关系

    声明:本博客为原创博客,未经同意.不得转载! 原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道.在Java中,除了 ...

  4. 深入理解Java中的IO

    深入理解Java中的IO 引言:     对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java >   本文的目录视图如下: ...

  5. Java中的内存处理机制和final、static、final static总结

    Java中的内存处理机制和final.static.final static总结   装载自:http://blog.csdn.net/wqthaha/article/details/20923579 ...

  6. 理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  7. 深入理解Java中的不可变对象

    深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...

  8. [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式

    使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...

  9. 理解Java中的ThreadLocal

    提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...

随机推荐

  1. 设计模式 单件-Singleton

    单件模式 Singleton 什么时候使用?当需要独一无二的对象时,请想起他. 举例:线程池(threadpool),缓存(cache),对话框,处理偏好设置和注册表(registry)的对象,驱动程 ...

  2. Application Cache

    轉發處:http://www.cnblogs.com/blackbird/archive/2012/06/12/2546751.html HTML5提供了一系列的特性来支持离线应用: applicat ...

  3. Python【基础第三篇】

    set集合 s1=set() 集合特点: 访问速度快 没有重复项 collections系列(数据类型容器模块) 一.计数器(Counter) Counter是对字典类型的补充,用于追踪值的出现次数. ...

  4. Camel In Action 阅读笔记 第一章 认识Camel 1.1 Camel 介绍

    1.1 Camel 介绍 Camel 是一个为了您的项目集成变得高效有趣的集成框架,Camel 项目在2007年初开始的,相对来说它还比较年轻,但它已然是一个非常成熟的开源项目,它所使用的是Apach ...

  5. WS之cxf的权限拦截器应用

    一.服务器端: 1.权限判断: package cn.tdtk.ws.interceptor; import java.util.List; import org.apache.cxf.binding ...

  6. Linux环境变量(小马哥推荐)

    /etc/profile:此文件为系统的每个用户设置环境信息,当用户第一次登录时,该文件被执行. 并从/etc/profile.d目录的配置文件中搜集shell的设置. /etc/bashrc:为每一 ...

  7. Message Forwarding

    [Preprocess] 在使用forwarding机制前,会先经历2个步骤,只有当这2个步骤均失败的情况下,才会激活forwarding. 1.+(BOOL)resolveInstanceMetho ...

  8. java -X 这不是标准的选项只是为了获取帮助信息

    -? -help      输出此帮助消息 获取帮助信息方式有三种: java java -? java -help -X            输出非标准选项的帮助 java -X -Xms< ...

  9. Linux目录介绍

    /: 根目录,一般根目录下只存放目录,不要存放文件,/etc./bin./dev./lib./sbin应该和根目录放置在一个分区中/bin:/usr/bin: 可执行二进制文件的目录,如常用的命令ls ...

  10. Objective-C 学习记录6--dictionary

    1.NSDictionary 和NSMutableDictionary NSDictionary dictionaryWithObjectsAndKeys:~,nil 使用键值对创建字典,用nil标志 ...