Java实例常量和方法的java定义常量有何区别

Java 语言中 Enum 类型的使用介绍
Enum 类型的介绍枚举类型(Enumerated Type)
很早就出现在编程语言中,它被用来将一组类似的值包含到一种类型当中。而这种枚举类型的名称则会被定义成独一无二的类型描述符,在这一点上和常量的定义相似。不过相比较常量类型,枚举类型可以为申明的变量提供更大的取值范围。举个例子来说明一下,如果希望为彩虹描绘出七种颜色,你可以在 Java 程序中通过常量定义方式来实现。清单 1.
常量定义 Public static class RainbowColor {
// 红橙黄绿青蓝紫七种颜色的常量定义
public static final int RED = 0;
public static final int ORANGE = 1;
public static final int YELLOW = 2;
public static final int GREEN = 3;
public static final int CYAN = 4;
public static final int BLUE = 5;
public static final int PURPLE = 6;
}使用的时候,你可以在程序中直接引用这些常量。但是,这种方式还是存在着一些问题。类型不安全由于颜色常量的对应值是整数形,所以程序执行过程中很有可能给颜色变量传入一个任意的整数值,导致出现错误。没有命名空间由于颜色常量只是类的属性,当你使用的时候不得不通过类来访问。一致性差因为整形枚举属于编译期常量,所以编译过程完成后,所有客户端和服务器端引用的地方,会直接将整数值写入。这样,当你修改旧的枚举整数值后或者增加新的枚举值后,所有引用地方代码都需要重新编译,否则运行时刻就会出现错误。类型无指意性由于颜色枚举值仅仅是一些无任何含义的整数值,如果在运行期调试时候,你就会发现日志中有很多魔术数字,但除了程序员本身,其他人很难明白其奥秘。如何定义 Enum 类型为了改进 Java 语言在这方面的不足弥补缺陷,5.0 版本 SDK 发布时候,在语言层面上增加了枚举类型。枚举类型的定义也非常的简单,用 enum
关键字加上名称和大括号包含起来的枚举值体即可,例如上面提到的彩虹颜色就可以用新的 enum 方式来重新定义: enum RainbowColor { RED, ORANGE, YELLOW, GREEN, CYAN, BLUE, PURPLE }从上面的定义形式来看,似乎 Java 中的枚举类型很简单,但实际上 Java
语言规范赋予枚举类型的功能非常的强大,它不仅是简单地将整形数值转换成对象,而是将枚举类型定义转变成一个完整功能的类定义。这种类型定义的扩展允许开发者给枚举类型增加任何方法和属性,也可以实现任意的接口。另外,Java
平台也为 Enum 类型提供了高质量的实现,比如默认实现 Comparable 和 Serializable 接口,让开发者一般情况下不用关心这些细节。回到本文的主题上来,引入枚举类型到底能够给我们开发带来什么样好处呢?一个最直接的益处就是扩大 switch 语句使用范围。5.0 之前,Java 中 switch
的值只能够是简单类型,比如 int、byte、short、char, 有了枚举类型之后,就可以使用对象了。这样一来,程序的控制选择就变得更加的方便,看下面的例子:清单 2. 定义 Enum
类型 // 定义一周七天的枚举类型
public enum WeekDayEnum { Mon, Tue, Wed, Thu, Fri, Sat, Sun }
// 读取当天的信息
WeekDayEnum today = readToday();
// 根据日期来选择进行活动
switch(today) {
 Mon:
 Tue:
 Wed:
 Thu:
 Fri:
 Sat:
 Sun:
}对于这些枚举的日期,JVM 都会在运行期构造成出一个简单的对象实例一一对应。这些对象都有唯一的 identity,类似整形数值一样,switch
语句就根据此来进行执行跳转。如何定制 Enum 类型除了以上这种最常见的枚举定义形式外,如果需要给枚举类型增加一些复杂功能,也可以通过类似 class 的定义来给枚举进行定制。比如要给 enum
类型增加属性,可以像下面这样定义:清单 3.
定制枚举类型 // 定义 RSS(Really Simple Syndication) 种子的枚举类型
public enum NewsRSSFeedEnum {
// 雅虎头条新闻 RSS 种子
YAHOO_TOP_STORIES(""),
//CBS 头条新闻 RSS 种子
CBS_TOP_STORIES(""),
// 洛杉矶时报头条新闻 RSS 种子
LATIMES_TOP_STORIES("");
// 枚举对象的 RSS 地址的属性
private String rss_
// 枚举对象构造函数
private NewsRSSFeedEnum(String rss) {
this.rss_url =
// 枚举对象获取 RSS 地址的方法
public String getRssURL() {
return this.rss_
}上面头条新闻的枚举类型增加了一个 RSS 地址的属性 , 记录头条新闻的访问地址。同时,需要外部传入 RSS
访问地址的值,因而需要定义一个构造函数来初始化此属性。另外,还需要向外提供方法来读取 RSS 地址。如何避免错误使用 Enum不过在使用 Enum 时候有几个地方需要注意:enum 类型不支持 public 和 protected 修饰符的构造方法,因此构造函数一定要是 private 或 friendly
的。也正因为如此,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。定义 enum
类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号';'隔开,不能用逗号或空格。 由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在 cluster
环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号 ( ‘ == ’ )
操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。 看下面的这个例子:清单 4. 避免错误使用 Enum
示例 // 定义一个一周七天的枚举类型
package example.enumeration.
public enum WeekDayEnum {
Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6), Sun(7);
WeekDayEnum(int idx) {
this.index =
public int getIndex() {
// 客户端程序,将一个枚举值通过网络传递给服务器端
package example.enumeration.
import java.io.IOE
import java.io.ObjectOutputS
import java.io.OutputS
import java.net.InetSocketA
import java.net.S
import java.net.UnknownHostE
public class EnumerationClient {
public static void main(String... args) throws UnknownHostException, IOException {
Socket socket = new Socket();
// 建立到服务器端的连接
socket.connect(new InetSocketAddress("127.0.0.1", 8999));
// 从连接中得到输出流
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
// 将星期五这个枚举值传递给服务器端
oos.writeObject(WeekDayEnum.Fri);
oos.close();
os.close();
socket.close();
// 服务器端程序,将从客户端收到的枚举值应用到逻辑处理中
package example.enumeration.
import java.io.*;
import java.net.ServerS
import java.net.S
public class EnumerationServer {
public static void main(String... args) throws IOException, ClassNotFoundException {
ServerSocket server = new ServerSocket(8999);
// 建立服务器端的网络连接侦听
Socket socket = server.accept();
// 从连接中获取输入流
InputStream is = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
// 读出客户端传递来的枚举值
WeekDayEnum day = (WeekDayEnum) ois.readObject();
// 用值比较方式来对比枚举对象
if (day == WeekDayEnum.Fri) {
System.out.println("client Friday enum value is same as server's");
} else if (day.equals(WeekDayEnum.Fri)) {
System.out.println("client Friday enum value is equal to server's");
System.out.println("client Friday enum value is not same as server's");
// 用 switch 方式来比较枚举对象
switch (day) {
System.out.println("Do Monday work");
System.out.println("Do Tuesday work");
System.out.println("Do Wednesday work");
System.out.println("Do Thursday work");
System.out.println("Do Friday work");
System.out.println("Do Saturday work");
System.out.println("Do Sunday work");
System.out.println("I don't know which is day");
ois.close();
is.close();
socket.close();
}打印结果如下: client Friday enum value is same as server's
Do Friday work通过程序执行结果,我们能够发现在分布式条件下客户端和服务端的虚拟机上都生成了一个枚举对象,即使看起来一样的 Fri 枚举值,如果使用等号‘ == ’进行比较的话会出现不等的情况。而
switch 语句则是通过 equal 方法来比较枚举对象的值,因此当你的枚举对象较复杂时候,你就需要小心 override 与比较相关的方法,防止出现值比较方面的错误。Enum 相关工具类JDK5.0 中在增加 Enum 类的同时,也增加了两个工具类 EnumSet 和 EnumMap,这两个类都放在 java.util 包中。EnumSet
是一个针对枚举类型的高性能的 Set 接口实现。EnumSet 中装入的所有枚举对象都必须是同一种类型,在其内部,是通过 bit-vector 来实现,也就是通过一个 long
型数。EnumSet 支持在枚举类型的所有值的某个范围中进行迭代。回到上面日期枚举的例子上: enum WeekDayEnum { Mon, Tue, Wed, Thu, Fri, Sat, Sun }你能够在每周七天日期中进行迭代,EnumSet 类提供一个静态方法 range 让迭代很容易完成: for(WeekDayEnum day : EnumSet.range(WeekDayEnum.Mon, WeekDayEnum.Fri)) {
System.out.println(day);
}打印结果如下: Mon
FriEnumSet 还提供了很多个类型安全的获取子集的 of 方法,使你很容易取得子集: EnumSet&WeekDayEnum& subset = EnumSet.of(WeekDayEnum.Mon, WeekDayEnum.Wed);
for (WeekDayEnum day : subset) {
System.out.println(day);
}打印结果如下: Mon
Wed与 EnumSet 类似,EnumMap 也是一个高性能的 Map 接口实现,用来管理使用枚举类型作为 keys 的映射表,内部是通过数组方式来实现。EnumMap 将丰富的和安全的
Map 接口与数组快速访问结合到一起,如果你希望要将一个枚举类型映射到一个值,你应该使用 EnumMap。看下面的例子:清单 5. EnumMap
示例 // 定义一个 EnumMap 对象,映射表主键是日期枚举类型,值是颜色枚举类型
private static Map&WeekDayEnum, RainbowColor& schema =
new EnumMap&WeekDayEnum, RainbowColor&(WeekDayEnum.class);
// 将一周的每一天与彩虹的某一种色彩映射起来
for (int i = 0; i & WeekDayEnum.values(). i++) {
schema.put(WeekDayEnum.values()[i], RainbowColor.values()[i]);
System.out.println("What is the lucky color today?");
System.out.println("It's " + schema.get(WeekDayEnum.Sat));当你询问周六的幸运色彩时候,会得到蓝色:清单 6.
运行结果 What is the lucky color today?
It's BLUE结束语Enum 类型提出给 JAVA 编程带了了极大的便利,让程序的控制更加的容易,也不容易出现错误。所以在遇到需要控制程序流程时候,可以多想想是否可以利用 enum 来实现。
相关主题,Java 语言规范描述。 ,Java 接口文档描述。 :这里有数百篇关于 Java 编程各个方面的文章。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=937050ArticleTitle=Java 语言中 Enum 类型的使用介绍publish-date=java中变量和常量有什么区别?_达内Java培训官网
400-111-8989
java中变量和常量有什么区别?
发布:达内
来源:网络
在使用 Java 语言进行程序设计时,经常需要用到常量和变量来存储信息。请简单叙述变量和常量有什么区别?
变量和常量是编程语言中最基本的两个知识点,变量的值可以改变而常量的值在初始化以后是无法改变的。常量在定义时要使用 final 关键字修饰。
下面的代码段首先定义了一个 int 型的常量 CONST,并赋值为 10;并定义了一个 int型的变量 num,其初始值为 100,并输出变量的值;然后在程序运行时改变该变量的值为180,并输出改变后变量的值;最后输出常量 CONST 的值。
package com.
public class VarientAndConstant {
public static void main(String[] args) {
final int CONST
int num = 100;
//定义并初始化变量
System.out.println("变量 num 的初始值是:" + num);
//改变变量的值为 180
System.out.println("改后变量 num 的值是:" + num);
System.out.println("常量 CONST 的值是:" + CONST);
// CONST = 100;
//试图在程序运行时改变常量的值,出错
运行程序,将在控制台输出变量 num 的初始值 100,改变后变量 num 的值 180,以及常量 CONST 的值 10,结果如图 3.1 所示。
图 3.1 在控制台输出变量和常量的值
常量只在声明时进行定义,一旦定义就不允许在程序运行时再改变其值,常量一般都
以大写字母表示,并使用 final 关键字进行修饰;在程序运行时,有些内容的值需要经常改
变,这时就要使用变量进行存储,也就
感谢大家阅读“Spring 知识点提炼”希望对大家有所帮助
感谢大家阅读由java培训机构分享的“教程|Java多线程建立方式”希望对大家有所帮助
作为 Java 开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如spring 去创建对象。然而这里有很多创建对象的方法,我们会在这篇文章中学到。
这几年,中国的互联网行业进入了高速发展的阶段,同时IT行业,也成为了热门,备受追捧和关注的行业。在全球云计算和移动互联网的产业环境下,Java工程师为何会如此火爆?
Copyright (C)
All Rights Reserved
选择城市和中心
达内北京亦庄大学生实训基地
达内北京网络营销中心
达内北京会计中心java内存分配和String类型的深度解析 - ImportNew
| 标签: ,
在java语言的所有数据类型中,String类型是比较特殊的一种类型,同时也是面试的时候经常被问到的一个知识点,本文结合java内存分配深度分析关于String的许多令人迷惑的问题。下面是本文将要涉及到的一些问题,如果读者对这些问题都了如指掌,则可忽略此文。
1、java内存具体指哪块内存?这块内存区域为什么要进行划分?是如何划分的?划分之后每块区域的作用是什么?如何设置各个区域的大小?
2、String类型在执行连接操作时,效率为什么会比StringBuffer或者StringBuilder低?StringBuffer和StringBuilder有什么联系和区别?
3、java中常量是指什么?String s = “s” 和 String s = new String(“s”) 有什么不一样?
本文经多方资料的收集整理和归纳,最终撰写成文,如果有错误之处,请多多指教!
二、java内存分配
1、JVM简介
Java虚拟机(Java Virtual Machine 简称JVM)是运行所有Java程序的抽象计算机,是Java语言的运行环境,它是Java 最具吸引力的特性之一。Java虚拟机有自己完善的硬体架构,如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。
一个运行时的Java虚拟机实例的天职是:负责运行一个java程序。当启动一个Java程序时,一个虚拟机实例也就诞生了。当该程序关闭退出,这个虚拟机实例也就随之消亡。如果同一台计算机上同时运行三个Java程序,将得到三个Java虚拟机实例。每个Java程序都运行于它自己的Java虚拟机实例中。
如下图所示,JVM的体系结构包含几个主要的子系统和内存区:
垃圾回收器(Garbage Collection):负责回收堆内存(Heap)中没有被使用的对象,即这些对象已经没有被引用了。
类装载子系统(Classloader Sub-System):除了要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。
执行引擎(Execution Engine):负责执行那些包含在被装载类的方法中的指令。
运行时数据区(Java Memory Allocation Area):又叫虚拟机内存或者Java内存,虚拟机运行时需要从整个计算机内存划分一块内存区域存储许多东西。例如:字节码、从已装载的class文件中得到的其他信息、程序创建的对象、传递给方法的参数,返回值、局部变量等等。
2、java内存分区
从上节知道,运行时数据区即是java内存,而且数据区要存储的东西比较多,如果不对这块内存区域进行划分管理,会显得比较杂乱无章。程序喜欢有规律的东西,最讨厌杂乱无章的东西。 根据存储数据的不同,java内存通常被划分为5个区域:程序计数器(Program Count Register)、本地方法栈(Native Stack)、方法区(Methon Area)、栈(Stack)、堆(Heap)。
程序计数器(Program Count Register):又叫程序寄存器。JVM支持多个线程同时运行,当每一个新线程被创建时,它都将得到它自己的PC寄存器(程序计数器)。如果线程正在执行的是一个Java方法(非native),那么PC寄存器的值将总是指向下一条将被执行的指令,如果方法是 native的,程序计数器寄存器的值不会被定义。 JVM的程序计数器寄存器的宽度足够保证可以持有一个返回地址或者native的指针。
栈(Stack):又叫堆栈。JVM为每个新创建的线程都分配一个栈。也就是说,对于一个Java程序来说,它的运行就是通过对栈的操作来完成的。栈以帧为单位保存线程的状态。JVM对栈只进行两种操作:以帧为单位的压栈和出栈操作。我们知道,某个线程正在执行的方法称为此线程的当前方法。我们可能不知道,当前方法使用的帧称为当前帧。当线程激活一个Java方法,JVM就会在线程的 Java堆栈里新压入一个帧,这个帧自然成为了当前帧。在此方法执行期间,这个帧将用来保存参数、局部变量、中间计算过程和其他数据。从Java的这种分配机制来看,堆栈又可以这样理解:栈(Stack)是操作系统在建立某个进程时或者线程(在支持多线程的操作系统中是线程)为这个线程建立的存储区域,该区域具有先进后出的特性。其相关设置参数:
-Xss –设置方法栈的最大值
本地方法栈(Native Stack):存储本地方方法的调用状态。
方法区(Method Area):当虚拟机装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,然后把这些类型信息(包括类信息、常量、静态变量等)放到方法区中,该内存区域被所有线程共享,如下图所示。本地方法区存在一块特殊的内存区域,叫常量池(Constant Pool),这块内存将与String类型的分析密切相关。
堆(Heap):Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域。在此区域的唯一目的就是存放对象实例,几乎所有的对象实例都是在这里分配内存,但是这个对象的引用却是在栈(Stack)中分配。因此,执行String s = new String(“s”)时,需要从两个地方分配内存:在堆中为String对象分配内存,在栈中为引用(这个堆对象的内存地址,即指针)分配内存,如下图所示。
JAVA虚拟机有一条在堆中分配新对象的指令,却没有释放内存的指令,正如你无法用Java代码区明确释放一个对象一样。虚拟机自己负责决定如何以及何时释放不再被运行的程序引用的对象所占据的内存,通常,虚拟机把这个任务交给垃圾收集器(Garbage Collection)。其相关设置参数:
-Xms — 设置堆内存初始大小
-Xmx — 设置堆内存最大值
-XX:MaxTenuringThreshold — 设置对象在新生代中存活的次数
-XX:PretenureSizeThreshold — 设置超过指定大小的大对象直接分配在旧生代中
Java堆是垃圾收集器管理的主要区域,因此又称为“GC 堆”(Garbage Collectioned Heap)。现在的垃圾收集器基本都是采用的分代收集算法,所以Java堆还可以细分为:新生代(Young Generation)和老年代(Old Generation),如下图所示。分代收集算法的思想:第一种说法,用较高的频率对年轻的对象(young generation)进行扫描和回收,这种叫做minor collection,而对老对象(old generation)的检查回收频率要低很多,称为major collection。这样就不需要每次GC都将内存中所有对象都检查一遍,以便让出更多的系统资源供应用系统使用;另一种说法,在分配对象遇到内存不足时,先对新生代进行GC(Young GC);当新生代GC之后仍无法满足内存空间分配需求时, 才会对整个堆空间以及方法区进行GC(Full GC)。
在这里可能会有读者表示疑问:记得还有一个什么永久代(Permanent Generation)的啊,难道它不属于Java堆?亲,你答对了!其实传说中的永久代就是上面所说的方法区,存放的都是jvm初始化时加载器加载的一些类型信息(包括类信息、常量、静态变量等),这些信息的生存周期比较长,GC不会在主程序运行期对PermGen Space进行清理,所以如果你的应用中有很多CLASS的话,就很可能出现PermGen Space错误。其相关设置参数:
-XX:PermSize –设置Perm区的初始大小
-XX:MaxPermSize –设置Perm区的最大值
新生代(Young Generation)又分为:Eden区和Survivor区,Survivor区有分为From Space和To Space。Eden区是对象最初分配到的地方;默认情况下,From Space和To Space的区域大小相等。JVM进行Minor GC时,将Eden中还存活的对象拷贝到Survivor区中,还会将Survivor区中还存活的对象拷贝到Tenured区中。在这种GC模式下,JVM为了提升GC效率, 将Survivor区分为From Space和To Space,这样就可以将对象回收和对象晋升分离开来。新生代的大小设置有2个相关参数:
-Xmn — 设置新生代内存大小。
-XX:SurvivorRatio — 设置Eden与Survivor空间的大小比例
老年代(Old Generation): 当 OLD 区空间不够时, JVM 会在 OLD 区进行 major collection;完全垃圾收集后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”Out of memory错误”
三、String类型的深度解析
让我们从Java数据类型开始说起吧!Java数据类型通常(分类方法多种多样)从整体上可以分为两大类:基础类型和引用类型,基础类型的变量持有原始值,引用类型的变量通常表示的是对实际对象的引用,其值通常为对象的内存地址。对于基础类型和引用类型的细分,直接上图吧,大家看了一目了然。当然,下图也仅仅只是其中的一种分类方式。
(原文图丢失)
针对上面的图,有3点需要说明:
char类型可以单独出来形成一类,很多基本类型的分类为:数值类型、字符型(char)和bool型。
returnAddress类型是一个Java虚拟机在内部使用的类型,被用来实现Java程序中的finally语句。
String类型在上图的什么位置?yes,属于引用类型下面的类类型。下面开始对String类型的挖掘!
1、String的本质
打开String的源码,类注释中有这么一段话“S their values cannot be changed after they are created. String buffers support mutable strings.Because String objects are immutable they can be shared.”。这句话总结归纳了String的一个最重要的特点:String是值不可变(immutable)的常量,是线程安全的(can be shared)。
接下来,String类使用了final修饰符,表明了String类的第二个特点:String类是不可继承的。
下面是String类的成员变量定义,从类的实现上阐明了String值是不可变的(immutable)。
private final char value[];
因此,我们看String类的concat方法。实现该方法第一步要做的肯定是扩大成员变量value的容量,扩容的方法重新定义一个大容量的字符数组buf。第二步就是把原来value中的字符copy到buf中来,再把需要concat的字符串值也copy到buf中来,这样子,buf中就包含了concat之后的字符串值。下面就是问题的关键了,如果value不是final的,直接让value指向buf,然后返回this,则大功告成,没有必要返回一个新的String对象。但是。。。可惜。。。由于value是final型的,所以无法指向新定义的大容量数组buf,那怎么办呢?“return new String(0, count + otherLen, buf);”,这是String类concat实现方法的最后一条语句,重新new一个String对象返回。这下真相大白了吧!
总结:String实质是字符数组,两个特点:1、该类不可被继承;2、不可变性(immutable)。
2、String的定义方法
在讨论String的定义方法之前,先了解一下常量池的概念,前面在介绍方法区的时候已经提到过了。下面稍微正式的给一个定义吧。
常量池(constant pool)指的是在编译期被确定,并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口等中的常量,也包括字符串常量。常量池还具备动态性,运行期间可以将新的常量放入池中,String类的intern()方法是这一特性的典型应用。不懂吗?后面会介绍intern方法的。虚拟机为每个被装载的类型维护一个常量池,池中为该类型所用常量的一个有序集合,包括直接常量(string、integer和float常量)和对其他类型、字段和方法的符号引用(与对象引用的区别?读者可以自己去了解)。
String的定义方法归纳起来总共为三种方式:
使用关键字new,如:String s1 = new String(“myString”);
直接定义,如:String s1 = “myString”;
串联生成,如:String s1 = “my” + “String”;这种方式比较复杂,这里就不赘述了,请参见。
第一种方式通过关键字new定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间,保证常量池中只有一个“myString”常量,节省内存空间。然后在内存堆中开辟一块空间存放new出来的String实例,在栈中开辟一块空间,命名为“s1”,存放的值为堆中String实例的内存地址,这个过程就是将引用s1指向new出来的String实例。各位,最模糊的地方到了!堆中new出来的实例和常量池中的“myString”是什么关系呢?等我们分析完了第二种定义方式之后再回头分析这个问题。
第二种方式直接定义过程:在程序编译期,编译程序先去字符串常量池检查,是否存在“myString”,如果不存在,则在常量池中开辟一个内存空间存放“myString”;如果存在的话,则不用重新开辟空间。然后在栈中开辟一块空间,命名为“s1”,存放的值为常量池中“myString”的内存地址。常量池中的字符串常量与堆中的String对象有什么区别呢?为什么直接定义的字符串同样可以调用String对象的各种方法呢?
带着诸多疑问,我和大家一起探讨一下堆中String对象和常量池中String常量的关系,请大家记住,仅仅是探讨,因为本人对这块也比较模糊。
第一种猜想:因为直接定义的字符串也可以调用String对象的各种方法,那么可以认为其实在常量池中创建的也是一个String实例(对象)。String s1 = new String(“myString”);先在编译期的时候在常量池创建了一个String实例,然后clone了一个String实例存储在堆中,引用s1指向堆中的这个实例。此时,池中的实例没有被引用。当接着执行String s1 = “myString”;时,因为池中已经存在“myString”的实例对象,则s1直接指向池中的实例对象;否则,在池中先创建一个实例对象,s1再指向它。如下图所示:
这种猜想认为:常量池中的字符串常量实质上是一个String实例,与堆中的String实例是克隆关系。
第二种猜想也是目前网上阐述的最多的,但是思路都不清晰,有些问题解释不通。下面引用一段内容。
在解析阶段,虚拟机发现字符串常量”myString”,它会在一个内部字符串常量列表中查找,如果没有找到,那么会在堆里面创建一个包含字符序列[myString]的String对象s1,然后把这个字符序列和对应的String对象作为名值对( [myString], s1 )保存到内部字符串常量列表中。如下图所示:
如果虚拟机后面又发现了一个相同的字符串常量myString,它会在这个内部字符串常量列表内找到相同的字符序列,然后返回对应的String对象的引用。维护这个内部列表的关键是任何特定的字符序列在这个列表上只出现一次。
例如,String s2 = “myString”,运行时s2会从内部字符串常量列表内得到s1的返回值,所以s2和s1都指向同一个String对象。
这个猜想有一个比较明显的问题,红色字体标示的地方就是问题的所在。证明方式很简单,下面这段代码的执行结果,javaer都应该知道。
String s1 = new String(“myString”);
String s2 = “myString”;
System.out.println(s1 == s2);
//按照上面的推测逻辑,那么打印的结果为true;而实际上真实的结果是false,因为s1指向的是堆中String对象,而s2指向的是常量池中的String常量。
虽然这段内容不那么有说服力,但是文章提到了一个东西——字符串常量列表,它可能是解释这个问题的关键。
文中提到的三个问题,本文仅仅给出了猜想,请知道真正内幕的高手帮忙分析分析,谢谢!
堆中new出来的实例和常量池中的“myString”是什么关系呢?
常量池中的字符串常量与堆中的String对象有什么区别呢?
为什么直接定义的字符串同样可以调用String对象的各种方法呢?
3、String、StringBuffer、StringBuilder的联系与区别
上面已经分析了String的本质了,下面简单说说StringBuffer和StringBuilder。
StringBuffer和StringBuilder都继承了抽象类AbstractStringBuilder,这个抽象类和String一样也定义了char[] value和int count,但是与String类不同的是,它们没有final修饰符。因此得出结论:String、StringBuffer和StringBuilder在本质上都是字符数组,不同的是,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以这就是为什么在进行大量字符串连接运算时,不推荐使用String,而推荐StringBuffer和StringBuilder。那么,哪种情况使用StringBuffe?哪种情况使用StringBuilder呢?
关于StringBuffer和StringBuilder的区别,翻开它们的源码,下面贴出append()方法的实现。
面第一张图是StringBuffer中append()方法的实现,第二张图为StringBuilder对append()的实现。区别应该一目了然,StringBuffer在方法前加了一个synchronized修饰,起到同步的作用,可以在多线程环境使用。为此付出的代价就是降低了执行效率。因此,如果在多线程环境可以使用StringBuffer进行字符串连接操作,单线程环境使用StringBuilder,它的效率更高。
四、参考文献
写的真好,点赞写的真好,点赞
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:@
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2017 ImportNew}

我要回帖

更多关于 js定义常量 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信