牛客网面试宝典之JAVA基础知识(二)总结

108 阅读7分钟

● 请你讲讲数组(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 方法后被改变了,这种形为通常被描述为引用传递

blog.csdn.net/bntX2jSQfEH…

blog.csdn.net/xiaojinlai1…

● 请你解释为什么会出现4.0-3.6=0.40000001这种现象?

考察点:计算机基础

参考回答:

原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。

● 请你讲讲一个十进制的数在内存中是怎么存的?

考察点:计算机基础

参考回答:

补码的形式。

● 你知道java8的新特性吗,请简单介绍一下

考察点:java8

参考回答:

  • Lambda表达式:允许把函数作为一个方法的参数(函数作为参数传递进方法中。
  • Stream流:函数式编程,流式计算
  • 默认方法:默认方法就是一个在接口里面有了一个实现的方法。参考文章:blog.csdn.net/hello_IT_/a…
  • 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。blog.csdn.net/pipizhen_/a…

默认方法举例:

// 接口
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要判断二者地址是否相同。如果要判断两个对象是否相等,需要同时满足地址 + 属性都相同!

参考文章: jiming.blog.csdn.net/article/det…

  • 如果两个对象相同(即:用 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 相等,那么地址也一定相同”的概念