第二章 JavaSE基础
一 、面向对象
1、简述面向对象的特征和你的理解
默认情况下面向对象有三大特征:封装继承多态.如果是四大特征,那么加上抽象.
1.继承
2.封装
将数据和操作数据的方法绑定,对数据访问只能通过已定义的接口.
3.多态
实现多态需要做的两件事:
a.方法重写:子类继承父类并重写父类中已有的或抽象方法
b.对象造型:
4.抽象
将一类对象的共同特征抽取,用来构造类的过程.包括数据抽象和行为抽象,抽象只关注对象有哪些属性和行为,并不关注行为的细节.
2、访问权限修饰符
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default | √ | √ | × | × |
private | √ | × | × | × |
3、如何理解clone对象
实现方式:
1)实现Cloneable接口,重写Object类中的clone()方法
2)实现Serializable接口,通过对象序列化和反序列化实现克隆,可以实现真正的深度克隆
1.为什么用clone?
已知一个对象A包含了一些有效值,此时可能需要一个和A完全相同的对象B,操作B对象时,A对象不受影响.Java中, 简单的赋值是无法完成这种需求的.满足的途径有多重,但最简单最高效的就是实现clone()
2.new对象和过程和clone对象的过程的区别
new操作会分配内存,分配完内存后再调用构造函数,给各个属性赋值,并返回(对象/引用地址)给调用者.
clone第一步也是分配内存,当调用clone方法时,分配的内存和原对象相同,然后再将原对象的各个属性赋值给新对象,完成后返回该对象.
3.clone对象的使用
a.引用复制
Person p = new Person();
Person p1 = p;
p和p1的地址值相同,即此操作只是将p1指向了p的地址
b.clone
Person p = new Person(20,“张三”);
Person p1 = (Person)p.clone();
此时p和p1是两个完全不同的对象,只不过数据相同
c.浅拷贝和深拷贝
p对象age是int类型,所以只需要将值进行拷贝即可.而name是String类型,它指向的是String对象.
克隆前后String地址相同,无论深浅拷贝,String都是指向同一个字符串,只有出现修改时才会创建新的字符串对象。
如果想实现深拷贝,则该对象和对象属性中引用的对象必须实现Cloneable接口.
二、JavaSE语法
1、Java有没有goto语句?
goto是Java中保留字 ,但目前并没有使用。
2、&和&&区别?
&有两种用法:(1)按位与 (2)逻辑与
逻辑与:&如果左边不成立,会继续判断右边,这样就降低了判断效率。
短路与:&&具有短路机制,左边不成立,则整个判断不成立,不会再进行右边的判断。
3、如何跳出当前的多重嵌套循环?
可以在循环前加标记如A,使用break A即可跳出多重循环。
a: for (int x = 0; x<100; x++){
b: for(int y=0; y<100; y++){
break b;
}
}
4、equals与hashCode关系?
如果两对象equals方法返回true,则他们的hashCode值一定相同。
试想,如果equals成立,而hashCode不成立的话,相同对象可以出现在Set集合中,hash码频繁冲突,则该集合的存取性能会急剧下降。
扩展:
equals方法必须满足
自反性( x.equals(x)必须返回true )
对称性( x.equals(y)返回ture时,y.equals(x)也必须返回true )
传递性( x.equals(y)和y.equals(z)都返回ture时,x.equals(z)也必须返回ture )
一致性( 当x和y引用的对象信息没有被修改时,多次调用equals方法应该得到同样的值 )
而且对于任何非Null值得引用x, x.equals(null)必须返回false.
实现高质量的equals方法的诀窍:
1、使用 == 操作检查 “参数是否为这个对象的引用”
2、使用 instanceof 操作检查 “参数是否为正确的类型”
3、对于类中的关键属性,检查参数传入对象的属性是否匹配
4、编写完equals方法后,检查是否满足对称性传递性一致性
5、重写equals方法时,总是要重写hashCode方法
6、不要将equals方法中的Object对象替换成其他类型,不要忘掉@Override注解
5、是否可以继承String?
String是final类,不可以被继承。
对String最好的重用方式是 关联关系(Has-A)和 依赖关系(Use-A)
而不是继承关系(Is-A)
6、对象传参是值传递还是引用传递?
当一个对象被当做参数传递到一个方法,此方法可改变该对象属性,并返回变化后的结果,那么这里是值传递还是引 用传递?
值传递。
Java方法调用只支持参数的值传递。对象传参只是对该对象的引用,对象属性可以在被调用过程中被改变,但并不会影响到调用者。
如果想实现该功能,需要通过方法调用修改的引用置于一个Wrapper类中,再讲Wrapper对象传入。
7、重载和重写区别?
重载:
1.方法名相同,参数列表的顺序,类型,个数不同。
2.重载与方法的返回值无关,存在于父类和子类,同类中。
3.可以抛不同的异常,可以有不同的修饰符。
重写:
1.参数列表和返回类型必须完全与被重写的方法一致。
2.构造方法不能重写,final修饰的方法不能重写,声明为static的方法不能重写,但能够被再次声明。
3.访问权限不能比父类中被重写的方法访问权更低。
4.重写的方法能够抛出任何非运行时异常,无论被重写的方法是否跑出异常。
但是,重写的方法不能抛出新的运行时异常,或者比被重写方法声明的运行时异常更广泛。
8、为什么方法不能根据返回值类型来区分重载?
因为调用时不能指定类型信息,编译器不知道要调用哪个方法。返回值只是作为方法执行后的一个“状态”,它是保持方法调用者与被调用者进行通信的关键,并不能作为某个方法的标识。
public int getMax();
public double getMax();
当调用getMax()方法时,无法确定要调用拿一个。
9、char存储一个中文汉字?
可以。因为Java中使用的编码是Unicode,一个char变量等于两字节(16比特),所以存放一个中文没有问题。
(使用Unicode意味着字符在JVM内部和外部有不同的表现形式,在JVM内统一是Unicode,而当转移到JVM外部时,比如存入文件系统中,需要进行编码的转换)
10、抽象类和接口的异同?
不同 抽象类: 接口:
1.可以定义构造器 1.不能定义构造器
2.可以定义成员变量 2.接口中的成员变量实际上都是常量
3.可以有抽象的方法和具体方法 3.全部都是抽象方法
4.接口中的成员全部都是public修饰 4.private、默认、protected、public
5.可以包含静态方法 5.不能有静态方法
6.一个类只能继承一个抽象类 6.一个类可以实现多个接口
7.有抽象方法的类必须声明为抽象类,而抽象类未必要有抽象方法
相同 1.不能够实例化
2.可以将抽象类和接口类型作为引用类型
3.一个类如果继承或者实现了某个类,需要对其中的抽象方法全部实现,否则该类需要声明为抽象类
11、抽象方法是否可同时是静态方法?是否可同时是本地方法?是否可同时被synchronized?
都不可以。
抽象方法需要子类重写,而静态方法是无法被重写的。
本地方法是由本地代码(如C代码)实现的方法,而抽象方法没事没有实现的。
synchronized和方法的实现有关,而抽象方法不涉及方法细节。
12、静态变量和实例变量的区别?
静态变量:被static修饰的变量,也被称为类变量,它属于类,不属于任何一个对象,一个类不管创建多少个对象,静态变量在内存中有且仅有一个拷贝。静态变量可以实现让多个对象共享内存。
实例变量:必须依存于某实例,需要先创建对象才可以访问到。
13、==和equals的区别?
==:基本数据类型,比较值。引用数据类型,比较地址值。
equals():用来比较两个对象的内容是否相等。
注意:equals方法不能用于基本数据类型变量,如果没有对equals进行重写,则比较的是引用类型所指向的地址值。
14、break和continue的区别?
都是用来控制循环的关键字。
break完全结束循环,即跳出该循环。
continue跳过本次循环,执行下次循环。
15、对字符串进行拼接操作,会影响到原始String对象吗?
不会。String是不可变类(immutable),所以它的所有对象都是不可变对象。
String s = “Hello”; s=s+“world”;
代码执行后,原对象并没有发生改变,而是s指向了另一个String对象(Hello world)。
因此,如果经常对String进行修改,会引起极大的内存开销,此时应该考虑使用StringBuffer类。
三、Java多态
1、Java中实现多态的机制?
父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,程序调用的方法在运行期动态绑定,即引用变量指向的就是子类或具体实现类的方法,也就是内存中运行的对象的方法,而不是引用变量类型中定义的方法。
UserService userService = new UserServiceImpl();
程序调用时,userService指向的是UserServiceImpl类,而不是其接口。
四、Java的异常处理
1、Java中异常的分类?
编译时异常(强制性异常) CheckedException
运行时异常(非强制性异常) RuntimeException
CheckedException:必须在代码编写时就进行处理(eclipse和idea的飘红),否则编译阶段就会发生错误。
知道如何处理时:使用try…catch捕获处理异常。
不知道如何处理时:在方法上声明抛出该异常。
RuntimeException:只有在代码运行时才会发生的异常,不需要try catch。
如:0作为除数,索引越界异常,空指针异常,类型转换异常等。
产生频繁,处理麻烦,声明或捕获对程序的可读性和运行效率影响很大,所以一般由系统自动检测并交给缺省
的异常处理程序。如果有处理要求,也可以进行捕获。
2、如果catch处理中有return,方法如何执行?
try{
int a = 1/0;
return 1;
}catch(Exception e){
return 2;
}finally{
return 3;
}
异常机制,如果catch处理中有return或者异常等能使方法终止,如果有finally那么需要先执行finally代码块中的代码,
再进行方法的终止(return或者抛异常)。
代码执行到1/0出抛出异常,跳转到catch中。catch中有return,所以跳转到finally。finally中return,程序结束。
3、error和exception的区别?
父类都是Throwable类,区别如下:
Error类一般指虚拟机相关的问题,如系统崩溃,内存不足,堆栈溢出等。程序本身无法恢复预防,建议终止程序。
Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应尽可能处理异常,使程序恢复运
行,而不应该随意终止。
4、Java异常处理机制?
Error表示应用程序本身无法克服和恢复的一种严重问题。
Exception:系统异常是软件本身缺陷导致,开发者考虑不周,如角标越界空指针类型转换。
普通异常是运行环境变化或异常导致,如断网硬盘空间不足等。
编译器强制普通异常必须try…catch处理或者throws声明抛出,所以普通异常也成为checked异常。
系统异常可以处理也可以不处理,编译器不进行强制要求,所以系统异常也被称为unchecked异常。
5、列举几个常见的RuntimeException?
java.lang.NullPointerException 空指针异常 调用了未经初始化或者不存在的对象。
java.lang.ClassNotFountException 指定类找不到 类名称或类路径家在错误 。
java.lang.NumberFormatException 字符串转换为数字异常 字符串数据中包含非数字类型字符。
java.lang.IndexOutOfBoundsException 数组角标越界异常 访问数组之外的索引
java.lang.IllegalArgumentException 非法参数异常 传递了规定参数类型之外的参数
java.lang.ClassCastException 数据类型转换异常
java.lang.NoClassDefFoundException 未找到类定义错误
java.lang.InstantiationException 实例化异常
java.lang.NoSuchMethodException 方法不存在异常
SQLException SQL异常 SQL语句编写错误
6、throw和throws的区别?
throw: 用在方法体内,由方法体内进行处理。
抛的是异常对象
throws: 用在方法声明后,由方法调用者进行处理。
抛出的是某种类型的异常,需要让使用者知道需要捕获的异常的类型。
表示出现异常的可能性,并不一定会发生该异常。
7、final、finally、finalize的区别?
final:声明属性、方法和类,被final修饰的属性不可变,方法不可覆盖,类不可继承。
finally:异常处理语句结构的一部分,无论是否出现异常该部分总是执行,一般用来释放资源等。
finalize:Object类的一个方法,垃圾回收器执行时会调用该方法。调用该方法时则代表这个对象即将“死亡”。
注意:我们主动去调用该方法并不会让对象“死亡”。这是被动方法(回调方法),不需要调用。
五、JavaSE常用API
1、Math.round()
四舍五入。 Math.round(10.5) = 11 Math.round(-10.5) = -10 Math.round(8.1) = 8
2、switch作用在byte、long、String
switch(expr) Java5添加enum,Java7添加String long始终不可以
Java7开始,expr可以是byte、short、char、int、enum、String
3、数组和String有没有length()方法?
数组没有length()方法,但是有length属性
String有length()方法。 注意:JavaScript中,获得字符串长度是通过length属性,而不是方法。
4、String、StringBuilder、StringBuffer的区别?
都可以储存可操作字符串。区别如下:
1、String只读,不可以改变。进行拼接操作是指向了拼接后的新String对象,而原本的String并不会发生改变。
2、StringBuffer/StringBuilder表示的字符串对象可以直接进行修改。
3、StringBuilder和StringBuffer方法完全相同
StringBuilder是单线程环境下使用,所有方法都没有被synchronized修饰,理论上效率更高。
5、字符串拼接和append方法区别?
使用反编译可以发现。“+”拼接在编译时仍然会使用StringBuilder进行操作,因此Java中无论何种方式操作String,实际上都是使用的StringBuilder。
在进行简单的顺序结构字符串拼接时,“+”和StringBuilder基本一样。
涉及到循环拼接时,每次循环都会创建一个StringBuilder对象,造成资源的大量浪费。
解决方法:1)直接使用StringBuilder进行操作,且放在循环外部。 2)“+”和StringBuilder不要混用
6、 String的值判断
String s1 = “Programming”; s1==s2 false
String s2 = new String(“Programming”); s1==s5 true
String s3 = “Program”; s1==s6 false
String s4 = “ming”; s1==s6.intern() true
String s5 = “Program” + “ming”; s2==s2.intern() false
String s6 = s3 + s4;
String的intern()方法会得到字符串对象在常量池中对应的引用(如果常量池中有一个字符串与String的equals结果为true)
如果没有对应字符串,则该字符串会添加到常量池中,然后返回常量池中字符串的引用。
7、Java中的日期和时间
1)获取年月日时分秒
Calendar c = Calendar.getInstance();
c.get( Calendar.YEAR / MONTH / DATE / HOUR_OF_DAY / MINUTE / SECOND )
LocalDateTime dt = LocalDateTime.now();
dt.getYear / getMonthValue / getDayOfMonth / getHour / getMinute / getSecond
2)获取1970-01-01 00:00:00到现在的毫秒值
Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis();
3)获取某月最后一天
LocalDate today = LocalDate.now();
LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);
LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());
4)格式化日期
SimpleDateFormat sdf = new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”);
sdf.format(new Date());
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(“yyyy-MM-dd HH:mm:ss”)
LocalDate.now().format(dtf);
5)打印昨天的当前时刻
Calendar c = Calendar.getInstance(); c.add(Calendar.DATE,-1);
LocalDateTime.now().minusDays(1);
六、Java的数据类型
1、Java的基本数据类型
四类 | 八种 | 字节数 | 数据范围 |
---|---|---|---|
整形 | byte | 1 | -128 ~ 127 |
short | 2 | -32768 ~ 32767 | |
int | 4 | -2147483648 ~ 2147483647 | |
long | 8 | -2^63 ~ 2^63-1 | |
浮点型 | float | 4 | -3.403E38 ~ 3.403E38 |
double | 8 | -1.798E308 ~ 1.798E308 | |
字符型 | char | 2 | 一个字符 |
布尔型 | boolean | 1 | true / false |
String是引用类型,底层是char数组。
2、 short判断
short s1 = 1; s1 = s1+1; 不正确,因为1是int类型,运算结束想要赋值给short类型的s1,必须进行强转。
short s1 = 1; s1 += 1; 正确,s1+=1 相当于 s1 = (short)(s1+1);隐含强转。
3、基本类型和包装类型
目的:方便将基本数据类型当成对象操作。
boolean char byte short int long float double
Boolean Character Byte Short Integer Long Float Double
4、Integer值比较
Integer a = 100; Integer b = 100; Integer c = 150; Integer d = 150;
a == b; true c == d; false
如果整型值在-128~127,则不会new新的Integer对象,而是直接引用常量池中的对象。
七、JavaIO
1、Java有几种类型的流
流方向:输入流(inputStream) 输出流(outputStream)
实现功能:节点流(FileReader) 处理流(BufferedReader)
处理数据单位:字节流(InputStream/OutputStream) 字符流(InputStreamReader/OutputStreamWriter)
2、字节流和字符流区别
通过构造函数传入字节流即可实现字节流转换为字符流。
字节流可以处理所有类型的数据,主要操作byte类型数据,以byte数组为准。
字符流处理2个字节的Unicode字符(字符字符串和其数组)。
八、Java的集合
1、集合安全性问题
ArrayList、HashSet、HashMap的每个方法都没有加锁,都是线程不安全的。
Vector和HashTable是线程安全的,其实就是在核心方法添加了synchronized关键字。
Collections工具类提供了相关API,可以让不安全集合变为安全的。
Collections.synchronizedCollection(collection) Collections.synchronizedList(list)
Collections.synchronizedMap(map) Collections.synchronizedSet(set)
2、ArrayList的内部实现
ArrayList 内部是用 Object[]实现的。
构造 当我们 new 一个空参 ArrayList 的时候,系统内部使用了一个 new Object[0]数组。
有参构造函数传入一个 int 值,该值作为数组的长度值。如果该值小于 0,则抛出一个运行时异常。如果等于 0,则
使用一个空数组,如果大于 0,则创建一个长度为该值的新数组。
有参构造传入一个Collection的子类,先判断是否为null,为null则抛空指针。不是,则将该集合转换为数组a,然后
将该数组赋值为成员变量array,数组长度作为成员变量size。
增加 add方法有两个重载,此处只介绍最简单的。
1、首先将成员变量array赋值给局部变量a,将成员变量size赋值给局部变量s。
2、判断集合长度s是否等于数组的长度,如果等于,说明数组已满,需要分配新数组。
MIN_CAPACITY_INCREMENT = 12 如果当前集合长度小于6,则分配12个长度;如果大于6,则分配当前长
度s的一半。里面用到了三元运算符和位运算符。
3、将新添加的object数组作为数组的a[s]个元素。
4、修改吉和长度size为s+1.
5、return true,除非报运行时异常,否则一直返回true。
删除 remove方法有两个重载,此处只介绍最简单的remove(int index)。
1、将成员变量array和size赋值给局部变量a和s。
2、判断形参index是否大于等于集合长度,成立则抛索引越界。
3、获取数组中角标为index的对象result,该对象作为方法返回值。
4、调用System的arraycopy函数System.arraycopy( a,index+1,a,index,–s-index )
删除index后一位的元素(index+1),并且将删除元素该数组后面的元素整体向前移动一个位置。
5、将集合最后一个元素设置为null,否则可能内存泄漏。
6、重新给成员变量array、size赋值。
7、返回删除的元素。
清空 clear() 如果集合长度不等于0,则将所有数组的值设置为null,将成员变量size设置为0。
3、并发集合和普通集合区别
java中有普通集合、同步(线程安全)集合、并发集合。普通集合通常性能最高,但是不保证多线程安全性和并发可靠性。线程安全集合仅仅给集合添加了synchronized同步锁,严重牺牲了性能,且对并发的效率更低。并发集合通过复杂的策略不进保证了多线程的安全,又提高了并发时的效率。并发集合:ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。位于java.util.concurrent包下。
ArrayList:底层结构是数组,查询快,增删慢。
LinkedList:底层结构是链表,查询慢,增删快。
voctor:底层结构是数组,线程安全,查询慢,增删慢。
4、List、Map、Set的区别
**结构:**List、Set存储单列数据,Map存储键值对双列数据。List存储有序允许重复;Map存储无序,键不能重复,值可以
重复;Set存储无序,不允许有重复,但其元素位置有hashCode决定,位置是固定的,但是用户无法控制。
实现类:
**List:**ArrayList,基于数组,增删快 ,查找慢;LinkedList,基于链表,查询慢,增删快;Vector,基于数组,线程安全,增删慢,查找慢。
**Map:**HashMap,基于hash表的Map接口实现,线程不安全,高效,支持null值和null键;HashTable,线程安全,低效,不支持null值和null键;LinkedHashMap,HashMap的子类,存取有序;SortMap接口,TreeMap,能够将保存的记录根据键排序,默认升序。
**Set:**HashSet:基于HashMap实现,不允许重复值,使用时需要重写equals()和hashCode()方法;LinkedHashSet,继承于HashSet,基于LinkedHashMap实现。
5、ArrayList和LinkedList区别
ArrayList底层数组实现,LinkedList使用了循环双链表数据结构,包含三个部分:元素内容,前驱表,后驱表。
ArrayList和LinkedList在性能上各有优缺点,都有各自适用的地方。
1)对ArrayList和LinkedList而言,在列表末尾增加一个元素的开销都是固定的。对于ArrayList,主要是在内部数组中增加一项;对于LinkedList,则是分配一个内部Entry对象。
2)ArrayList中间插入或删除意味着该 集合中所有元素都会移动,而LinkedList插入删除元素则不是。
3)LinkedList不支持高效随机元素访问。
4)ArrayList的空间浪费主要体现在list列表结尾预留一定的空间,LinkedList是每个元素都要消耗相当的空间。
当操作是在一列数据的后面添加或删除,而不是在中间和前面,并且需要随机访问,ArrayList会有比较好的性能。
当操作是在一列数据的前面或中间添加或删除,并且按照顺序访问元素,LinkedList会有比较好的性能。
6、用队列模拟堆栈结构
Queue a = [ “a”,“b”,“c”,“d” ,“e”] Queue b = [ ] ArrayList arr ;
进栈:for(String q : a){ arr.add(q);System.out.print(q) }
出栈:for(int i=arr.size();i>=0;i–){ b.offer(arr.get(i)); } for( String q:b){System.out.print(q); }
九、Java的多线程和并发库
1、创建线程
1)继承Thread类,重写run方法;new该子类,开启线程。
2)实现Runnable接口,重写run方法;创建Thread对象时传入Runnable实现类。
3)使用ExecutorService、Callable、Future 实现有返回结果的多线程。Callable接口提供了一个call()方法作为线程
主体,比run()方法功能强大。call()方法可以有返回值,可以声明抛出异常。步骤如下:
a,创建Callable接口的实现类,实现call()方法,然后创建实现类的实例。
b,使用FutureTask类来包装Callable对象,Future对象封装了Callable对象的call()方法的返回值。
c,使用FutureTask对象作为Thread对象的target,创建并启动线程(FutureTask实现了Runnable接口)
d,调用FutureTask对象的get()方法获得子线程执行结束后的返回值。
2、线程互斥与同步
当线程并发执行时,由于资源共享和线程协作,线程之间会存在以下两种制约关系
1、间接互斥制约。资源共享时,一个线程使用,其他线程都要等待。如I/O设备 ,打印机。(互斥)
2、直接相互制约。线程A将结果给线程B作进一步处理时,B在A送达数据以前都将处于阻塞状态。(同步)
3、ThreadLocal
线程局部变量:用于实现线程内的数据共享,即在某个程序中,相同线程的模块运行时需要使用同一份数据(如,连接)。
每个线程调用ThreadLocal对象的set方法,在set方法中,根据当前线程获得当前线程的ThreadLocalMap对象,向map中插入数据,key就是Thread独享,value是各自set方法传入的值。线程结束时可以调用remove()方法,或者自动调用。
4、多线程共享数据
1)多个线程行为一致,共同操作一个数据源。可以使用同一个Runnable对象,例如卖票系统。
2)多个线程行为不一致,共同操作一个数据源。需要使用不同的Runnable对象,例如银行存款。
5、线程的6种状态
初始(NEW):新创建线程对象,还没有调用start()方法;
运行(RUNNABLE):就绪(ready)和运行中(running)两种状态笼统的称为运行;
阻塞(BLOCKED):表示线程阻塞于锁;
等待(WAITING):进入该状态的线程需要无期限的等待其他线程做出特定动作;
超时等待(TIMED_WAITING):可以在指定时间后自行返回;
终止(TERMINATED):线程执行完毕。
线程调度方法:
new.start() ===> runnable runnable.wait() ===> waiting waiting.notify() ===> blocked
runnable.sleep(毫秒值) ===> timed_waiting runnable和blocked的状态切换由cpu控制
启动线程是调用start()方法,并不意味着会运行;run()是线程启动后进行回调的方法。
受阻塞和等待(休眠)的区别:受阻塞,线程有资格,cpu没时间;等待(休眠),线程放弃执行资格。
6、wait和sleep方法的区别
等待时 wait 会释放锁,而 sleep 一直持有锁。wait 通常被用于线程间交互,sleep 通常被用于暂停执行。
7、synchronized和volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰,就具有了两层语义:
1)保证了不同线程对该变量操作时的可见性,即某线程修改该变量,其他线程立即可见。
2)禁止进行指令重排序,volatile本质在告诉jvm当前变量在工作内存中值不确定,需要从主存中读取。
volatile仅能使用在变量;synchronized可以使用在变量、方法、类。
volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized可以保证变量的修改可见性和原子性。
volatile不会造成线程的阻塞;synchronized可能造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
8、线程池
1、什么是线程池:
线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用 new 线程而是直接去池中拿线程即可,节
省了开辟子线程的时间,提高的代码执行效率。
2、为什么用线程池:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定
性,使用线程池可以进行统一的分配,调优和监控。
3、如何使用线程池:
调用以下对象的execute()方法即可:
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
创建一个可缓存的线程池,不会对线程池大小做限制,完全依赖于系统(或JVM)能够创建的最大线程大小。
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);
创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);
创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
创建一个单线程的线程池,此线程池支持定时以及周期性执行任务的需求。
9、 同类中两个方法都加了同步锁,多线程能同时访问这两个方法吗
Lock 可以让等待锁的线程响应中断,Lock 获取锁,之后需要释放锁。如下代码,多个线程不可访问同一个类中的 2 个加了 Lock 锁的方法。
使用synchronized 时,当我们访问同一个类对象的时候,是同一把锁,所以可以访问该对象的其他 synchronized 方法。
10、线程死锁
死锁是指多个线程因竞争资源而造成的互相等待,如无外力作用,这些进程都将无法继续运行。
死锁产生的必要条件:
互斥条件:线程要求对分配的资源(打印机)进行排他性控制,如有线程在使用,其他线程请求只能等待。
不剥夺条件:线程获得的资源只能自己释放,不能被强行夺走。
请求和保持条件:线程获得资源,又提出新资源请求,如果新资源被占用,则进程堵塞,且不释放原资源。
循环等待条件:线程资源的循环等待链中每一个线程已获得的资源同时被下一个线程所请求。
如何避免:
1)顺序加锁,线程按照一定顺序加锁
2)加锁时限,线程尝试获取锁是加上时限,超过时限放弃锁请求,释放锁。
11、线程和进程的区别
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的单位。
线程:是进程的一个实体,cpu调度和分派的基本单位,是比进程更小的可以独立运行的基本单位。
特点:线程划分尺度小,使多线程程序拥有高并发性,进程在运行时各自内存单元互相独立,线程之间内存共享,
这使得多线程编程可以拥有更好的性能和用户体验。
注意:多线程编程对其他程序是不友好的,占据大量cpu资源。
十、Java内部类
1、静态嵌套类和内部类
静态嵌套类:声明为静态的内部类,可以不依赖于外部类实例被实例化,也不能使用外部类的非静态成员或方法。
内部类:外部类实例化以后才能被实例化,内部类连本类方法的成员变量都不可访问,只能访问final成员。
第三章 JavaSE高级
一、Java中的反射
1、反射是什么
通过Java类的字节码,将字节码中的方法、变量、构造函数等映射成相应的Method、Filed、Constructor等。
2、API
1、获取字节码文件
1)Class.forName(className); 2)className.class; 3)this.getClass( );
2、获取和执行构造
Constrcutor con = clazz.getConstructor( );Object obj = con.newInstance( );
Constrcutor con = clazz.getConstrcutor(参数类型.class…);con.newInstance(实际参数…);
Constrcutor con = clazz.getDeclaredConstrcutor(参数类型.class…);
con.setAccessible(true); Object obj = con.newInstance(实际参数…);
所有构造:Constructor[ ] constructors = clazz.getConstructors( );
暴力反射所有构造:Constructor[ ] constructors = clazz.getDeclaredConstructors( );
3、获取和执行成员方法
无参数:Method m = clazz.getMethod(“方法名”); Object obj = m.invoke(对象);
有参数:Method m = clazz.getMethod(“方法名”,参数类型.class);Object obj = m.invoke(对象,实际参数);
所有方法:Method[ ] ms = clazz.getMethods( );
暴力反射方法:Method m = clazz.getDeclaredMethod(“方法名”,参数类型.class…);
m.setAccessible(true); Object obj = m.invoke(对象,实际参数…);
暴力反射所有方法:Method[ ] ms = clazz.getDeclaredMethods( );
4、获取成员变量
Field f = getField(“成员变量名”); Field f = getDeclaredField(“成员变量名”);
Filed[] fs = getFields(); Filed[] fs = getDeclaredFields();
Object obj = f.get(对象);//数据类型 变量名 = 对象.成员变量
f.set(对象,值); //对象.成员变量 = 值
3、BeanUtils
void setProperty(Object bean,String name,Object value); //给对象的某个属性赋值
String getProperty(Object bean.String name); //获取对象的某个属性值
void populate(Object bean,Map<String,String> map); //使用map集合给对象赋值
二、Java动态代理
1、模拟ArrayList的动态代理类
final List list = new ArrayList();
List proxyInstance = Proxy.new ProxyInstance( list.getClass().getClassLoader()),
list.getClass().getInterfaces(),new InvocationHandler(){ @Override
public Object invoke(Object proxy,Method method,Object[ ] args){
return method.invoke( list,args );} } });
proxyInstance.add(“ABC”); System.out.println(“list”); // [ “ABC” ]
2、动态代理和静态代理
静态代理通常指代理一个类,事先知道要做什么。
动态代理是代理一个接口下的多个实现类,只有在运行时才知道要做什么。
动态代理有两种:一种是实现JDK里的InvocationHandler接口的invoke方法,注意代理的是接口,也就是业务类必须要实现接口,通过Proxy的new ProxyInstance得到代理对象;另一种是动态代理CGLIB,代理的是类,不需要业务类继承接口,通过派生子类来实现代理,通过在运行时动态修改字节码达到修改类的目的。
AOP编程是基于动态代理实现的。比如Spring、Hibernate等框架。
三、Java设计模式
1、设计模式分类
创建型模式 :工厂方法模式,抽象工厂模式,单例模式,建造者模式,原型模式。
结构型模式:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式。
行为型模式:策略模式,模板方法模式,观察者模式,迭代子模式,责任链模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式。
2、单例模式
饿汉式:public static Singleton instance = new Singleton( ); private Singleton( ){ }
public static Singleton getInstance( ){ return instance;}
懒汉式:private static volatile Singleton singleton = null; private Singleton( ){ }
public static Singleton getInstance( ){
if(singleton == null){
synchronized( Singleton.class ){
if(singleton == null){ singleton = new Singleton(); }
} } } return singleton; }
3、工厂设计模式
工厂模式分为工厂方法模式和抽象工厂模式。
**工厂方法模式:**分为三种,普通工厂模式、多个工厂方法模式、静态工厂方法模式。
普通工厂方法模式:建立一个工厂类,对实现了统一接口的一些类进行实例的创建。
多个工厂方法模式:普通工厂传字符串出错不能创建对象,该模式则提供多个工厂方法,可以分别创建对象。
静态工厂方法模式:将多个工厂方法模式中的方法设置成静态的,不需要创建实例,直接调用即可。
**抽象工厂模式:**工厂方法模式类的创建以来工厂类,想要扩展应用就必须对工厂类进行修改。抽象工厂模式可以创建多个工厂类,这样一旦需要增加新的功能,直接增加新工厂即可。
4、创建者模式Builder
建造者模式是将各种产品集中管理,创建复合对象,所谓复合对象就是指某个类具有不同的属性。
5、适配器设计模式
主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。
类的适配器模式:一个统一接口声明所有方法,方法的实现由不同类完成,适配器类只需要继承实现这些类即可。
对象的适配器模式:基本思路和类适配器相同,只是Adapter类不继承Source类,而是持有Source类实例以解决兼容。
接口的适配器模式:有时一个接口中有多个抽象方法,就需要借助一个抽象类实现该接口,而我们不用关心接口,只需要继承该抽象类,重写和调用需要的方法即可。
6、装饰模式Decorator
装饰模式就是给一个对象动态增加新功能,要求装饰者和被装饰者实现同一个接口,装饰者有被装饰者的对象引用。
7、策略模式Strategy
策略模式定义了若干方法,将每个方法封装起来,使他们可以相互替换。需要设计一个接口,声明统一方法,多个实现类实现该接口重写方法。调用方法时,通过创建的实例决定调用的方法。
8、观察者模式Observer
当一个对象变化时,其他以来该对象的对象都会收到通知,并且随之变化,对象之间是一对多关系。浏览一些博客或wiki是,经常会看到RSS图标,就是订阅了该文章,有后续更新会及时通知。
9、JVM垃圾回收机制
理论上讲,Sun公司值定义了垃圾回收机制规则而不局限于实现算法,因此不同厂商生产的虚拟机采用的算法也不尽相同。GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象,常用的搜索算法如下:
1)引用计数器算法(废弃)
给每个对象设置一个计数器,当被引用时,计数器+1,引用失效;计数器-1,当计数器为0是,JVM就认为是垃圾。实现简单,效率高;但是不能解决循环引用问题,且计时器的操作带来了额外开销,所以JDK1.1后废弃。
2)根搜索算法(使用)
根搜索算法是通过一些GC Roots对象作为起点,从这些节点开始往下搜索。搜索通过的路径成为引用链,当一个对象没有被引用链连接时,说明该对象不可用。GC Roots对象包括:①虚拟机栈(栈帧中的本地变量表)中的引用对象,②方法区域中的类静态属性引用的对象,③方法区域中常量引用对象,④本地方法栈中JNI(Native方法)的引用对象。
通过上面算法搜索到无用对象后,执行回收,回收算法如下:
1)标记 - 清除算法(Mark-Sweep) (DVM使用的算法)
标记 - 清除算法包括“标记”和“清除”两个阶段。在标记阶段,确定回收对象,做标记。清除阶段,紧随标记阶段,将标记的对象清除。该算法是基础的收集算法,小效率不高,而且清除后产生大量的不连续空间,无法给大内存对象使用。
2)复制算法
复制算法是把内存分成大小相等的两块,每次试用其中一块。垃圾回收时,把村会的对象赋值到另一块上,然后把这块内存整个清理。复制算法实现简单效率高,但是每次只能使用其中的一半,造成内存的利用率不高,现在JVM用的赋值方法收集新生代,由于新生代中大部分对象(98%)都是朝生夕死的,所以两块内存的比例不是1:1,大概是8:1。
3)标记 - 整理算法(Mark-Compact)
标记 - 整理算法和标记清除一样,但是标记 - 整理算法不是把存活对象复制到另一块内存,而是移动到内存的另一端,然后直接回收边界以外的内存。标记 - 整理算法提高了内存利用率,且适合在手机对象存活时间较长的老年代。
4)分代收集(Generational Collection)
分代收集是根据对象的存活时间把对象分为新生代和老生代,根据各自存活特点,采用不同的垃圾回收算法。新生代采用复制算法,老生代采用标记整理算法。垃圾算法的实现涉及大量细节,而且不同平台实现方法也不同。
10、JVM的内存结构和内存分配
Java虚拟机将内存大致分为三个逻辑部分:方法区Method Area、Java栈、Java堆。
1)方法区是静态分配的,编译器将变量绑定位置,常量池、源码中命名常量、String、static变量保存在方法区。
2)Java Stack(栈)是逻辑概念,特点后进先出。一个栈空间可能是连续或不连续的。最典型的Stack应用是方法的调用,Java虚拟机每调用一个方法就创建一个方法帧(frame),退出则方法帧弹出(pop)。存储的数据是运行时确定的。
3)Java堆分配(heap allocation)意味着以随意顺序,在运行时进行存储空间分配和收回的内存管理模型。堆中存储的数据常常是大小、数量、生命周期在编译时无法确定。Java对象的内存总是在heap中分配。
Java的内存分配:
1)基础数据类型直接在栈空间分配;
2)方法的形参,直接在栈空间分配,方法调用完成后从栈空间回收;
3)引用数据类型,需要用new来创建的,在栈空间分配地址空间,在堆空间分配对象的类变量;
4)方法的引用参数,在栈空间分配地址空间,指向堆空间的对象去,方法调用完成后从栈空间回收;
5)局部变量new出来时,在栈空间和堆空间中分配空间,生命周期结束后,栈空间立即回收,堆空间等待GC回收;
6)方法调用时传入的实参,在栈空间分配,方法调用完成后从栈空间释放;
7)字符串常量在DATA区域分配,this在堆空间分配;
8)数组在栈空间分配数组名称,在堆空间分配数组实际大小。
11、Java中的引用类型
Java中的引用类型分为四种级别,由高到低依次为:强引用、软引用、弱引用、虚引用。
**强引用(**StrongReference):代码中使用的就是强引用。一个对象被强引用,那么GC绝不会回收它。内存不足,虚拟机抛OutOfMemoryError终止程序,也不会回收该对象。
软引用(SoftReference):一个对象被软引用,如果内存足够,不会回收;内存不够,GC会回收。只要没有被回收,它就可以被使用。软引用用来实现内存敏感的高速缓存。它可以和一个引用队列联合使用,如果软引用对象被垃圾回收,Java虚拟机就会把该引用加入到关联的引用队列中。被Soft Reference指到的对象,及时没有任何Direct Reference,也不会被清除。一直到JVM内存不足且没有Direct Reference时才清除。不但可以把对象缓存,也不会造成内存不足。
弱引用(WeakReference):一个对象被弱引用,它就是可有可无的,只要被GC扫描到随时就会执行回收。它可以和一个引用队列联合使用,如果弱引用对象被垃圾回收,Java虚拟机就会把该引用加入到关联的引用队列中。
虚引用(PhantomReference):虚引用不会决定对象生命周期,一个对象被虚引用,跟没有任何引用一样,任何时候都可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。
**弱引用与软引用的区别:**弱引用对象生命周期更短,由于垃圾回收器优先级很低,因此不一定会很快被发现。
**虚引用与软引用弱引用的区别:**虚引用必须和引用队列联合使用。
12、stack和heap的区别
1)申请方式:stack由系统分配;heap需要程序员自己申请并指明大小。
2)申请后系统的响应:stack只要有空间,一直提供内存,否则抛栈溢出;heap自己百度。
3)申请大小限制:stack编译时确定2M或1M;heap受限于计算机系统中有效的虚拟内存。
4)申请效率:stack自由分配速度较快无法控制;heap速度较慢,容易产生内存碎片,方便。
5)存储内容:stack在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,静态变量不入栈。调用结束后,局部变量先 出栈,然后参数,最后是 开始存的地址。heap一般在堆头部用一个字节存放堆的大小,内容由程序员安排。
6)数据结构:stack是满足先进后出性质的数据结构;heap是(满足堆性质的)优先队列的数据结构。
7)a、栈、堆都是Java在Ram中存放数据的地方。与C++不同,Java自动管理堆栈,程序员不能直接设置。
b、栈优点是存取快,仅次于cpu的寄存器,数据可以共享;缺点是数据大小和生存期确定,缺乏灵活性。堆优点是可以动态分配内存和生存期且不必事先告诉编译器,GC自动回收不再使用的数据,缺点由于动态分配,所以 存取慢 。
四、Java类加载器
1、类加载器原则
双亲委派模型的工作过程为:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的加载器都是如此,因此所有的类加载请求都会传给顶层的启动类加载器,只有当父加载器反馈自己无法完成该加载请求(该加载器的搜索范围中没有找到对应的类)时,子加载器才会尝试自己去加载。
1)委托原则:如果一个类还没有被加载,类加载器会委托父类加载器去加载它;
2)可见性原则:被父亲类加载器加载的类对于孩子加载器是可见的,但关系相反相反则不可见;
3)独特性原则:当一个类加载器加载一个类时,它的孩子加载器绝不会重新加载
2、类加载器种类
1)根类加载器(Bootstrap):C++写的,看不到源码;
2)扩展类加载器(Extension):加载位置:classpath中;
3)应用类加载器(System\App):加载位置:classpath中;
4)自定加载器(必须继承ClassLoader)
3、类加载过程
JVM把描述类数据的字节码.Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。
加载 ==> 连接(验证、准备、解析) ==> 初始化 ==> 使用 ==> 卸载
加载(装载)、验证、准备、初始化和卸载这五个阶段顺序是固定的,类的加载过程必须按照这种顺序开始,而解析阶段不一定。这些阶段通常都是互相交叉的混合式进行的,通常会在一个阶段执行的过程中调用或激活另外一个阶段。
4、类什么时候被初始化
1)创建类的实例,new对象; 2)访问某个类或接口的静态变量,或者对该静态变量赋值;
3)调用类的静态方法; 4)初始化一个类的子类(会首先初始化子类的父类);
5)反射; 6)JVM启动时标明的启动类,即文件名和类名相同的类。
五、JVM基础知识
1、既然有GC机制,为什么还会有内存泄漏
实际开发中,可能会存在无用但可达的对象,这些对象不能被GC回收,因此也会导致内存泄漏的发生。例如,hibernate的Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收的,然而这些对象中可能存在无用的垃圾对象如果不及时关闭或清空一级缓存,就可能导致内存泄漏。
六、GC基础知识
1、Java中为什么有GC机制
1)安全性考虑; – for security
2)减少内存泄漏; – erase memory leak in some degree
3)减少程序员工作量 – Programmers don’t worry about memory releasing
2、对于GC有哪些内存需要回收
一个接口的多个实现类需要的内存可能不一样,只有在程序运行期间才会知道创建那些对象,这部分内存的分配和回收都是动态的,GC主要关注的是这部分内存。总而言之,GC主要回收的时JVM中的方法区和堆。
3、Java的GC什么时候回收垃圾
1)从对象存活看,引用计数器和可达性分析:对象的计数器值为0 / 对象到GC Roots没有任何引用链可以到达。
2)从对象的引用看 :根据引用类型分析:强引用、软引用、弱引用、虚引用
3)方法区中回收的废弃常量和无用类
①废弃常量的回收:根据引用计数器
②无用类的回收:a、该类所有实例被回收,也就是Java堆中不存在该类的任何实例
b、加载该类的ClassLoader被回收
c、该类对用的java.lang.Class对象没有任何地方被引用,无法再任何地方反射访问该类的方法
总结:
对于堆中对象,可达性分析判断对象是否被引用。而根据实际对引用的不同需求,又分成了4种不同回收机制的引用。
对于方法区中的常量和类,当一个常量没有被任何对象引用,当一个类被判定为无用类,就可以被回收。
七、Java8的新特性
1、lambda表达式
大话设计模式