以下是自己收集总结的java基础知识,后期持续更新.
1、字面值常量:
一个数值,在编译期就能确定的数.
2、关于类型强制转换:
类型强制转换必须建立在两个类型有关系的前提下,例如继承或者实现,无关的东西不能进行强转,抛ClassCastException,int类型无法转成String类型.
3、赋值符号:
x+=3;相当于x=x+3;,但还包括隐式类型转换.
4、比较运算符:
==:若比较的是两个操作数都是数值类型,即使他们的类型不同,只要值相同就返回true.
赋值运算符:
面试题:
int[] arr = {1,2,3};
int a = 0;
int b = 2;
arr[a] = a = b;//arr[] = {2,2,3}
5、数组:
数组必须先经过初始化才能使用,初始化表示在内存中分配空间.
静态初始化,必须在声明之后立刻进行初始化,即 int[] arr = {1,2,3},不能先声明然后在进行初始化.
6、局部变量:
基本数据:直接把这个变量的值保存到改变量所对应的内存中.
引用数据:这个变量内存中存的是地址,通过该地址引用到该变量实际引用堆里的对象.
7、package:
该句作为java文件第一行代码,此时
编译命令:
javac -d . Demo.java
运行命令:
java com.hongyousoft.Demo
8、继承:
public:子类直接继承父类的成员.
protected:如果父类的成员(字段\方法)用protected修饰,而此时子父类不在同一包中,那么此时父类必须是public修饰,也就是说不同包下发生继承的前提是能发现这个类.
包访问权限:如果子父类在同一包中,可以继承父类缺省修饰符的成员.
private:子类无法继承父类的成员,原因:private修饰的成员所属于本类.
构造器:子类无法继承父类的构造器,因为构造器要求必须和当前类名相同.
9、自动拆装箱:
包装类型直接比较,如果都是Integer,那么比较的是数值.
自动装箱:
Integer.valueOf(),而不是调用new Integer();如果整数在[-128,127]范围之内,也就是在缓冲池里直接拿数据(否则,就会重新创建一个新的Integer对象),那么自动装箱都地址相同.
自动拆箱:
Integer.intValue();
String转Integer:
Integer.valueOf();
String转基本数据类型(int):
Integer.parseInt();
10、抽象类:
构造方法不能都定义成私有的,否则不能有子类(创建子类对象前会先调用父类的构造方法)----如果没有继承体系,那么构造器私有化不会报错,禁止创建该类的实例对象.
直接创建抽象类的实例对象系统报错:Cannot instantiate the type
11、枚举:
枚举的直接父类是java.lang.Enum,但是不能显示的继承Enum.
可以把枚举当成一个类,可以定义构造,成员,成员方法和抽象方法.
默认私有的构造方法,底层没有无参构造器
switch语句支持枚举,底层是因为枚举有ordinal索引,而索引是int类型.
建议用枚举类来做单例模式,很安全,及时反射也不能创建对象.
enum Demo{
INSTANCE
}
12、字符串:
使用""创建的字符创都是直接量,编译期都已经确定存储到常量池(Constant pool)中.
使用new String("")创建的对象会存储到堆内存中,运行期才会创建.(如果是编译期能确定的数据相连接,那么,在编译期就会完成,属于编译期可以优化的).通过变量/方法去连接字符串,都只能在运行期间才能确定变量的值和方法的返回值,不存在编译期优化操作.
13、StringBuilder:
用StringBuilder的无参构造器,在底层(父类AbstractStringBuilder)创建了一个长度为的char数组,(char[] value = new char[16])默认长度为16,如果超过了,会自动扩容(创建一个长度更大的数组,把之前数组的全部数据拷贝到新数组,性能比较低,建议:在使用前估计数组大小,在构造器中手动指定).
14、this:
当一个对象创建之后,jvm会分配一个引用自身的引用:this,相当于创建一个实例对象之后,对象内有一个字段this,值为该对象的内存地址.继承中的super相当于子类实例对象中的一个字段,引用父类的实例对象,通过super.成员可以访问父类的成员.
15、创建子类对象:
当子类和父类存在相同的字段的时候,无论修饰符是什么(即使是private),都会在各自的内存空间中存储数据,互不影响.子类通过super引用指向父类实例对象在堆内存中的空间.
16、类加载顺序:
如果这个类采用的是组合方式,那么,创建A的字节码文件对象时,首先会把B类的字节码加载进JVM,原因:A.class依赖于B.class,如果B类不存在,A.class会构建失败.
所有非static的字段初始化,是在构造器内部优先完成.
static的字段初始化,是通过static代码块完成的.
class A{
private B b = new B();
}
上面的代码等价于:
class A{
private B b = null;
A(){
b = new B();
System.out.println("A Constructor")
}
}
17、super:
满足继承访问权限的条件下,通过super可以访问父类的静态方法,若子类的静态方法和父类的静态方法方法签名一直,那么此时子类的静态方法就把父类的方法隐藏了.
满足继承访问权限下,隐藏父类字段,若子类的字段和父类的字段一致(不管类型),此时父类的字段就被隐藏了,只能通过super来访问父类被隐藏的字段.
18、日期类(DateFormat):
格式化(Date-->String):String format(Date date)
解析(String-->Date):Date parse(String source)
19、异常:
当程序中抛出异常,底层相当于 new ArithmeticExecption("by zero"),创建了算术异常对象,把异常对象传递给对应的catch,而catch中相当于ArithmeticException e = new ArithmeticExecption("by zero");
如果finally中有return语句,永远返回finally中的结果,也就是会把前面产生的异常吞掉,try-catch-finally语句是一个整体,当程序判断没有进入try语句,那么finally也不会执行到.
20、异常处理:
异常只能用于处理非正常情况,主要考虑到性能影响.
异常的粒度很重要,应该为一个基本操作定义一个try-catch块.
不建议在循环中进行异常处理,应该在循环外对异常进行捕获处理.
自定义异常尽量使用RuntimeException类型的.
21、并发和并行:
并行:两个或多个事件在用一时刻点发生.
并发:两个或多个事件在同一时间段内发生.
进程:一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间.
线程:进程中的一个执行任务(控制单元),一个进程可以同时并发运行多个线程.
进程和线程区别:
进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程.
线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响.
线程调度:
JVM采用的是抢占式调度,没有采用分时调度,可能造成多线程执行结果的随机性.
多线程优势:
进程之间不能共享内存,而线程之间共享内存(堆内存).
系统创建进程是需要为该进程重新分布配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.
22、同步:
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
这里的锁对象是this
C:静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象
注意:
不能用synchronized修饰run(),否则,会由一个线程执行完所有功能.应该是定义一个新的方法,在run()方法内完成调用.
使用同步锁会降低性能,建议:尽量减小synchronized的作用域.
单例模式(懒汉式):
如果修改为同步,性能受影响,此时,推荐使用"双重检查加锁"机制来实现线程安全的单例.
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
java5.0版本之后,提供了Lock(同步锁),使用面向对象来进行加锁(lock.lock())/解锁操作(lock.unlock()).
23、同步锁池:
同步锁必须选择多个线程共同的资源对象.
当前线程拥有同步锁,其他线程就在锁池中等待获取锁.当线程执行完同步代码块的时候,就会释放同步锁,其他线程开始抢锁的使用权.
多个线程只有使用相同一个对象的时候,多线程之间才有互斥效果.
24、线程通讯:
wait():执行该方法的县城对象释放同步锁,jvm把该线程存放到等待池中,等待其他的线程唤醒该线程.
notify():执行该方法的线程唤醒在等待池中等待的任意一个线程,该线程转到锁池中等待(和等待池不一样).
注意:上述方法只能被同步监听锁对象来调用,否则报错IllegalMonitorStateException
线程休眠:
调用sleep后,当前线程放弃CPU,在指定时间段之内,sleep所在线程不会获得执行的机会,此状态下的线程不会释放同步锁/同步监听器.该方法更多的用户模拟网络延迟,让多线程并发访问同一个资源的错误效果更明显.
java5.0以后,使用Lock机制取代了synchronized代码块和synchronized方法,没有自动获取锁和自动释放锁的概念,所以不能用wait()等方法.
解决:
java提供了处理Lock机制的通讯控制的Condition接口,通过调用接口对象的await(),signal()取代之前的方法.
Condition condition = lock.newCondition();
25、死锁问题:
避免死锁法则:
当多个线程都要访问共享的资源A,B,C时,保证每一个线程都按照相同的顺序去访问他们.
26、哈希表:
在一般数组中,元素在数组中的索引位置是随机的,元素的取值和元素的位置之间不存在确定的关系,因此,在数组中查找特定的值时,需要把查找值和一系列的元素进行比较.
如果数组中元素的值和索引位置存在对应的关系,这样的数组就称之为哈希表,优点:提供查找数据的效率.
哈希表保存数据:不会直接把哈希吗作为索引保存.
元素值-->hash(value)-->哈希吗-->映射关系-->元素存储索引
当哈希表接近装满时,因为数组的扩容问题,性能较低(转移到更大的哈希表)
27、增强for循环:
foreach可以操作数组,底层依然采用普通for循环+索引来获取数组元素.
foreach可以操作Iterable的实例,底层采用的Iterator+普通for循环(for (Iterator it = list.iterator(); it.hasNext();)),所以foreach也可能会产生并发修改异常.
28、并发修改异常:
List对象有一个成员变量modCount,代表该List对象被修改的次数,每修改一次他自增1,Itr这个内部类有一个成员变量expectedModCount,它的值为创建Itr对象的时候List的modCount的值,用此变量来检验在迭代过程中List对象是否被修改了,在每次调用Itr对象的next()方法的时候都会调用checkForComodification()方法进行一次检验,checkForComodification()方法中做的工作就是比较expectedModCount 和modCount的值是否相等,如果不相等,就认为还有其他对象正在对当前的List进行操作,那个就会抛出ConcurrentModificationException异常。
29、泛型:
泛型类中的泛型只能适用于费静态方法,对于静态方法应该使用单独的泛型方法.
一般来说,把自定义的泛型作为该方法的返回类型才有意义,而且此时的泛型必须是由参数设置进来的
泛型的上限和下限:
用来限定元素的类型必须是X类的子类或相同,X类的父类或相同.
30、HashSet:
底层使用散列算法,底层其实也是一个数组,主要是提高查询和插入速度,但是用于少量数据的插入操作(数组涉及到扩容问题).
当往HashSet集合中添加新的对象的时候,会先判断该对象和集合对象中的的hashCode值:
不等:直接把该新的对象存储到hashCode指定的位置.
相等:再继续判断equals方法:
equals-->true,说明两个是同一对象,不保存.
equals-->false,存储在之前对象同槽位的链表上.
建议:存储在哈希表中的对象,都应该重写equals和hashCode方法,并且保证equals相同的时候,hashCode也应该相同.
31、TreeSet:
集合底层采用红黑树算法,对存储的元素默认使用自然排序(从小到大),
注意:必须保证TreeSet集合中的元素对象是相同的数据类型,否则报错.
自然排序:
TreeSet调用集合元素的compareTo方法来比较元素的大小关系,然后进行排序.
注意:TreeSet集合中的元素必须实现java.util.Comparable接口.(也就是说保存的元素具有可比较性).返回0代表相同.
定制排序:
在TreeSet的构造器中传递java.lang.Comparator对象,并覆盖compare方法,编写比较规则.
32、Map:
Map可以看成两个非空集合,key集合(Set)和value集合(List),存在一种映射关系,在value集合中能找到唯一一个值与key集合中的值对应.
33、Arrays:
如果是定义基本数据类型int[],那么在调用Arrays.asList(),时候,会把int[]数组整体作为对象进行参数传递,如果是包装类型,那么,数组中的每个元素会作为对象保存到集合中.另外:asList()返回的是Arrays的内部类(Arrays$ArrayList),他的底层是Object[]
34、Class:
用来描述类和接口,其他类一旦被夹在到jvm中就变成Class对象(字节码对象),所以 Class可以理解成描述类的类,也就是元数据.他的实例对象就是在JVM中的一份字节码对象,用来表示jvm中的类或者接口,通过Class<String>泛型进行区分.
Integer.class ≠ int.class 两个是不同的数据类型,可以实现重载方法,在包装类中有TYPE属性,用来代表基本数据类型的字节码对象
数组的字节码对象:
所有具有相同元素类型和维数的数组都共享该 Class 对象。和元素无关.
35、可变参数:
是一种语法糖,底层仍旧是数组,反射调用方式:m.invoke(null,int[].class),invoke方法第二个参数需要Object[],对于基本数据类型数组不会自动拆包,但是对于引用类型会先拆包成为单个的对象,然后赋值给Object[],此时报参数不匹配异常,应该多包一层:m.invoke(null, new Object[]{new String[]{"a","b","c"}});(自动解包后然后把String数组赋值给Object数组)
36、加载资源文件:
相对路径:
方式1:
相对于classpath的根路径(字节码输出目录)
此时要使用类加载器ClassLoader,类加载器默认就是从classpath根路径去寻找文件的:
InputStream in = loader.getResourceAsStream("db.properties");
获取类加载器推荐从Thread类中获取:
ClassLoader loader = Thread.currentThread().getContextClassLoader();
方式2:
相对于当前加载资源文件的字节码路径(也就是说相对于当前class文件的位置查找):
InputStream in = Demo.class.getResourceAsStream("db.properties");
37、flush方法:
对于字节流,flush方法不是都有作用(只适合用带缓冲区的字节流),对于字符流都起作用,如果调用close方法,系统会在关闭资源之前,先调用flush方法.
先到这里,后面在进行整理...