Java对象类的综合教程(附代码实例)

66 阅读12分钟

在这篇文章中,我们将介绍一篇关于Java对象类的综合文章,它是所有类的父类。这篇文章和所使用的例子都是基于JDK 8的。

你也可以在以下视频中查看Java类和对象教程。

Java类和对象教程 - 视频

1.简介

面向对象编程的本质是使用类和对象进行抽象。

所有的Java程序都使用对象。对象的类型由类来定义。

创建一个对象的过程被称为实例化。对象是使用类作为其蓝图来创建的。在Java中,java.lang.Object 是指Object,java.lang.Class 是指class。java.lang.Object 是class层次结构的根。所有的Java类都直接或间接地扩展java.lang.Object。

一个Object是使用new 操作符进行实例化的。实例化过程会动态地分配对象所需的内存。

2.对象的部分

Java Object

Java对象

2.1.实例变量/方法

一个对象的字段被称为实例变量。一个对象的实例变量表示它的状态。一个对象的方法被称为实例方法。这些方法定义了对象的行为。

2.2.静态变量/方法

一个属于类的字段被称为静态变量。当类在运行时被加载时,它被初始化。同样地,属于类的方法被称为静态方法。静态成员(变量/方法)是用关键字static来声明的。静态成员可以使用类名或使用对象引用来访问。

2.3.构造器

构造函数与方法类似。它不返回一个值。它的名字与类的名字相同。一旦使用new 操作符创建了对象,它就被调用。它的主要目的是初始化对象。默认情况下,编译器会生成一个无参数的构造函数。 一个类可以有额外的构造函数。更多细节请参考这里

2.4.静态初始化块

这是一个包含在大括号内的代码块,前面有一个静态关键字。一个类可以有一个以上的静态初始化器,在类主体的任何地方。当一个类被运行时加载时,系统会按照它们在源代码中出现的顺序来执行静态块。

static {
    //code for initializing
}

2.5.实例初始化块

这是一个包含在大括号内的代码块。编译器将初始化块复制到每个构造函数中。

{
   //code for initializing
}

2.6.终结者

protected void finalize() throws Throwable

当垃圾收集器(GC)确定不再有对该对象的引用时,finalize() 方法就会被调用。JVM对调用finalize() 方法的决定可能有所不同。它可能会也可能不会调用该方法。

关于垃圾收集的详细研究,请参考这里

下面的例子说明了在对象实例化过程中静态初始化器、实例初始化器和构造器的执行顺序。

CreateObjectExample.java

public class CreateObjectExample {

    {
        System.out.println("Instance initializer 1");
    }
    
    static {
        System.out.println("Static initializer 1");
    }

    static {
        System.out.println("Static initializer 2");
    }
    
    public CreateObjectExample(){
        System.out.println("no-arg Constructor");
    }
    
    public CreateObjectExample(boolean status){
        System.out.println("boolean-arg Constructor");
    }
    
    public static void main(String[] args){
        //Object creation - using no-arg constructor
        CreateObjectExample obj1 = new CreateObjectExample();
        
        //Object creation - using boolean-arg constructor
        CreateObjectExample obj2 = new CreateObjectExample(true);
        
        //calling instance method
        obj1.print();
        
        //calling static method using classname
        CreateObjectExample.print1();
        
        //calling static method using object reference
        obj2.print1();
    }
    //instanceinitiliser
    {
        System.out.println("Instance initializer 2");
    }
    
    public void print(){
        System.out.println("instance method: print method of the object");
    }
    
    public static void print1(){
        System.out.println("static method: print method of the class");
    }
    
    static {
        System.out.println("static initializer 3");
    }
    
    protected void finalize() throws Throwable
    {
        super.finalize();
        System.out.println("finalizer");
    }
}


OUTPUT

Static initializer 1
Static initializer 2
static initializer 3
Instance initializer 1
Instance initializer 2
no-arg Constructor
Instance initializer 1
Instance initializer 2
boolean-arg Constructor
instance method: print method of the object
static method: print method of the class
static method: print method of the class


3.对象方法

java.lang.Object 中最常用的一些方法是。

3.1.equals()方法

public boolean equals(Object obj)

equals() 方法被用来比较两个对象。对象的平等是由这个方法实现的。默认的实现只是检查两个对象的引用是否相等。这意味着,默认实现只在比较对象指向同一个对象时返回真。

一个类可以提供自己的定义,说明如何检查对象是否相等。

3.2.hashCode()方法

public int hashCode()

这个方法返回对象的哈希代码值。当对象被存储在哈希表中时,哈希码值就会被使用。hashCode的一般契约是:

  • 如果两个对象是相等的,那么它们的hashCode值必须相等。然而,反之亦然,即如果两个对象不相等,它们的hashCode值也不一定相等。由于这个原因,如果equals() 方法被重写,hashCode() 方法也需要相应地实现。

  • 不管hashCode() 方法在同一个对象上被调用多少次,它必须返回相同的hashCode值。

请参考下面的例子。在User.java ,equals和hashcode方法被重写,以基于字段userId 进行比较。请参考ObjectComparison.java 的第6行,第7行。引用user4user 5 是以相同的userId 。因此,在第12行,通过equals返回true。

User.java

public class User {
    private int userId;
    private String username;
    
    public User(int id, String name){
        this.userId = id;
        this.username = name;
    }
    @Override
    public boolean equals(Object obj){
        if( obj == this) { return true; }
        
        if(obj ==null || !(obj instanceof User) ) {return false;}
        
        
        if( userId == ((User)obj).getUserId()) {
            return true;
        } else {return false;}
        
    }
    
    @Override
    public int hashCode(){
        return userId;
    }
    
    public int getUserId(){ return userId;}
}


ObjectComparison.java

public class ObjectComparison {
    public static void main(String[] args){
        User user1 = new User(1,"Ashley");
        User user2 = user1;
        User user3 = null;
        User user4 = new User(2,"Brian");
        User user5 = new User(2, "Chetna");
        
        System.out.println("user1 vs user2 :" + user1.equals(user2));
        System.out.println("user1 vs user3 :" + user1.equals(user3));
        System.out.println("user1 vs user4 :" + user1.equals(user4));
        System.out.println("user4 vs user5 :" + user4.equals(user5));
    }

}

user1 vs user2 :true
user1 vs user3 :false
user1 vs user4 :false
user4 vs user5 :true

3.3.clone()方法

克隆是原始对象的一个精确拷贝。clone()方法创建一个被克隆的对象的副本并返回新的引用。实现clone()方法的对象必须实现一个标记接口Cloneable。clone()方法是protected ,在java.lang.Object 。所以,它需要被重载为public。

有2种类型的克隆。 1.浅层克隆 2.深度克隆

浅层克隆是由clone()方法实现的默认复制过程。一个浅层拷贝,在创建时具有与原始对象完全相同的字段。如果该对象有对另一个对象的引用,只有这些引用被复制到浅层拷贝中。如果你改变了浅层拷贝的值,它也会反映在原始拷贝中。因此,一个浅层克隆是依赖于原始对象的。

在深度克隆中,被克隆的对象拥有原始对象的精确字段。如果该对象有对另一个对象的引用,那么这些引用也会通过调用各自的clone()方法进行克隆。因此,深度克隆的对象是独立于被克隆对象的。

请参考下面的例子。Student 类实现了Cloneable接口。Student ,其属性之一是Course

Student.java

public class Student implements Cloneable{
    private int studentId;
    private String studentName;
    private Course enrolledCourse;

    public Student(int id, String name, Course course){
        this.studentId = id;
        this.studentName = name;
        this.enrolledCourse = course;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException 
    {
        return super.clone();
    }

     public String getStudentName() {
        return studentName;
    }
    public Course getEnrolledCourse() {
        return enrolledCourse;
    }
    
}


Course.java

public class Course {
    String subject1;
    String subject2;

    public Course(String subj1, String subj2){
        this.subject1 = subj1;
        this.subject2 = subj2;
    }    
}

ShallowCloner.java 中,在第6行,student2 是通过克隆student1 创建的。请注意输出中的第3行和第6行。student1student2Course 有相同的对象引用。

ShallowCloner.java

public class ShallowCloner {
    public static void main(String[] args){
        Course grade5 = new Course("Maths", "Science");
        Student student1 = new Student(1,"Krish", grade5);
        try {
        Student student2 = (Student) student1.clone();
        System.out.println("-----Student 1--------");
        System.out.println(student1.getStudentName());
        System.out.println(student1.getEnrolledCourse());
        System.out.println("-----Student 2--------");
        System.out.println(student2.getStudentName());
        System.out.println(student2.getEnrolledCourse());
        } catch(CloneNotSupportedException ex){
            ex.printStackTrace();
        }
    }
}

-----Student 1--------
Krish
jcg.methods.examples.clone.Course@15db9742
-----Student 2--------
Krish
jcg.methods.examples.clone.Course@15db9742

现在,让我们看一个深度克隆的例子。在下面的例子中,CloneableStudentCloneableCourse 都实现了它们的一个克隆方法。

CloneableStudent's clone() 实现中,课程也被克隆了。请参考CloneableStudent 的第16、17行。

CloneableCourse.java

public class CloneableCourse extends Course implements Cloneable{

    public CloneableCourse(String sub1, String sub2){
        super(sub1, sub2);
    }
    @Override
    protected Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}

CloneableStudent.java

public class CloneableStudent implements Cloneable {

    private int studentId;
    private String studentName;
    private CloneableCourse enrolledCourse;

    public CloneableStudent(int id, String name, CloneableCourse course){
        this.studentId = id;
        this.studentName = name;
        this.enrolledCourse = course;
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException 
    {
        CloneableStudent deepClone = (CloneableStudent)super.clone();
        deepClone.enrolledCourse = (CloneableCourse)this.enrolledCourse.clone();
        return deepClone;
    }

    public String getStudentName() {
        return studentName;
    }

    public Course getEnrolledCourse() {
        return enrolledCourse;
    }
    
    
}


DeepCloner 类中,在第7行,student2 是通过克隆student1 创建的。现在,作为深度克隆的结果,enrolledCourse 属性也被克隆了,两个克隆体现在都是相互独立的,有自己的实例。

DeepCloner.java

public class DeepCloner {
    
    public static void main(String[] args){
        CloneableCourse grade6 = new CloneableCourse("History", "Science");
        CloneableStudent student1 = new CloneableStudent(2,"Ratha", grade6);
        try {
        CloneableStudent student2 = (CloneableStudent) student1.clone();
        System.out.println("-----Student 1--------");
        System.out.println(student1.getStudentName());
        System.out.println(student1.getEnrolledCourse());
        System.out.println("-----Student 2--------");
        System.out.println(student2.getStudentName());
        System.out.println(student2.getEnrolledCourse());
        } catch(CloneNotSupportedException ex){
            ex.printStackTrace();
        }
    }


}


-----Student 1--------
Ratha
jcg.methods.examples.clone.CloneableCourse@15db9742
-----Student 2--------
Ratha
jcg.methods.examples.clone.CloneableCourse@6d06d69c

3.4.toString()方法

toString() 方法返回对象的文本表示,并返回一个字符串。该方法默认被System.out.println()方法和String concatenation所调用。

默认情况下,toString()方法的实现会返回一个String,表示类的名称和对象的无符号十六进制表示的哈希码,格式为classname@hashcode

在下面的例子中,类Order使用了由Object 类提供的默认的toString() 方法。在第11行,System.out.println() 调用对象的toString() 函数,将对象转换为文本表示。

Order.java

public class Order {
    private int id;
    private String description;

    public Order(int id, String desc){
        this.id = id;
        this.description = desc;
    }
    public static void main(String[]args){
        Order instance = new Order(1,"daily order");
        System.out.println(instance);
    }
}

OUTPUT

jcg.methods.examples.Order@15db9742

现在让我们重写toString()方法,看看会发生什么。toString()方法的返回值被打印在第11行。

Order.java

@Override
    public String toString(){
        return id+ ":"+description;
    }

OUTPUT

1:daily order

3.5.getClass()方法

getClass()返回对象的运行时类别。请注意,输出是字符串 "class "与类的名称相连接。这是因为java.lang.Class的toString()方法被重写,以显示 "class/interface "和类名。

BakeryOrder.java

public class BakeryOrder extends Order
{
    public BakeryOrder(int id, String desc) {
        super(id, desc);
    }
    
    public static void main(String[] args){
        BakeryOrder obj1 = new BakeryOrder(1, "Bakes & Bakes");
        System.out.printf("obj1 : %s%n",obj1.getClass());
        Order obj2 = new Order(2, "Garments order");
        System.out.printf("obj2 : %s%n" ,obj2.getClass());
        Order obj3 = new BakeryOrder(3,"Cake order");
        System.out.printf("obj3 : %s%n" ,obj3.getClass());
        
    }

}

obj1 : class jcg.methods.examples.BakeryOrder
obj2 : class jcg.methods.examples.Order
obj3 : class jcg.methods.examples.BakeryOrder

4.访问修改器

访问修饰符指定哪个类可以访问该类,并最终访问相应的对象、其字段、方法和构造函数。一个访问修饰符可以在

  • 类级别
  • 成员级别

以下是按可访问性顺序排列的访问修改器。

  • 公共的
  • 保护的
  • 默认(包)
  • 私有

public和default访问修饰符适用于类级别。一个公有类可以从代码中的任何地方被访问。默认的访问修饰符用 "default "关键字标记。当没有指定访问修饰符时,它也采用默认的访问级别。默认访问级别也被称为 "包默认"。在包默认访问级别中,类只能在同一个包内被访问。私有和保护不适用于类级别。

访问修饰符也可以被指定给字段、方法和构造函数。如果一个方法/变量被标记为私有,那么它只能在类和嵌套的类中被访问。如果一个方法/变量被标记为缺省或没有任何访问修饰符,那么它可以在类和嵌套类以及同一包中的其他类中被访问。属于不同包的子类不能访问默认成员。如果一个方法/变量被标记为受保护,那么这个成员可以在类和嵌套类、同一包的其他类和不同包的子类中被访问。

4.1.成员的可见性

访问修改器全局子类在包内类内
公共是的是的
受保护的是的
默认是的
私人没有没有

5.Objects utility

java.util.Objects 是一个用于对Object进行操作的实用类。这个实用类是在Java 1.7中引入的。 该类主要关注对java的无效安全操作,并减少模板代码:

  • 比较
  • 深度等价
  • 等价
  • 哈希
  • 哈希代码
  • isNull
  • nonNull
  • 要求非空
  • toString

我们将在以下章节中看到一些方法的例子。

5.1.Objects.isNull()

public static boolean isNull(Object obj)

当作为参数传递的对象为空时,isNull()方法返回真。obj==nullObjects.isNull(obj) 之间没有区别。该方法旨在用于lambda过滤。关于Lambda的更多细节,请阅读这里

5.2.Objects.nonNull()

public static boolean nonNull(Object obj)

如果传递给它的对象不是空的,该方法返回true。更多细节,请参考这里。下面是一个不言自明的小例子,Objects.nonNull被用作谓词。filter 是一个接受Predicate的Stream方法。

UtilityCheckNull.java

public class UtilityCheckNull {
    public static void main(String[] args){
        List productList = new ArrayList();
        productList.add("AppleCake");
        productList.add("Muffins");
        productList.add(null);
        productList.add("Brownie");
        System.out.println((productList==null));
        System.out.println((Objects.isNull(productList)));
        System.out.println((Objects.nonNull(productList)));
        System.out.println(productList);
        List filtered = productList.stream().filter(Objects::nonNull).collect(Collectors.toList());
        System.out.println(filtered);
    }

}

下面的输出中突出显示的一行是去除空值后的过滤列表

OUTPUT

false
false
true
[AppleCake, Muffins, null, Brownie]
[AppleCake, Muffins, Brownie]

5.3.Objects.requireNonNull()

public static <T> T requireNonNull(T obj)

public static <T> T requireNonNull(T obj, String message )

public static <T> T requireNonNull(T obj, Supplier messageSupplier)

requireNonNull()方法检查作为参数传递的对象是否为空。如果对象为非空值,则返回所传递的对象。否则,它提供了另外两种处理null的方法。请参考下面的例子。

UtilityClass1.java

public static void main(String[] args){
        Map testMap = new HashMap();
        
        System.out.println(Objects.requireNonNull(testMap));
        //Throws NullPointerException with a customised message
        System.out.println(Objects.requireNonNull(testMap.get("key1"),  "key1 is not present"));
    }

UtilityClass1.java 的第4行,testMap 是一个空的地图,并且是非空的。因此,该地图被返回到System.out.println() 。这导致在输出行#1中打印出空地图。

在第6行,由于testMap 是空的,所以testMap.get("key1") 行返回空。在这一行,一个NullPointerException被赋予了一个自定义的消息"key1 is not present" ,从而帮助故障排除更容易。

OUTPUT

{}
Exception in thread "main" java.lang.NullPointerException: key1 is not present
	at java.util.Objects.requireNonNull(Objects.java:228)
	at com.jcg.utilities.UtilityClass1.main(UtilityClass1.java:17)

请参考下面的例子:UtilityClass2.javahandleError 这里有一个名为Supplier 的方法用来处理返回的null值。handleError方法将processStatus 设置为false,同时返回一个自定义的错误信息。NullPointerException与自定义的错误信息一起被抛出。

UtilityClass2.java

public class UtilityClass2 {
    private boolean processStatus;
    
    public static void main(String[] args){
        
        UtilityClass2 obj = new UtilityClass2();
        obj.execute();
     
    }
    
    private void execute(){
        Map testMap = new HashMap();
        
        System.out.println(Objects.requireNonNull(testMap.get("key1"), handleError()));
        //Throws NullPointerException with a customised message
    }
    
    private String handleError(){
        processStatus = false;
        return "Technical Error in the Utility Module. Please contact admin.";
    }
}

OUTPUT

Exception in thread "main" java.lang.NullPointerException: Technical Error in the Utility Module. Please contact admin.
	at java.util.Objects.requireNonNull(Objects.java:228)
	at com.jcg.utilities.UtilityClass2.execute(UtilityClass2.java:24)
	at com.jcg.utilities.UtilityClass2.main(UtilityClass2.java:17)

5.4.Objects.toString()

public static String toString(Object o)

public static String toString(Object o, String nullDefault)

在Objects类中有两个重载的toString方法。让我们看看重载的方法的例子:nullDefault

如果toString()方法返回null,该方法返回一个默认值。请看下面的例子。在第9行,提供了一个默认值 "null String"。即使在代码中testMap.get("key1") 返回null,在第9行也没有抛出NullPointerException。相反,它被处理为返回一个默认的字符串。

在第11行,Object的toString()方法被调用到testMap.get("key1") 。由于地图中没有 "key1 "的条目,所以返回null。对null调用toString()方法会抛出NullPointerException。

UtilityClass3.java

public class UtilityClass3 {

    public static void main(String[] args){
        UtilityClass3 obj = new UtilityClass3();
        obj.execute();
    }
    public  void execute(){
        Map testMap = new HashMap();
        String value = Objects.toString(testMap.get("key1"), "null string");
        System.out.println("value using Objects utility " + value);
        String value1 = testMap.get("key1").toString();
        System.out.println("value using Object toString " + value1);

    }
}

OUTPUT

value using Objects utility null string
Exception in thread "main" java.lang.NullPointerException
	at com.jcg.utilities.UtilityClass3.execute(UtilityClass3.java:20)
	at com.jcg.utilities.UtilityClass3.main(UtilityClass3.java:14)