欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java 基础面试题

程序员文章站 2022-05-05 18:02:46
...

一、java 基础面试题

1.Java语言有哪些优点

  1. 纯面向对象的语言
  2. 具有平台无关性,一次编译到处运行。
  3. 类库丰富,减少开发人员程序设计工作。如支持多线程、网络通信。
  4. 提供对web应用开发的支持。如 Servlet。
  5. 安全性:java提供提供防止恶意代码攻击的安全机制(数据边界检查和Bytecode校验等)
  6. 健壮性:具有强类型机制、垃圾回收器、异常处理、安全检查机制
  7. 去除C++语言中难以理解的特性,如头文件、指针、结构、单元运算符重载、多重继承等。

2.Java与C++有什么异同

相同点: 都是面向对象的语言,都使用面向对象的思想(例如继承、封装、多态等)因此都有较好的可重用性。

不同点:

  1. java是解释型语言,java源程序经过java编译器生成字节码,然后JVM解释执行。C++为编译型语言,源代码经过编译和链接后生成可执行的二进制代码。故C++执行速度快,java可以跨平台。
  2. java为纯面向对象的语言,所有代码(包括函数变量等)必须在类中实现,除基本数据类型外其他类型都是类。java不存在全局变量或全局函数,C++兼具面向过程和面向对象的特点。可定义全局变量和全局函数
  3. java中没有指针,由于操作指针可能引起系统问题,从而使程序更加安全。
  4. java不支持多重继承但是引入接口的概念,可以实现多个接口。通过这种方式实现C++多重继承的目的。
  5. C++中开发人员需要对内存进行管理,java提供垃圾回收器进行垃圾回收。
  6. C++支持运算符重载、预处理、默认函数参数、自动强制类型转换,java不支持。
  7. java平台无关对每种数据类型分配固定的长度,C++每种数据类型在不同平台类型长度不同。
  8. java包含标准库完成特定任务如JDBC库,C++依靠非标准的其他厂商提供的库。

3.为什么需要public static void main(String[] args)方法

​ 此方法为java程序的入口方法,public表明任何类和对象都可以访问。static表明是静态方法,代码存储在静态存储区类加载后即可访问无需通过实例化对象访问。main是JVM识别的特殊方法名。args参数为开发人员在命令行状态下与程序交互提供手段。

此方法的正确写法: 只需保证使用static public 修饰 返回值为void。

  1. public static void main(String[] args)
  2. public static final void main(String[] args)
  3. static public void main(String[] args)
  4. static public synchronized void main(String[] args)

4.如何实现在main()方法执行前输出"hello world"

​ 利用静态代码块。

5.java程序初始化的顺序是怎样的

​ java语言中,当实例化对象时,对象所在类的所有成员变量首先初始化,当所有类成员完成初始化后回调用构造函数进行初始化。

初始化原则:

  1. 静态对象(变量)优先于非静态对象(变量)初始化,静态对象(变量)只初始化一次,非静态对象(变量)可能被初始化多次。
  2. 父类优于子类被初始化。
  3. 按照成员变量定义顺序进行初始化。

执行顺序:父类静态变量、父类静态代码块、子类静态变量、子类静态代码块、父类非静态变量、父类非静态代码块、父类构造函数、子类非静态变量、子类非静态代码块、子类构造函数。

public class A extends B{
	static {
		System.out.println("load A");
	}
	public A() {
		System.out.println("create A");
	}
	public static void main(String[] args) {
		new A();
	}
}
class B{
	static {
		System.out.println("load B1");
	}
	public B() {
		System.out.println("create B");
	}
	static {
		System.out.println("load B2");
	}
}

输出:

load B1
load B2
load A
create B
create A

6.Java中的作用域有哪些

成员变量与方法的作用域:

作用域 当前类 同一package 子类 其他package
public yes yes yes yes
private yes no no no
protected yes yes yes no
default yes yes no no

ps:以上修饰符只能修饰成员变量和方法不能修饰局部变量!!private和protected不能修饰类

java中的变量类型:

1.成员变量:成员变量的作用范围和类的实例化对象的作用范围一致,当类被实例化时成员变量就会在内存中分配空间并初始化,直至这个被实例化对象的生命周期结束,成员变量的生命周期也结束。

2.静态变量:被static修饰的成员变量,不依赖特定的实例,被所有实例共享,当类加载时虚拟机就会给类的变量分配空间。可以通过类名.变量名的方式访问。

3.局部变量:其可见性局限于所在花括号内。

7.一个java文件中是否可以定义多个类

​ 可以,但是最多只有一个类能被public修饰,并且这个类的类名和文件名必须相同,若这个文件中没有public的类,则文件名随便是一个类的名字即可。

​ 当使用javac指令编译这个.java文件时,会为每一个类生成一个对应的.class文件。

8.什么是构造函数

​ 一个特殊的函数,主要用于在对象实例化时初始化对象的成员变量。

特点:

1.构造函数名必须与类名相同,不能有返回值(也不能是void)。

2.每个类可以有多个构造函数,当开发人员未提供构造函数时,编译器在编译阶段会提供一个无参的默认构造函数。若提供构造函数,那么编译器不会创建无参的默认构造函数。

3.构造函数可以有任意个参数。

4.构造函数在对象实例化时被系统调用(只能通过new自动调用),不能由程序编写者直接调用。只运行一次(普通方法是程序运行到它时调用,可以被调用多次)。

5.构造函数不能被继承,所以不能被覆盖。但是可以被重载,可以使用不同的参数个数或参数类型来定义构造函数。

6。子类可以通过super关键词来显式调用父类的构造函数,当父类没有提供无参的构造函数时,子类的构造函数中必须显式的调用父类的构造函数。如果父类中提供了无参的构造函数,此时子类的构造函数就可以不显式的调用父类的构造函数,在这种情况下编译器会默认调用父类提供的无参构造函数。当有父类时,实例化对象时会先执行父类的构造函数然后执行子类的构造函数。

7.默认构造函数的修饰符与当前类的修饰符相同。

9.为什么Java中有些接口没有任何方法

​ 没有任何方法声明的接口被叫做标识接口,标识接口对实现它的类没有任何语义上的要求,仅仅充当一个标识的作用,用来表明实现它的类属于一个特定的类型。java类库中已存在的标识接口有Cloneable和Serializable等。可以用instanceof关键字来判断实例对象的类型是否实现了一个给定的标识接口。

**补:**java中不支持多重继承,为了克服单继承的缺点引入接口的概念。接口是抽象方法定义的集合,是特殊的抽象类。接口中只有方法的定义,没有方法的实现。接口中所有的方法都是抽象的。接口的修饰符只能是public和abstract。接口中成员的修饰符都为public,接口中常量默认使用public static final修饰。一个类可以实现多个接口从而达到多重继承的效果。

10.说说clone()方法有啥用,谈谈浅克隆和深克隆的区别

**前言:**Java在处理基本数据类型时采用的是值传递(传递的是值得复制)。除此以外其他类型均采用引用传递(传递的是对象的引用)。一个对象在作为函数参数调用时采用引用传递,使用"="赋值时也是引用传递。

**clone()方法的介绍:**实际编程中,当需要从已有对象a中创建一个与a具有相同状态的对象b时可以用到clone()方法。Object类中提供一个clone()方法,其作用为返回Object对象的复制,这个返回对象是一个新的对象而不是一个引用。

使用clone()方法的步骤如下:

  1. 使用clone()方法的类需要实现Cloneable接口,这个接口仅仅起到标识作用。
  2. 在此类中重写clone()方法。

浅克隆代码示例

public class Low implements Cloneable{
	public String name; //String 属性
	public int a;
	public Target target;// 引用类型
	public Low() {
		super();
	}
	//浅拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object low = null;
		//这里完成对基本数据类型(属性)和String的克隆。
		low = super.clone(); 
		Low res = (Low)low;
		return res;
	}
}

​ 这里我们假设原对象为A,根据以上克隆方法返回对象B,此时对B修改基本数据类型a和String类型的name不会对A中的a和name造成影响。而当B修改引用类型target时也会对A中的target进行一样的修改。说明A和B中的target是引用相同的对象。而要让他们分别引用不同的对象见深克隆。

深克隆代码示例

public class Deep implements Cloneable{
	public String name; //String 属性
	public int a;
	public Target target;// 引用类型
	public Deep() {
		super();
	}
	//深拷贝
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object deep = null;
		//这里完成对基本数据类型(属性)和String的克隆。
		deep = super.clone(); 
		Deep res = (Deep)deep;
		//对引用类型的属性,进行单独处理
		res.target  = (Target)target.clone();
		return res;
	}
}

11.什么是反射机制

反射机制主要作用:

  1. 得到一个对象所属的类;

  2. 获取一个类的所有成员变量和方法;

  3. 在运行时创建对象;

  4. 在运行时调用对象的方法;

​ 反射是java语言中重要的特性,允许程序在运行进行自我检查同时也允许对其内部成员进行操作。能够实现在运行时对类进行装载增加程序灵活性。

如何获取class类:

  1. class.forName(“类名”)

  2. 类名.class

  3. 实例.getClass()

Java中创建对象的方法:

  1. 通过new语句实例化一个对象
  2. 通过反射机制创建一个对象
  3. 通过clone()方法创建一个对象
  4. 通过反序列化的方法创建一个对象

12.面向对象有哪些特征

​ 抽象、继承、封装、多态。

  1. 抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分的注意与当前目标有关的方面。抽象并不为了解决全部问题,而是选择其中的一部分,暂时不用部分细节。抽象包括两个方面:一是过程抽象;二是数据抽象。
  2. 继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供一种明确表示共性的方法。对象的一个新类可以从现有的类中派生,这个过程叫做继承。新类继承了原始类的特性,新类成为原始类的派生类(子类),原始类称为新类的基类(父类)。派生类可以从他的基类继承方法和实例变量,并且派生类可以修改或增加新的方法使之更适合特殊的需求。
  3. 封装:封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
  4. 多态:多态是指允许不同类的对象对同一消息作出相应。多态包括参数化多态和包含多态。多态语言具有灵活、抽象、行为共享、代码共享等优势,解决了应用程序函数同名的问题。

13.多态的实现机制是什么

​ 多态是面向对象设计中代码重用的重要机制,它表示当同一个操作作用在不同对象时,会有不同的语义,从而会产生不同的结果。主要有两种表现方式:

1.重载:当一个类有多个同名方法时,但这些方法有不同的参数,因此在编译时可以确定到底调用哪个方法,它是一种编译时多态。重载可以被看做一个类中的方法多态性

2.重写/覆盖:子类可以覆盖父类的方法,因此同样的方法会在父类与子类中有不同的表现。父类的引用变量不仅可以指向父类的实例变量,也可以指向子类的实例变量。同样接口的引用变量也可以指向其实现类的实例变量。程序调用的方法在运行时动态绑定。通过动态绑定实现多态。由于只有运行时才能知道具体调用哪个类中的方法,因此通过重写实现的多态可被称为运行时多态

14.重载和重写的区别

重载是在一个类中多态性的一种表现,是指在一个类中定义多个同名的方法,他们或有不同的参数个数或有不同的参数类型。

  1. 重载是通过不同的方法参数进行区分,如不同的参数个数,不同的参数类型或不同的参数顺序。

  2. 不同通过改变方法的访问权限,返回值类型和抛出的异常类型来进行重载。

重写是指子类函数覆盖父类函数。使用时有以**意点。

  1. 参数列表必须完全与被重写方法的相同。
  2. 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  3. 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  4. 父类的成员方法只能被它的子类重写。
  5. 声明为 final 的方法不能被重写。
  6. 声明为 static 的方法不能被重写,但是能够被再次声明。
  7. 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  8. 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  9. 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  10. 构造方法不能被重写。
  11. 如果不能继承一个方法,则不能重写这个方法。

总结:

  1. 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法重载
  2. 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写
class Super{
	public int f(){
        return 1;
	}
}
public class SubClass extends Super{
    public float f(){
        return 2f;
    }
    public static void main(String[] args){
        Super s = new SubClass();
        System.out.println(s.f());
    }
}
会编译出错。

15.组成和继承有什么区别

​ 组合和继承是面向对象中两种代码复用的方式。组合是指在新类里面创建原有类的对象,重复利用已有类的功能。继承是面向对象的主要特性之一,它允许设计人员根据其他类的实现来定义一个类的实现。组合和继承都允许在新的类中设置子对象,只是组合是显式的,继承是隐式的。组合和继承存在着对应关系:组合中的整体类和继承中的子类对应,组合中的局部类和继承中的父类对应。

继承是is-a的关系,如Car是Vehicle的一种:

class Vehicle{
    
}
public class Car extends Vehicle{
    
}

组合是has-a的关系,如一个Car有轮胎:

class Tire{
    
}
public class car{
    Tire tire = new Tire();
}

继承和组合的选择原则:除非是is-a的关系,否则慎用继承,继承会破坏代码的可维护性,父类被修改时子类也会受影响。不要仅仅为了多态使用继承,如果没有is-a的关系可以通过实现接口与组合达到同样的目的。总之,能用组合就尽量不用继承。

16.内部类有哪些

​ 在java中,可以把一个类定义在另一个类的内部,在类里面的这个累叫做内部类,外面的类叫做外部类。此时内部类可以看做外部类的一个成员(与类的属性和方法类似)。还有一种类称为顶层类,指的是定义代码不嵌套在其他类定义中的类。

​ 内部类有四种:静态内部类、成员内部类、局部内部类、匿名内部类。

public class Outer {
	//静态内部类
	static class Inner1{}
	//成员内部类
	class Inner2{}
	//局部内部类
	public void func() {
		class Inner3{}
	}
}
//匿名内部类
abstract class Person {
    public abstract void eat();
}
public class Demo {
    public static void main(String[] args) {
        Person p = new Person() {
            public void eat() {
                System.out.println("eat something");
            }
        };
        p.eat();
    }
}
  1. 静态内部类:被声明为static的内部类,他可以不依赖于外部类实例而被实例化。而通常的内部类需要在外部实例化后才能实例化。静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问外部类中的静态方法和静态成员。
  2. 成员内部类:静态内部类去掉static关键字即为成员内部类,因为是非静态内部类它可以*的引用外部类的属性和方法,无论这些属性和方法是静态的还是非静态的。但是它与一个实例绑定在了一起,不可以定义静态的属性和方法。只有在外部类被实例化后这个内部类才能被实例化,非静态内部类不能有静态成员。
  3. 局部内部类:它是定义在一个代码块中的类,其作用范围为其所在代码块。局部内部类如同局部变量一样不能被public、static、protected、private修饰。
  4. 匿名内部类:一种没有类名的内部类,不使用关键字class、extends、implements、没有构造函数,它必须继承其他类或实现其他接口。其好处是使代码更紧凑但是降低可读性,有以下原则需注意下:
    1. 匿名内部类不能有构造函数
    2. 匿名内部类不能定义静态成员、方法、类
    3. 匿名内部类不能是public、protected、private、static
    4. 只能创建匿名内部类的一个实例
    5. 一个匿名内部类一定new的后面,这个匿名类必须继承一个父类或是实现一个接口
    6. 匿名内部类为局部内部类,局部内部类的限制对匿名内部类也生效。

17.volatile有什么用

​ 在用java语言编写的程序中,有时为了提高程序运行效率,编译器会自动对其进行优化,把经常被访问的变量缓存起来,程序在读取这个变量时有可能直接从缓存(如寄存器)中读取这个值。这样做在单线程中可以提高效率,但是在多线程中,变量的值可能已经被其他线程修改,而缓存中的值未改变,从而造成程序读取的值和实际变量的值不一致的情况。

​ volatile是一个类型修饰符,它用来修饰被不同线程访问的变量。被volatile修饰的变量,系统每次用到它时都直接从对应内存中读取而不会借助缓存。所以被volatile修饰的变量在任何时候被不同线程读取都是相同的。

​ volatile不保证操作的原子性,所以volatile不能替代synchronized。此外volatile会阻止编译器的优化会降低程序的执行效率。(假如要基于得到的数值进行修改,那么操作有三步:获取–计算–写入。但是这三步不是原子性的,volatile不能保证原子性操作)。

18."==",equals和hashCode有什么区别

  1. "":如果两个变量是基本数据类型,可以直接使用"“运算符比较对应的值是否相等。如果一个变量指向的是引用类型,那么此时涉及两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存。如String s = new String(),变量s占用一块内存,new String()占用一块堆内存。s内存中存储的数值就是对象所占用内存的首地址。对于指向对象类型的变量如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应内存中的数值是否相等(这两个对象是否指向同一存储空间),这时候可以使用”"运算符进行比较,但是要是比较两对象内容是否一致“”无法实现。
  2. equals是Object类提供的方法之一,Object类中定义的equals(Object)方法是直接使用"“运算符比较,所以在没有重写equals方法的情况下,equals和”"没什么差别。但是通过重写equals方法可以实现比较两个对象中内容的比较。
  3. hashCode()方法是从Object那里继承而来,也用于鉴定两个对象是否相等。Object类中的hashCode()方法返回对象在内存中地址转换成的int值,所以假如没有重写hashCode方法其返回值均不相同。

**ps:**一般覆盖equals方法时也要覆盖hashCode方法,原则一般为:若x.equals(y)为true,那么x,y调用hashCode方法要返回相同数值。若x.equals(y)为false,那么x,y调用hashCode方法返回值可以相等也可以不相等。(在hashmap中插入数值需要先判断key是否已经存在,此时的判断条件为利用equals或hashCode方法判断只要有一个方法判断出不等即可认为不重复)

19.正确理解值传递和引用传递

​ java中处理8中基本数据类型用的是值传递。其他所有类型都是引用传递,但是请注意8种基本类型的包装类都是不可变量,所以要对值传递和引用传递正确理解才行。如下例子:

public class Test{
    public static void changeStringBuffer(StringBuffer ss1,StringBuffer ss2){
		ss1.append(" world");
		ss2 = ss1;
	}
	public static void main(String[] args){
        Integer a = 1;
        Integer b = a;
        b++;
        System.out.println(a);
        System.out.println(b);
        StringBuffer s1 = new StringBuffer("hello");
        StringBuffer s2 = new StringBuffer("hello");
        changeStringBuffer(s1,s2);
        System.out.println(s1);
        System.out.println(s2);
	}
}
1
2
hello world
hello

**总结:**call by value不会改变实际参数的值。call by reference不能改变实际参数地址,能改变实际参数的内容。

20.强制类型转换的注意事项

​ Java语言在涉及byte、short和char类型的远算时,首先会把这些类型的变量值强制转换成int类型,然后对int类型的值进行计算,最后的到结果也是int型;如果把两个byte类型的值相加最后也会得到itn类型的值。如果要得到short类型的结果,就必须进行显式转换否则会报错。如下:

short i = 1;
i = (short)(i+1);

21.什么是不可变类

​ 不可变类是指当创建了这个实例后,就不允许修改它的值了。在java类库中,所有基本类型的包装类都是不可变类,String也是不可变类。平时我们对String类型对象进行修改操作时其实并没有修改原字符串而是创建一个值等于修改后的字符串,并把原String对象指向新的字符串。原来的值依然存在内存未被改变。

​ 要创建一个不可变类有四条原则:

  1. 类中所以成员被private所修饰
  2. 类中没有修改成员变量的方法,例如set方法,只提供构造函数,一次生成永不改变。
  3. 确保类中所有方法不会被子类覆盖,可以通过类定义为final或者方法用final修饰实现。
  4. 如果一个类成员不是不可变量,那么在成员初始化或者使用get方法获取该成员变量时,需要通过clone方法确保类的不可变性。见下例子:
//一个错误示范
import java.util.Date;
public class Test{
    public static void main(String[] args) {
		Date d = new Date();
		A a = new A(d);
		a.printState();
		d.setYear(22);
		a.printState();
	}
}
class A{
	private Date d;
	public A(Date d) {
		this.d = d;
	}
	public void printState() {
		System.out.println(d);
	}
}
Sun Dec 29 19:25:55 CST 2019
Fri Dec 29 19:25:55 CST 1922
//正确做法
import java.util.Date;
public class Test{
    public static void main(String[] args) {
		Date d = new Date();
		A a = new A(d);
		a.printState();
		d.setYear(22);
		a.printState();
	}
}
class A{
	private Date d;
	public A(Date d) {
		this.d = (Date) d.clone(); //解除引用关系
	}
	public void printState() {
		System.out.println(d);
	}
}
Sun Dec 29 19:28:11 CST 2019
Sun Dec 29 19:28:11 CST 2019

22、String的创建和存储

String 被声明为 final,因此它不可被继承。是不可变类。

在 Java 8 中,String 内部使用 char 数组存储数据。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
}

在 Java 9 之后,String 类的实现改用 byte 数组存储字符串,同时使用 coder来标识使用了哪种编码。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final byte[] value;

    /** The identifier of the encoding used to encode the bytes in {@code value}. */
    private final byte coder;
}

String类作为不可变类的好处:

1. 可以缓存 hash 值

因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,因此只需要进行一次计算。

2. String Pool 的需要

如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool。

java 基础面试题

3. 安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。

4. 线程安全

String 不可变性天生具备线程安全,可以在多个线程中安全地使用。

String Pool

字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程将字符串添加到 String Pool 中。

当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。

下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得同一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。

String s1 = new String("aaa");
String s2 = new String("aaa");
System.out.println(s1 == s2);           // false
String s3 = s1.intern();
String s4 = s1.intern();
System.out.println(s3 == s4);           // true

如果是采用 “bbb” 这种字面量的形式创建字符串,会自动地将字符串放入 String Pool 中。

String s5 = "bbb";
String s6 = "bbb";
System.out.println(s5 == s6);  // true

在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。

new String(“abc”)

使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 “abc” 字符串对象)。

  • “abc” 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 “abc” 字符串字面量;
  • 而使用 new 的方式会在堆中创建一个字符串对象。

以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。

public String(String original) {
    this.value = original.value; //value是byte[]类型
    this.hash = original.hash;
}

23.Java 异常的体系结构及分类

Java 异常的概念:

Java语言在设计的当初就考虑到这些问题,提出异常处理的框架的方案,所有的异常都可以用一个异常类来表示,不同类型的异常对应不同的子类异常(目前我们所说的异常包括错误概念),定义异常处理的规范,在JDK1.4版本以后增加了异常链机制,从而便于跟踪异常。

Java异常是一个描述在代码段中发生异常的对象,当发生异常情况时,一个代表该异常的对象被创建并且在导致该异常的方法中被抛出,而该方法可以选择自己处理异常或者传递该异常。

Java 异常的体系结构:

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。

在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

Java异常层次结构图如下图所示:

java 基础面试题

从图中可以看出所有异常类型都是内置类Throwable的子类,因而Throwable在异常类的层次结构的顶层。接下来Throwable分成了两个不同的分支,一个分支是Error,它表示不希望被程序捕获或者是程序无法处理的错误。另一个分支是Exception,它表示用户程序可能捕捉的异常情况或者说是程序可以处理的异常。其中异常类Exception又分为运行时异常(RuntimeException)和非运行时异常。Java异常又可以分为不受检查异常(Unchecked Exception)和检查异常(Checked Exception)。下面将详细讲述这些异常之间的区别与联系:

ErrorError类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。

Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。。

注意

ErrorException的区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。

检查异常与非检查异常

  1. 检查异常:在正确的程序运行过程中,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。
提示:除了RuntimeException及其子类以外,其他的Exception类及其子类都属于这种异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
  1. 不受检查异常:包括RuntimeException及其子类和Error
 提示:不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。

24.泛型是什么,有什么好处

是什么:

泛型其实就是在定义类、接口、方法的时候不局限地指定某一种特定类型,而让类、接口、方法的调用者来决定具体使用哪一种类型的参数。泛型在编译期间能发现类型的错误,防止非定义的类型出现。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

好处:

  1. 泛型简单易用
  2. 类型安全 泛型的主要目标是实现java的类型安全。 泛型可以使编译器知道一个对象的限定类型是什么,这样编译器就可以在一个高的程度上验证这个类型
  3. 消除了强制类型转换 使得代码可读性好,减少了很多出错的机会
  4. Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

,很容易出现的、情理可容的异常状况,在一定程度上这种异常的发生是可以预测的,并且一旦发生该种异常,就必须采取某种方式进行处理。

提示:除了RuntimeException及其子类以外,其他的Exception类及其子类都属于这种异常,当程序中可能出现这类异常,要么使用try-catch语句进行捕获,要么用throws子句抛出,否则编译无法通过。
  1. 不受检查异常:包括RuntimeException及其子类和Error
 提示:不受检查异常为编译器不要求强制处理的异常,检查异常则是编译器要求必须处置的异常。

24.泛型是什么,有什么好处

是什么:

泛型其实就是在定义类、接口、方法的时候不局限地指定某一种特定类型,而让类、接口、方法的调用者来决定具体使用哪一种类型的参数。泛型在编译期间能发现类型的错误,防止非定义的类型出现。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

好处:

  1. 泛型简单易用
  2. 类型安全 泛型的主要目标是实现java的类型安全。 泛型可以使编译器知道一个对象的限定类型是什么,这样编译器就可以在一个高的程度上验证这个类型
  3. 消除了强制类型转换 使得代码可读性好,减少了很多出错的机会
  4. Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
相关标签: Java面试题总结