● 请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?
考察点:Array
参考回答:
Array和ArrayList的不同点:
- Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
- ArrayList 存储基本类型时,会自动装箱成对应的包装类,只存其引用,而不能存基本类型!
- Array 大小是固定的,ArrayList 的大小是动态变化的(动态扩容数组)。
- ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
扩展: ArrayList源码分析
● 请你解释什么是值传递和引用传递?
考察点:JAVA引用传递
参考回答:
- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量;
- 引用传递一般是对于对象(引用)型变量而言的,传递的是该对象地址的一个副本,,并不是原对象本身 。 所以对引用对象进行操作会同时改变原对象;
- 一般认为,java 内的传递都是值传递;
案例:
public class StringBase {
public static void main(String[] args) {
int c = 66; //c 叫做实参
String d = "hello"; //d 叫做实参
StringBase stringBase = new StringBase();
stringBase.test5(c, d); // 此处 c 与 d 叫做实参
System.out.println("c的值是:" + c + " --- d的值是:" + d);// c的值是:66 --- d的值是:hello
}
public void test5(int a, String b) { // a 与 b 叫做形参
a = 55;
b = "no";
}
}
可以看出通过方法传递后,int 类型与 String 类型的原值并没有受到前面 test5 方法执行后的影响,还是输出了原值。这种形为通常被说成值传递。如果原值经过 test5 方法后被改变了,这种形为通常被描述为引用传递。
● 请你解释为什么会出现4.0-3.6=0.40000001这种现象?
考察点:计算机基础
参考回答:
原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
● 请你讲讲一个十进制的数在内存中是怎么存的?
考察点:计算机基础
参考回答:
补码的形式。
● 你知道java8的新特性吗,请简单介绍一下
考察点:java8
参考回答:
- Lambda表达式:允许把函数作为一个方法的参数(函数作为参数传递进方法中。
- Stream流:函数式编程,流式计算
- 默认方法:默认方法就是一个在接口里面有了一个实现的方法。
- 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
默认方法举例:
// 接口
public interface MyInterface {
// 抽象方法
String abstractMethod();
// 默认方法
default String defaultMethod(){
return "MyInterface---->defaultMethod";
}
// 静态方法
static String staticMethod(){
return "MyInterface---->staticMethod";
}
}
// 接口实现类
public class MyInterfaceImpl implements MyInterface{
// 实现接口中的抽象方法
@Override
public String abstractMethod() {
return "MyInterface---->abstractMethod";
}
}
// 测试
public class MyInterfaceTest {
public static void main(String[] args) {
MyInterfaceImpl myInterface = new MyInterfaceImpl();
// 接口实现类调用接口中的默认方法
System.out.println(myInterface.defaultMethod());// MyInterface---->defaultMethod
// 接口实现类调用接口中的抽象方法
System.out.println(myInterface.abstractMethod());// MyInterface---->abstractMethod
// 直接调用接口静态方法
System.out.println(MyInterface.staticMethod());// MyInterface---->staticMethod
}
}
方法引用代码举例:
- 引用静态方法:
类名::静态方法名
- 引用某个对象的方法:
对象::实例方法
- 引用特定类型的方法:
特定类::实例方法
- 引用构造方法:
类名::new
public class Test04 {
public static void main(String[] args) {
// 引用静态方法: 类名::静态方法名
Inter1<Integer, String> inter1 = String :: valueOf;
String str1 = inter1.m1(100);
System.out.println(str1); // 100
// 引用某个对象的普通方法: 对象::实例方法
Inter2<String> inter2 = "HELLO" :: toLowerCase;
String str2 = inter2.m2();
System.out.println(str2); // hello
// 引用特定类型的方法: 特定类::实例方法
Inter3<String> inter3 = String :: compareTo;
int res = inter3.m3("aa", "bb");
System.out.println(res); // -1
// 引用构造方法: 类名::new
Inter4<Book> inter4 = Book :: new;
Book b = inter4.m4("java编程入门", 25.36);
System.out.println(b); // Book{name='java编程入门', price=25.36}
}
}
interface Inter1<T, E> {
public E m1(T t);
}
interface Inter2<T> {
public T m2();
}
interface Inter3<T> {
public int m3(T t1, T t2);
}
interface Inter4<T> {
public T m4(String s, double d);
}
class Book {
String name;
double price;
public Book() {
}
public Book(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Book{" + "name='" + name + '\'' +
", price=" + price + '}';
}
}
● 请你说明符号“==”比较的是什么?
考点:基础
参考回答:
- 如果
==
两边是基本类型,就比较数值是否相等。 - 如果
==
两边是对象类型,就对比两个对象基于内存引用,如果两个对象的引用完全相同(指向同一个对象)时返回true,否则返回false。
● 请你解释Object 中的 hashCode()是如何计算出来的?
考点:基础
参考回答:
Object 的 hashcode 方法是本地方法,也就是用 c 语言或 c++ 实现的,该方法直接返回对象的内存地址。
● 请你解释为什么重写equals还要重写hashcode?
考点:java基础
参考回答:
equals只是判断对象属性是否相同,hashcode要判断二者地址是否相同。如果要判断两个对象是否相等,需要同时满足地址 + 属性
都相同!
- 如果两个对象相同(即:用 equals 比较返回true),那么它们的 hashCode 值一定要相同;
- 如果两个对象的 hashCode 相同,它们并不一定相同(即:用 equals 比较返回 false);
举例子:
只重写 equals() 方法,不重写 hashcode() 方法:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
// 省略 get,set方法...
}
执行下面的程序看看效果:
public class hashTest {
@Test
public void test() {
Student stu1 = new Student("Jimmy",24);
Student stu2 = new Student("Jimmy",24);
System.out.println("两位同学是同一个人吗?"+stu1.equals(stu2));
System.out.println("stu1.hashCode() = "+stu1.hashCode());
System.out.println("stu1.hashCode() = "+stu2.hashCode());
}
}
执行结果:
两位同学是同一个人吗?true
stu1.hashCode() = 379110473
stu1.hashCode() = 99550389
如果重写了 equals() 而未重写 hashcode() 方法,可能就会出现两个没有关系的对象 equals 相同(因为equal都是根据对象的特征进行重写的),但 hashcode 不相同的情况。因为此时 Student 类的 hashcode 方法就是 Object 默认的 hashcode方 法,由于默认的 hashcode 方法是根据对象的内存地址经哈希算法得来的,所以 stu1 != stu2,故两者的 hashcode 值不一定相等。
根据 hashcode 的规则,两个对象相等其 hash 值一定要相等,矛盾就这样产生了。上面我们已经解释了为什么要使用 hashcode 算法,所以即使字面量相等,但是产生两个不同的 hashCode 值显然不是我们想要的结果。
2. 如果我们在重写 equals() 时,也重写了 hashCode() 方法:
public class Student {
private String name;
private int age;
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
// 省略 get,set方法...
}
执行结果:
两位同学是同一个人吗?true
stu1.hashCode() = 71578563
stu1.hashCode() = 71578563
从 Student 类重写后的 hashcode() 方法中可以看出,重写后返回的新的 hash 值与 Student 的两个属性是有关,这样就确保了对象和对象地址之间的关联性。
一句话:实现了“两个对象 equals 相等,那么地址也一定相同”的概念!