java学习散记(记录一些震惊到小白的java真相)

记录一些震惊到小白的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

edit

反转

shuffle

edit

混淆

sort

edit

排序

swap

edit

交换

rotate

edit

滚动

synchronizedList

edit

线程安全化

 

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 中我们不被允许实现多继承。如果有两个类共同继承一个有特定方法的超类,那么该方法会被两个子类重写。然后,如果你决定同时继承这两个子类,那么在你调用该重写方法时,编译器不能识别你要调用哪个子类的方法。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值