深刻理解Java中final的作用(一):从final的作用剖析String被设计成不可变类的深层原因
声明:本博客为原创博客,未经同意,不得转载!小伙伴们假设是在别的地方看到的话,建议还是来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被设计成不可变类的深层原因的更多相关文章
- 为什么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 ...
- 深刻理解Java中的String、StringBuffer和StringBuilder的差别
声明:本博客为原创博客,未经同意.不得转载!小伙伴们假设是在别的地方看到的话,建议还是来csdn上看吧(链接为http://blog.csdn.net/bettarwang/article/detai ...
- 深刻理解Java中形參与实參,引用与对象的关系
声明:本博客为原创博客,未经同意.不得转载! 原文链接为http://blog.csdn.net/bettarwang/article/details/30989755 我们都知道.在Java中,除了 ...
- 深入理解Java中的IO
深入理解Java中的IO 引言: 对程序语言的设计者来说,创建一个好的输入/输出(I/O)系统是一项艰难的任务 < Thinking in Java > 本文的目录视图如下: ...
- Java中的内存处理机制和final、static、final static总结
Java中的内存处理机制和final.static.final static总结 装载自:http://blog.csdn.net/wqthaha/article/details/20923579 ...
- 理解Java中的弱引用(Weak Reference)
本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...
- 深入理解Java中的不可变对象
深入理解Java中的不可变对象 不可变对象想必大部分朋友都不陌生,大家在平时写代码的过程中100%会使用到不可变对象,比如最常见的String对象.包装器对象等,那么到底为何Java语言要这么设计,真 ...
- [转]深刻理解Python中的元类(metaclass)以及元类实现单例模式
使用元类 深刻理解Python中的元类(metaclass)以及元类实现单例模式 在看一些框架源代码的过程中碰到很多元类的实例,看起来很吃力很晦涩:在看python cookbook中关于元类创建单例 ...
- 理解Java中的ThreadLocal
提到ThreadLocal,有些Android或者Java程序员可能有所陌生,可能会提出种种问题,它是做什么的,是不是和线程有关,怎么使用呢?等等问题,本文将总结一下我对ThreadLocal的理解和 ...
随机推荐
- Linux系统上安装Python
1.下载Python安装包,官网下:http://www.python.org/getit/ http://jingyan.baidu.com/article/eae07827f7f2d01fec54 ...
- Selenium2Library系列 keywords 之 _SelectElementKeywords 之 select_all_from_list(self, locator)
def select_all_from_list(self, locator): """Selects all values from multi-select list ...
- linux下安装filezilla客户端遇到的问题
访问filezilla ./filezilla 出现error while loading shared libraries : libpng12.so.o 缺少libpng12.so.o这个文件 解 ...
- 使用U盘安装win7系统,遇到“无法定位现有系统分区”问题
朋友的本子貌似因为安装360wifi而导致一进入系统就蓝屏重启,虽然之后就卸载了360wifi,但是问题依旧,上网Google了一下,发觉网上不少网友诉苦,也有人分析原因,说是因为360wifi导致了 ...
- Camel In Action 阅读笔记 第一章 认识Camel 1.1 Camel 介绍
1.1 Camel 介绍 Camel 是一个为了您的项目集成变得高效有趣的集成框架,Camel 项目在2007年初开始的,相对来说它还比较年轻,但它已然是一个非常成熟的开源项目,它所使用的是Apach ...
- 第二百二十六天 how can I 坚持
今天弟弟生日,只是简单的说了句生日快乐,幸亏看了下日历,要不又忘了. 在家待了一天. 明天还想去爬山,八大处太远了,该去哪呢. 不想在家待着. 日复一日,啊,年复一年啊.想想好可怕,人生,太快.该如何 ...
- linux常用基本命令
Linux中许多常用命令是必须掌握的,这里将我学linux入门时学的一些常用的基本命令分享给大家一下,希望可以帮助你们. 系统信息 arch 显示机器的处理器架构(1) uname -m 显示机器 ...
- HDU 5702 Solving Order (水题,排序)
题意:给定几种不同的颜色和它的权值,按它的权值排序. 析:排序. 代码如下: #include <cstdio> #include <string> #include < ...
- Linux下用arptables防arp攻击
Linux下网络层防火墙iptables很强大,链路层也有类似的防火墙arptables,可针对arp地址进行限制,防止ARP网关欺骗攻击,再配合静态绑定MAC和向网关报告正确的本机MAC地址,有效解 ...
- ASP.NET中使用 Response.Write("<script>alert('****');</script>");后CSS界面发生变化的解决方法 (经验证)
在后台使用Response.Write("<script>alert('Hello World');</script>);弹出alert窗口后发现网页的界面和原来CS ...