Java程序员面试笔记(基础知识)

目录

1:Java语言

1.1:Java语言优点

1.2:Java与C++有什么异同

1.3:语法关键

1.4:为什么有的接口没有任何方法(成为标识接口)

1.5:Java clone

1.6:Java创建对象的四种方式

2:面向对象

2.1:面向对象有哪些特征

2.2:抽象类与接口

2.3:内部类

3:关键字

3.1:final,finally,finalize

3.2:assest

3.3:static

3.4:volatile

3.5:instanceof

4:基本数据类型与运算

4.1:基本数据类型

4.2:不可变类

4.3:i++和++i

4.4:无符号数右移

4.5:String,StringBuffer,StringBuilder,StringTokenizer(都是对字符串进行操作)

4.6:Java流

4.7:Java socket

4.8:Java序列化(序列化和外部序列化)

4.9:jvm加载class文件的机制

4.10:Java堆栈

5:集合

5.1:迭代器

5.2:Collection和Collections有什么区别

5.3:ArrayList,Vector,LinkedList区别

5.4:HashMap,HashTable,TreeMap,WeakHashMap区别

6:多线程

6.1:为什么使用多线程

6.2:如何实现Java多线程

6.3:run()和start()方法区别

6.4:sleep()和wait()方法异同

6.5:sleep()和yield()方法异同

6.6:守护线程(服务进程,精灵进程,后台线程)


1:Java语言

1.1:Java语言优点

  • Java为纯面向对象语言
  • 平台无关性(为解释型语言,由jdk解释为机器码)
  • Java提供了很多内置的类库
  • 提供了对web应用开发的支持
  • 具有较好的安全性和健壮性(安全机制:数组边界检测和Bytecode效验等)
  • 去除了c++中难以理解的易混淆的概念(指针,结构,多重继承等),使代码更严谨更简洁

1.2:Java与C++有什么异同

  • Java为解释型语言(源程序->被编译器编译成字节码->由jvm执行)(c++:编译型语言,源程序经过编译和链接之后生成可执行的二进制代码)。因此C/C++运行速度比Java快,但Java可跨平台运行;
  • Java为纯面向对象语言
  • 相比于c/c++,java没有指针概念,使程序更安全
  • Java不支持多继承,但是引入了接口。并且可以使用多态来达到多重继承的目的
  • 自动的垃圾回收机制,当垃圾回收器将要释放无用对象的内存时,会调用该对象的finalize方法;

1.3:语法关键

  • 其中main方法必须要有void static(保证在类还没加载时就可调用) public,除此之外可加final,synchnized
  • 在Java中,静态代码块不管顺序如何,都会在在类被加载时被调用,所以在类加载过程中执行顺序如下(父类静态代码块->子类静态代码块->父类非静态代码块->父类构造函数->子类非静态代码块(只有一个函数块)->子类构造函数)
  • 一个Java文件可以有多个类,但最多只能有一个被public修饰,且此类必须与文件名相同,但在编译时会生成多个字节码文件;

1.4:为什么有的接口没有任何方法(成为标识接口)

  • eg:Serializable。目的:为了唯一标识此类为何种信息。比如继承了Serializable接口的都需要序列化

1.5:Java clone

  • 前提:Java在处理基本数据类型时都是采用按值传递,其他类型都是按引用传递(其中赋值语句也是采用引用传递);
  • 使用clone步骤
  1. 实现clone的类首先需要继承Cloneable接口,Cloneable是一个标识接口,没有任何方法
  2. 在类中重写Object的clone方法
  3. 在clone方法中调用super.clone()方法,无论clone类的继承结构是什么,都会直接或者间接的调用Object类的clone方法;
  4. 把浅复制的引用指向原型对象新的克隆体

浅复制与深复制(判断是否有非基本数据类型)

  • 浅复制:调用clone方法后,其中的基本数据类型复制成功,引用类型复制失败(只复制了引用)
  • 深复制:调用clone方法后,基本类型数据复制成功,接着对对象的非基本数据也调用clone方法完成深复制

1.6:Java创建对象的四种方式

  • 通过new语句实例化一个对象
  • 通过反射机制创建一个对象(Class clazz = Class.forName("Sub");Base c = (Base)class.newInstance();),反射机制最重要的一个作用是可以在运行时动态的创建类的对象;
  • 通过clone方法创建一个对象
  • 通过反序列化的方式创建对象

2:面向对象

2.1:面向对象有哪些特征

  • 抽象:忽略一个主题中与当前目标无关的方面,以便更充分地注意与当前目标有关的方面,抽象分为过程抽象(抽象方法)和数据抽象(抽象类)
  • 继承:类的继承(注意继承和组合,尽量少使用继承降低程序耦合性),Java不支持多继承,子类只能继承父类的非私有(public 和 protected)变量和方法,子类成员变量或方法和父类是同名,子类会覆盖父类的成员变量和方法;通过getClass().getName()来获取父类的类名;
  • 封装:将客观事物抽象成类,每个类对自身的方法和数据实行保护
  • 多态:允许不同类的对象对同意消息做出响应,分为方法的重载(编译时多态,同一个类中有相同的方法,但参数不同)和方法的覆盖(运行时多态,子类可以覆盖父类的方法,运行时根据调用的类型判断那种方法体)。

2.2:抽象类与接口

如果一个类包含抽象方法,那么这个类就是抽象类,表示的是一个实体,接口表示的是一个概念

  • 抽象类:使用时不能被实例化,必须通过继承且实现抽象方法(否则也是抽象类)后实例化,声明抽象方法时不能写大括号;
  • 接口:接口中的成员变量默认都是public static final类型(所以在定义时就应该被初始化),接口中的方法只能用关键字public和abstract修饰,可以通过接口实现多继承;

2.3:内部类

静态内部类:static inner class

成员内部类:member inner class

局部内部类:local inner class

匿名内部类:anonymous inner class:参考Runnable;匿名内部类是一种没有类名的内部类,必须继承其他类或实现其他接口。其中匿名内部类不能有构造函数,不能定义静态成员和方法,不能是public protected private static,只能创建匿名内部类的一个实例,必须使用new来创建。

3:关键字

3.1:final,finally,finalize

  • final:声明属性,方法和类,分别表示属性不可变且必须被初始化,方法不可覆盖,类不可被继承
  • finally:作为try catch快的一部分,不管程序是否出现异常,finally的代码一定会执行;
try{
    return 0;
}catch(Exception e){
    return 1;
}finallly{
    return 2;
}
//finally一定会执行,为了防止这种情况,finally块中的代码是在return前执行;且finally中的return会覆盖别处的return,所以此代码返回2;
  • finalize:是Object类的一个方法,在垃圾回收器执行时会调用被回收对象的finalize方法,可以覆盖此方法来实现对其他资源的回收;

3.2:assest

  • assest(断言):作为一种软件调试的方法,主要作用是对一个boolean表达式进行检查
  • eg:assest 1+1 == 2 :"assest right";

3.3:static

  • static成员变量:可以通过static达到全局的效果;
  • static成员方法:不需要被创建对象就可调用(在单例模式中声明static方法);
  • static代码块:是独立于成员变量和成员函数的代码块的,不在任何一个方法体内,先于任何方法执行,且只会执行一次;
  • static内部类:可以不依赖于外部类实例对象而被实例,普通的内部类需要外类实例之后才可进行实例。

3.4:volatile

  • 保证了多线程的一致性
  • 被volatile修饰的变量,系统每次用到时都是直接从对应的内存当中去提取,而不是使用缓存中数据(详情参见jmm(Java线程内存模型))
  • 使用volatile会阻止编译器对代码的优化,因此会降低程序的执行效率;

3.5:instanceof

  • 判断一个引用类型的变量所指向的对象是否是一个类;eg:if(a instanceof String)

4:基本数据类型与运算

4.1:基本数据类型

  • byte short int long float double char boolean(都对应有封装类)

4.2:不可变类

  • 不可变类:当创建了这个类的实例后,就不允许修改他的值了;在Java类库中,所有基本类型的包装类都是不可变类,例如Integer,Float,其中String也是不可变类。
String s = "hello";
s+=" world";
//此时s变为了hello world
//String为不可变类,此操作是重新开辟了一块内存空间,重新引用了而已
String str = "hello world";
//此时创建str时,会在内存中找已经创建的,如果有相同的,那么直接创建引用,不需要重新开辟内存,即如下
if(str == s){
    System.out.print("true");
}
//此处返回true

//但new时不一样,例如
String str1 = new String("hello world");
//无论内存中是否已有该对象,都会重新开辟空间进行存储。
  • 如何创建一个不可变类
  1. 类中所有的成员变量都被private修饰
  2. 类中没有set方法(无法修改)
  3. 确保类中所有方法不会被子类覆盖(定义成final)
  4. 如果一个类成员不是不可变量,那么在初始化时或者使用get方法时,需要通过clone方法保证不可变性
  5. 有必要时,重写equal和hashcode方法
  6. tips:由于类的不可变性,在创建对象是就应初始化,因此最好提供一个带参构造方法进行初始化

4.3:i++和++i

  • i++:在程序执行完毕后进行自增
  • ++i:在程序执行开始前进行自增

4.4:无符号数右移

  • >>:有符号右移运算符(正数高位补0,负数高位补1)
  • >>>:无符号右移运算符(正负数高位都补0)
  • <<:左移运算符,左移n位表示原来的值乘2的n次方(常用来代替乘法),其中左移不区分无符号和有符号,都是在低位补0;

4.5:String,StringBuffer,StringBuilder,StringTokenizer(都是对字符串进行操作)

  • String:对字符串进行操作,但属于不可变类,可用赋值语句进行初始化
  • StringBuffer:属于可变类,其中必要时可对方法进行同步(保证线程安全),字符串如果经常需要修改时,尽量用StringBuffer。如果用String,会附加很多操作(先创建一个StringBuffer,在调用StringBuffer的append方法,最后调用toString方法进行返回),降低程序效率。其中StringBuffer只能通过构造函数进行初始化赋值。
  • StringBuilder:可修改字符串,非线程安全的,因此单线程时StringBuilder效率更高;
  • StringTokenizer:是用来分割字符串的工具类

4.6:Java流

  • 流的本质是数据传输
  • 字节流:继承于InputStream和OutputStream,在处理输入输出是不会用到缓存
  • 字符流:继承于Reader和Writer,使用缓存

4.7:Java socket

  • Soclet(套接字):分为面向连接的Socket通讯协议(tcp)和面向无连接的Socket通讯协议(udp),任何一个socket都是由Ip地址和端口号唯一确定;
//Socket服务端
ServerSocket server = new ServerScket(8080);
Socket socket = server.accpet();
br = new BufferReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream(),true);
String s = br.readLine();
//获取到输入的字符串
br.close();
pw.close();


//客户端
Socket socket = new Socket("127.0.0.1",8080);
br = new BufferReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream(),true);
pw.println("Hello");
String s = null;
while(true){
    s = br.readLine();
    if(s!=null){
        braek;
    }
}
br.close();
pw.close();

4.8:Java序列化(序列化和外部序列化)

4.8.1:序列化

  • 序列化可以将对象的状态写在流中进行网络传输,或者保存在文件数据库等系统中,并且在需要时把该对象读取出来重新构造一个相同的对象。实现Serializable接口即可,特点如下:
  1. 如果一个类可被序列化,那么它的子类也可被序列化
  2. 由于static,transient分别为静态和临时变量,所以此类型数据不会被序列化
  • 每个类都有特定的serialVersionUID,在反序列化的过程中,通过serialVersionUID来判断类的兼容性,最好在类的声明中西安市定义serialVersionUID(static final)

4.8.2:外部序列化,Externalizable

  • 使用Serializable时,类中的所有属性都会序列化,怎么实现部分序列化?
  1. 实现Externalizable接口,可以根据实际需求来实现readExtenrnal和writeExternal方法来控制序列化与反序列化所使用的属性。
  2. 使用transient来控制序列化的属性,被transient修饰的是临时属性,不会被序列化。

4.9:jvm加载class文件的机制

  • 参考之前博文:浅谈Java虚拟机及其优化
  • 类加载器将class文件加载到jvm中,具体是由ClassLoader和他的子类来实现的,类加载器本身也是一个类,本质是将类文件从硬盘读取到内存中。
  • 类加载分为显示加载和隐式加载
  1. 隐式加载:程序在使用new等方法创建对象时,会隐式的调用类的加载器把对应的类加载到jvm中
  2. 显示加载:通过直接调用class.forName()方法把所需的类加载到jvm中
  • 类加载器的主要步骤:
  1. 装载:根据查找路径找到相对应的class文件,然后导入
  2. 链接:分为三小步骤(检查,检查class文件的正确性。准备:给类中的静态变量分配存储空间。解析:将符号引用转换为直接引用)
  3. 初始化:对静态变量和静态代码块执行初始化工作

4.10:Java堆栈

  • 栈内存主要存放基本数据类型和引用类型。
  • 堆内存用来存放运行时创建的对象
  • JVM是基于堆栈的虚拟机,每个Java程序都运行在一个单独的JVM实例上,每一个实例唯一对应一个堆,一个Java程序内的多个线程也就运行到同一个JVM实例上,因此这些线程之间会共享堆内存。鉴于此,多线程在访问堆中数据时需要对数据进行同步;
  • 从功能来说,对主要用来存放对象的,栈主要用来执行程序的。较于堆,栈的存取速度更快,但栈的大小和生存期必须是确定的,因此缺乏一定的灵活性。堆却可以在运行时动态的分配内存,生存期不需要提前告诉编译器,这也导致了其存取速度缓慢。

5:集合

关于集合的详细知识参考上一篇博文:Java集合类汇总详解

5.1:迭代器

  • 迭代器是一个对象,为了遍历并选择序列中的对象,使用迭代器如下:
//Java关键代码
List<String> ll = new LinkedList<String>();
ll.add("first");
ll.add("second");
for(Iterator<String> iter = ll.iterator();iter.hasNext();){
    String str = (String)iter.next();
    System.out.println(str);
}
  1. 使用容器的iterator()方法返回一个Iterator,然后通过Iterator的next方法返回第一个元素;
  2. 使用Iterator的hasNext()判断容器中是否还有元素,如果有,可以使用next()获取下一个元素
  3. 可以通过remove()方法删除迭代器返回的元素

5.2:Collection和Collections有什么区别

  • Collection是一个集合接口,他提供了对集合对象进行基本操作的通用接口方法,实现接口的类主要有List和Set;
  • Collections是针对集合类的一个包装类,它提供一系列静态方法以实现对各种集合的搜索,排序,线程安全化等操作,其中大部分方法都是用来处理线性表;

5.3:ArrayList,Vector,LinkedList区别

  • 均为可变长数组
  • ArrayList:连续空间,可随机访问,超过初始化容量大小时进行扩容(ArrayList默认扩容1.5倍,没有提供方法来设置空闲扩充),非同步,非线程安全
  • Vector:连续空间,可随机访问,超过初始化容量大小时进行扩容(Vector默认扩容2倍,每次空间扩充的大小是可以设置的),大部分方法是直接或者间接synchronized同步的,线程安全
  • LinkedList:双向链表来实现,随机访问效率较低,插入数据效率较高,非线程安全。

5.4:HashMap,HashTable,TreeMap,WeakHashMap区别

  • map是用来存储键值对的数据结构
  • HashMap:根据键的HashCode值存储数据,是HashTable的轻量级实现(非线程安全的实现),允许一条key值为null,(较HashTable的contains方法,变成了containsvalue和containskey),hash数组默认大小是16,而且一定是2的倍数,HashMap中的key是强引用,当key没有被引用时,只有remove后,才能被垃圾回收(参考WeakHashMap)
//HashMap实现同步
Map m = Collections.synchornizedMap(new HashMap());
  • HashTable:不允许key为null,线程安全(效率低),hash数组默认大小是11,增加的方法是old*2+1,HashTable直接使用对象的hashCode
  • TreeMap:实现了SortMap接口,可以根据键排序;
  • WeakHashMap:WeakHashMap中的key不被外部引用之后,就会被垃圾回收器回收;

6:多线程

关于多线程的详细知识可以参考上一篇博文:详解Java线程创建和通讯(有后续)

6.1:为什么使用多线程

  • 使用多线程可以减少程序的响应时间;
  • 与进程相比,现成的创建和切换开销更小;
  • 多CPU或者多核计算机本身就有执行多线程的能力,如果使用单线程,将无法重复利用计算机资源,造成巨大的资源浪费
  • 使用多线程能简化程序的结构,使程序便于理解和维护

6.2:如何实现Java多线程

  • 继承Thread类,重写run方法
//示例
class MyThread extends Thread{
    public void run(){
        System.out.println("我run啦!");
    }
}

public class Test{
    public static void main(String[] args){
        MyThread my = new MyThread();
        my.start();
    }
}
  • 实现Runnable接口,并实现该接口的run方法
//示例
class MyThread implements Runnable{
    public void run(){
        System.out.println("我run啦!");
    }
}

public class Test{
    public static void main(String[] args){
        MyThread my = new MyThread();
        Thread t = new Thread(my);
        t.start();
    }
}
  1. 自定义类实现runnable接口,实现run方法
  2. 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
  3. 调用Thread的run方法
  • 实现Callable接口,重写call方法(与Runnable接口相比
public class CallableAndFuture{
    public static class CallableTest implements Callable<String>{
        public String call() throws Exception{
            return "我又run啦!";
        }
    }

    public static void main(String[] args){
        ExecutorService threadPool = Executor.newSingleThreadExecutor();
        Future<String> future = threadPool.submit(new CallableTest());
        try{
            System.out.println("waiting……");
            System.out.pringln(future.get());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

//运行结果为
//waiting……
//我又run啦!
  1. Callable可以在任务结束后提供一个返回值,Runnab无法提供这个功能
  2. Callable中的call方法可以抛出异常,而Runna的run方法不能抛出异常
  3. 运行Callab可以拿到一个Future对象,来监视目标线程调用call方法的情况,当调用Future的get方法以获取结果时,当前线程就会阻塞,知道call方法结束返回结果

6.3:run()和start()方法区别

  • start():启动线程,此时线程处于就绪状态,在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()结束时,线程就会终止;
  • run():直接调用时会当作一个普通的函数调用,程序中仍然只有main这一个主线程(其实还有垃圾回收线程)。start()方法可以异步的调用run()方法,直接调用时不能达到同步效果;

6.4:sleep()和wait()方法异同

  • sleep()是Thread类的静态方法,是线程用来控制自身流程的,会使线程短暂暂停执行。wait()是Object的方法,用于线程通讯,会使当前拥有该对象锁的进程等待知道notify()。
  • sleep()不会释放锁,wait()后,线程会释放掉他所占用的锁。
  • wait()必须放在同步控制方法或者同步语句块中使用,sleep()可在任何地方使用
  • sleep()必须捕获异常,wait()不需要捕获异常

6.5:sleep()和yield()方法异同

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,yield()方法只会给相同优先级或更高优先级的线程已运行的机会。
  • sleep()方法执行后线程会进入阻塞状态,本线程短期内不会被执行,yield()方法只是使线程重新回到可执行状态,所以执行yield()方法的线程有可能会再次马上执行
  • sleep()抛出异常,yield()方法没有声明异常

6.6:守护线程(服务进程,精灵进程,后台线程)

  • 如果用户线程已经全部退出,只剩下守护线程了,那么jvm也就退出了
  • 守护线程一般有较低的优先级
  • 当一个守护线程中产生了其他线程,那么这新产生的线程默认还是守护线程。
  • 守护线程一个典型例子就是垃圾回收器
//设置守护线程
//在调用start()方法启动线程之前调用对象的setDaemon(true)方法
Thread t1 = new Thread();
t1.setDaemon(true);
t1.start();

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值