Java面试系列 — 基础篇(二)
先整理出一批面试笔试面试题。后续将继续更新,如果本文中出现问题,请及时与蛐蛐联系,蛐蛐马上继续修改,后续也会同步更新。
static关键字
- static方法的作用
- 操作静态成员、静态变量和静态方法
- static方法的访问方式
- 通过类名调用
- 通过对象名调用
注意事项:
静态方法中不可以调用非静态方法
静态方法中不可以使用this
静态方法不可以调用非静态方法
原因: 从时间角度考虑,先有静态方法,而非静态方法在创建对象时才有,所以当静态方法调用非静态方法时,可能非静态方法还不存在。
备注: 非静态方法调用静态方法正好相反!
static代码块
- 局部代码块:
- 位置:定义在方法中
- 数量:可以是一个,也可以是多个
- 时间:调用对应方法时自上而下依次执行各个局部代码块
- 局部代码块中定义的变量,作用范围当前局部代码块
- 代码块
- 位置:定义在类中
- 数量:一个类中可以有多个代码块
- 时间:每次创建对象时先自上而下依次执行各个代码块,然后执行构造方法
- 代码块中定义的变量,作用范围当前代码块
- 意义:使用的很少;一般将各个构造方法中的重复的代码提取出来放入代码块
- static 代码块
- 位置:定义在类中
- 数量:一个类中可以有多个static代码块
- 时间:第一次加载类的时候执行静态代码块,并且只执行一次
- 语法作用:给静态变量赋初始值,调用静态方法
- 实际应用场合:做一些全局性的初始化操作:统计网站人数,每次访问人数+1.启动时给网站人数初始值。
封装好处
- 简单方便,降低了操作难度
- 安全
继承的好处
- 避免代码的重复
==和equals的区别和联系
- ==
- 都是比较的栈内存的内容
- 对于基本数据类型,比较的是变量的值(数据)
- 对应引用数据类型,比较的是变量的值(地址)
- equals()
- 用来比较对象的内容是否相同,比较是堆内存的数据
- 对于Object来说,equals比较是地址,实质上是使用==进行比较
public boolean equals(Object obj) { return (this == obj); }
- 对于自定义类,需要重写equals(),从而实现内容的比较
- 对于系统类,必如String,Date,Scanner,早已经重写过了equals
Object类的6个常用方法
- obj.equals(obj); 比较两个对象的内容是否相同
- obj.toString();将一个对象的内容格式化成字符串并返回(一般返回各个属性的值)
- obj.getClass(); 得到一个类的结构信息(反射技术)
- obj.hashCode(); 得到一个对象哈希码(集合类)
- obj.notify(); 唤醒任意一个等待的线程 (线程)
- obj.notifyAll();唤醒所有等待的线程 (线程)
- obj.wait();当前线程等待 (线程)
备注: Java不是完美的,Java的不足除了体现在运行速度上要比传统的C++慢许多之外,Java无法直接访问到操作系统底层(如系统硬件等),为此Java使用native方法来扩展Java程序的功能。
什么是多态
- 创建子类对象赋给父类的引用,调用子类重写的方法,结果是子类重写后的结果,而不是父类的
多态的好处
- 减少方法重载的数量,避免代码重复
- 符合开闭原则
多态的前提
- 必须有继承(继承是多态的前提 对)
- 子类重写父类的方法
- 创建子类对象赋给父类的引用
- 调用重写的方法
抽象类和抽象方法
- 抽象类和抽象方法都是使用abstract修饰
- 抽象类不能new,只能被继承
- 抽象类必须有构造方法,创建子类对象时需要
- 抽象类可以有0,1或者多个抽象方法
- 抽象方法只有声明,没有实现
- public abstract void method(){ }不是抽象方法
- 子类必须重写抽象方法,如果不重写,还是抽象类
- 抽象类的实例化其实是靠具体的子类实现的,是多态的方式
final、finally、finalize的区别
- final修饰符(关键字)如果一个类被声明为final,意味着它不能再派生出新的子类,不能作为父类被继承。将变量或方法声明为final,可以保证它们在使用中不被改变。被声明为final的变量必须在声明时给定初值,而在以后的引用中只能读取,不可修改。被声明为final的方法也同样只能使用,不能重载。
- finally在异常处理时提供 finally 块来执行任何清除操作。如果有finally的话,则不管是否发生异常,finally语句都会被执行。
- finalize方法名。Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要清理工作。finalize()方法是在垃圾收集器删除对象之前被调用的。它是在 Object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize()方法以整理系统资源或者执行其他清理工作。
private/默认/protected/public权限修饰符的区别
访问控制 | public | protected | 默认 | private |
---|---|---|---|---|
同一类中成员 | 是 | 是 | 是 | 是 |
同一包中其它类 | 是 | 是 | 是 | - |
不同包中的子类 | 是 | 是 | - | - |
不同包中对非子类 | 是 | - | - | - |
- 类的访问权限只有两种:
- public 公共的 可被同一项目中所有的类访问。 (必须与文件名同名)
- default 默认的 可被同一个包中的类访问。
- 成员(成员变量或成员方法)访问权限共有四种:
- public 公共的,可以被项目中所有的类访问。(项目可见性)
- protected受保护的。可以被这个类本身访问;同一个包中的所有其他的类访问;被它的子类(同一个包以及不同包中的子类)访问。(子类可见性)
- default 默认的,被这个类本身访问;被同一个包中的类访问。(包可见性)
- private 私有的,只能被这个类本身访问。(类可见性)
继承条件下构造方法的执行过程
- 如果子类的构造方法中没有通过super显式调用父类的有参构造方法,也没有通过this显式调用自身的其他构造方法,则系统会默认先调用父类的无参构造方法。在这种情况下,写不写“super();”语句,效果是一样的。
- 如果子类的构造方法中通过super显式调用父类的有参构造方法,那将执行父类相应构造方法,而不执行父类无参构造方法。
- 如果子类的构造方法中通过this显式调用自身的其他构造方法,在相应构造方法中应用以上两条规则。
- 特别注意的是,如果存在多级继承关系,在创建一个子类对象时,以上规则会多次向更高一级父类应用,一直到执行顶级父类Object类的无参构造方法为止。
多态的技能点(前提条件,向上转型、向下转型)
- 实现多态的三个条件
- 继承的存在;(继承是多态的基础,没有继承就没有多态)
- 子类重写父类的方法。(多态下会调用子类重写后的方法)
- 父类引用变量指向子类对象。(涉及子类到父类的类型转换)
- 向上转型 Class A = new Class()
- 将一个父类的引用指向一个子类对象,成为向上转型,自动进行类型转换。
- 此时通过父类引用变量调用的方法是子类覆盖或继承父类的方法,而不是父类的方法
- 此时通过父类引用变量无法调用子类特有的方法
- 向下转型 Class B = (Class)A;
- 将一个指向子类对象的引用赋给一个子类的引用,成为向下转型,此时必须进行强制类型转换。
- 向下转型必须转换为父类引用指向的真实子类类型,否则将出现ClassCastException,不是任意的强制转换
- 向下转型时可以结合使用instanceof运算符进行强制类型转换,比如出现转换异常。
接口和抽象类的异同之处
- 相同点
- 抽象类和接口均包含抽象方法,类必须实现所有的抽象方法,否则是抽象类
- 抽象类和接口都不能实例化,他们位于继承树的顶端,用来被其他类继承和实现
- 两者的区别主要体现在两方面:语法方面和设计理念方面
- 语法方面的区别是比较低层次的,非本质的,主要表现在:
- 接口中只能定义全局静态常量,不能定义变量。抽象类中可以定义常量和变量。
- 接口中所有的方法都是全局抽象方法。抽象类中可以有0个、1个或多个,甚至全部都是抽象方法。
- 抽象类中可以有构造方法,但不能用来实例化,而在子类实例化是执行,完成属于抽象类的初始化操作。接口中不能定义构造方法。
- 一个类只能有一个直接父类(可以是抽象类),但可以充实实现多个接口。一个类使用extends来继承抽象类,使用implements来实现接口。
- 二者的主要区别还是在设计理念上,其决定了某些情况下到底使用抽象类还是接口。
- 抽象类体现了一种继承关系,目的是复用代码,抽象类中定义了各个子类的相同代码,可以认为父类是一个实现了部分功能的“中间产品”,而子类是“最终产品”。父类和子类之间必须存在“is-a”的关系,即父类和子类在概念本质上应该是相同的。
- 接口并不要求实现类和接口在概念本质上一致的,仅仅是实现了接口定义的约定或者能力而已。接口定义了“做什么”,而实现类负责完成“怎么做”,体现了功能(规范)和实现分离的原则。接口和实现之间可以认为是一种“has-a的关系”
简述Java的垃圾回收机制
-
传统的C/C++语言,需要程序员负责回收已经分配内存。显式回收垃圾回收的缺点:
- 程序忘记及时回收,从而导致内存泄露,降低系统性能。
- 程序错误回收程序核心类库的内存,导致系统崩溃。
-
Java语言不需要程序员直接控制内存回收,是由JRE在后台自动回收不再使用的内存,称为垃圾回收机制。
- 可以提高编程效率。
- 保护程序的完整性。
- 其开销影响性能。Java虚拟机必须跟踪程序中有用的对象,确定哪些是无用的。
-
垃圾回收机制的特点
- 垃圾回收机制回收JVM堆内存里的对象空间,不负责回收栈内存数据。
- 对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力。
- 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
- 可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
- 现在的JVM有多种垃圾回收实现算法,表现各异。
- 垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
- 程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
- 永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
try-catch异常处理的执行步骤
- try块中代码没有出现异常,不执行catch块代码,执行catch块后边的代码
- try块中代码出现异常,catch中异常类型匹配(相同或者父类)不执行try块后续代码,执行catch块代码,执行catch块后边的代码
- try块中代码出现异常, catch中异常类型不匹配
不执行try块后续代码,不执行catch块代码,不执行catch块后边的代码,会抛出异常给所在方法调用者,让对方来处理
catch块中如何处理异常
- 输出用户自定义异常信息
logger.error("除数不能为零");
logger.error("被除数和除数必须是整数");
-
调用异常对象的方法输出异常信息
- toString ( )方法,显示异常的类名和产生异常的原因
- void printStackTrace() 输出异常的堆栈信息
- String getMessage()返回异常信息描述字符串,是printStackTrace()输出信息的一部分
-
继续向上抛出异常
- throw Exception;
finally意义何在
IO流的关闭,数据库连接的关闭,网络连接的关闭,不管操作是否出现异常,都要执行
finally语句唯一不执行的情况
System.exit()
尝试通过if-else来解决异常问题
不可行!!
- 代码臃肿
- 程序员要花很大精力"堵漏洞“
- 程序员很难堵住所有“漏洞”
异常的类型
- 运行时异常
- 检查异常
如何处理异常
- try-catch-finally 有异常我处理
- throws 我不处理,我的调用者处理
- try-catch-finally throw 有异常我处理,让调用者也处理
Error和Exception的区别
- Error类,表示仅靠程序本身无法恢复的严重错误,比如说内存溢出、动态链接异常、虚拟机错误。应用程序不应该抛出这种类型的对象。假如出现这种错误,除了尽力使程序安全退出外,在其他方面是无能为力的。所以在进行程序设计时,应该更关注Exception类。
- Exception类,由Java应用程序抛出和处理的非严重错误,比如所需文件没有找到、零作除数,数组下标越界等。它的各种不同子类分别对应不同类型异常。可分为两类:Checked异常和Runtime异常
Checked异常和Runtime异常的区别
- 运行时异常:包括RuntimeException及其所有子类。不要求程序必须对它们作出处理,比如InputMismatchException、ArithmeticException、NullPointerException等。即使没有使用try-catch或throws进行处理,仍旧可以进行编译和运行。如果运行时发生异常,会输出异常的堆栈信息并中止程序执行。
- Checked异常(非运行时异常):除了运行时异常外的其他异常类都是Checked异常。程序必须捕获或者声明抛出这种异常,否则出现编译错误,无法通过编译。处理方式包括两种:通过try-catch捕获异常,通过throws声明抛出异常从而交给上一级调用方法处理。
Java异常处理try-catch-finally的执行过程
- try-catch-finally程序块的执行流程以及执行结果比较复杂。基本执行过程如下:
- 程序首先执行可能发生异常的try语句块。如果try语句没有出现异常则执行完后跳至finally语句块执行;如果try语句出现异常,则中断执行并根据发生的异常类型跳至相应的catch语句块执行处理。catch语句块可以有多个,分别捕获不同类型的异常。catch语句块执行完后程序会继续执行finally语句块。finally语句是可选的,如果有的话,则不管是否发生异常,finally语句都会被执行。
备注: 即使try和catch块中存在return语句,finally语句也会执行。是在执行完finally语句后再通过return退出。
异常处理中throws和throw的区别
- 作用不同:throw用于程序员自行产生并抛出异常;throws用于声明在该方法内抛出了异常
- 使用的位置不同:throw位于方法体内部,可以作为单独语句使用;throws必须跟在方法参数列表的后面,不能单独使用。
- 内容不同:throw抛出一个异常对象,且只能是一个;throws后面跟异常类,而且可以有多个。
wait和sleep的区别
- sleep()方法道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的
- sleep()方法导致了程序暂停执行指定的时间,当指定的时间到了又会自动恢复运行状态。而当调用wait()方法的时候,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
- 在调用sleep()方法的过程中,线程不会释放对象锁;而当调用wait()方法的时候,线程会放弃对象锁
什么是值传递和引用传递
- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量.
- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本, 并不是原对象本身 。
Java支持多继承吗?
Java中类不支持多继承,只支持单继承(即一个类只有一个父类)。 但是java中的接口变相的多继承,即一个子接口可以实现多个接口。
String能被继承吗?为什么?
不可以,因为String类有final修饰符,而final修饰的类是不能被继承的。
父类与子类之间的调用顺序
- 父类的静态代码块
- 子类的静态代码块
- 父类的构造方法
- 子类的构造方法
- 子类的普通方法
- 重写父类的方法,打印重写后的方法
内部类与外部类的调用
- 内部类可以直接调用外部类包括private的成员变量,使用外部类引用的this.关键字调用即可
- 而外部类调用内部类需要建立内部类对象
面向对象的特征有哪些方面
- 封装
- 继承
- 多态
Integer与int的区别
- int是java提供的8种原始数据类型之一。Java为每个原始类型提供了封装类,Integer是java为int提供的封装类。int的默认值为0,而Integer的默认值为null,即Integer可以区分出未赋值和值为0的区别,int则无法表达出未赋值的情况,例如,要想表达出没有参加考试和考试成绩为0的区别,则只能使用Integer。在JSP开发中,Integer的默认为null,所以用el表达式在文本框中显示时,值为空白字符串,而int默认的默认值为0,所以用el表达式在文本框中显示时,结果为0,所以,int不适合作为web层的表单数据的类型。
- 在Hibernate中,如果将OID定义为Integer类型,那么Hibernate就可以根据其值是否为null而判断一个对象是否是临时的,如果将OID定义为了int类型,还需要在hbm映射文件中设置其unsaved-value属性为0。
- 另外,Integer提供了多个与整数相关的操作方法,例如,将一个字符串转换成整数,Integer中还定义了表示整数的最大值和最小值的常量。
String类为什么是final的
- 为了效率。若允许被继承,则其高度的被使用率可能会降低程序的性能。
- 为了安全
String、StringBuffer、StringBuilder区别与联系
- String类是不可变类,即一旦一个String对象被创建后,包含在这个对象中的字符序列是不可改变的,直至这个对象销毁。
- StringBuffer类则代表一个字符序列可变的字符串,可以通过append、insert、reverse、setChartAt、setLength等方法改变其内容。一旦生成了最终的字符串,调用toString方法将其转变为String
- JDK1.5新增了一个StringBuilder类,与StringBuffer相似,构造方法和方法基本相同。不同是StringBuffer是线程安全的,而StringBuilder是线程不安全的,所以性能略高。通常情况下,创建一个内容可变的字符串,应该优先考虑使用StringBuilder
String类型是基本数据类型吗?基本数据类型有哪些?
- 基本数据类型包括byte、int、char、long、float、double、boolean和short。
_ java.lang.String类是引用数据类型,并且是final类型的,因此不可以继承这个类、不能修改这个类。为了提高效率节省空间,我们应该用StringBuffer类
String s="Hello";s=s+"world!";
执行后,s内容是否改变
没有。因为String被设计成不可变(immutable)类,所以它的所有对象都是不可变对象。
String s = new String("xyz")
;创建几个String Object?
两个或一个,”xyz”对应一个对象,这个对象放在字符串常量缓冲区,常量”xyz”不管出现多少遍,都是缓冲区中的那一个。New String每写一遍,就创建一个新的对象,它一句那个常量”xyz”对象的内容来创建出一个新String对象。如果以前就用过’xyz’,这句代表就不会创建”xyz”自己了,直接从缓冲区拿。
下面这条语句一共创建了多少个对象:String s="a"+"b"+"c"+"d"
;
对于如下代码:
String s1 = "a";
String s2 = s1 + "b";
String s3 = "a" + "b";
System.out.println(s2 == "ab"); // false
System.out.println(s3 == "ab"); // true
运行结果说明javac编译可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。
题目中的第一行代码被编译器在编译时优化后,相当于直接定义了一个”abcd”的字符串,所以,上面的代码应该只创建了一个String对象.
String s = "a" + "b" + "c" + "d";
System.out.println(s == "abcd");
最终打印的结果应该为true。
java.sql.Date和java.util.Date的联系和区别
- java.sql.Date是java.util.Date的子类,是一个包装了毫秒值的瘦包装器,允许 JDBC 将毫秒值标识为 SQL DATE 值。毫秒值表示自 1970 年 1 月 1 日 00:00:00 GMT 以来经过的毫秒数。
- 为了与 SQL DATE 的定义一致,由java.sql.Date 实例包装的毫秒值必须通过将时间、分钟、秒和毫秒设置为与
该实例相关的特定时区中的零来“规范化”。
使用递归算法输出某个目录下所有文件和子目录列表
import java.io.File;
public class $ {
public static void main(String[] args) {
String path = "D:/301SXT";
Test(path);
}
private static void Test(String path) {
File f = new File(path);
File[] fs = f.listFiles();
if (fs == null) {
return;
}
for (File file : fs) {
if (file.isFile()) {
System.out.println(file.getPath());
} else {
Test(file.getPath());
}
}
}
}
Java有没有goto?
Java中的保留字,现在没有在Java中使用
启动一个线程是用run()还是start()?
启动一个线程是调用start()方法,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。这并不意味着线程就会立即运行。run()方法可以产生必须退出的标志来停止一个线程。
为什么重写equals时必须重写hashCode方法?
public class DemoBean {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DemoBean demoBean = (DemoBean) o;
if (id != null ? !id.equals(demoBean.id) : demoBean.id != null) return false;
return name != null ? name.equals(demoBean.name) : demoBean.name == null;
}
@Override
public int hashCode() {
int result = id != null ? id.hashCode() : 0;
result = 31 * result + (name != null ? name.hashCode() : 0);
return result;
}
}
- 如果两个对象相同(即用equals比较返回true),那么它们的hashCode值一定要相同;
- 如果两个对象的hashCode相同,它们并不一定相同(即用equals比较返回false)
- 由于为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的次数,提高效率。