JAVA对象中使用 static 和 String 基础探究

目录
  • 前言
  • 原题
  • static
    • 简介
    • 使用
  • String
    • == 与 equals()
    • 常量与非常量
    • intern()
      • JAVA 源码
      • native 源码
      • 使用
  • 总结

前言

跟同学在讨论 JAVA 期末试题时,对于一些 static 和 String 在对象中的使用方法,若有所思,特此记录一下,也祝没有对象的友友可以自己 new 一个出来!

那我们先来看一看试卷里的原题;

原题

主要就是两个类 MyClass.java 和 TestMyClass.java,填代码的部分就直接跳过了,然后就是输出结果,看看你是否也能全部正确,

两个类的具体代码如下:

MyClass.java

public class MyClass {
    private int count;
    String info;
    public static String message = "Good";

    public MyClass increase() {
        count++;
        return this;
    }
    private MyClass() {
        this.count=0;
        this.info = "GoodLuck";
    }
    public int getCount() {
        return count;
    }
    public static MyClass getInstance() {
        return new MyClass();
    }
}

TestMyClass.java

public class TestMyClass {
    public static void main(String[] args) {
        MyClass mc1 = MyClass.getInstance();
        MyClass mc2 = MyClass.getInstance();
        mc1.message = "Great";
        mc2.message = "Excellent";
        MyClass.message = "Nice";

        System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message);
        System.out.println(mc1.info == mc2.info);
        mc2.info = new String("GoodLuck");
        System.out.println(mc1.info == mc2.info);
        System.out.println(mc1.info.equals(mc2.info));
        System.out.println(mc1.increase().increase().getCount());
    }
}

运行结果:

Nice:Nice:Nice
true
false
true
2

如果你全部答对了,那么恭喜你,基础很不错哟;答错的小伙伴也不要丧气,接下来听我娓娓道来,扎实基础;

static

工欲善其事必先利其器,在开始解析之前,我们先回顾一下一些关于 static 的知识;

简介

static 表示 “全局” 或者 “静态” 的意思,用来修饰成员变量和成员方法,也可以形成静态 static 代码块,但是 Java 语言中没有全局变量的概念;

被 static 修饰的成员变量和成员方法独立于该类的任何对象,也就是说,它不依赖类特定的实例,被类的所有实例共享;

只要这个类被加载,Java 虚拟机就能根据类名在运行时数据区的方法区内定找到他们,因此,static 对象可以在它的任何对象创建之前访问,无需引用任何对象;

用 public 修饰的 static 成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象时,不生成 static 变量的副本,而是类的所有实例共享同一个 static 变量;

static 变量前可以有 private 修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用,但是不能在其他类中通过类名来直接引用,这一点很重要;

实际上你需要搞明白,private 是访问权限限定,static 表示不要实例化就可以使用,这样就容易理解多了,static 前面加上其它访问权限关键字的效果也以此类推。

static 修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:

类名.静态方法名(参数列表…)

使用

回顾了 static 相关知识之后,我们来看一下题目中的使用吧;

// MyClass.java
public static String message = "Good";

// TestMyClass.java
MyClass mc1 = MyClass.getInstance();
MyClass mc2 = MyClass.getInstance();
mc1.message = "Great";
mc2.message = "Excellent";
MyClass.message = "Nice";

System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message);

先是用 static 修饰了成员变量 message,然后通过下断点调试可以获知,两个对象 mcl1 和 mcl2 被分配到了两个不同的地址;

在往下调试时,发现 mc1.messagemc2.messageMyClass.message 三个成员变量的值是一样的,且都从 Great → Excellent → Nice,这就是刚才所回顾的,被 static 修饰的成员变量成了共享变量,被类的所有实例共享;

接下来我们再做个试验验证一下:

//修改前
private int count;

//修改后
private static int count;

可以发现,我们只是对 mcl1 对象进行了操作,但是 mcl2 的成员变量 count 也跟着改变了,这就是因为在 MyClass 类中,成员变量 count 被 static 修饰,已经成了该类的共享变量了,但凡是该类的对象,都访问的是同一个 count 变量;

当然也可以通过身份码进行验证:

System.out.println("mcl1: " + System.identityHashCode(mc1));
System.out.println("mcl2: " + System.identityHashCode(mc2));
System.out.println("mcl1_count: " + System.identityHashCode(mc1.getCount()));
System.out.println("mcl2_count: " + System.identityHashCode(mc2.getCount()));
mcl1: 940553268
mcl2: 1720435669
mcl1_count: 1020923989
mcl2_count: 1020923989

因此 System.out.println(mc1.message+":"+mc2.message+":"+MyClass.message); 输出的是 Nice:Nice:Nice

接下来讲一些关于 String 的小知识;

String

关于 String 的话,这里用到啥聊啥,就不全面的进行了;

== 与 equals()

先来讲讲关于 String 的比较,一般常见的比较有两种,即 == 和 equals()

其中,== 比较的是两个字符串的地址是否为相等(同一个地址),equals() 方法比较的是两个字符串对象的内容是否相同(当然,若两个字符串引用同一个地址,使用 equals() 比较也返回 true);

这里就不得不提第二个知识点了,String 常量与非常量的区别;

常量与非常量

那什么是常量,什么是非常量呢,简单了解就是,String name = "sid10t." 这个是常量,属于是对 name 进行赋值,直接存储在常量池中,而 String name = new String("sid10t.") 这个就是非常量,因为重新创建了一个对象,这会将字符串 sid10t. 存储在常量池中,然后在 Heap 中创建对象指向 name

那这里为什么要提这个呢?当然是因为他们有较大的区别;

在 Java 语言规范(JavaSE 1.8版本)章节3.10.5 中有做规范,所有的 Java 语言编译、运行时环境实现都必须依据此规范来实现,里面有这么一句话:

Moreover, a string literal always refers to the same instance of class String. This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are "interned" so as to share unique instances, using the method String.intern.

大致意思就是凡是内容一样的字符串常数,都要引用同一个字符串对象,换句话说就是内存地址相同;

因为其值为常量的字符串,都会通过 String.intern() 函数被限定为共享同一个对象;

稍后会解析 intern() 函数,也可以自行参考说明String (Java Platform SE 8 )

回到正题,看一下语言规范里的这段代码:

package testPackage;

class Test {
    public static void main(String[] args) {
        String hello = "Hello", lo = "lo";
        System.out.print((hello == "Hello") + " ");
        System.out.print((Other.hello == hello) + " ");
        System.out.print((other.Other.hello == hello) + " ");
        System.out.print((hello == ("Hel"+"lo")) + " ");
        System.out.print((hello == ("Hel"+lo)) + " ");
        System.out.println(hello == ("Hel"+lo).intern());
    }
}
class Other { static String hello = "Hello"; }

以及另一个包中的类:

package other;
public class Other { public static String hello = "Hello"; }

运行结果:

true true true true false true

结论:

  • 同一个包中同一个类中的字符串表示对同一个 String 对象的引用;
  • 同一个包中不同类中的字符串表示对同一个 String 对象的引用;
  • 不同包中不同类中的字符串同样表示对同一 String 对象的引用;
  • 由常量表达式计算的字符串在编译时计算,然后将其视为文字;
  • 在运行时通过连接计算的字符串是新创建的,因此是不同的;
  • 显式地嵌入一个计算出来的字符串的结果与任何现有的具有相同内容的字面值字符串的结果相同;

如果对结论的理解不是很深刻的话,那就看看接下来的解释:

System.out.print((hello == "Hello") + " ");
System.out.print((Other.hello == hello) + " ");
System.out.print((other.Other.hello == hello) + " ");

“Hello” 和 “lo” 是字符串常量,在编译期就被确定了,先检查字符串常量池中是否含有 “Hello” 和 “lo”,如果没有则添加 “Hello” 和 “lo” 到字符串常量池中,并且直接指向它们,所以 hello 和 lo 分别直接指向字符串常量池的 “Hello” 和 “lo”,也就是 hello 和 lo 指向的地址分别是常量池中的 “Hello” 和 “lo” ,因此第一个输出中 hello 实际就是 “Hello”,所以 "Hello" == "Hello" 为 true,前三个输出都是同理的;

System.out.print((hello == ("Hel"+"lo")) + " ");

"Hel" 和 "lo" 都是字符串常量,当一个字符串多个字符串常量连接而成时,它自己肯定也是字符串常量,在编译器会被编译器优化成 "Hello",因为 "Hello" 在常量池中了,因此输出为 true

System.out.print((hello == ("Hel"+lo)) + " ");
System.out.println(hello == ("Hel"+lo).intern());

JVM 对于字符串引用,由于在字符串的 + 连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即 "Hel"+lo,所以在不执行 intern() 方法的前提下,"Hel"+lo 不会存到常量池中,但 "Hel" 会被存到常量池中去,所以输出一个为 true,一个为 false

intern()

String.intern() 是一个 Native 方法,它的作用是如果字符串常量池已经包含一个等于此 String 对象的字符串,则返回字符串常量池中这个字符串的引用, 否则将当前 String 对象的引用地址(堆中)添加到字符串常量池中并返回。

JAVA 源码

/*
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String.
When the intern method is invoked, if the pool already contains a string equal to
this String object as determined by the equals(Object) method, then the string from
the pool is returned. Otherwise, this String object is added to the pool and a
reference to this String object is returned.

It follows that for any two strings s and t, s.intern() == t.intern() is true if and
only if s.equals(t) is true.

All literal strings and string-valued constant expressions are interned. String
literals are defined in section 3.10.5 of the The Java Language Specification.

Returns:
a string that has the same contents as this string, but is guaranteed to be from a
pool of unique strings.
*/
public native String intern();

native 源码

String.c

Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}  

jvm.h

/*
* java.lang.String
*/
JNIEXPORT jstring JNICALL
JVM_InternString(JNIEnv *env, jstring str);   

jvm.cpp

JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
  JVMWrapper("JVM_InternString");
  JvmtiVMObjectAllocEventCollector oam;
  if (str == NULL) return NULL;
  oop string = JNIHandles::resolve_non_null(str);
  oop result = StringTable::intern(string, CHECK_NULL);
  return (jstring) JNIHandles::make_local(env, result);
JVM_END   

symbolTable.cpp

oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  unsigned int hashValue = java_lang_String::hash_string(name, len);
  int index = the_table()->hash_to_index(hashValue);
  oop string = the_table()->lookup(index, name, len, hashValue);
  // Found
  if (string != NULL) return string;
  // Otherwise, add to symbol to table
  return the_table()->basic_add(index, string_or_null, name, len,
                                hashValue, CHECK_NULL);
}
oop StringTable::lookup(int index, jchar* name,
                        int len, unsigned int hash) {
  for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) {
    if (l->hash() == hash) {
      if (java_lang_String::equals(l->literal(), name, len)) {
        return l->literal();
      }
    }
  }
  return NULL;
}  

它的大体实现结构就是:JAVA 使用 jni 调用 c++ 实现的 StringTable 的 intern 方法,StringTable 的 intern 方法跟 Java 中的 HashMap 的实现是差不多的,只是不能自动扩容,默认大小是1009。

要注意的是,String 的 String Pool 是一个固定大小的 Hashtable,默认值大小长度是1009,如果放进 String Pool 的 String 非常多,就会造成 Hash 冲突严重,从而导致链表会很长,而链表长了后直接会造成的影响就是当调用 String.intern 时性能会大幅下降(因为要一个一个找)。

在 JDK6 中 StringTable 是固定的,就是1009的长度,所以如果常量池中的字符串过多就会导致效率下降很快。

在 JDK7 中,StringTable 的长度可以通过一个参数指定:-XX:StringTableSize=99991

使用

在 JDK1.7 之前的版本,调用这个方法的时候,会去常量池中查看是否已经存在这个常量了,如果已经存在,那么直接返回这个常量在常量池中的地址值,如果不存在,则在常量池中创建一个,并返回其地址值。

但是在 JDK1.7 以及之后的版本中,常量池从 perm 区搬到了 heap 区。intern() 检测到这个常量在常量池中不存在的时候,不会直接在常量池中创建该对象了,而是将堆中的这个对象的引用直接存到常量池中,减少内存开销。

来看这段代码:

public static void main(String[] args) {
	// part1
    String s1 = new String("sid10t.");
    s1.intern();
    String s2 = "sid10t.";
    System.out.println(s1 == s2);

	// part2
    String s3 = new String("Hello ") + new String("World!");
    s3.intern();
    String s4 = "Hello World!";
    System.out.println(s3 == s4);
}

在 JDK7 之前两个都是 false,在 JDK7 之后输出分别是 falsetrue

接下来根据 JDK7 进行分析,

先来看 part1 部分:

String s1 = new String("sid10t."); 这行代码生成了两个最终对象:一个是常量池中的字符串常量 sid10t.,另一个是在堆中的 s1 引用指向的对象;

然后是第二行 s1.intern();,返回常量池中的字符串常量 sid10t.,因为常量池中已经存在了该常量,所以这里就直接返回即可,因此,在 part1 的此情此景中,这句话可写可不写,对输出结果没有任何影响;

所以最后输出的肯定是 false,一个地址在堆中,一个在常量池里;

接下来看看 part2 部分:

String s3 = new String("Hello ") + new String("World!"); 这行代码生成了三个最终对象:两个分别是常量池中的对象 Hello World!,还有一个在堆中的 s3 引用指向的对象;

然后是第二行 s3.intern();,由于现在的常量池中不存在字符串常量 Hello World!,因此它会直接存储 s3 在堆中的引用地址,而不是拷贝一份;

这时候,String s4 = "Hello World!";,常量池中已经有了 Hello World! 常量,也就是 s3 的引用地址,因此 s4 的值就是 s3 的引用地址,所以输出的是 true

根据上述分析,我们将 part2 的代码略作调整,如下:

String s3 = new String("Hello ") + new String("World!");
// s3.intern();
String s4 = "Hello World!";
s3.intern();
System.out.println(s3 == s4);

输出的就是 false 了,但如果是 s3.intern() == s4,则输出的就是 true

想必你应该理解了!

总结

到此这篇关于JAVA对象中使用 static 和 String 基础探究的文章就介绍到这了,更多相关JAVA static和String内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java中的static--静态变量你了解吗

    目录 引言 引言 什么时候使用静态变量? 小总结 引用访问静态变量 小总结 总结 引言 static 是 java 语言中的关键字,意思是"静态的",它可以用来修饰变量.方法.代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块. 初学stat 引言 static 是 java 语言中的关键字,意思是"静态的",它可以用来修饰变量.方法.代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块. 初学sta

  • 实例分析Java中public static void main(String args[])是什么意思

    本文实例讲述了Java中public static void main(String args[])的来龙去脉.分享给大家供大家参考,具体如下: public static void main(String[] args) 这绝对不是凭空想出来的,也不是没有道理的死规定,而是java程序执行的需要. jvm在试图运行一个类之前,先检查该类是否包含一个特殊方法.这个方法必须是公有的,以便在任何位置都能访问得到.这个方法必须是static的,因为这个方法不能依赖任何该类的实例即可运行,而非stati

  • java中String字符串删除空格的七种方式

    目录 trim() strip() stripLeading() 和 stripTrailing() replace replaceAll replaceFirst 总结 在Java中从字符串中删除空格有很多不同的方法,如trim,replaceAll等.但是,在JDK 11添加了一些新的功能,如strip.stripLeading.stripTrailing等. 想要从String中移除空格部分,有多少种方法,下面介绍JDK原生自带的方法,不包含第三方工具类库中的类似方法 trim() : 删

  • Java中实现String字符串用逗号隔开

    目录 String字符串用逗号隔开 1.如果我们的需求是要让分隔符号可以兼容中英文逗号 2.如果我们的需求是取到第一个逗号前面的字符串 以逗号为分割符拼接字符串的技巧 实现代码如下所示 String字符串用逗号隔开 在Java中,有两个方法可以用逗号把String分开 一个是 public String[] split(String regex) { return split(regex, 0); } 另一个是 public String[] split(String regex, int li

  • Java关键字详解之final static this super的用法

    目录 1. final 关键字: 2. static 关键字: 3. this 关键字: 4. super 关键字: final,static,this,super 关键字总结 正文开始@Assassin 1. final 关键字: final 关键字,意思是最终的.不可改变的,初始化之后就不能再次修改 ,用来修饰类.方法和变量,具有以下特点: final 修饰的类不能被继承,final类中的所有成员方法都会被隐式的指定为 final 方法: final 修饰的方法不能被重写: final 修饰

  • Java修饰符abstract与static及final的精华总结

    目录 修饰符abstract (抽象的) 一.abstract可以修饰类 二.abstract可以修饰方法 修饰符之static? (静态的) 一.static可以修饰属性 二.static可以修饰方法 三.static可以修饰初始化代码块 修饰符之final? (最终的) 一.final可以修饰变量 二.final可以修饰方法 三.final可以修饰属性 修饰符abstract (抽象的) 一.abstract可以修饰类 (1) 被abstract修饰的类称为抽象类(2) 语法: abstra

  • Java中String的split切割字符串方法实例及扩展

    目录 一.public String[] split(String regex) 二.public String[] split(String regex, int limit) 三.扩展 总结 一.public String[] split(String regex) public String[] split(String regex): 根据传入的字符串参数,作为规则,切割当前字符串 String a="198,168,10,1"; String [] arr=a.split(&

  • Java中的static关键字修饰属性和方法(推荐)

    目录 static关键字 1.static修饰属性(静态属性) 1.1.哪些成员属性可以被static修饰. 1.2.静态属性的访问. 2.static关键字修饰方法 1.那些方法可以使用static修饰 2.常见的问题 static关键字 static关键词与对象无关.static关键字主要修饰四个部分的内容 这里我们主要介绍static修饰属性和修饰方法. 1.static修饰属性(静态属性) 1.1.哪些成员属性可以被static修饰. 我们把static修饰的属性称为静态属性,又叫类属性

  • java如何把逗号分隔的String字符串转int集合

    目录 把逗号分隔的String字符串转int集合 集合或数组转变为逗号分隔的字符串的几种方式 1.自己编码实现 2.org.apache.commons.lang3.StringUtils 3.StringJoiner, JDK1.8+ 4.String.join(), JDK1.8+ 5.Stream, Collectors.joining(), JDK1.8+ 把逗号分隔的String字符串转int集合 代码通过Java 8 Stream实现起来非常容易! String ids = "1,2

  • JAVA对象中使用 static 和 String 基础探究

    目录 前言 原题 static 简介 使用 String == 与 equals() 常量与非常量 intern() JAVA 源码 native 源码 使用 总结 前言 跟同学在讨论 JAVA 期末试题时,对于一些 static 和 String 在对象中的使用方法,若有所思,特此记录一下,也祝没有对象的友友可以自己 new 一个出来! 那我们先来看一看试卷里的原题: 原题 主要就是两个类 MyClass.java 和 TestMyClass.java,填代码的部分就直接跳过了,然后就是输出结

  • 实例分析java对象中浅克隆和深克隆

    引言: 在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用.首先看一下容易犯的几个小问题 clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable.Cloneable

  • java 获取对象中为null的字段实例代码

    下面一段简单的代码给大家分享java 获取对象中为null的字段,具体代码如下所述: private static String[] getNullPropertyNames(Object source) { final BeanWrapper src = new BeanWrapperImpl(source); java.beans.PropertyDescriptor[] pds = src.getPropertyDescriptors(); Set<String> emptyNames

  • 深入剖析Java编程中的序列化

    Java提供一种机制叫做序列化,通过有序的格式或者字节序列持久化java对象,其中包含对象的数据,还有对象的类型,和保存在对象中的数据类型. 所以,如果我们已经序列化了一个对象,那么它可以被读取并通过对象的类型和其他信息进行反序列化,并最终获取对象的原型. ObjectInputStream 和 ObjectOutputStream对象是高级别的流对象,包含序列化和反序列化的方法. ObjectOutputStream 拥有很多序列化对象的方法,最常用的是: private void write

  • Json转化为Java对象的实例详解

    Json转化为Java对象的实例详解 问题:前后端数据交互时,经常会遇到Json串与Java对象转化的问题,有的Java对象中还包含了List对象等. 解决方案: 引入 json-lib包,Maven坐标如下: <dependency> <groupId>net.sf.json-lib</groupId> <artifactId>json-lib</artifactId> <version>2.4</version> &l

  • Java中JSON字符串与java对象的互换实例详解

    在开发过程中,经常需要和别的系统交换数据,数据交换的格式有XML.JSON等,JSON作为一个轻量级的数据格式比xml效率要高,XML需要很多的标签,这无疑占据了网络流量,JSON在这方面则做的很好,下面先看下JSON的格式, JSON可以有两种格式,一种是对象格式的,另一种是数组对象, {"name":"JSON","address":"北京市西城区","age":25}//JSON的对象格式的字符串 [

  • Java中Object.equals和String.equals的区别详解

    前言 Java中的堆和常量池的区别是什么呢?Object.equals与String.equals的区别呢?下面让我妈通过一个小示例让你明白它- 1.基础知识 Java的存储空间:寄存器.栈.堆.静态存储区.常量存储区(常量池).其他存储位置. 此处重点介绍堆和常量存储区: 堆:存储new的对象; 常量池:用来存储final static.String的常量. 2.Object.equals与String.equals的区别 Object.equals(==):比较内存地址: String.eq

  • java对象转化成String类型的四种方法小结

    目录 方法1:采用 Object#toString()方法 方法2:采用类型转换(String)object方法 方法3:采用 String.valueOf(Object) 方法 方法4:采用 ""+object方法 在java项目的实际开发和应用中,常常需要用到将对象转为String这一基本功能.本文将对常用的转换方法进行一个总结.常用的方法有Object#toString(),(String)要转换的对象,String.valueOf(Object)等. 方法1:采用 Object

  • Java中的static的使用指南

    一.Java中的static使用之静态变量 1.Java 中被static修饰的成员称为静态成员或类成员.它属于整个类所有,而不是某个对象所有,即被类的所有对象所共享.且优先于对象存在.静态成员可以使用类名直接访问,也可以使用对象名进行访问.使用 static 可以修饰变量.方法和代码块. 2.public 修饰符表示公开的.公有的,静态变量使用static修饰 3.静态方法中可以直接调用同类中的静态成员,但不能直接调用非静态成员. public class HellWorld{ String

随机推荐