每日五题-java面试题220802



1、两个对象值相同(x.equals(y) == true),但却可有不同的 hashcode,这句话对不对?

不对,如果两个对象 xy 满足 x.equals(y) == true, 它们的哈希码(hash code)应当相同。Java 对于 equals 方法和hashCode 方法是这样规定的:

  • 如果两个对象相同(equals 方法返回true),那么它们的 hashCode 值 一定要相同。
  • 如果两个对象的hashCode 相同,它们并不一定相同。当然,你未必要按照要求去做,但是如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在 Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。

补充:

实现高质量的equals 方法的诀窍包括:

  1. 使用==操作符检查 “参数是否为这个对象的 引用”;
  2. 使用 instanceof 操作符检查“参数是否为正确的类型”;
  3. 对于类中的关键属性,检查参数传入对象的属性是否与之想匹配
  4. 编写完 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。
  5. 重写 equals 时 总是要重写hash Code;
    • 为什么?
      • 如果一个类没有重写equals(Object obj)方法,则等价于通过==比较两个对象,即比较的是对象在内存中的空间地址是否相等;如果重写了equals(Object obj)方法,则根据重写的方法内容去比较相等,返回true则相等,false则不相等。equals方法注释中的大致意思是:当我们将equals方法重写后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。
  6. 不要将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、StringStringBuilderStringBuffer 的区别?

跳转至目录
Java 平台提供了两种类型的字符串:StringStringBuffer/StringBuilder,它们可以储存和操作字符串。其中 String 是只读字符串,也就意味着String引用的字符串内容是不能改变的。而 StringBufferStringbuilder 类表示的字符串对象可以直接进行修改。StringBuilderjava5 中引入的,它和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();
        }*/
    }
    

在这里插入图片描述
源链接-掘金

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值