Java基础面试题之基础(三)

equals 与 == 的区别?

  1. 值类型(int,char,long,boolean 等)的话

    • 都是用 == 判断相等性
  2. 对象引用的话

    • == 判断引用所指的对象是否是同一个。
    • equals 方法,是 Object 的成员函数,有些类会覆盖(override) 这个方法,用于判断对象的等价性。

例如 String 类,两个引用所指向的 String 都是 “abc” ,但可能出现他们实际对应的对象并不是同一个(和 JVM 实现方式有关),因此用 == 判断他们可能不相等,但用 equals 方法判断一定是相等的。

    public class Demo2 {
        public static void main(String[] args) {
            String demo1 = "abc";
            String demo2 = new String("abc");

            if (demo1.equals(demo2)) {
                System.out.println("相等");
            } else {
                System.out.println("不相等");
            }

            if (demo1 == demo2) {
                System.out.println("相等");
            } else {
                System.out.println("不相等");
            }
        }
    }

如何在父类中为子类自动完成所有的 hashCode 和 equals 实现?这么做有何优劣?

父类的 equals ,一般情况下是无法满足子类的 equals 的需求。

  1. 比如所有的对象都继承 Object ,默认使用的是 Object 的 equals 方法,在比较两个对象的时候,是看他们是否指向同一个地址。但是我们的需求是对象的某个属性相同,就相等了,而默认的 equals 方法满足不了当前的需求,所以我们要重写 equals 方法。
  2. 如果重写了 equals 方法,就必须重写 hashCode 方法,否则就会降低 Map 等集合的索引速度。

说一说你对 java.lang.Object 对象中 hashCode 和 equals 方法的理解。在什么场景下需要重新实现这两个方法?

答案和上一题一样

这样的 a.hashCode() 有什么用,与 a.equals(b) 有什么关系?

  1. equals 方法,用于比较对象的内容是否相等。

当覆盖了 equals 方法时,比较对象是否相等将通过覆盖后的 equals 方法进行比较(判断对象的内容是否相等)。

  1. hashCode 方法,大多在集合中用到。

将对象放入到集合中时,首先判断要放入对象的 hashCode 值与集合中的任意一个元素的 hashCode 值是否相等,如果不相等直接将该对象放入集合中。
如果 hashCode 值相等,然后再通过 equals 方法判断要放入对象与集合中的任意一个对象是否相等,如果 equals 判断不相等,直接将该元素放入到集合中,否则不放入。

有没有可能 2 个不相等的对象有相同的 hashCode?

可能会发生,这个被称为哈希碰撞。当然,相等的对象,即我们重写了 equals 方法,一定也要重写 hashCode 方法,否则将出现我们在 HashMap 中,相等的对象作为 key ,将找不到对应的 value 。

所以说,equals 和 hashCode 的关系会是:

  1. equals 不相等,hashCode 可能相等。
  2. equals 相等,请重写 hashCode 方法,保证 hashCode 相等。

final、finally、finalize 的区别?

final

  1. final,是修饰符关键字。

  2. 如果一个类被声明为 final ,意味着它不能再派生出新的子类,不能作为父类被继承。因此一个类不能既被声明为 abstract(抽象类必须被继承) 的,又被声明为 final 的。

  3. 将变量或方法声明为 final ,可以保证它们在使用中不被改变。被声明为 final 的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为 final 的方法也同样只能使用,不能重写

finally

在异常处理时提供 finally 块来执行任何清除操作。如果抛出一个异常,那么相匹配的 catch 子句就会执行,然后控制就会进入 finally 块(如果有的话)

在以下 4 种特殊情况下,finally块不会被执行:

  1. 在finally块中发生了异常
  2. 在代码中用了System.exit()退出程序
  3. 程序的所有线程死亡
  4. 关闭CPU

finalize

Java 允许使用 #finalize() 方法,在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。

  1. 它是在 Object 类中定义的,因此所有的类都继承了它。
  2. 子类覆盖 finalize() 方法,以整理系统资源或者执行其他清理工作。
  3. finalize() 方法,是在垃圾收集器删除对象之前对这个对象调用的。

抽象类和接口有什么区别?

从设计层面来说,抽象是对类的抽象,是一种模板设计,接口是行为的抽象,是一种行为的规范

  1. Java 提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:接口中所有的方法隐含的都是抽象的,而抽象类则可以同时包含抽象和非抽象的方法

  2. 类可以实现很多个接口,但是只能继承一个抽象类。类可以不实现抽象类和接口声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。

  3. 抽象类可以在不提供接口方法实现的情况下实现接口。

  4. Java 接口中声明的变量默认都是 final 的。抽象类可以包含非 final 的变量。

接口声明的变量默认都是final

    public interface InterfaceDemo {
        String a = "李四";

        public void save(String username, String password);
    }

    public class Main implements InterfaceDemo {

    @Override
    public void save(String username, String password) {
        System.out.println("实现方法");
    }

    public static void main(String[] args) {
        //cannot assign a value to final variable a;
    //        a = "2299";
            System.out.println(a);
        }
    }

抽象类中可以包含非final变量

    public abstract class Abstract {
        public static String name = "张三";

        abstract void save(String username, String password);
    }

    public class Main extends Abstract {

    @Override
    void save(String username, String password) {
        System.out.println("实现Save方法");
    }

    public static void main(String[] args) {
        System.out.println(Abstract.name);
    }
}
  1. Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public 。

  2. 接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 #main(String[] args) 方法的话是可以被调用的。

继承和组合的区别在哪?

继承:指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。在 Java 中,此类关系通过关键字 extends 明确标识,在设计时一般没有争议性。

组合:组合是关联关系的一种特例,他体现的是整体与部分、拥有的关系,即 has-a 的关系,此时整体与部分之间是可分离的,他们可以具有各自的生命周期,部分可以属于多个整体对象,也可以为多个整体对象共享。

  • 比如,计算机与 CPU 、公司与员工的关系等。
  • 表现在代码层面,和关联关系是一致的,只能从语义级别来区分

因为组合能带来比继承更好的灵活性,所以有句话叫做“组合优于继承”。

请详细讲述一下 RandomAccess 接口有什么作用?

RandomAccess 用来当标记的,是一种标记接口,接口的非典型用法。意思是,随机访问任意下标元素都比较快。

用处,当要实现某些算法时,会判断当前类是否实现了 RandomAccess 接口,会根据结果选择不同的算法。

讲讲类的实例化顺序?

初始化顺序如下:

  1. 父类静态变量

  2. 父类静态代码块

  3. 子类静态变量、

  4. 子类静态代码块

  5. 父类非静态变量(父类实例成员变量)

  6. 父类构造函数

  7. 子类非静态变量(子类实例成员变量)

  8. 子类构造函数

     class Father {
         static {     // 1
             System.out.println("Father static block");
         }
    
         {     // 5
             System.out.println("Father not static block");
         }
    
         public Father() {     // 7
             System.out.println("Father Constructor");
         }
    
         public static int i = 0;     // 2
         public int j = 0;     // 6
     }
    
     public class Test extends Father {
         public static int i = 0;     // 3
         public int j = 0;     // 8
    
         public Test() {     // 10
             System.out.println("Test Constructor");
         }
    
         {     // 9
             System.out.println("Test not static block");
         }
    
         static {     // 4
             System.out.println("Test static block");
         }
    
         public static void main(String[] args) throws Exception {
             new Test();
         }
     }
    

类的初始化

每一次的 new 都是创建一个 Object 对象,也就是 Class 类中的 newInstance() 需要 Class 对象的引用去调用,如:Test.class.newInstance();

但是 Class 可以在不实例化类的情况下将类初始化,也就是 .class 或者 Class.formName()

    public class Test2 {
        static {
            System.out.println("static");
        }

        {
            System.out.println("not static");
        }

        public static void main(String[] args) throws Exception {
            Class test = Test2.class;
        }
    }

输出

static

此时类只初始化,执行static,之后的实例化都不会再执行static

什么是内部类?

简单的说,就是在一个类、接口或者方法的内部创建另一个类。这样理解的话,创建内部类的方法就很明确了。

内部类的作用是什么?

内部类提供了更好的封装,除了该外围类,其他类都不能访问。

Anonymous Inner Class(匿名内部类)是否可以继承其它类?是否可以实现接口?

可以继承其他类或实现其他接口,在 Java 集合的流式操作中,我们常常这么干。

内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制?

一个内部类对象可以访问创建它的外部类对象的成员,包括私有成员。

JavaIO

Java IO 相关的类,在 java.io 包下,具体操作分成面向字节(Byte)和面向字符(Character)两种方式。

在这里插入图片描述

什么是Java序列化

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。

  1. 可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
  2. 序列化是为了解决在对对象流进行读写操作时所引发的问题。

反序列化的过程,则是和序列化相反的过程。

另外,我们不能将序列化局限在 Java 对象转换成二进制数组,例如说,我们将一个 Java 对象,转换成 JSON 字符串,或者 XML 字符串,这也可以理解为是序列化。

如何实现 Java 序列化?

将需要被序列化的类,实现 Serializable 接口,该接口没有需要实现的方法,implements Serializable 只是为了标注该对象是可被序列化的

序列化

  1. 使用一个输出流(如:FileOutputStream)来构造一个 ObjectOutputStream(对象流)对象

  2. 使用 ObjectOutputStream 对象的 #writeObject(Object obj) 方法,就可以将参数为 obj 的对象写出(即保存其状态)。

     public class Employee implements java.io.Serializable
     {
     public String name;
     public String address;
     public transient int SSN;
     public int number;
     public void mailCheck()
     {
         System.out.println("Mailing a check to " + name
                             + " " + address);
     }
     }
    
    
     import java.io.*;
     
     public class SerializeDemo
     {
     public static void main(String [] args)
     {
         Employee e = new Employee();
         e.name = "Reyan Ali";
         e.address = "Phokka Kuan, Ambehta Peer";
         e.SSN = 11122333;
         e.number = 101;
         try
         {
             FileOutputStream fileOut =
             new FileOutputStream("/tmp/employee.ser");
             ObjectOutputStream out = new ObjectOutputStream(fileOut);
             out.writeObject(e);
             out.close();
             fileOut.close();
             System.out.printf("Serialized data is saved in /tmp/employee.ser");
         }catch(IOException i)
         {
             i.printStackTrace();
         }
     }
     }
    

反序列化

    import java.io.*;
    
    public class DeserializeDemo
    {
    public static void main(String [] args)
    {
        Employee e = null;
        try
        {
            FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            e = (Employee) in.readObject();
            in.close();
            fileIn.close();
        }catch(IOException i)
        {
            i.printStackTrace();
            return;
        }catch(ClassNotFoundException c)
        {
            System.out.println("Employee class not found");
            c.printStackTrace();
            return;
        }
        System.out.println("Deserialized Employee...");
        System.out.println("Name: " + e.name);
        System.out.println("Address: " + e.address);
        System.out.println("SSN: " + e.SSN);
        System.out.println("Number: " + e.number);
        }
    }

Java 序列话中,如果有些字段不想进行序列化怎么办?

对于不想进行序列化的变量,使用 transient 关键字修饰。

  1. 当对象被序列化时,阻止实例中那些用此关键字修饰的的变量序列化。

  2. 当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。

  3. transient 只能修饰变量,不能修饰类和方法。

如何实现对象克隆?

一般来说,有两种方式:

  1. 实现 Cloneable 接口,并重写 Object 类中的 #clone() 方法。可以实现浅克隆,也可以实现深克隆。

  2. 实现 Serializable 接口,**通过对象的序列化和反序列化实现克隆。**可以实现真正的深克隆。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值