190515
01 - 多线程start() 和 run()
高赞解析(via:Magic图)
start
方法
用start
方法来启动线程,是真正实现了多线程, 通过调用Thread
类的start()
方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()
方法。但要注意的是,此时无需等待run()
方法执行完毕,即可继续执行下面的代码。所以run()
方法并没有实现多线程。run
方法
run()
方法只是类的一个普通方法而已,如果直接调用run
方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run
方法体执行完毕后才可继续执行下面的代码。
02 - 结果输出
高赞解析(via:ど゛低调、)
- 不论有什么运算,小括号的优先级都是最高的,先计算小括号中的运算,得到
x+y +""+25+y
- 任何字符与字符串相加都是字符串,但是是有顺序的,字符串前面的按原来的格式相加,字符串后面的都按字符串相加,得到
25+“”+25+5
我的理解:与字符串拼接都是字符串,但识别到是输出字符串之前,仍然按照正常数据类型进行加减,识别到字符串之后再进行拼接。
03 - 多态
高赞解析(via:stevenniu)
public class Father {
public void say(){
System.out.println("father");
}
public static void action(){
System.out.println("爸爸打儿子!");
}
}
public class Son extends Father{
public void say() {
System.out.println("son");
}
public static void action(){
System.out.println("打打!");
}
public static void main(String[] args) {
Father f=new Son();
f.say();
f.action();
}
}
输出: son
爸爸打儿子!
当调用say
方法执行的是Son
的方法,也就是重写的say
方法
而当调用action
方法时,执行的是father
的方法。
- 普通方法,运用的是动态单分配,是根据new的类型确定对象,从而确定调用的方法;
- 静态方法,运用的是静态多分派,即根据静态类型确定对象,因此不是根据new的类型确定调用的方法。
04 - 数组赋值效率比较
高赞解析(via:云想衣裳花想容春风拂槛露华浓)
- 从速度上看:
System.arraycopy
>clone
>Arrays.copyOf
>for
for
的速度之所以最慢是因为下标表示法每次都从起点开始寻位到指定下标处(现代编译器应该对其有进行优化,改为指针),另外就是它每一次循环都要判断一次是否达到数组最大长度和进行一次额外的记录下标值的加法运算。- 查看
Arrays.copyOf
的源码可以发现,它其实本质上是调用了System.arraycopy
。之所以时间差距比较大,是因为很大一部分开销全花在了Math.min
函数上了。
190517
01 - JVM基础
高赞解析(via:StrongYoung)
运行时数据区包括:虚拟机栈区,堆区,方法区,本地方法栈,程序计数器
- 虚拟机栈区 :也就是我们常说的栈区,线程私有,存放基本类型,对象的引用和
returnAddress
,在编译期间完成分配。 - 堆区 , JAVA 堆,也称
GC
堆,所有线程共享,存放对象的实例和数组, JAVA 堆是垃圾收集器管理的主要区域。 - 方法区 :所有线程共享,存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。这个区域的内存回收目标主要是针对常量池的对象的回收和对类型的卸载。
- 本地方法栈: 为虚拟机使用的
Native
方法服务,也是线程私有。 - 程序计数器 :线程私有,每个线程都有自己独立的程序计数器,用来指示下一条指令的地址。
- 运行时常量池: 线程共享 ,是方法区的一部分,
Class
文件中存放编译期生成的各种字面量和符号引用,类加载后进入方法区的运行时常量池中。
02 - 构造的运用
public class Test {
public static void main(String [] args){
System.out.println(new B().getValue()); //输出为
}
static class A{
protected int value;
public A(int v) {
setValue(v);
}
public void setValue(int value){
this.value = value;
}
public int getValue(){
try{
value++;
return value;
} catch(Exception e){
System.out.println(e.toString());
} finally {
this.setValue(value);
System.out.println(value);
}
return value;
}
}
static class B extends A{
public B() {
super(5);
setValue(getValue() - 3);
}
public void setValue(int value){
super.setValue(2 * value);
}
}
}
高赞解析(via:谷哥的小弟)
http://blog.csdn.net/zhumintao/article/details/53818972
03 - 基本类型转换
高赞解析(via:Pandora)
------------知识点------------
Java表达式转型规则由低到高转换:
1、所有的byte,short,char型的值将被提升为int型;
2、如果有一个操作数是long型,计算结果是long型;
3、如果有一个操作数是float型,计算结果是float型;
4、如果有一个操作数是double型,计算结果是double型;
5、被fianl修饰的变量不会自动改变类型,当2个final修饰相操作时,结果会根据左边变量的类型而转化。
--------------解析--------------
语句1错误:b3=(b1+b2);自动转为int,所以正确写法为b3=(byte)(b1+b2);或者将b3定义为int;
语句2正确:b6=b4+b5;b4、b5为final类型,不会自动提升,所以和的类型视左边变量类型而定,即b6可以是任意数值类型;
语句3错误:b8=(b1+b4);虽然b4不会自动提升,但b1仍会自动提升,所以结果需要强转,b8=(byte)(b1+b4);
语句4错误:b7=(b2+b5); 同上。同时注意b7是final修饰,即只可赋值一次,便不可再改变。
04 - 泛型的级别
高赞解析(via:晓宇大美女~)
- 只看尖括号里边的!!明确点和范围两个概念
- 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如
List<A>
,List<B>
,List<Object>
- 如果尖括号里面带有问号,那么代表一个范围,
<? extends A>
代表小于等于A的范围,<? super A>
代表大于等于A的范围,<?>
代表全部范围 - 尖括号里的所有点之间互相赋值都是错,除非是俩相同的点
- 尖括号小范围赋值给大范围,对;大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值
List<?>
和List
是相等的,都代表最大范围
- 补充:
List
既是点也是范围,当表示范围时,表示最大范围
public static void main(String[] args) {
List<A> a;
List list;
list = a; //A对,因为List就是List<?>,代表最大的范围,A只是其中的一个点,肯定被包含在内
List<B> b;
a = b; //B错,点之间不能相互赋值
List<?> qm;
List<Object> o;
qm = o; //C对,List<?>代表最大的范围,List<Object>只是一个点,肯定被包含在内
List<D> d;
List<? extends B> downB;
downB = d; //D对,List<? extends B>代表小于等于B的范围,List<D>是一个点,在其中
List<?extends A> downA;
a = downA; //E错,范围不能赋值给点
a = o; //F错,List<Object>只是一个点
downA = downB; //G对,小于等于A的范围包含小于等于B的范围,因为B本来就比A小,B时A的子类嘛
}
190520
01 - 静态内部类
高赞解析(via:bbblemon)
- 静态内部类:
1) 静态内部类本身可以访问外部的静态资源,包括静态私有资源。但是不能访问非静态资源,可以不依赖外部类实例而实例化。 - 成员内部类:
1)成员内部类本身可以访问外部的所有资源,但是自身不能定义静态资源,因为其实例化本身就还依赖着外部类。 - 局部内部类:
1)局部内部类就像一个局部方法,不能被访问修饰符修饰,也不能被static修饰。
2) 局部内部类只能访问所在代码块或者方法中被定义为final的局部变量。 - 匿名内部类:
1)没有类名的内部类,不能使用class,extends和implements,没有构造方法。
2) 多用于GUI中的事件处理。
3) 不能定义静态资源
4) 只能创建一个匿名内部类实例。
5) 一个匿名内部类一定是在new后面的,这个匿名类必须继承一个父类或者实现一个接口。
6) 匿名内部类是局部内部类的特殊形式,所以局部内部类的所有限制对匿名内部类也有效。
02 - try…catch
高赞解析(via:牛客网。)
try块后面不一定需要跟着catch块,可以只跟着finally块,如下:
//case 1
try {
System.out.println("try");
}catch (Exception e){
System.out.println("catch");
}finally {
System.out.println("finally");
}
//case 2
try {
System.out.println("try");
}finally {
System.out.println("finally");
}
//case 3
try {
System.out.println("try");
}catch (Exception e){
System.out.println("catch");
}
03 - 多态
package Wangyi;
class Base
{
public void method()
{
System.out.println("Base");
}
}
class Son extends Base
{
public void method()
{
System.out.println("Son");
}
public void methodB()
{
System.out.println("SonB");
}
}
public class Test01
{
public static void main(String[] args)
{
Base base = new Son();
base.method();
base.methodB();
}
}
高赞解析(via:方爷)
Base base=new Son();
是多态的表示形式。父类对象调用了子类创建了Son
对象。
base
调用的method()
方法就是调用了子类重写的method()
方法。
而此时base
还是属于Base
对象,base
调用methodB()
时Base
对象里没有这个方法,所以编译不通过。
要想调用的话需要先通过Son son=(Son)base;
强制转换,然后用son.methodB()
调用就可以了。
04 - 静态方法
高赞解析(via:wangtingkui)
首先:
成员方法又称为实例方法
静态方法又称为类方法
其次:
a,静态方法中没有this
指针
c,可以通过类名作用域的方式调用Class.fun();
d,太绝对化了,在类中申请一个类对象或者参数传递一个对象或者指针都可以调用;
可以将this
理解为对象,而类方法属于类,不属于对象,所以类方法前不能加this
指针。(via:pangeneral)
190522
01 - String
高赞解析(via:hanking)
1)String
类是final
类,也即意味着String
类不能被继承,并且它的成员方法都默认为final方法。在Java中,被final
修饰的类是不允许被继承的,并且该类中的成员方法都默认为final
方法。
2)String
类底层是char[]
数组来保存字符串的。
对String
对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
字符串常量池
在class文件中有一部分来存储编译期间生成的字面常量以及符号引用,这部分叫做class文件常量池,在运行期间对应着方法区的运行时常量池。
JVM为了减少字符串对象的重复创建,其维护了一个特殊的内存,这段内存被成为字符串常量池或者字符串字面量池
工作原理
当代码中出现字面量形式创建字符串对象时,JVM首先会对这个字面量进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回,否则新的字符串对象被创建,然后将这个引用放入字符串常量池,并返回该引用。
实现前提
字符串常量池实现的前提条件就是Java中String
对象是不可变的,这样可以安全保证多个变量共享同一个对象。如果Java中的String
对象可变的话,一个引用操作改变了对象的值,那么其他的变量也会受到影响,显然这样是不合理的。
String str1 = "hello";
这里的str1
指的是方法区中的字符串常量池中的“hello”
,编译时期就知道的;
String str2 = "he" + new String("llo");
这里的str2
必须在运行时才知道str2
是什么,所以它是指向的是堆里定义的字符串“hello”
,所以这两个引用是不一样的。
如果用str1.equal(str2)
,那么返回的是true
;因为String
类重写了equals()
方法。
编译器没那么智能,它不知道"he" + new String("llo")
的内容是什么,所以才不敢贸然把"hello"
这个对象的引用赋给str2
.
如果语句改为:"he"+"llo"
这样就是true
了。
new String("zz")
实际上创建了2个String
对象,就是使用“zz”
通过双引号创建的(在字符串常量池),另一个是通过new
创建的(在堆里)。只不过他们的创建的时期不同,一个是编译期,一个是运行期。
String s = "a"+"b"+"c";
语句中,“a”
,"b"
, "c"
都是常量,编译时就直接存储他们的字面值,而不是他们的引用,在编译时就直接将它们连接的结果提取出来变成"abc"
了。
02 - try…catch…
高赞解析(via:重塑辉煌)
还是需要理解try...catch...finally
与直接throw
的区别:try catch
是直接处理,处理完成之后程序继续往下执行,throw
则是将异常抛给它的上一级处理,程序便不往下执行了。本题的catch
语句块里面,打印完1之后,又抛出了一个RuntimeException
,程序并没有处理它,而是直接抛出,因此执行完finally
语句块之后,程序终止了。
03 - 静态代码块的优先级
高赞解析(via:望山333)
Java 类执行顺序:
- 父类的静态变量和静态块赋值(按照声明顺序)
- 自身的静态变量和静态块赋值(按照声明顺序)
- 父类成员变量和块赋值(按照声明顺序)
- 父类构造器赋值
- 自身成员变量和块赋值(按照声明顺序)
- 自身构造器赋值
04 - 泛型的擦除
高赞解析(via:好吃不过炸酱面&我差不多是废了)
1、创建泛型对象的时候,一定要指出类型变量T的具体类型。争取让编译器检查出错误,而不是留给JVM运行的时候抛出类不匹配的异常。
2、JVM如何理解泛型概念 —— 类型擦除。事实上,JVM并不知道泛型,所有的泛型在编译阶段就已经被处理成了普通类和方法。 处理方法很简单,我们叫做类型变量T
的擦除(erased
) 。
总结:泛型代码与JVM
① 虚拟机中没有泛型,只有普通类和方法。
② 在编译阶段,所有泛型类的类型参数都会被Object
或者它们的限定边界来替换。(类型擦除)
③ 在继承泛型类型的时候,桥方法的合成是为了避免类型变量擦除所带来的多态灾难。
无论我们如何定义一个泛型类型,相应的都会有一个原始类型被自动提供。原始类型的名字就是擦除类型参数的泛型类型的名字。
// 通过反射得到T的真实类型
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
T的真是类型 = (Class) pt.getActualTypeArguments()[0];//取第一个泛型的类型
05 - 线程的并发
高赞解析(via:leozam&Likwind)
假设两线程为A、B,设有3种情况:
1.AB不并发:此时相当于两个方法顺序执行。A执行完后a=-1,B使用-1作为a的初值,B执行完后a=-2
2.AB完全并发:此时读写冲突,相当于只有一个线程对a的读写最终生效。相同于方法只执行了一次。此时a=-1
3.AB部分并发:假设A先进行第一次读写,得到a=1;之后A的读写被B覆盖了。B使用用1作为a的初值,B执行完后a=0
190524
01 - 多态
高赞解析(via:武岩)
- 首先,需要明白类的加载顺序。
(1) 父类静态代码块(包括静态初始化块,静态属性,但不包括静态方法)
(2) 子类静态代码块(包括静态初始化块,静态属性,但不包括静态方法 )
(3) 父类非静态代码块( 包括非静态初始化块,非静态属性 )
(4) 父类构造函数
(5) 子类非静态代码块 ( 包括非静态初始化块,非静态属性 )
(6) 子类构造函数
其中:类中静态块按照声明顺序执行,并且(1)和(2)不需要调用new类实例的时候就执行了(意思就是在类加载到方法区的时候执行的) - 其次,需要理解子类覆盖父类方法的问题,也就是方法重写实现多态问题。
Base b = new Sub();
它为多态的一种表现形式,声明是Base
,实现是Sub
类, 理解为b
编译时表现为Base
类特性,运行时表现为Sub
类特性。
当子类覆盖了父类的方法后,意思是父类的方法已经被重写,题中 父类初始化调用的方法为子类实现的方法,子类实现的方法中调用的baseName为子类中的私有属性。
由1.可知,此时只执行到步骤4.,子类非静态代码块和初始化步骤还没有到,子类中的baseName
还没有被初始化。所以此时baseName
为空。 所以为null
。
02 - 多线程
高赞解析(via:猴子派来的逗比)
线程间协作:wait
、notify
、notifyAll
在 Java 中,可以通过配合调用 Object
对象的 wait()
方法和 notify()
方法或 notifyAll()
方法来实现线程间的通信。在线程中调用 wait()
方法,将阻塞等待其他线程的通知(其他线程调用 notify()
方法或 notifyAll()
方法),在线程中调用 notify()
方法或 notifyAll()
方法,将通知其他线程从 wait()
方法处返回。
Object
是所有类的超类,它有 5 个方法组成了等待/通知机制的核心:notify()
、notifyAll()
、wait()
、wait(long)
和 wait(long,int)
。在 Java 中,所有的类都从 Object
继承而来,因此,所有的类都拥有这些共有方法可供使用。而且,由于他们都被声明为 final
,因此在子类中不能覆写任何一个方法。
这里详细说明一下各个方法在使用中需要注意的几点。
- wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
该方法用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用 wait()
之前,线程必须要获得该对象的对象级别锁,即只能在同步方法或同步块中调用 wait()方
法。进入 wait()
方法后,当前线程释放锁。在从 wait()
返回前,线程与其他线程竞争重新获得锁。如果调用 wait()
时,没有持有适当的锁,则抛出 IllegalMonitorStateException
,它是 RuntimeException
的一个子类,因此,不需要 try-catch
结构。
- notify()
public final native void notify() throws IllegalMonitorStateException
该方法也要在同步方法或同步块中调用,即在调用前,线程也必须要获得该对象的对象级别锁,的如果调用 notify()
时没有持有适当的锁,也会抛出 IllegalMonitorStateException
。
该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个 wait()
状态的线程来发出通知,并使它等待获取该对象的对象锁(notify
后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized
代码块后,当前线程才会释放锁,wait
所在的线程也才可以获取该对象锁),但不惊动其他同样在等待被该对象notify
的线程们。当第一个获得了该对象锁的 wait
线程运行完毕以后,它会释放掉该对象锁,此时如果该对象没有再次使用 notify
语句,则即便该对象已经空闲,其他 wait
状态等待的线程由于没有得到该对象的通知,会继续阻塞在 wait
状态,直到这个对象发出一个 notify
或 notifyAll
。这里需要注意:它们等待的是被 notify
或 notifyAll
,而不是锁。这与下面的 notifyAll()
方法执行后的情况不同。
- notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
该方法与 notify ()
方法的工作方式相同,重要的一点差异是:
notifyAll
使所有原来在该对象上 wait
的线程统统退出 wait
的状态(即全部被唤醒,不再等待 notify
或 notifyAll
,但由于此时还没有获取到该对象锁,因此还不能继续往下执行),变成等待获取该对象上的锁,一旦该对象锁被释放(notifyAll
线程退出调用了 notifyAll
的 synchronized
代码块的时候),他们就会去竞争。如果其中一个线程获得了该对象锁,它就会继续往下执行,在它退出 synchronized
代码块,释放锁后,其他的已经被唤醒的线程将会继续竞争获取该锁,一直进行下去,直到所有被唤醒的线程都执行完毕。
深入理解
如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
当有线程调用了对象的 notifyAll()
方法(唤醒所有 wait
线程)或 notify()
方法(只随机唤醒一个 wait
线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()
方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
03 - 方法的声明
高赞解析(via:阿森先生)
A:抽象方法只可以被public
和 protected
修饰;
B:final
可以修饰类、方法、变量,分别表示:该类不可继承、该方法不能重写、该变量是常量
C:static final
可以表达在一起来修饰方法,表示是该方法是静态的不可重写的方法
D:private
修饰方法(这太常见的)表示私有方法,本类可以访问,外界不能访问