文章目录
1、两个对象值相同(x.equals(y) == true)
,但却可有不同的 hashcode
,这句话对不对?
不对,如果两个对象 x
和 y
满足 x.equals(y) == true
, 它们的哈希码(hash code)应当相同。Java 对于 equals 方法和hashCode
方法是这样规定的:
- 如果两个对象相同(
equals
方法返回true
),那么它们的hashCode
值 一定要相同。 - 如果两个对象的
hashCode
相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
补充:
实现高质量的equals
方法的诀窍包括:
- 使用==操作符检查 “参数是否为这个对象的 引用”;
- 使用
instanceof
操作符检查“参数是否为正确的类型”; - 对于类中的关键属性,检查参数传入对象的属性是否与之想匹配
- 编写完 equals 方法后,问自己是否满足对称性、传递性、一致性;
- 对称性:对于任何非空引用 x 和 y ,如果且仅当
y.equals(x)
返回 true 时,x.equals(y)
必须返回true; - 传递性: 对于任何非空引用 x、y、z,如果
x.equals(y)
返回true,y.equals(z)
返回true,则x.equals(z)
必须返回 true - 一致性:对于任何非空引用 x 和 y, 如果在 equals 比较中使用的信息没有修改,则
x.equals(y)
的多次调用必须始终返回true 或false。
- 对称性:对于任何非空引用 x 和 y ,如果且仅当
- 重写 equals 时 总是要重写hash Code;
- 为什么?
- 如果一个类没有重写equals(Object obj)方法,则等价于通过==比较两个对象,即比较的是对象在内存中的空间地址是否相等;如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。equals方法注释中的大致意思是:当我们将equals方法重写后有必要将
hashCode
方法也重写,这样做才能保证不违背hashCode
方法中“相同对象必须有相同哈希值”的约定。
- 如果一个类没有重写equals(Object obj)方法,则等价于通过==比较两个对象,即比较的是对象在内存中的空间地址是否相等;如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。equals方法注释中的大致意思是:当我们将equals方法重写后有必要将
- 为什么?
- 不要将equals 方法参数中的 Object 对象替换为其他的类型,在重写时不要忘掉@Override 注解。
2、是否可以继承String类?
跳转至目录
String类是 final 类,不可以被继承。
补充:继承String 本身就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A) 和依赖关系(Use-A)而不是继承关系(ls-A)
-
关联关系
-
概念:对象和对象之间的连接
-
定义:A类关联B类,指的是B类对象作为A类的属性存在,称为“has”关联关系
-
生命周期:如果A类关联B类,那么创建A类的对象时实例化B类的对象,直到A类对象被销毁,所关联的B类对象也被销毁
即只要A类对象存在,B类对象就存在
-
分类:关联关系分为单向关联和双向关联
- 单向关联:A类关联B类
- 双向关联:A类关联B类,B类关联A类
-
特点:如果两个互相关联的类中有整体和部分的关系,关联关系分为: 聚合和组合,主要区别在于生命周期不同
-
-
依赖关系
- 概念:指一个类使用到了另一个类
- 定义:A类依赖B类,指的是B的对象作为A类的方法参数存在,称为“use”依赖关系
- 生命周期:如果A类依赖B类,那么只有当A类对象调用到相应方法时,B类对象才被临时创建,方法执行结束,B类对象即被回收A类和B类之间的依赖关系是一种瞬时的关系
- 特性:这种关系是具有偶然性的、临时性的、非常弱的,但是类B的变化会影响到类A;具体表现在某个method方法中使用
博客园原文
3、当一个对象被当作参数传递到一个方法后,此方法可以改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
跳转至目录
是值传递。 Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对 对象引用的改变是不会影响到调用者的。
-
如果参数类型是原始类型,那么传过来的就是这个参数的一个副本,也就是这个原始参数的值,这个跟之前所谈的传值是一样的。如果在函数中改变了副本的值不会改变 原始的值.
public void swap(int a, int b){ // 传递原始类型,相当于传的是 a 和 b 的副本 int temp = a; a = b; b =temp; System.out.println("swap方法里 a==>"+a+",b==>"+b); // 2 1 } @Test public void test08() { int A = 1; int B = 2; swap(1,2); // 2 1 System.out.println("a==>"+A+",b==>"+B); // 1 2 证明:改变了副本的值不会改变 原始的值. }
-
如果参数类型是引用类型,那么传过来的就是这个引用参数的副本,这个副本存放的是参数的地址。如果在函数中没有改变这个副本的地址,而是改变了地址中的 值,那么在函数内的改变会影响到传入的参数。如果在函数中改变了副本的地址,如new一个,那么副本就指向了一个新的地址,此时传入的参数还是指向原来的 地址,所以不会改变参数的值。
class Employee{ Integer id; // 编号 Integer age; // 年龄 @Override public String toString() { return "Employee{" + "id=" + id + ", age=" + age + '}'; } } @Test public void test08() { Employee employee = new Employee(); // 实例化对象 employee.id = 9527; employee.age = 18; swapIdAndAge(employee); // 传递一份引用类型 employee 的地址值副本 System.out.println(employee); // id:18 age:18 } public void swapIdAndAge(Employee employee){ // 交换 编号(id)与 年龄(age) Employee temp1 = employee; // 将employee 地址值给 temp1,此时temp1 与employee 指向同一处引用。 /* * employee.age=18 赋值给 temp1.id后 * 因为 temp1 和 employee 指向同一个地址,所以 temp1.id = 18,employee.id = 18 */ temp1.id = employee.age; temp1.age = employee.id; // 这里employee.id = 18 ===> temp1.age=18 System.out.println(temp1); // id:18 age:18 // employee的属性初始化 employee.id = 9527; employee.age = 18; Employee temp2 = new Employee(); // 新的 new的对象,与employee的地址不是同一处。 temp2.id = employee.age; // temp2.id = 18 temp2.age = employee.id; // temp2.age = 9527 System.out.println(temp2); // id:18 age:9527 }
4、String
和 StringBuilder
、StringBuffer
的区别?
跳转至目录
Java
平台提供了两种类型的字符串:String
和 StringBuffer
/StringBuilder
,它们可以储存和操作字符串。其中 String
是只读字符串,也就意味着String
引用的字符串内容是不能改变的。而 StringBuffer
、Stringbuilder
类表示的字符串对象可以直接进行修改。StringBuilder
是 java5
中引入的,它和StringBuffer
的 方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized
修饰,因此它的效率比StringBuffer
要高。
补充:
-
什么情况下用 + 运算符进行字符串连接比调用
StringBuffer
/StringBuilder
对象的append 方法连接字符串性能更好?-
在Java中无论使用何种方式进行字符串连接,实际上都使用的是
StringBuilder
-
"+"和
StringBuilder
是完全等效的 -
如下代码,每执行一次循环,就会创建一个
StringBuilder
对象(对于本例来说,是创建了10个StringBuilder
对象),因此,在循环中使用“+”
拼接字符串会消耗更多的资源。在循环中,应使用append()
,这样消耗的资源也更少。在使用
StringBuilder
时要注意,尽量不要"+"
和StringBuilder
混着用,否则会创建更多的StringBuilder对象public class TestComplexPlus { public static void main(String[] args) { String s = ""; Random rand = new Random(); for (int i = 0; i < 10; i++) { s = s + rand.nextInt(1000) + " "; } System.out.println(s); } } public class TestStringBuilder { public static void main(String[] args) { String s = ""; Random rand = new Random(); StringBuilder result = new StringBuilder(); /*for (int i = 0; i < 10; i++) { result.append(rand.nextInt(1000)); result.append(" "); }*/ // "+"和StringBuilder混着用,会创建更多的StringBuilder对象 for (int i = 0; i < 10; i++ ) { result.append(rand.nextInt(1000) + " "); } System.out.println(result.toString()); } } ———————————————— 版权声明:本文为CSDN博主「atomic_age」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/atomic_age/article/details/1656964
-
-
String
对象 的intern
方法会得到字符串对象在常量池中对应的版本引用(如果常量池中有一个字符串与String
对象的equals
结果是true
),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中,然后返回常量池中字符串的引用。@Test public void test09(){ String str1 = new StringBuilder("pa").append("ram").toString(); // 常量池中没有 “param”,所以,调用intern() 该字符串将被添加到常量池中,然后返回常量池中字符串的引用 System.out.println(str1.intern() == (str1)); // true == 比较的是地址值 String str2 = new StringBuilder("ja").append("va").toString(); // 常量池中原本就有有 “java” System.out.println(str2.intern() == str2); // false String str3 = new StringBuilder("ja").append("va1").toString(); // 常量池中没有“java1” System.out.println(str3.intern() == (str3)); // true }
-
字符串的
+
操作其本质是创建了StringBuilder
对象进行append
操作,然后将拼接后的StringBuilder
对象用toString
放处理成String
对象,这一点可以用javap -c StringEqualTest.class
命令获得class
文件对应的JVM
字节码指令就可以看出来。
5、重载(OverLoad
) 和重写(Override
) 的区别。重载的方法能否根据返回值类型进行区分?
跳转至目录
方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
-
重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同,参数的类型的顺序不同)则被视为重载;
public void method1(int a, double b){ // 原方法, 重载与方法的返回值类型无关 System.out.println(a); System.out.println(b); } public int method1(double b, int a){ // 形参顺序不同 System.out.println(a); System.out.println(b); return 0; } public double method1(double a, int b,float c){ // 形参个数不同 System.out.println(a); System.out.println(b); System.out.println(c); return 0L; } public void method1(String a,char c ){ // 形参类型不同 System.out.println(a); System.out.println(c); }
-
重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写的方法访问访问权限要低(如父类方法是protected,子类不能是private,default,而是可以是 protected,public),不能比父类被重写方法声明更多的异常(里氏代换原则)。
class Person{ Integer id; Integer age; protected void show(){ System.out.println(id); System.out.println(age); } @Override public String toString() { return "Employee{" + "id=" + id + ", age=" + age + '}'; } } class American extends Person{ @Override protected void show() { super.show(); } // 报错 /*@Override private void show() { super.show(); }*/ }