在本文中,对之前未涉及的知识点进行讲解
一:scanner
在 Java 中,常用的输入语句是通过使用 Scanner 类来实现的。Scanner 类是 Java 提供的用于读取用户输入的类,它可以从标准输入(键盘)或其他输入流中读取数据。
首先,您需要导入 java.util 包中的 Scanner 类:
import java.util.Scanner;
然后,可以创建一个 Scanner 对象来读取用户的输入:
Scanner scanner = new Scanner(System.in);
下面我来对这句话进行解释:
当我们在 Java 中使用 Scanner 类时,需要先创建一个 Scanner 对象。上述代码中的 Scanner scanner = new Scanner(System.in);
表示创建了一个名为 scanner
的 Scanner 对象,并将其与标准输入流 System.in
关联起来。
通过将 System.in
传递给 Scanner 构造函数,我们告诉 Scanner 对象要从标准输入读取数据。标准输入是指通过控制台进行输入的数据源。
创建了 Scanner 对象后,我们可以使用其提供的方法来读取不同类型的输入。
请注意,在使用完 Scanner 对象后,我们应该调用其 close()
方法来释放资源,例如:scanner.close()
。这样可以确保程序在不再需要 Scanner 对象时能够正确关闭。
1.1 next的各类方法
在Java中,Scanner类提供了一些方法用于从输入流中读取不同类型的数据。让我们逐个讨论这些方法在遇到换行符和空格时的行为:
- nextInt():
nextInt()
: 读取下一个整数。- 从非换行符和非空格字符开始读取,当遇到换行符( /n )或空格时,nextInt()方法将停止读取
- 如果输入流中的下一个标记不是整数,则会抛出InputMismatchException异常。
- nextDouble():
nextDouble()
: 读取下一个浮点数。- 从非换行符和非空格字符开始读取,当遇到换行符或空格时,nextDouble()方法将停止读取
- 如果输入流中的下一个标记不是表示双精度数的字符序列,则会抛出InputMismatchException异常。
- nextBoolean():
nextBoolean()
: 读取下一个布尔值。- 从非换行符和非空格字符开始读取,当遇到换行符或空格时,nextBoolean()方法将停止读取
- 该方法将读取输入流中的下一个标记并尝试将其解析为布尔值。如果无法解析,则会抛出InputMismatchException异常。
- nextLine():
nextLine()
: 读取下一行文本。- 从非换行符开始读取nextLine()方法会读取输入流中的一行文本,能够读取空格,但是遇到换行符停止读取
- 它将返回包含行文本的字符串,不会跳过任何空格或换行符。
这些方法会根据数据类型来解析输入,并返回相应的值。例如,scanner.nextInt()
会读取下一个整数,并将其作为返回值。
接下来可以使用 Scanner 对象的不同方法来读取不同类型的输入。以下是几个常用的示例:
- 读取字符串(一行):
System.out.print("请输入一个字符串:"); String str = scanner.nextLine(); System.out.println("您输入的字符串是:" + str);
在此示例中,nextLine()
方法用于读取用户输入的一行字符串,并将其存储在变量 str
中。
- 读取整数:
System.out.print("请输入一个整数:"); int num = scanner.nextInt(); System.out.println("您输入的整数是:" + num);
在此示例中,nextInt()
方法用于读取用户输入的整数,并将其存储在变量 num
中。
注意:在读取完整数后,如果需要接着读取其他类型的输入(例如字符串),则需要在读取整数后调用 scanner.nextLine()
来消耗掉换行符。
- 读取浮点数:
System.out.print("请输入一个浮点数:"); float floatValue = scanner.nextFloat(); System.out.println("您输入的浮点数是:" + floatValue);
在此示例中,nextFloat()
方法用于读取用户输入的浮点数,并将其存储在变量 floatValue
中。
这只是几个基本示例,您还可以使用 Scanner 类的其他方法来读取其他类型的输入,如读取布尔值、读取字符等。
1.2 输出输出缓冲区
在Java中,输入缓冲区是用来提高从输入流中读取数据性能的一种机制。它通过在内存中创建一个缓冲区来存储待读取的数据,并从缓冲区中逐一读取数据,从而减少对底层输入流的频繁访问,提高读取数据的效率。
下面是一个简单的示意图来说明输入缓冲区的使用:
当我们从键盘输入1234回车的时候,我们会将1234\n输入到缓冲区,接着我们可以通过nextInt()来读取数据,nextInt 从非换行符和非空格字符开始读取,当遇到换行符( /n )或空格时,nextInt()方法将停止读取,所以nextInt会读取到1234,并将这个数字作为返回值返回
从这个示意图可以看出,应用程序不直接从输入流中读取数据,而是通过输入缓冲区来获取数据。当应用程序需要一部分数据时,输入缓冲区会从输入流中读取足够的数据并存储在缓冲区中。应用程序则从输入缓冲区中逐一读取数据。当输入缓冲区中的数据被读取完毕后,输入缓冲区会再次从输入流中读取足够的数据。这种机制避免了频繁读取底层输入流的操作,提高了读取数据的效率。
下面是一个简单的代码示例:
import java.util.Scanner; public class InputValidation { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 读取一个整数 System.out.print("请输入一个整数: "); int intValue = scanner.nextInt(); System.out.println("读取整数:" + intValue); // 读取一个浮点数 System.out.print("请输入一个浮点数: "); double doubleValue = scanner.nextDouble(); System.out.println("读取浮点数:" + doubleValue); // 清空缓冲区 scanner.nextLine(); // 读取一行字符串 System.out.print("请输入一行字符串: "); String lineValue = scanner.nextLine(); System.out.println("读取一行字符串:" + lineValue); // 读取一个字符 System.out.print("请输入一个字符: "); char charValue = scanner.nextchar() System.out.println("读取字符:" + charValue); // 关闭Scanner对象 scanner.close(); } }
1.3 hasNext的各种方法
Scanner 类中的 hasNext() 方法有多个重载形式,用于判断输入流中是否还有下一个元素。下面是各种形式的 hasNext() 方法的详细说明以及示例代码:
boolean hasNextBoolean()
:判断输入流中下一个标记是否为一个 boolean 值。返回一个 boolean 值,当且仅当下一个标记为 true 或 false 时,才返回 true。
import java.util.Scanner; public class Main { public static void main(String[] args) { String str = "true false"; Scanner scanner = new Scanner(str); boolean hasBoolean = scanner.hasNextBoolean(); System.out.println(hasBoolean); // 输出:true boolean booleanValue = scanner.nextBoolean(); // 读取下一个 boolean 值 System.out.println(booleanValue); // 输出:true scanner.close(); } }
boolean hasNextInt()
:判断输入流中下一个标记是否为一个整数值。返回一个 boolean 值,当且仅当下一个标记为整数时,才返回 true。
import java.util.Scanner; public class Main { public static void main(String[] args) { String str = "123 45.6"; Scanner scanner = new Scanner(str); boolean hasInt = scanner.hasNextInt(); System.out.println(hasInt); // 输出:true int intValue = scanner.nextInt(); // 读取下一个整数值 System.out.println(intValue); // 输出:123 scanner.close(); } }
boolean hasNextDouble()
:判断输入流中下一个标记是否为一个浮点数值(double)。返回一个 boolean 值,当且仅当下一个标记为浮点数时,才返回 true。
import java.util.Scanner; public class Main { public static void main(String[] args) { String str = "123 45.6"; Scanner scanner = new Scanner(str); boolean hasDouble = scanner.hasNextDouble(); System.out.println(hasDouble); // 输出:true double doubleValue = scanner.nextDouble(); // 读取下一个浮点数值 System.out.println(doubleValue); // 输出:45.6 scanner.close(); } }
除了上述的 hasNext() 方法及其各种重载形式外,Scanner 类还提供了其他用于判断输入流中下一个标记的方法,如 hasNextByte()
、hasNextShort()
、hasNextLong()
、hasNextFloat()
等,用法类似。这些方法可以根据需要选择和使用。
我们可以通过hasNext来配合next的各类方法来使用,多了一种检验会更加安全
二:for - each循环
当我们需要迭代遍历一个集合或数组时,Java提供了for-each循环,也称为增强型for循环。它提供了一种更简洁和易读的方式来遍历数据结构,而不必使用传统的for循环和索引。
for-each循环的语法如下:
for (elementDataType element : collection) { // 在这里执行需要针对每个元素执行的操作 }
其中,elementDataType是要遍历集合中元素的数据类型,element是一个迭代的临时变量,用于引用每个集合中的元素,collection是要遍历的集合名字。
以下是一个示例程序来演示如何使用for-each循环遍历一个整型数组并打印每个元素的值:
public class ForEachExample { public static void main(String[] args) { int[] numbers = {1, 2, 3, 4, 5}; for (int x : numbers) { System.out.println(number); } } }
上述代码中,我们定义了一个整型数组numbers
,然后使用for-each循环遍历该数组。在每次迭代中,将当前元素赋值给number
变量,并通过System.out.println()
打印出来。
这段代码的输出将是:
1 2 3 4 5
这个例子展示了如何使用for-each循环遍历数组。同样的方式也适用于其他类型的集合,比如使用List
集合或Set
集合。
需要注意的是,for-each循环是只读的,无法修改集合中的元素。如果需要修改集合中的元素,应该使用传统的for循环并结合索引操作。
三:JVM内存分布
- 方法区和堆中的内存是所有线程共享的数据区
- 虚拟机栈,本地方法栈和程序计数器是线程隔离的数据区
接着我们对每个内存的作用做出解释:
- 程序计数器 (PC Register): 只是一个很小的空间, 保存下一条执行的指令的地址
- 虚拟机栈(JVM Stack):与方法调用相关的一些信息,每个方法在执行时,都会先创建一个栈帧,栈帧中包含有:局部变量表、操作数栈、动态链接、返回地址以及其他的一些信息,保存的都是与方法执行时相关的一些信息。比如:局部变量。当方法运行结束后,栈帧就被销毁了,即栈帧中保存的数据也被销毁了。
- 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈的作用类似. 只不过保存的内容是Native方法的局部变量. 在有些版本的 JVM 实现中(例如HotSpot), 本地方法栈和虚拟机栈是一起的
- 堆(Heap): JVM所管理的最大内存区域. 使用 new 创建的对象都是在堆上保存 (例如前面的 new int[]{1, 2,3} ),堆是随着程序开始运行时而创建,随着程序的退出而销毁,堆中的数据只要还有在使用,就不会被销毁。
- 方法区(Method Area): 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数 据.
方法编译出的的字节码就是保存在这个区域
四:get和set方法
在Java中,get和set方法是一种常见的命名规范,用于访问和修改一个类的私有属性。这种命名规范被广泛应用于面向对象编程中,被称为"getter"和"setter"方法。
基本格式:
- get方法:用于获取私有属性的值,通常以"get"开始,后面跟着要获取属性的名称,没有参数,返回属性的值。
public dataType getPropertyName() { return propertyName; }
- set方法:用于修改私有属性的值,通常以"set"开始,后面跟着要设置属性的名称,接受一个参数,参数类型与属性类型相同或兼容。
public void setPropertyName(dataType value) { propertyName = value; }
这两个方法的存在意义:
- 封装数据:通过将属性设置为私有(private)的,可以保护数据不被外部直接访问和修改。只能通过get和set方法来访问和修改属性的值,这样可以控制数据的安全性。
- 控制访问权限:通过定义set方法,可以对属性的值进行限制,比如进行合法性检查和处理。同时,只提供get方法可以只允许读取属性的值,而不允许修改,提高代码的可控性和灵活性。
- 面向对象原则:get和set方法符合面向对象编程的封装原则,将数据和操作数据的行为封装在同一个类中,提供了一种面向对象的访问方式。
示例代码:
public class Person { private String name; private int age; public String getName() { return name; } public void setName(String value) { if (value != null && !value.isEmpty()) { name = value; } } public int getAge() { return age; } public void setAge(int value) { if (value >= 0 && value <= 150) { age = value; } } } public class Main { public static void main(String[] args) { Person person = new Person(); person.setName("John"); person.setAge(25); System.out.println("Name: " + person.getName()); System.out.println("Age: " + person.getAge()); } }
以上代码中,Person类中定义了name和age两个私有属性,并通过get和set方法进行访问和修改。setName方法检查传入的值是否为空或空字符串,setAge方法检查年龄是否在有效范围内。在Main类中,我们创建了一个Person对象,使用set方法设置属性的值,然后使用get方法获取属性的值并打印输出。
这样的设计可以确保对属性的访问和修改通过统一接口进行,同时可以对属性值进行限制和检查,提高代码的健壮性和可扩展性。
五:匿名内部类和匿名对象
5.1匿名内部类
匿名内部类、匿名对象和匿名方法在Java中都是用来简化代码编写的特殊语法结构。
- 匿名内部类:
匿名内部类是指在创建对象的同时定义一个类,但是没有对该类进行命名。它的语法形式可以类比于创建接口的实现类的方式。
interface Animal { void sound(); } public class Main { public static void main(String[] args) { Animal animal = new Animal() { @Override public void sound() { System.out.println("The animal makes a sound."); } }; animal.sound(); } }
在匿名内部类中, new Animal()代表的是实例化一个实现了Animal接口的对象,接着我们直接在后面接上{ },在里面实现方法的重写即可,因为new会返回这个对象的地址,所以我们把它赋值给Anmial类型的引用即可,这里发生了向上转型,因为如果一个类实现了一个接口,那么这个类和这个接口之间算是一种特殊的父类子类关系,当我们用一个接口类型的引用去指向一个实现了这个接口的类(在这里是匿名内部类,没有名字而已,本质上还是一个类),此时也算是一种向上转型。
当然,上述代码还可以写出这样:
interface Animal { void sound(); } public class Main { public static void main(String[] args) { new Animal() { @Override public void sound() { System.out.println("The animal makes a sound."); } }.sound(); }
这样做的结果是一样的,它会输出 “The animal makes a sound.”,这样做可以直接在创建匿名内部类的同时调用其方法,而不需要额外的引用变量。
上述两端代码和这段代码都是等效的:
interface Animal { void sound(); } class Dog implements Animal { @Override public void sound() { System.out.println("The dog makes a sound."); } } public class Main { public static void main(String[] args) { Animal animal = new Dog(); animal.sound(); } }
5.2 匿名对象
- 匿名对象:
匿名对象是指创建对象时,没有给对象命名,而是直接将对象赋值给一个引用变量或者作为方法的参数传递。它的主要作用是临时使用,不需要重复使用该对象。
public class Main { public static void printMessage() { System.out.println("This is a message."); } public static void main(String[] args) { printMessage(); // 调用方法时创建匿名对象 new Main().printMessage(); // 创建对象时创建匿名对象并调用方法 } }
- 在上述代码中,使用匿名对象来调用
printMessage
方法,且在创建对象时直接创建了匿名对象并调用方法。通过匿名对象,我们可以在不创建额外的对象的情况下使用对象的方法,从而简化了代码的编写。
5.3 Lambda表达式
Lambda表达式是Java 8中引入的一个重要特性,它允许我们以更简洁的方式实现函数式接口(只包含一个抽象方法的接口),我们可以采用Lambda表达式对匿名内部类进行简写
interface Animal { void sound(); } public class Main { public static void main(String[] args) { Animal animal = new Animal() { @Override public void sound() { System.out.println("The animal makes a sound."); } }; animal.sound(); } }
使用Lambda表达式可以将上述代码简化为以下形式:
interface Animal { void sound(); } public class Main { public static void main(String[] args) { Animal animal = () -> { System.out.println("The animal makes a sound."); }; animal.sound(); } }
我们来看一下Lambda表达式的语法和格式:
- Lambda表达式的基本语法为:
(参数列表) -> { 方法体 }
。 - 参数列表可以为空,也可以包含一个或多个参数。在这个例子中,参数列表为空。
->
箭头用于将参数列表和方法体分隔开。- 方法体可以是一个表达式(没有花括号)或一个代码块(有花括号)。
- 如果方法体只有一行代码,可以省略花括号。(最好不要省)
- Lambda表达式只适用于函数式接口,即匿名内部类中只能有一个抽象方法
六:toString,equals,compareTo,clone
6.1 toString方法
在Java中,toString()是一个非常常用的方法,它是Object类的一个实例方法。默认情况下,当我们调用一个对象的toString()方法时,它会返回一个字符串,该字符串由对象的类名、@符号和对象的哈希码组成。然而,通常我们需要重写这个方法来返回一个更有意义的字符串表示对象的内容。
要重写toString()方法,我们只需要在我们的类中定义这个方法,并返回我们希望的字符串表示。让我们通过一个例子来说明:
public class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } public static void main(String[] args) { Person person = new Person("John Doe", 30); System.out.println(person); } }
在上面的例子中,我们定义了一个Person类,并在类中重写了toString()方法。重写后的toString()方法返回了一个以"name"和"age"的值为内容的字符串。最后,在main方法中创建一个Person对象,并调用toString()方法打印出字符串表示(打印引用类型的对象的时候会自动调用toString方法)
运行上述代码,将会输出以下内容:
Person [name=John Doe, age=30]
正如你所看到的,通过重写toString()方法,我们能够自定义一个表示对象内容的字符串。
toString()方法是Object类的一个公共方法,意味着在任何一个类中都可以直接调用该方法。由于所有的类都继承自Object类,所以每个类都有toString()方法。这也是为什么我们可以在上面的例子中直接调用toString()方法。
6.2equals方法
当我们在Java中比较两个对象是否相等时,可以使用equals()方法。equals()方法是Object类中定义的方法,所有的Java类都直接或间接继承自Object类,因此所有的对象都可以调用该方法。
在Object类中,equals()方法的默认实现是比较两个对象的引用是否相等,即判断两个对象是否是同一个实例。有时候这不符合我们的需求,因为我们可能希望比较的是对象的属性值是否相等。
为了实现自定义的相等比较,我们可以重写equals()方法。在重写equals()方法时,我们应该遵循以下几个约定:
- 对称性:如果两个对象相互比较,结果应该是一致的。
- 反射性:任何非null的对象与自身比较,结果应该为true。
- 传递性:如果对象A与对象B相等,对象B与对象C相等,那么对象A与对象C也应该相等。
- 一致性:对于不变的对象,多次调用equals()方法应该返回一致的结果。
下面是一个示例代码,说明如何重写equals()方法:
class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null || getClass() != obj.getClass()) { return false; } Person other = (Person) obj; return age == other.age && Objects.equals(name, other.name); } }
在给定的示例代码中,我们定义了一个名为Person的类,该类具有私有属性name(姓名)和age(年龄)。在这个类中,我们重写了equals()方法。
equals()方法用于比较两个对象是否相等。然而,仅使用默认的equals()方法来比较两个对象可能会导致不准确的结果。因此,我们重写equals()方法以提供更准确的比较。
首先,在equals()方法中,我们使用以下代码来检查两个对象是否引用了同一个对象,这个对应了对称性:
if (this == obj) {
return true;
}
这是一种优化机制,如果两个对象在内存中的引用是相同的,则它们肯定相等。
接下来,我们使用以下代码来检查传入的对象是否为空或者该对象的类与当前对象的类不同,这个对应了反射性,并保证对比的两个对象要是同类型的:
if (obj == null || getClass() != obj.getClass()) {
return false;
}
这是为了确保我们正在比较同一种类型的对象。如果传入的对象是null或者传入的对象不是当前对象所属的类的实例,那么它们肯定不相等。这个检查确保了比较的准确性和健壮性。
然后,我们将传入的对象强制转换为Person类的实例,以便可以比较它们的属性:
Person other = (Person) obj;
最后,我们使用以下代码来比较两个Person对象的name和age属性:
return age == other.age && Objects.equals(name, other.name);
这里我们使用了两个条件来判断两个Person对象是否相等。首先,我们比较它们的age属性,使用==运算符比较两个整数值。然后,我们使用Objects.equals()方法(这个是重写之前的,比较两个字符串相等应该通过equals方法)来比较name属性。Objects.equals()方法会首先检查name是否为null,然后再使用equals()方法比较两个字符串的内容。
如果两个Person对象的name和age属性都相等,那么它们被认为是相等的,equals()方法将返回true;否则,它们被认为是不相等的,equals()方法将返回false。
通过这种方式,我们可以确保equals()方法在比较Person对象时是准确且健壮的。它不仅比较对象引用,还比较对象的属性,从而提供了更全面的相等性判断。
6.3compareTo方法
Java中的compareTo方法是一个用于比较两个对象的方法,它通常用于排序和排序相关的操作。它是Comparable接口中的一个方法。
Comparable接口是一个泛型接口,定义了一个compareTo方法,用于比较两个对象的顺序。这个接口的目的是使类的对象具有可比较性,以便在各种排序算法中使用。
要实现Comparable接口,需要进行以下步骤:
- 在类的声明中指定类实现Comparable接口,如下所示:
public class MyClass implements Comparable<MyClass> { // 类的成员和方法 }
- 实现Comparable接口后,需要重写compareTo方法,根据对象的特定属性或条件来定义对象之间的比较规则。compareTo方法应返回一个整数值,用于表示两个对象之间的关系。
@Override public int compareTo(MyClass other) { // 定义比较规则的逻辑 }
在compareTo方法的实现中,常见的比较逻辑有:
- 如果当前对象小于other对象,应返回负整数;
- 如果当前对象等于other对象,应返回零;
- 如果当前对象大于other对象,应返回正整数。
以下是一个示例代码,演示了如何实现Comparable接口和compareTo方法来对学生对象进行按照学生分数的降序排序:
public class Student implements Comparable<Student> { private String name; private int score; public Student(String name, int score) { this.name = name; this.score = score; } @Override public int compareTo(Student other) { // 按分数降序排序 return other.score - this.score; } public static void main(String[] args) { List<Student> students = new ArrayList<>(); students.add(new Student("Alice", 85)); students.add(new Student("Bob", 76)); students.add(new Student("Charlie", 92)); Collections.sort(students); for (Student student : students) { System.out.println(student.name + ": " + student.score); } } }
在以上示例中,Student类实现了Comparable接口,并重写了compareTo方法。在main方法中,我们创建了一个Student对象列表,并使用Collections.sort方法对列表进行排序。最终,按分数降序输出了排序结果。
通过实现Comparable接口和重写compareTo方法,我们可以根据自己定义的比较规则对对象进行排序,从而更好地满足业务需求。
6.4Clone方法
Java中的clone()
方法是一种对象复制方法,它允许创建一个对象的精确副本。clone()
方法在Object
类中定义,它会复制对象的字段值,并返回克隆后的对象。但是需要注意的是,clone()
方法是浅拷贝,即只能复制对象本身,而不能复制其所引用的其他对象。
为了使用clone()
方法,我们需要让要复制的类实现Cloneable
接口。Cloneable
接口是一个标记接口,它没有任何方法,但它告诉Java虚拟机可以安全地使用clone()
方法来复制该类的对象。
下面是一个简单的示例代码,演示了如何使用clone()
方法和实现Cloneable
接口:
class Person implements Cloneable { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } public class Main { public static void main(String[] args) { Person person1 = new Person("John", 25); try { Person person2 = (Person) person1.clone(); System.out.println("person1: " + person1); System.out.println("person2: " + person2); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }
下面我来详细解释一下这段代码:
这段代码定义了一个 Person
类,它实现了 Cloneable
接口来启用克隆功能。 Person
类有两个私有属性:name
(姓名)和 age
(年龄)。
构造函数 Person(String name, int age)
用于初始化 Person
对象的 name
和 age
属性。
clone()
方法是重写的 Object
类的 clone()
方法,用于创建并返回当前对象的克隆副本。需要注意的是,它抛出了 CloneNotSupportedException
异常,因此在使用 clone()
方法时需要处理异常。
toString()
方法是重写的 Object
类的 toString()
方法,用于将 Person
对象转换为字符串表示形式。
在 Main
类的 main()
方法中,首先创建了一个 person1
对象,姓名为 “John”,年龄为 25。
然后,使用 clone()
方法创建了 person1
的克隆副本,并将其赋值给 person2
。
最后,通过打印 person1
和 person2
的字符串表示形式,展示了两个对象的内容。
通过运行上面的代码,您会看到输出结果如下:
person1: Person [name=John, age=25] person2: Person [name=John, age=25]
可以看到,通过clone()
方法,我们成功地创建了person1
的副本person2
,它们的字段值相同。
请注意,如果要深度复制对象,也就是复制对象以及其所引用的对象,需要在clone()
方法中进行额外的操作。这超出了本示例的范围。
七:Object类
Java中的Object类是所有类的根类,它是Java标准库中提供的一个基本类,位于java.lang包下。所有Java类都是直接或间接地继承自Object类。Object类没有任何直接的父类,它是类继承层次结构的顶层。
Object类的主要用途是作为其他类的基类,提供了一些通用的方法和功能,以帮助其他类实现一些基本的行为和特性。比如上述的toString,equals,compareTo,clone。
在Java中,Object类是所有类的祖先类,因此可以将任何类的对象赋值给Object类型的变量。我们可以用Object来接收任何类型的对象
例如,假设我们有两个类,分别是Person和Car。Person类表示一个人,Car类表示一辆汽车。我们可以创建这两个类的对象,并将它们赋值给Object类型的变量:
Person person = new Person("John"); Car car = new Car("Toyota"); Object obj1 = person; // 通过Person对象赋值给Object类型的变量 Object obj2 = car; // 通过Car对象赋值给Object类型的变量
在上面的示例中,我们将Person对象和Car对象分别赋值给了Object类型的变量obj1和obj2。由于Object类是所有类的祖先类,所以可以接收任意类型的对象。
需要注意的是,当我们将对象赋值给Object类型的变量后,就失去了对原始对象特定类型的访问。如果想要再次使用原始对象的特定方法或属性,需要进行类型转换。例如:
Person person = new Person("John"); Object obj = person; // 需要将Object类型的变量再次转换为Person类型 Person person2 = (Person) obj; person2.sayHello(); // 调用Person类的sayHello方法
在上面的示例中,我们首先将Person对象赋值给Object类型的变量obj。然后,我们将obj转换为Person类型的变量person2,以便可以再次使用Person类的sayHello方法。
初始java完结,恭喜你走到这里
送给读者一句话: 轻舟已过万重山