1 什么是构造函数、构造代码块、静态代码块?分别的作用是什么?三者执行顺序?
1 构造函数:
构造函数是一种特殊的函数。其主要功能是用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。构造函数与类名相同,可重载多个不同的构造函数。
注:
1.构造函数的命名必须和类名完全相同。在java中普通函数可以和构造函数同名,但是必须带有返回值;
2.构造函数的功能主要用于在类的对象创建时定义初始化的状态。它没有返回值,也不能用void来修饰。这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择。而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的;
3.构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用的;
4.当定义一个类的时候,通常情况下都会显示该类的构造函数,并在函数中指定初始化的工作也可省略,不过Java编译器会提供一个默认的构造函数.此默认构造函数是不带参数的。而一般的方法不存在这一特点。
2 构造代码块:
构造代码块的作用和构造函数类似可以完成类中的成员变量进行初始化也可以调用成员方法
①、格式
在java类中使用{}声明的代码块(和静态代码块的区别是少了static关键字):
public class CodeBlock {
static{
System.out.println(“静态代码块”);
}
{
System.out.println(“构造代码块”);
}
}
②、执行时机
构造代码块在创建对象时被调用,每次创建对象都会调用一次,但是优先于构造函数执行。需要注意的是,听名字我们就知道,构造代码块不是优先于构造函数执行,而是依托于构造函数,也就是说,如果你不实例化对象,构造代码块是不会执行的。怎么理解呢?我们看看下面这段代码:
public class CodeBlock {
{
System.out.println(“构造代码块”);
}public CodeBlock(){
System.out.println("无参构造函数");
}
public CodeBlock(String str){
System.out.println("有参构造函数");
}
public CodeBlock(){
System.out.println("无参构造函数");
}
public CodeBlock(String str){
System.out.println("有参构造函数");
}
}
如果存在多个构造代码块,则执行顺序按照书写顺序依次执行。
③、构造代码块的作用
和构造函数的作用类似,都能对对象进行初始化,并且只要创建一个对象,构造代码块都会执行一次。但是反过来,构造函数则不一定每个对象建立时都执行(多个构造函数情况下,建立对象时传入的参数不同则初始化使用对应的构造函数)。
利用每次创建对象的时候都会提前调用一次构造代码块特性,我们可以做诸如统计创建对象的次数等功能。
3 静态代码块:
静态代码块属于类的,静态代码块可以随着类的加载而加载可以初始化中的静态成员变量,也可以在连接JBCD时用于读取文件中的连接信息
①、格式
在java类中(方法中不能存在静态代码块)使用static关键字和{}声明的代码块:
public class CodeBlock {
static{
System.out.println(“静态代码块”);
}
}
②、执行时机
静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。后面在比较的时候会通过具体实例来证明。
③、静态代码块的作用
一般情况下,如果有些代码需要在项目启动的时候就执行,这时候就需要静态代码块。比如一个项目启动需要加载的很多配置文件等资源,我们就可以都放入静态代码块中。
执行顺序:
静态代码块>构造代码块>构造函数>普通代码块
2 double和Double区别?
1、Double是java定义的类,而double是预定义数据类型(8种中的一种)
2、Double就好比是对double类型的封装,内置很多方法可以实现String到double的转换,以及获取各种double类型的属性值(MAX_VALUE、SIZE等等)
注:
基于上述两点,如果你只是普通的定义一个浮点类型的数据,两者都可以,但是Double是类所以其对象是可以为NULL的,而double定义的不能为NULL,如果你要一些数字字符串,那么就应该使用Double类型了,其内部帮你实现了强转。
Double类型是double的包装类,在JDK1.5以后,二者可以直接相互赋值,称为自动拆箱和自动装箱。
3 2<<3等于多少?
2左移三位等于8
4 判断字符串为空时如何避免空指针异常?
判断一个字符串str不为空的方法有:
1. str!=null;
2. “”.equals(str);
3. str.length()!=0;
例子:
String str1 = null; str引用为空
String str2 = ""; str应用一个空串
str1还不是一个实例化的对象,而str2已经实例化。
对象用equals比较,null用等号比较。
如果str1=null;下面的写法错误:
if (str1.equals("") || str1==null) {//如果这样写就会报空指针异常错误
}
正确的写法是
if(str1 == null || str1.equals("")) { //先判断是不是对象,如果是,再判断是不是空字符串
}
说明:
1 null表示这个字符串不指向任何的东西,如果这时候你调用它的方法,那么就会出现空指针异常。
2 "“表示它指向一个长度为0的字符串,这时候调用它的方法是安全的。
3 null不是对象,”“是对象,所以null没有分配空间,”"分配了空间,例如:
5 什么是重载和重写
定义:
重载
简单说,就是函数或者方法有同样的名称,但是参数列表不相同的情形(方法名相同,参数列表不同),这样的同名不同参数的函数或者方法之间,互相称之为重载函数或者方法。
重写
重写指的是在Java的子类与父类中有两个名称、参数列表都相同的方法的情况。由于他们具有相同的方法签名,所以子类中的新方法将覆盖父类中原有的方法。
区别:
首先,重载和重写都是多态的一种体现方式。
重载是编译期间的活动,重写是运行期间的活动。
其次,重载是在一个类中定义相同的名字的方法,方法的参数列表或者类型要互相不同,但是返回值类型不作为是否重载的标准,可以修改可见性;
重写是不同的,要求子类重写基类的方法时要与父类方法具有相同的参数类型和返回值,可见性需要大于等于基类的方法。
6 实现线程的三种方式
java中实现多线程有三种方法:
继承Thread类
实现Runnable接口
实现Callable接口
一、Java中创建线程主要有三种方式:
1、继承Thread类创建线程类
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
2、通过Runnable接口创建线程类
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
3、通过Callable和Future创建线程
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
public interface Callable
{
V call() throws Exception;
}
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。)
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
二、创建线程的三种方式的对比
1、采用实现Runnable、Callable接口的方式创建多线程时,
优势是:
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。
在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
劣势是:
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。
2、使用继承Thread类的方式创建多线程时,
优势是:
编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。
劣势是:
线程类已经继承了Thread类,所以不能再继承其他父类。
3、Runnable和Callable的区别
(1) Callable规定(重写)的方法是call(),Runnable规定(重写)的方法是run()。
(2) Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
(3) call方法可以抛出异常,run方法不可以。
(4) 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
7 Thread类中的start()和run()方法有什么区别
区别:
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
注:
start()方法被用来启动新创建的线程,而且start()内部调用了run()方法。
8 final修饰的方法可以被继承/重载/重写吗?
final表示最终的意思,它修饰的类是不能被继承的;final修饰的方法能被继承(Math类里就有),但是不能够被重写。其实关系并不复杂,你只需要记住这样一句话:final可用于声明属性、方法和类,分别表示属性不可变,方法不可重写,类不可继承。
- final修饰的类,为最终类,该类不能被继承。如String 类
- final修饰的方法可以被继承和重载,但不能被重写
- final修饰的变量不能被修改,是个常量
9 遍历二叉树的方式有哪些?选择一种方法用程序实现
二叉树的遍历分为三种:前序遍历 中序遍历 后序遍历
前序遍历:按照“根左右”,先遍历根节点,再遍历左子树 ,再遍历右子树
中序遍历:按照“左根右“,先遍历左子树,再遍历根节点,最后遍历右子树
后续遍历:按照“左右根”,先遍历左子树,再遍历右子树,最后遍历根节点
其中前,后,中指的是每次遍历时候的根节点被遍历的顺序
import java.util.ArrayList;
import java.util.List;
public class Tree {
private Node root;
private List<Node> list=new ArrayList<Node>();
public Tree(){
init();
}
//树的初始化:先从叶节点开始,由叶到根
public void init(){
Node x=new Node("X",null,null);
Node y=new Node("Y",null,null);
Node d=new Node("d",x,y);
Node e=new Node("e",null,null);
Node f=new Node("f",null,null);
Node c=new Node("c",e,f);
Node b=new Node("b",d,null);
Node a=new Node("a",b,c);
root =a;
}
//定义节点类:
private class Node{
private String data;
private Node lchid;//定义指向左子树的指针
private Node rchild;//定义指向右子树的指针
public Node(String data,Node lchild,Node rchild){
this.data=data;
this.lchid=lchild;
this.rchild=rchild;
}
}
/**
* 对该二叉树进行前序遍历 结果存储到list中 前序遍历:ABDXYCEF
*/
public void preOrder(Node node)
{
list.add(node); //先将根节点存入list
//如果左子树不为空继续往左找,在递归调用方法的时候一直会将子树的根存入list,这就做到了先遍历根节点
if(node.lchid != null)
{
preOrder(node.lchid);
}
//无论走到哪一层,只要当前节点左子树为空,那么就可以在右子树上遍历,保证了根左右的遍历顺序
if(node.rchild != null)
{
preOrder(node.rchild);
}
}
/**
* 对该二叉树进行中序遍历 结果存储到list中,中序结果XdYbaecf
*/
public void inOrder(Node node)
{
if(node.lchid!=null){
inOrder(node.lchid);
}
list.add(node);
if(node.rchild!=null){
inOrder(node.rchild);
}
}
/**
* 对该二叉树进行后序遍历 结果存储到list中,后续结果:XYdbefca
*/
public void postOrder(Node node)
{
if(node.lchid!=null){
postOrder(node.lchid);
}
if(node.rchild!=null){
postOrder(node.rchild);
}
list.add(node);
}
/**
* 返回当前数的深度
* 说明:
* 1、如果一棵树只有一个结点,它的深度为1。
* 2、如果根结点只有左子树而没有右子树,那么树的深度是其左子树的深度加1;
* 3、如果根结点只有右子树而没有左子树,那么树的深度应该是其右子树的深度加1;
* 4、如果既有右子树又有左子树,那该树的深度就是其左、右子树深度的较大值再加1。
*
* @return
*/
public int getTreeDepth(Node node) {
if(node.lchid == null && node.rchild == null)
{
return 1;
}
int left=0,right = 0;
if(node.lchid!=null)
{
left = getTreeDepth(node.lchid);
}
if(node.rchild!=null)
{
right = getTreeDepth(node.rchild);
}
return left>right?left+1:right+1;
}
//得到遍历结果
public List<Node> getResult()
{
return list;
}
public static void main(String[] args) {
Tree tree=new Tree();
System.out.println("根节点是:"+tree.root);
//tree.preOrder(tree.root);
tree.postOrder(tree.root);
for(Node node:tree.getResult()){
System.out.println(node.data);
}
System.out.println("树的深度是"+tree.getTreeDepth(tree.root));
}
}
10 java常见的排序方法有哪些?选择一种实现
11 什么是反射?如何通过反射访问私有字段?
官方解释:
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
访问私有字段:
为了访问私有字段,可以调用Class.getDeclaredField(String name)或者Class.getDeclaredFields()方法,并且需要开启权限setAccessible(true)。方法Class.getField(String name)和Class.getFields()仅仅返回共有的字段,所以它们都无法起到作用。