Java中Object类和String类的常用方法

Object类的常用方法

- clone()

​ Object类中的clone()方法用来复制自定义类的实例对象,clone()方法会返回一个拷贝的新对象,而不是原对象的引用,这个新对象中已经包含了一些原来对象的信息。

​ 继承自Object类的自定义类都会继承该方法,要clone的类还要实现Clonebla接口才能使用clone()方法。Cloneable接口不包含任何方法,它是针对Object类中clone()方法的一个标识,如果要clone的类没有实现Cloneable接口,并调用了Object的clone()方法(super.clone()),那么Object的clone()方法会抛出CloneNotSupportedException

package clone;
public class Student {
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return this.age;
    }
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) {
        Student std = new Student();
        std.setAge(12);
        std.setName("Shirley");
        try {
            std.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
//output:
//java.lang.CloneNotSupportedException: clone.Student
//  at java.lang.Object.clone(Native Method)
//  at clone.Student.clone(Student.java:25)
//  at clone.Student.main(Student.java:33)

​ 克隆的实现方式有两种:

  1. 浅克隆(shallow clone):对于要克隆的对象,对于其基本数据类型的属性,复制一份给新产生的对象,对于引用类型的属性,复制一份引用给新产生的对象。也就是说,新产生的对象和原始对象中的引用类型的属性指向都是同一个对象。这就会出现一个问题,当在原始对象中修改引用类型的属性时,新产生对象的对应属性也会发生改变,反之亦然。

    如何实现浅克隆:实现java.lang.Cloneable接口,重写java.lang.Object.clone()方法。(注意:Object中的clone()是一个protected属性的方法,重写之后要把clone()方法的属性设置为public.

    public class Teacher {
        private String name;
        private String subject;
    
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getSubject() {
            return this.subject;
        }
        public void setSubject(String subject) {
            this.subject = subject;
        }
        @Override
        public String toString() {
            return "Teacher [name: " + this.name + ", subject: " + this.subject + "]";
        }
    }
    
    public class Student implements Cloneable {
        private String name;
        private int age;
        private Teacher teacher;
    
        public String getName() {
            return this.name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return this.age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public Teacher getTeacher() {
            return this.teacher;
        }
        public void setTeacher(Teacher teacher) {
            this.teacher = teacher;
        }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
        @Override
        public String toString() {
            return "Student [name: " + this.name + ", age: " + this.age + ", teacher: " + this.teacher.toString() + "]";
        }
    
        public static void main(String[] args) {
            Teacher teacher = new Teacher();
            teacher.setName("Jack");
            teacher.setSubject("Math");
            Student std = new Student();
            std.setAge(12);
            std.setName("Shirley");
            std.setTeacher(teacher);
    
            try {
                Student clonedStd = (Student) std.clone();
                System.out.println("std == clonedStd: " + (std == clonedStd));
                System.out.println("std.getTeacher() == clonedStd.getTeacher(): " + (std.getTeacher() == clonedStd.getTeacher()));
                System.out.println("std:" + std.toString());
                System.out.println("clonedStd" + clonedStd.toString());
    
                Teacher clonedStdTeacher = clonedStd.getTeacher();
                clonedStdTeacher.setName("Ann");
                clonedStdTeacher.setSubject("Music");
                clonedStd.setName("Sue");
                clonedStd.setAge(14);
    
                System.out.println("After-->std:" + std.toString());
                System.out.println("After-->clonedStd" + clonedStd.toString());
    
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
        }
    }
    //output
    //std == clonedStd: false
    //std.getTeacher() == clonedStd.getTeacher(): true
    //std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]]
    //clonedStdStudent [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]]
    //After-->std:Student [name: Shirley, age: 12, teacher: Teacher [name: Ann, subject: Music]]
    //After-->clonedStdStudent [name: Sue, age: 14, teacher: Teacher [name: Ann, subject: Music]]
    
    

    由代码输出可以看出:

    1. 通过clone()方法得到的拷贝的对象与原对象不是同一个对象。
    2. 原对象和拷贝对象的teacher属性指向了同一个引用地址。
    3. 拷贝对象得到了原对象的属性值(使用clone()方法后打印原对象和拷贝对象,二者属性值是相同的)。
    4. 修改拷贝对象的基本数据类型的属性后,与之对应的原对象的属性值并没有发生改变,然而修改值为引用类型的属性值后(teacher属性),原对象和拷贝对象的teacher属性都发生了改变(指向相同的引用地址)。这是我们在实际应用当中很棘手的一个问题,因为通常情况下,我们希望对拷贝对像和原对象的属性值是各自独立的,对拷贝对象中属性值的修改不会影响其对应的原对象的值。这时就需要使用深克隆了。
  2. 深克隆(deep clone):在浅克隆的基础上,对于克隆的对象中的引用类型的属性对应的类,也实现克隆。这样一来对于引用类型的属性,复制的就不再是一份引用,也就是说,拷贝对象和原对象的引用类型的属性不再指向同一个对象。

    如何实现深克隆:对于要克隆的类和类中所有非基本数据类型的属性对应的类都实现java.lang.Cloneable接口,都重写java.lang.Object.clone()方法。

    //我们将浅克隆的改写为深克隆
    //1.Teacher类实现Cloneable接口,重写clone()方法
    public class Teacher implements Cloneable{
        //...
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    //2.修改Student类的clone()方法
    public class Student implements Cloneable {
       //...
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Student newStudent = (Student) super.clone();
            newStudent.teacher = (Teacher) this.teacher.clone();
            return newStudent;
        }
    }
    //再次运行main方法,输出为:
    //std == clonedStd: false
    //std.getTeacher() == clonedStd.getTeacher(): false
    //std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]]
    //clonedStdStudent [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]]
    //After-->std:Student [name: Shirley, age: 12, teacher: Teacher [name: Jack, subject: Math]]
    //After-->clonedStdStudent [name: Sue, age: 14, teacher: Teacher [name: Ann, subject: Music]]
    
    

    ​ Student类修改了clone()方法的实现。通过调用Student类的super.clone()实现对于基本数据类型数据的拷贝,再通过调用引用类型属性teacher的值(Teacher对象)的super.clone()返回了一个新的Teacher对象,并将其赋值给了拷贝对象的teacher属性,这样一来,原对象和拷贝对象的teacher属性就不再指向同一个对象了。通过观察代码输出可以看出,通过深克隆得到的拷贝对象的属性和原对象中的属性是相互独立,不会相互影响的。

    注意:

    1. 以上这种方法可以实现大部分属性值为对象时的深克隆。但是当属性值为一些特殊数据结构,例如HashMap时,直接调用super.clone()可能仍然无法实现深克隆,这时可能需要使用循环赋值的方法去复制该属性。
    2. 虽然以上这种方法可以实现深克隆,但在遇到嵌套很深的对象时,这种方法实现起来就会非常复杂。这种情况下使用对象序列化和反序列化实现深克隆,代码会简化很多。

-equals()

equals()方法返回一个布尔值,用于比较两个非空对象是否相等。默认情况下equals()方法比较的是两个对象的引用是否指向同一个内存地址,也就是说,equals()方法的返回值和==是一致的。

public class Test {
    public static void main(String[] args) {
        Test t1 = new Test();
        Test t2 = new Test();
        Test t3 = t1;

        System.out.println("t1 == t2: " + (t1 ==t2));
        System.out.println("t1 equals t2: " + (t1.equals(t2)));
        System.out.println("t1 == t3: " + (t1 ==t3));
        System.out.println("t1 equals t3: " + (t1.equals(t3)));
    }
}
//output
//t1 == t2: false
//t1 equals t2: false
//t1 == t3: true
//t1 equals t3: true

equals()方法是Object的方法,我们创建的所有对象都拥有这个方法,并且可以重写这个方法。例如,String类就对其equals()方法进行了重写,用于比较两个字符串的内容是否相等。这种情况下,equals()方法和==就会返回不同的值。

public class Test {
    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = new String("abc");

        System.out.println("str1 == str2: " + (str1 == str2));
        System.out.println("str1.equals(str2: " + (str1.equals(str2)));
    }
}
//output
//str1 == str2: false
//str1.equals(str2: true

​ 在实际应用中,我们会根据需要对某些类的equals()方法进行重写,在重写的时候,需要遵循以下原则:

  • Reflexive(自反性):for any non-null reference value x, x.equals(x) should return true.

  • Symmetric(对称性):for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.

  • Transitive(传递性): for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.

  • Consistent(一致性):for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equalscomparisons on the objects is modified.

  • For any non-null reference value x, x.equals(null) should return false.

注意:通常情况下,当某个类当equals()方法被重写时,也需要同时重写该类的hashCode()方法。这是因为,通常情况下,调用equals()方法返回为true的两个对象,在调用hashCode()方法时也应返回相同的数值。

-hashCode()

hashCode()方法返回一个int型数值。hashCode()方法其实就是一种hash算法,自定义类可以根据需要对其进行重写。java文档中对于hashCode的一般约定如下:

  1. 运行期间多次调用的返回值应该保持一致:

    Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.

  2. 根据equals()方法比较结果为相等的两个对象在调用hashCode()时应返回相同的整数:

    If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

  3. Java并不要求根据equals()方法比较结果为不相等的两个对象在调用hashCode()时必须返回不同的结果,但是这样做可以提高hash表的性能:

    It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

注意:

  1. 在重写equals()方法后通常需要重写hashCode()方法
  2. 如果两个对象调用hashCode()的返回值相同,调用equals()的结果不一定为true(哈希碰撞)

-getClass()

getClass()是Object类中的一个方法,对象实例调用这个方法返回代表实例运行时的类型的Class类。

  • 什么是Class类呢?

    Java除了基本类型外其他都是class(包括interface)。JVM每加载一个class,就为其创建一个Class类型的实例,关联起来,并在实例中保存该class的完整信息(例如,类名,package名称,它的super class,它实现的interface,它所有的字段和方法等等)。如果获取了某个Class实例,则可以获取到该实力对应的class的所有信息。通过Class实例获取class信息的方法称为反射(Reflection),也可以理解为在运行期获取对象类型信息的操作。

  • 如何获取Class实例?

  1. .class:当我们知道数据类型(类名)缺没有实例对象时,可以通过类名.class这种形式来获取Class实例。这也是基本数据类型获取Class实例最方便的一种方法。.class语句也可以被用来获取多维数组对应的Class实例。

    Class cls = String.class;
    Class c = int[][][].class;
    
  2. Object.getClass():这是实例对象拥有的一个方法,只能用于继承自Object的引用类型数据(不能用于基本数据类型)。

    boolean b;
    Class c = b.getClass();//编译错误
    Class c = boolean.class;//正确
    
    String str = "hello";
    Class cls = str.getClass();
    
  1. Class.forName():如果可以获取完整类名,还可以使用Class类的静态方法Class.forName(),将完整类名作为参数传入该方法来获得Class实例。该方法可以用于引用类型和基本数据类型。使用该方法还可以利用JVM动态加载class的特性在运行期根据条件加载不同的实现类。

    Class cls = Class.forName("java.lang.String");
    
    boolean isClassPresent(String name){
       try{
           Class.forName(name);
           return true;
       }catch(Exception e){
           return false;
       }
    }
    
  • 调用Class实例中的方法可以让我们获得class相关的信息,判断class的类型,并且创建class实例对象。

  • Class实例在JVM中时唯一的,可以用==比较两个Class实例

    Class cls1 = String.class;
    String s = "hello";
    Class cls2 = s.getClass();
    Class cls3 = Class.forName("java.lang.String");
    
    boolean b1 = (cls1 == cls2); //true
    boolean b2 = (cls2 == cls3); //true
    
  • Class实例比较和instanceof的差别:

    • instanceof不但匹配当前类型,还匹配当前类型的子类
    • ==判断Class实例时,只能精确判断数据类型,不能做子类的比较

-toString()

​ Object的toString()方法返回一个代表该对象实例的字符串。一般情况下,返回的字符串应该能准确得,完善得表达该对象的内容,并且易于阅读。推荐所有子类都重写这个方法。

默认的toString()方法返回一个由类名,‘@’符号,和转换为十六进制的hash code组成:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

当我们创建一个新类时,可以重写toString()方法在控制台中显示有关于类的有用信息。

package test;
public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    //不重写toString()方法
    public static void main(String[] args) {
        Person p = new Person("jack", 28);
        System.out.println(p.toString());
        //output:test.Person@61bbe9ba
        //对于表示对象的内容没有任何意义
    }
}

//重写toString()方法
    @Override
    public String toString() {
        return "Person: [name: " + this.name + ", age: " + this.age + "]";
    }
//再次打印
    System.out.println(p.toString());
    //Person: [name: jack, age: 28]
    //显示出了该实例对象包含的信息。

String类的常用方法

-compareTo()

​ String类实现了Compareable接口,而compareTo()方法是Compareable接口唯一需要实现的方法。compareTo()方法按照字典顺序比较两个字符串,返回一个int值。比较是基于字符串中每一个字符的Unicode值。按照字典顺序排列,当该String对象位于参数字符串之前时,返回一个负整数;当该String对象位于参数字符串之后时,返回一个正整数;当二者相同时,返回0。只有当equals(Object)返回true时,compareTo()返回0.

字典顺序的定义如下:当两个字符串不同时,它们要么在相同索引处有不同字符(索引位置在两个字符串中都有效),要么两个字符串的长度不同,或者以上两种情况同时存在。

  • 当两个字符串在一个或多个索引处有不同的字符时,我们定义一个变量k的值为出现不同字符的最小索引的值。哪个字符串在索引k处的字符数值越小,按字典顺序该字符串就排在另一个字符串前面。在这种情况下,compareTo()返回两个字符串在索引k处的字符值的差值:

    this.charAt(k) - anotherString.charAt(k);
    
  • 当两个字符串在有效索引范围内,相同的索引处没有出现不同的字符时,按字典顺序长度较短的字符串排在长度较长的字符串前面。在这种情况下,compareTo()返回两个字符串长度的差值:

    this.length() - anotherString.length();
    
    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "abcd";
        String str3 = "abc";
        String str4 = "dcba";
        
        //compareTo()可以传入String字面量
        System.out.println(str1.compareTo(str2));//-1:字符串长度不同(结果为长度差)
        System.out.println(str1.compareTo(str3));//0: 两字符串相同
        System.out.println( str1.equals(str3));//true
        System.out.println(str1 == str3);//true
        System.out.println(str1.compareTo(str4));//-3: 字符不同(结果为字符值的差)
        System.out.println(str1.compareTo("abc"));//0:两字符串相同

        //也可以传入String对象
        String s1 = new String("await");
        String s2 = new String("await");
        System.out.println(s1 == s2);//false:二者没有指向同一个对象
        System.out.println(s1.equals(s2));//true:二者的内容相同
        System.out.println(s1.compareTo(s2));//0:两字符串相同
    }

注意:compareTo()是大小写敏感的,如果需要忽略大小写,可以使用compareToIgnoreCase()

-concat()

concat()可以在给定字符串的末尾拼接一个字符串,例如:"Hello".concat("World")会返回"HelloWorld"。concat()方法的规则如下:

  • 当传入的字符串参数长度为0时,concat()会返回原字符串对象。

  • 当传入的字符串参数长度不为0时,调用concat()方法会返回一个由原字符串和传入的参数字符串拼接而成的新字符串。

  • 可以进行链式操作,调用concat()不会改变原字符串的内容

    //在给定字符串末尾拼接字符串
    String str1 = "abc";
    String str2 = "abcd";
    String test = str1.concat(str2);
    
    System.out.println(test);//“abcabcd"
    System.out.println(str1);//"abc"->不改变原字符串
    
    //链式操作
    String welcome = "Welcome ".concat("to ").concat("my place.");
    System.out.println(welcome);//“Welcome to my place.“
    
    //在给定字符串前面拼接字符串
    String s = ".com";
    String webSite = "www.google".concat(s);
    System.out.println(webSite);//"www.google.com"
    

-equals()

equals()比较当前字符串与作为参数的字符串是否相同。如果参数是不为null的String对象,并且字符串中字符的序列与当前字符串完全一致,equals()返回true。其他情况下返回false。equals()方法大小写敏感,如果需要忽略大小写进行比较,需要调用equalsIgnoreCase()

String str1 = "abc";
str1.equals("abc");//true
str1.equals("ABC");//false
str1.equalsIgnoreCase("ABC");//true

contentEquals()equals()功能相似。contentEquals()只比较两者的内容是否相同,不检查比较对象的类型。

String str1 = "hello";
StringBuffer sb1 = "hello";
System.out.println(str1.equals(sb1));//false:因为被比较对象sb1不是String类型
System.out.println(str1.contentEquals(sb1));//true:内容相同即返回true,不检查比较对象的类型

-join()

join()是Java 8中新引入String class的一个方法。它可以用来拼接字符串并返回拼接后的新字符串,也可以用来将可迭代的字符串的集合拼接成一个新的字符串。语法如下:

public static String join(CharSequence delimiter,
                          CharSequence... elements)

public static String join(CharSequence delimiter,
                          Iterable<? extends CharSequence> elements)

在使用join()拼接多个字符串时,join()的第一个参数为标点符号(用于分隔/拼接),后面紧跟着需要拼接的字符串,字符串之间以逗号分隔。

String str = String.join("-","hello","world");
System.out.println(str);//"hello-world"

join()可以用来拼接字符串数组:

String[] strs = {"a", "b", "c", "d"};
String str = String.join(",", strs);
System.out.println(str);//"a,b,c,d"

join()还可以用来拼接可迭代的字符串的集合:

import java.util.ArrayList;
import java.util.List;

public class Test{
       public static void main(String[] args) {
        List<String> names = new ArrayList();
        names.add("Ann");
        names.add("Joe");
        names.add("Caroline");
        names.add("Kris");

        String name = String.join("|", names);
        System.out.println(name);//"Ann|Joe|Caroline|Kris"
    }
}

除了join()方法外,StringJoiner类也可以实现拼接字符串的功能。了解更多

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容