在这篇文章中,我们将介绍一篇关于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对象
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行。引用user4
和user 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行。student1
和student2
对Course
有相同的对象引用。
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
现在,让我们看一个深度克隆的例子。在下面的例子中,CloneableStudent
和CloneableCourse
都实现了它们的一个克隆方法。
在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==null
和Objects.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.java
。 handleError
这里有一个名为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)