Java基础面试题第三弹

Java基础面试题第三弹

1. char型变量能不能存储一个中文汉字,为什么

  1. char类型可以存储一个中文汉字,因为Java使用的编码是Unicode【不选择任何特定的编码,直接使用字符在字符集中的编号,这是统一的唯一方法】。一个char类型占2个字节【16比特】,所以放一个中文是没问题的。
  2. 使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内部都是Unicode。当这个字符被从JVM内部转移到外部时【例如,存入文件系统中】,需要进行编码转换。
  3. 所以Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如InputStreamReaderOutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务。

2. 抽象类和接口有什么异同

  1. 抽象类和接口都不能够被实例化,但可以定义抽象类或者接口的引用。
  2. 一个类如果继承了某个抽象类或实现了某个接口,都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类。
  3. 接口比抽象类更加抽象:
    1. 抽象类中可以定义构造器、可以有抽象方法和具体方法,成员可以是private、默认、protected、public,可以定义成员变量;
    2. 接口中不能定义构造器而且其中的方法全部都是抽象方法,成员全都是public,定义的成员变量实际上都是常量;
    3. 有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。

3. 静态内部类和内部类的异同

  1. 静态内部类是被声明为静态的内部类,不依赖与外部类实例而可以被实例化;
  2. 普通静态内部类需要外部类实例化后才能被实例化;
/**
 * @author: zipeng Li
 * 2021/5/25  15:15
*/
class Test {
 public static void main(String[] args) {
     Poker poker = new Poker();
     poker.shuffle(); // 洗牌
     Poker.Card c1 = poker.deal(0); // 发第一张牌
     // 对于非静态内部类 Card
     // 只有通过其外部类 Poker 对象才能创建 Card 对象
     Poker.Card c2 = poker.new Card("红心", 1); // 自己创建一张牌
     System.out.println(c1); // 洗牌后的第一张
     System.out.println(c2); // 打印: 红心 A
 }
}

/**
 * 扑克类(一副扑克)
*/
class Poker {
 private static String[] suites = {"黑桃", "红桃", "草花", "方块"};
 private static int[] faces = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13};
 private Card[] cards;

 /**
     * 构造器
  */
 public Poker() {
     cards = new Card[52];
     for (int i = 0; i < suites.length; i++) {
         for (int j = 0; j < faces.length; j++) {
             cards[i * 13 + j] = new Card(suites[i], faces[j]);
         }
     }
 }

 /**
     * 洗牌 (随机乱序)
  */
 public void shuffle() {
     for (int i = 0, len = cards.length; i < len; i++) {
         int index = (int) (Math.random() * len);
         Card temp = cards[index];
         cards[index] = cards[i];
         cards[i] = temp;
     }
 }

 /**
     * 发牌
  *
     * @param index 发牌的位置
  */
 public Card deal(int index) {
     return cards[index];
 }

 /**
     * 卡片类(一张扑克)
     * [内部类] * @author 骆昊
  */
 public class Card {
     private String suite; // 花色
     private int face; // 点数

     public Card(String suite, int face) {
         this.suite = suite;
         this.face = face;
     }

     @Override
     public String toString() {
         String faceStr = "";
         switch (face) {
             case 1:
                 faceStr = "A";
                 break;
             case 11:
                 faceStr = "J";
                 break;
             case 12:
                 faceStr = "Q";
                 break;
             case 13:
                 faceStr = "K";
                 break;
             default:
                 faceStr = String.valueOf(face);
         }
         return suite + faceStr;
     }
 }
}

4. 下面的代码哪些地方会产生编译错误

class Outer{
    class Inner {}
    public static void foo() { new Inner(); } // 1. 编译错误
    public void bar() { new Inner(); } // 2.
    public static void main(String[] args) {
        new Inner(); // 3. 编译错误
    }
}

注意:

  1. Java中非静态内部类对象的创建要依赖其外部类对象。
  2. 上述的位置1 和 位置3 都是在静态方法内部,而静态方法内部是没有this的,也就是说没有所谓的外部类对象,因此无法创建内部类对象。
  3. 如果需要在静态方法内创建内部类对象,可以这样做:
new Outer().new Inner();

5. Java中会存在内存泄漏吗,请简单描述

  1. 理论上,Java有垃圾回收机制,不会存在内存泄漏问题,这也是Java被广泛使用于服务器端编程的一个重要原因;
  2. 然而实际开发中,可能会存在无用但是可达的对象,这些对象不能被GC回收,因此也会导致内存泄漏的发生。
  3. 如Hibernate的Session【一级缓存】中的对象属于持久态,垃圾回收器是不会回收这些对象的。然而这些对象中可能存在无用的垃圾对象,如果不及时关闭或者清空一级缓存就可能导致内存泄漏。
  4. 在下面的例子中会导致内存泄漏:
class MyStack<T> {
    private T[] elements;
    private int size = 0;
    private static final int INIT_CAPACITY = 16;

    public MyStack() {
        elements = (T[]) new Object[INIT_CAPACITY];
    }

    public void push(T elem) {
        ensureCapacity();
        elements[size++] = elem;
    }

    public T pop() {
        if (size == 0)
            throw new EmptyStackException();
        return elements[--size]; // 导致内存泄漏的关键
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}
  1. 当使用pop()弹出栈中的对象时,该对象不会被当做垃圾回收,即使使用栈的程序不再引用这些对象,因为栈内部维护着这些对象的过期引用。
  2. 在支持垃圾回收的语言中,内存泄漏是很隐蔽的,这种内存泄漏其实就是无意识的对象保持。如果一个对象引用被无意识的保留,那么垃圾回收就不会处理该对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也有可能导致很多对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发【物理内存和硬盘的虚拟内存交换数据】,甚至造成OOM异常。

6. 抽象方法是否可以被static、native、synchronized修饰

答:都不能;

  1. 抽象方法需要子类重写,而静态方法无法被重写,因此二者矛盾;
  2. 本地方法是由本地代码【如C语言】实现的方法,而抽象方法是没有实现的,因此二者矛盾;
  3. synchronized和方法的实现细节有关,而抽象方法不涉及实现细节,因此二者矛盾。

7. 说说静态变量与实例变量的区别

答:

  1. 静态变量是被static修饰符修饰的变量,也称为类变量,是属于类的,且不属于类的任何一个实例,静态变量在内存中有且仅有一个拷贝,可以实现多个对象共享内存;
  2. 实例变量必须依存于某一个实例,需要先创建对象然后通过对象才能访问到它;
  3. 在Java开发中,上下文类和工具类通常会有大量的静态成员。

8. 是否可以在静态方法中调用非静态方法

答:不可以;

静态方法只能调用静态成员,而非静态方法的调用需要依赖实例对象。在调用静态方法时,类实例可能还没有被创建。

9. 如何实现对象克隆

答:有两种方式:

  1. 实现Cloneable接口并重写Object类的clone()方法
  2. 实现Serializable接口,通过对象的序列化和反序列化可以实现真正的深度克隆。
class MyUtil {
    private MyUtil() {
        throw new AssertionError();
    }

    @SuppressWarnings("unchecked")
    public static <T extends Serializable> T clone(T obj) throws
            Exception {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bout);
        oos.writeObject(obj);
        ByteArrayInputStream bin = new
                ByteArrayInputStream(bout.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bin);
        return (T) ois.readObject();
        // 说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream对象的 close 方法没有任何意义
        // 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,这一点不同于对外部资源(如文件流)的释放
    }
}

注意:

  1. 基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化;
  2. 这些检查是编译器完成的,而不是在运行时抛出异常;
  3. 这种方案明显优于使用Object类的clone方法克隆对象,让问题再编译的时候暴露出来总是好过把问题留到运行时。

10. String s = new String(“xyz”);创建了几个字符串对象

答:两个对象;一个是在静态区的“xyz”;一个是用 new 在堆上创建的对象。

后序

  • 我是一名大三本科生,专业是软件工程【一本】。目前,正在准备找实习以及秋招,意向岗位是Java后端开发工程师。为此,在码云托管了一个项目,以整理我所有所学知识。涉及内容:计算机网络、操作系统、Java基础、主流Java后端框架、设计模式、Web前端框架等内容。欢迎大家访问我的开源项目编程之路
  • 码云地址:https://gitee.com/alizipeng/the-way-of-programming
  • 以上内容均记载在我的开源项目中
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值