JAVA基础知识(二)总结

JAVA基础知识(二)总结

请你讲讲数组(Array)和列表(ArrayList)的区别?什么时候应该使用Array而不是ArrayList?

Array和ArrayList的不同点:

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型
    • ArrayList存储基本类型的时候,会自动装箱成对应的包装类,只存其引用,而不能存基本数据类型。
  • Array大小是固定的,ArrayList的大小是动态变化的(动态扩容数组)。
  • ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。对于基本数据类型,集合使用自动装箱来减少编码工作量,但是,当处理固定大小的基本数据类型的时候,这种方式相对比较缓慢。

扩展:ArrayList源码分析

请你解释什么是值传递和引用传递?

参考回答

  • 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本变量不影响原变量;
  • 引用传递一般是对于对象(引用)型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身。所以对引用对象进行操作会同时改变原对象;
  • 一般认为,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.4000000001这种现象?

参考回答

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

注意:一个十进制数字在内存中是以补码的形式存在的。

你知道JDK8的新特性吗?请简单介绍一下

参考回答

  • Lambda表达式:允许把函数作为一个方法的参数(函数作为参数传递进方法中)

  • Stream流:函数式编程,流式计算

  • 默认方法:即default修饰的方法,默认方法就是在一个接口里面有了一个实现的方法

    参考博文:JDK8接口的默认方法和静态方法

  • 方法引用:方法引用提供了非常有用的语法,可以直接引用已有JAVA类或者对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更加的紧凑简洁,减少冗余代码。

    参考博文:JDK8新特性,方法引用的四种形式

默认方法举例:

/**
 * @author wcc
 * @date 2021/9/22 14:39
 * 定义的接口
 */
public interface MyInterface {
    //抽象方法
    String abstractMethod();

    //默认方法
    default String defaultMethod(){
        return "MyInterface -------> defaultMethod";
    }

    //静态方法 在JDK8中,接口里面可以有静态方法,必须要有body,有静态方法不需要实现
    static String staticMethod(){
        return "MyInterface ------> staticMethod";
    }
}

public class MyInterfaceImpl implements MyInterface {
    //实现接口中的抽象方法
    @Override
    public String abstractMethod() {
        return "MyInterface -----> abstractMethod";
    }

    //测试
    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
/**
 * @author wcc
 * @date 2021/9/22 14:58
 * 方法引用测试
 */
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);

        //引用特定类型的方法:特定类::实例方法
        Inter3<String> inter3=String::compareTo;
        int res=inter3.m3("aa","bb");
        System.out.println(res);

        //引用构造方法:类名::new
        Inter4<Book> inter4=Book::new;
        Book book=inter4.m4("java编程入门",25.36);
        System.out.println(book);
    }
}
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方法?

参考回答

equals只是判断对象属性是否相同,hashCode要判断二者地址是否相同,如果要判断两个对象是否相等,需要同时满足地址+属性都相同!

参考文章:为什么重写equals()方法时,必须要求重写hashCode()方法

简单总结一下:

首先,equals()方法和hashCode()方法间的关系是这样的:

  • 如果两个对象相同(即:用 equals比较返回 true),那么他们的hashCode值一定要相同
  • 如果两个对象的hashCode相同,他们并不一定相同(即:用equals比较返回false)

在每个覆盖了equals()方法的类中,也必须覆盖hashCode()方法,如果不这样做的话,就会违反Object.hashCode的通用的约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括HashMap、HashSet、HashTable。

先解释下,为什么一定要使用ahshCode()方法

归根结底就是为了提高程序的效率才实现了hashCode()方法。

程序先进行ahshCode值的比较,如果不同,那就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用:

  • 我们都知道JAVA中的List集合有序,因此是可以重复的,而Set集合是无序的,因此是不能重复的。那么怎么能保证不能被放入重复的元素呢?单靠equals()方法比较的话,如果原来集合中有10000个元素,那么放入第10001个元素的时候,难道要将前面的所有元素都进行比较,看看是否有重复?这个效率可想而知。
  • 因此hashCode就应遇而生了,JAVA就采用了Hash表,利用哈希算法(也叫散列算法),就是将对象数据根据该对象的特征使用特定的算法将其定义到一个地址上,那么在后面定义进来的数据只要看对应的hashCode地址,那么就用equals进行比较,如果没有则直接插入,这样就大大减少了equals的使用次数,执行效率就大大提高了。

只重写equals()方法,不重写hashCode()方法

public class Student {
    private String name;
    private int age;

    public Student(String name, int age) {
        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;
    }
}

执行下面程序看看效果:

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相同(因为equals都是根据对象的特征进行重写的),但hashCode不相同的情况。因为此时Student类的hashCode方法就是Object默认的hashCode方法,由于默认的hashCode方法是根据对象的内存地址经过哈希算法得来的,所以stu1!=stu2,故两者的hashCode值不一定相等。

根据hashCode的规则,两个对象相等其hash值一定要相等,矛盾这样就有产生了。上面解释了为什么要使用 hashCode算法,所以即使字面量相等,但是产生两个不同的hashCode值显然不是我们想要的结果。

如果我们在重写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相等,那么地址也一定相同的概念

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值