目录
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步骤
- 实现clone的类首先需要继承Cloneable接口,Cloneable是一个标识接口,没有任何方法
- 在类中重写Object的clone方法
- 在clone方法中调用super.clone()方法,无论clone类的继承结构是什么,都会直接或者间接的调用Object类的clone方法;
- 把浅复制的引用指向原型对象新的克隆体
浅复制与深复制(判断是否有非基本数据类型)
- 浅复制:调用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");
//无论内存中是否已有该对象,都会重新开辟空间进行存储。
- 如何创建一个不可变类
- 类中所有的成员变量都被private修饰
- 类中没有set方法(无法修改)
- 确保类中所有方法不会被子类覆盖(定义成final)
- 如果一个类成员不是不可变量,那么在初始化时或者使用get方法时,需要通过clone方法保证不可变性
- 有必要时,重写equal和hashcode方法
- 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接口即可,特点如下:
- 如果一个类可被序列化,那么它的子类也可被序列化
- 由于static,transient分别为静态和临时变量,所以此类型数据不会被序列化
- 每个类都有特定的serialVersionUID,在反序列化的过程中,通过serialVersionUID来判断类的兼容性,最好在类的声明中西安市定义serialVersionUID(static final)
4.8.2:外部序列化,Externalizable
- 使用Serializable时,类中的所有属性都会序列化,怎么实现部分序列化?
- 实现Externalizable接口,可以根据实际需求来实现readExtenrnal和writeExternal方法来控制序列化与反序列化所使用的属性。
- 使用transient来控制序列化的属性,被transient修饰的是临时属性,不会被序列化。
4.9:jvm加载class文件的机制
- 参考之前博文:浅谈Java虚拟机及其优化
- 类加载器将class文件加载到jvm中,具体是由ClassLoader和他的子类来实现的,类加载器本身也是一个类,本质是将类文件从硬盘读取到内存中。
- 类加载分为显示加载和隐式加载
- 隐式加载:程序在使用new等方法创建对象时,会隐式的调用类的加载器把对应的类加载到jvm中
- 显示加载:通过直接调用class.forName()方法把所需的类加载到jvm中
- 类加载器的主要步骤:
- 装载:根据查找路径找到相对应的class文件,然后导入
- 链接:分为三小步骤(检查,检查class文件的正确性。准备:给类中的静态变量分配存储空间。解析:将符号引用转换为直接引用)
- 初始化:对静态变量和静态代码块执行初始化工作
4.10:Java堆栈
- 栈内存主要存放基本数据类型和引用类型。
- 堆内存用来存放运行时创建的对象
- JVM是基于堆栈的虚拟机,每个Java程序都运行在一个单独的JVM实例上,每一个实例唯一对应一个堆,一个Java程序内的多个线程也就运行到同一个JVM实例上,因此这些线程之间会共享堆内存。鉴于此,多线程在访问堆中数据时需要对数据进行同步;
- 从功能来说,对主要用来存放对象的,栈主要用来执行程序的。较于堆,栈的存取速度更快,但栈的大小和生存期必须是确定的,因此缺乏一定的灵活性。堆却可以在运行时动态的分配内存,生存期不需要提前告诉编译器,这也导致了其存取速度缓慢。
5:集合
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);
}
- 使用容器的iterator()方法返回一个Iterator,然后通过Iterator的next方法返回第一个元素;
- 使用Iterator的hasNext()判断容器中是否还有元素,如果有,可以使用next()获取下一个元素
- 可以通过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:多线程
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();
}
}
- 自定义类实现runnable接口,实现run方法
- 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象
- 调用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啦!
- Callable可以在任务结束后提供一个返回值,Runnab无法提供这个功能
- Callable中的call方法可以抛出异常,而Runna的run方法不能抛出异常
- 运行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();