本人在学习过程走会遇到一些疑惑,在这里记录一些自己之前不太清楚的问题。如果在参考过程中有什么错误,可以指出来。
目录
一、为什么重写 equals() 时必须重写 hashCode() 方法?
三、String s = new String("abc");这句话创建了几个字符串对象?
一、为什么重写 equals() 时必须重写 hashCode() 方法?
在初学时,对这个问题充满了疑问,为什么一定要按照这个规则来呢?网上的参考资料大部分也都比较含糊其辞。我在参考了部分大佬的解释后,终于有了比较清晰的理解。
是否一定要重写hashCode()方法主要是看对象用在何处。
1.不用做hash对象使用时
public class NotHashDemoTest {
public static void main(String[] args) {
//使用equals判断两个对象是否相等
Person p1 = new Person("test", 100);
Person p2 = new Person("test", 100);
Person p3 = new Person("text", 200);
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
结果:
p1.equals(p2) : true; p1(1360767589) p2(873415566)
p1.equals(p3) : false; p1(1360767589) p3(1007251739)
可以看到,两个hash编码不相等的对象,但他们的equals是相等的。
2.用做hash对象使用
用做hash对象使用,就是需要比较对象本身的hash值是否相等。比如:HashMap、HashSet等。
此处以hashSet举例:
public class HashDemoTest {
public static void main(String[] args) {
Person p1 = new Person("test", 100);
Person p2 = new Person("test", 100);
Person p3 = new Person("text", 200);
HashSet hashSet = new HashSet<>();
hashSet.addAll(Arrays.asList(p1, p2, p3));
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
hashSet.forEach(x -> System.out.print(x + ","));
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " - " +age;
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
结果为:
p1.equals(p2) : true; p1(1360767589) p2(873415566)
test - 100,text - 200,test - 100,
可以看到,两个相等的对象还是添加进入了hashSet内。
当我们对hashCode()方法进行重写后:
public class HashDemoTest {
public static void main(String[] args) {
Person p1 = new Person("test", 100);
Person p2 = new Person("test", 100);
Person p3 = new Person("text", 200);
HashSet hashSet = new HashSet<>();
hashSet.addAll(Arrays.asList(p1, p2, p3));
System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
hashSet.forEach(x -> System.out.print(x + ","));
}
/**
* @desc Person类。
*/
private static class Person {
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " - " +age;
}
@Override
public int hashCode(){
return name.hashCode() + age;
}
/**
* @desc 覆盖equals方法
*/
@Override
public boolean equals(Object obj){
if(obj == null){
return false;
}
//如果是同一个对象返回true,反之返回false
if(this == obj){
return true;
}
//判断是否类型相同
if(this.getClass() != obj.getClass()){
return false;
}
Person person = (Person)obj;
return name.equals(person.name) && age==person.age;
}
}
}
结果为:
p1.equals(p2) : true; p1(3556598) p2(3556598)
test - 100,text - 200,
此时便可以看到,两个对象相等,未能加入到hashSet内。
总结:
是否需要重写hashCode()方法,需要根据实际情况来判定。
二、深拷贝与浅拷贝
深拷贝与浅拷贝重点是:拷贝的对象与原对象是否共用一块内存。
浅拷贝:
浅拷贝是拷贝的对象引用,而不是对象本身。
public class Address implements Cloneable{
private String name;
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Address clone(){
try {
return (Address) super.clone();
}catch (CloneNotSupportedException e){
throw new RuntimeException();
}
}
}
public class Person implements Cloneable{
private Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Person clone(){
try {
return (Person) super.clone();
}catch (CloneNotSupportedException e){
throw new RuntimeException();
}
}
}
public class MainDemo {
public static void main(String[] args) {
Person person = new Person(new Address("123"));
Person person1 = person.clone();
System.out.println(person.getAddress() == person1.getAddress());
System.out.println(person.getAddress().hashCode());
System.out.println(person1.getAddress().hashCode());
}
}
true
329611835
329611835
结果为true。这说明,person和person1指向的地址是一致的。只是person1是拷贝的引用而已。
深拷贝:
深拷贝的拷贝是会新建一个新的对象,指向新的内存地址。
public class Address implements Cloneable{
private String name;
public Address(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Address clone(){
try {
return (Address) super.clone();
}catch (CloneNotSupportedException e){
throw new RuntimeException();
}
}
}
public class Person implements Cloneable{
private Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public Person clone(){
try {
Person person = (Person) super.clone();
person.setAddress(person.getAddress().clone());
return person;
}catch (CloneNotSupportedException e){
throw new RuntimeException();
}
}
}
public class MainDemo {
public static void main(String[] args) {
Person person = new Person(new Address("123"));
Person person1 = person.clone();
System.out.println(person.getAddress() == person1.getAddress());
System.out.println(person.getAddress().hashCode());
System.out.println(person1.getAddress().hashCode());
}
}
false
1468177767
434091818
结果为false。说明现在两个对象已经不是共用一块内存了。
三、String s = new String("abc");这句话创建了几个字符串对象?
这个问题在我初始学习的时候,理解的不是很明白。工作之后,了解了JVM之后也不是理解的太清楚。最近好好钻研了一下。
这个问题需要了解一点JVM中关于字符串常量池的知识,这里我大致介绍一下,以便能讲清问题。
字符串常量池在JDK1.7之前,都是存放在方法区(也就是永久代,JDK1.8开始替换为元空间)之中。从1.7开始,因为永久代GC回收率太低,就将字符串常量池移动了java堆中。
字符串常量池的作用:字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
在新建字符串对象时,如果已经有了对象,那么就直接返回其引用。
//创建字符串对象
//同时会将ab的引用保存到字符串常量池内
String a = "ab";
//字符串常量池内已经有了ab,直接返回其引用
String b = "ab";
System.out.println(a == b); //结果为true
那么我们再看new String("ab")会创建几个对象。
当已经有了字符串之后,那么只会创建一个字符串对象。
//已经有了ab对象的引用在字符串常量池内
String a = "ab";
//创建时只会创建一个字符串对象ab
String b = new String("ab");
当没有时,那么就会创建两个对象。
String a = new String("ab");
总结:
在创建对象时,如果已经在字符串常量池内有了引用,那么就只会创建一个对象。如果没有,那么就会创建两个,一个创建在字符串常量池内,一个创建在堆内。
简单来说:只要new一定会创建对象,String s = new String("abc")至少会创建一个,另外一个需要根据字符串常量池内是否已经创建来判断。
四、java为什么只有值传递?
对于这个问题,我们可以通过代码的运行结果去查看。
基本数据类型:
public class SampleTest {
public static void main(String[] args) {
int x = 1;
sysValue(x);
System.out.println("实参:" + x);
}
public static void sysValue(int x) {
x = 2;
System.out.println("形参:" + x);
}
}
形参:2
实参:1
可以看出形参的改变并没有影响到实参。
字符串:
public class StringTest {
public static void main(String[] args) {
String name = "one";
sysValue(name);
System.out.println("实参:" + name);
}
public static void sysValue(String name) {
name = "two";
System.out.println("形参:" + name);
}
}
形参:two
实参:one
我们都知道字符串是不可变的,那么形参的改变不会影响实参是正常的。
对象:
public class UserTest {
static class User {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
User user = new User();
user.setName("zhangsan");
user.setAge(20);
sysValue(user);
System.out.println("实参:" + user.getName() + ":" + user.getAge());
}
public static void sysValue(User user) {
user.setAge(21);
System.out.println("形参:" + user.getName() + ":" + user.getAge());
}
}
形参:zhangsan:21
实参:zhangsan:21
当我们传递对象的时候,发现形参的改变影响了实参。我们再看一段代码。
public class UserTest {
static class User {
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void main(String[] args) {
User user = new User();
user.setName("zhangsan");
user.setAge(20);
sysValue(user);
System.out.println("实参:" + user.getName() + ":" + user.getAge());
}
public static void sysValue(User user) {
user = new User();
user.setName("wangwu");
user.setAge(21);
System.out.println("形参:" + user.getName() + ":" + user.getAge());
}
}
形参:wangwu:21
实参:zhangsan:20
我们在传递形参的方法内,将user指向了新的地址,之后新设置的值,也没有影响到实参。说明,对象在传递的过程中,传递的是对象引用的复制,传递后形参的改变并不会影响到实参。
总结:
整体写的比较繁琐,最后总结成一句话也就是:简单变量(基本数据类型)是值传递;引用对象类型传递的是引用的复制,也是值的传递。所以说,java中只有值传递!