记录一些震惊到小白的java真相 =》不断更新中
一 、 JVM 内存分析
1. Java中的数据类型有两种。(都说的是局部变量,所以存在栈中)
一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。值得注意的是,自动变量存的是字面值,不是引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了,出于追求速度的原因,就存在于栈中。
因而当我们声明一个新的基本类型变量, JVM会优先查找有没有 值为“XX” 的内存地址 ,如果有,直接指向此地址,如果没有,才创建。
java 中只有当两个变量指向同一块内存地址是,“==” 才会返回true.
另一种是包装类,事实上,所有的包装类都被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
包装类数据,如Integer, String, Double等全部存在栈中,new()的时候才申请空间。
eg.
String a = "123" ; //在栈中创建
String b = "123 ";
if (a == b ) => true //a b 指向同一地址,都在栈中保存 ,数据实现共享。
String c = new String(“123”); // c 在堆中创建新的对象,c是新的引用
if(c == a ) => false // a在栈中,c在堆中,数据不共享。
// 对象a = 对象 b
// a 内存地址直接指向b的地址
练习题:
class a{public int test=2;}
class b extends a{public int test = 22;}
a aa = new a(); b bb = new b();
aa.test = 126; bb.test=621;
aa = bb;
aa.test=?
bb.test=?
answer:
2
621
注意!!!
对于两个非new生成的Integer对象,进行比较时,如果两个变量的值在区间-128到127之间,则比较结果为true,如果两个变量的值不在此区间,则比较结果为false
原因:
非new定义integer会调用接口 valueof() . 而java API中对Integer类型的valueOf的定义如下:
public static Integer valueOf(int i){
assert IntegerCache.high >= 127;
if (i >= IntegerCache.low && i <= IntegerCache.high){
return IntegerCache.cache[i + (-IntegerCache.low)];
}
return new Integer(i);
}
注意!! 字符串拼接操作 ‘+’ 很费时,尽量避免
Java对String的相加是通过StringBuffer实现的,先构造一个StringBuffer里面存放”tao”,然后调用append()方法追加”bao”,然后将值为”taobao”的StringBuffer转化成String对象。
2. 引用
强引用 : 无论如何都不会收,想要回收需要显示的赋成 null
软引用(SoftReference) : 内存不足时回收 ,常常用来实现缓存,如果软引用对象被回收,aSoftRef.get()返回null
//构造方法:
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
//构造软引用 并且关联引用队列 ,
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
//使用示例
MyObject aRef = new MyObject();
SoftReference aSoftRef=new SoftReference(aRef);
那么当这个SoftReference所软引用的aMyOhject被垃圾收集器回收的同时,ref所强引用的SoftReference对象被列入ReferenceQueue。也就是说,ReferenceQueue中保存的对象是Reference对象,而且是已经失去了它所软引用的对象的Reference对象。另外从ReferenceQueue这个名字也可以看出,它是一个队列,当我们调用它的poll()方法的时候,如果这个队列中不是空队列,那么将返回队列前面的那个Reference对象。
在任何时候,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果队列为空,将返回一个null,否则该方法返回队列中前面的一个Reference对象。利用这个方法,我们可以检查哪个SoftReference所软引用的对象已经被回收。于是我们可以把这些失去所软引用的对象的SoftReference对象清除掉。
弱引用(WeakReference): 无论内存是否充足,都会回收被弱引用关联的对象
虚引用(PhantomReference):
finalize是Object的方法,所有类都继承了该方法。 当一个对象满足垃圾回收的条件,并且被回收的时候,其finalize()方法就会被调用
二 、 变量
成员变量:(1)定义在类里,方法之外的变量。
(2)成员变量可以不显式初始化,它们可以由系统设定默认值;
(3)成员变量在所在类被实例化后,存在堆内存中
局部变量:(1)定义在方法体内部的变量。
(2)局部变量没有默认值,所以必须设定初始赋值。
(3)局部变量在所在方法调用时,存在栈内存空间中
三 、 枚举类
继承java.lang.Enum , 枚举类的所有实例自动添加 public static final 。
枚举类的构造器 只能是私有的。
枚举类中可以定义静态或者非静态的方法。
eg.
package enumcase;
public enum Operation {
PLUS{
@Override
public double eval(double x, double y) {
return x + y;
}
},
MINUS{
@Override
public double eval(double x, double y) {
return x - y;
}
},
TIMES{
@Override
public double eval(double x, double y) {
return x * y;
}
},
DIVIDE{
@Override
public double eval(double x, double y) {
return x / y;
}
};
/**
* 抽象方法,由不同的枚举值提供不同的实现。
* @param x
* @param y
* @return
*/
public abstract double eval(double x, double y);
public static void main(String[] args) {
System.out.println(Operation.PLUS.eval(10, 2));
System.out.println(Operation.MINUS.eval(10, 2));
System.out.println(Operation.TIMES.eval(10, 2));
System.out.println(Operation.DIVIDE.eval(10, 2));
}
}
四 、 String、 StringBuffer 、 StringBinder
1. 操作速度 String < StringBuffer < StringBinder // 原因String 是字符串常量,值不可变,详见第一部分,内存分析。
2. String 、 StringBuilder:线程非安全的
StringBuffer: 线程安全的
StringBuffer 的常用方法:
append、delete(int s , int e)、
insert(int offset , int i)//在指定位置出入int参数的字符串、
replace、reverse(字符串反转)、replace(int s , int e , String str)
五 、 RunTime类常用的方法:
(1) getRuntime():该方法用于返回当前应用程序的运行环境对象。
(2) exec(String command):该方法用于根据指定的路径执行对应的可执行文件。
六 、 线程池
线程安全: 不安全:
Hashtable HashMap
StringBuffer StringBuilder
Vector ArrayList
1. newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
2.newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
3. newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
4.newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
eg. newFixedThreadPool
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 通过实现Runnable接口,实现多线程
* Runnable类是有run()方法的;
* 但是没有start方法
* 参考:
*
http://blog.csdn.net/qq_31753145/article/details/50899119
* */
public class fixedThreadExecutorTest{
public static void main(String[] args) {
// TODO Auto-generated method stub
//创建一个可重用固定线程数的线程池
ExecutorService pool=Executors.newFixedThreadPool(2);
//创建实现了Runnable接口对象,Thread对象当然也实现了Runnable接口;
Thread t1=new MyThread();
Thread t2=new MyThread();
Thread t3=new MyThread();
Thread t4=new MyThread();
Thread t5=new MyThread();
//将线程放到池中执行;
pool.execute(t1);
pool.execute(t2);
pool.execute(t3);
pool.execute(t4);
pool.execute(t5);
//关闭线程池
pool.shutdown();
}
}
运行结果:
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-1正在执行....
pool-1-thread-2正在执行....
5.同步
A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
七 异常
所有异常 都是 Throwable的子类或者间接子类。
catch 的顺序 ,是 子类在前 父类在后:
每一个catch块用于处理一个异常。异常匹配是按照catch块的顺序从上往下寻找的,只有第一个匹配的catch会得到执行。匹配时,不仅运行精确匹配,也支持父类匹配,因此,如果同一个try块下的多个catch异常类型有父子关系,应该将子类异常放在前面,父类异常放在后面,这样保证每个catch块都有存在的意义。
Java则是让执行流恢复到处理了异常的catch块后接着执行,这种策略叫做:termination model of exception handling(终结式异常处理模式)
finally块不管异常是否发生,只要对应的try执行了,则它一定也执行。(!! 无论是否发生异常, finally 一定会执行,所以是一个关闭资源的好地方!!)在 try块中即便有return,break,continue等改变执行流的语句,finally也会执行。
***finally中的return 会覆盖 try 或者catch中的返回值( throw or return ) ,导致 异常无法抛出,返回值 直接消失。***
不要在fianlly中使用return。
不要在finally中抛出异常。
减轻finally的任务,不要在finally中做一些其它的事情,finally块仅仅用来释放资源是最合适的。
将尽量将所有的return写在函数的最后面,而不是try … catch … finally中。
异常的链化
异常链化:以一个异常对象为参数构造新的异常对象。新的异对象将包含先前异常的信息。这项技术主要是异常类的一个带Throwable参数的函数来实现的。这个当做参数的异常,我们叫他根源异常(cause)。
//获取输入的2个整数返回
/*试图计算两个int值相加,然而由于输入的值不是int, 导致在getInputNumbers()中发生
InputMismatchException ,进而导致add 函数发生异常,如果不使用链式异常的方式,
add() 中的异常信息,会覆盖之前的异常信息。
*/
private static List<Integer> getInputNumbers()
{
List<Integer> nums = new ArrayList<>();
Scanner scan = new Scanner(System.in);
try {
int num1 = scan.nextInt();
int num2 = scan.nextInt();
nums.add(new Integer(num1));
nums.add(new Integer(num2));
}catch(InputMismatchException immExp){
throw immExp;
}finally {
scan.close();
}
return nums;
}
//执行加法计算
private static int add() throws Exception
{
int result;
try {
List<Integer> nums =getInputNumbers();
result = nums.get(0) + nums.get(1);
}catch(InputMismatchException immExp){
throw new Exception("计算失败",immExp); //!!!链化:以一个异常对象为参数构造新的异常对象。
}
return result;
}
/*
请输入2个加数
r 1
java.lang.Exception: 计算失败
at practise.ExceptionTest.add(ExceptionTest.java:53)
at practise.ExceptionTest.main(ExceptionTest.java:18)
Caused by: java.util.InputMismatchException
at java.util.Scanner.throwFor(Scanner.java:864)
at java.util.Scanner.next(Scanner.java:1485)
at java.util.Scanner.nextInt(Scanner.java:2117)
at java.util.Scanner.nextInt(Scanner.java:2076)
at practise.ExceptionTest.getInputNumbers(ExceptionTest.java:30)
at practise.ExceptionTest.add(ExceptionTest.java:48)
... 1 more
*/
Throw
throws与throw这两个关键字接近,不过意义不一样,有如下区别:
1. throws 出现在方法声明上,而throw通常都出现在方法体内。
2. throws 表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某个异常对象
Java中的异常是线程独立的,线程的问题应该由线程自己来解决,而不要委托到外部,也不会直接影响到其它线程的执行。
八 、 HashCode() 与 equels()
hashcode() 方法 与 equels()方法 必须保持一致性
- 在程序执行期间,只要equals方法的比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始终如一地返回同一个整数。
- 如果两个对象根据equals方法比较是相等的,那么调用两个对象的hashCode方法必须返回相同的整数结果。
- 如果两个对象根据equals方法比较是不等的,则hashCode方法不一定得返回不同的整数。
对于第二条和第三条很好理解,但是第一条,很多时候就会忽略。在《Java编程思想》一书中的P495页也有同第一条类似的一段话:
“设计hashCode()时最重要的因素就是:无论何时,对同一个对象调用hashCode()都应该产生同样的值。如果在讲一个对象用put()添加进HashMap时产生一个hashCdoe值,而用get()取出时却产生了另一个hashCode值,那么就无法获取该对象了。所以如果你的hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的散列码”。
下面举个例子:
package com.cxh.test1;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
class People{
private String name;
private int age;
public People(String name,int age) {
this.name = name;
this.age = age;
}
public void setAge(int age){
this.age = age;
}
@Override
public int hashCode() {
// TODO Auto-generated method stub
return name.hashCode()*37+age;
}
@Override
public boolean equals(Object obj) {
// TODO Auto-generated method stub
return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
}
}
public class Main {
public static void main(String[] args) {
People p1 = new People("Jack", 12);
System.out.println(p1.hashCode());
HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
hashMap.put(p1, 1);
p1.setAge(13);
System.out.println(hashMap.get(p1));
}
}
这段代码输出的结果为“null”,想必其中的原因大家应该都清楚了。
因此,在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段
九 、Collection
1. Collection是 Set List Queue和 Deque的接口
Queue: 先进先出队列
Deque: 双向链表
2. Collections是一个类,容器的工具类
reverse | 反转 |
shuffle | 混淆 |
sort | 排序 |
swap | 交换 |
rotate | 滚动 |
synchronizedList | 线程安全化 |
3. HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式
区别1:
HashMap可以存放 null
Hashtable不能存放null
区别2:
HashMap不是线程安全的类
Hashtable是线程安全的类 //Hashtable不能用null作key,不能用null作value
4. HashSet: 无序
LinkedHashSet: 按照插入顺序
TreeSet: 从小到大排序
5. 比较器
Comparator<T> c =
new
Comparator<T>() {
@Override
public
int
compare(Hero h1, Hero h2) {}
}
或使需要比较的类实现Comparable接口
十、泛型
1. ?extends A
ArrayList heroList<? extends Hero> 表示这是一个Hero泛型或者其子类泛型
heroList 的泛型可能是Hero
heroList 的泛型可能是APHero
heroList 的泛型可能是ADHero
所以 可以确凿的是,从heroList取出来的对象,一定是可以转型成Hero的
但是,不能往里面放东西,因为
放APHero就不满足<ADHero>
放ADHero又不满足<APHero>
2. ? super A
ArrayList heroList<? super Hero> 表示这是一个Hero泛型或者其父类泛型
heroList的泛型可能是Hero
heroList的泛型可能是Object
可以往里面插入Hero以及Hero的子类
但是取出来有风险,因为不确定取出来是Hero还是Object
3. ?
泛型通配符? 代表任意泛型
既然?代表任意泛型,那么换句话说,这个容器什么泛型都有可能
所以只能以Object的形式取出来
并且不能往里面放对象,因为不知道到底是一个什么泛型的容器
综上所述:
如果只想取出 不插入,请使用 ?extends A
如果只想插入,不取出,请使用 ?super A
如果只想以object 形式取出来,不插入 ,请使用 ?
十一、什么是菱形继承问题?
菱形继承问题反映了为什么在 Java 中我们不被允许实现多继承。如果有两个类共同继承一个有特定方法的超类,那么该方法会被两个子类重写。然后,如果你决定同时继承这两个子类,那么在你调用该重写方法时,编译器不能识别你要调用哪个子类的方法。