Java 笔记1

来源:互联网 发布:网吧指纹软件推广 编辑:程序博客网 时间:2024/06/03 22:05

final实例域

构建对象时必须初始化这样的域(只能初始化一次),并且初始化之后就不能被修改。
特别的,当final修饰的不是基本数据类型,修饰的是一个引用类型,那么就表示该引用值不能变,但其对象本身是可以被修改的。

  1. 显示初始化;
  2. 没有显示初始化,就要在构造器或者构造代码块中进行初始化。

注意:将一个类声明为final,只有其中的方法自动成为final,而不包括域。


动态绑定

这里写图片描述

至此,编译器已经获得需要调用的方法名字和参数类型。

这里写图片描述

在覆盖一个方法时,子类方法不能低于超类方法的可见性。

例如:方法参数是一个接口类型,在程序运行期间,虚拟机会根据传入的具体的对象,来调用某一个类方法。


抽象类:

  • 类即使不含有抽象方法,也可以将其声明为抽象类。
  • 抽象类不能被实例化。
  • 可以定义一个抽象类的对象变量。

Object:所有类的超类

Object类:public boolean equals(Object obj) {    return (this == obj);}String类:public boolean equals(Object anObject) {    if (this == anObject) {        return true;    }    if (anObject instanceof String) {        String anotherString = (String) anObject;        int n = value.length;        if (n == anotherString.value.length) {            char v1[] = value;            char v2[] = anotherString.value;            int i = 0;            while (n-- != 0) {                if (v1[i] != v2[i])                        return false;                i++;            }            return true;        }    }    return false;}

Objects类:

public static boolean equals(Object a, Object b) {    return (a == b) || (a != null && a.equals(b));}

注意:equals方法的参数是Object,不是其他具体类型!
equals相等,则hashCode也相等。
equals和hashCode应用


反射

一、Class类

类类型
Object类中的getClass()方法将会返回一个Class类型的实例。
这里写图片描述
这里写图片描述
这个方法只有在className是类名或接口名时才能执行,否则,将会抛出checked exception(已检查异常),需要有异常处理器(try..catch..)。

这里写图片描述
这里写图片描述

二、利用反射分析类的能力

这里写图片描述
这里写图片描述

package reflection;import java.util.*;import java.lang.reflect.*;/** * This program uses reflection to print all features of a class. * @version 1.1 2004-02-21 * @author Cay Horstmann */public class ReflectionTest{   public static void main(String[] args)   {      // read class name from command line args or user input      String name;      if (args.length > 0) name = args[0];      else      {         Scanner in = new Scanner(System.in);         System.out.println("Enter class name (e.g. java.util.Date): ");         name = in.next();      }      try      {         // print class name and superclass name (if != Object)         Class cl = Class.forName(name);         Class supercl = cl.getSuperclass();         String modifiers = Modifier.toString(cl.getModifiers());         if (modifiers.length() > 0) System.out.print(modifiers + " ");         System.out.print("class " + name);         if (supercl != null && supercl != Object.class) System.out.print(" extends "               + supercl.getName());         System.out.print("\n{\n");         printConstructors(cl);         System.out.println();         printMethods(cl);         System.out.println();         printFields(cl);         System.out.println("}");      }      catch (ClassNotFoundException e)      {         e.printStackTrace();      }      System.exit(0);   }   /**    * Prints all constructors of a class    * @param cl a class    */   public static void printConstructors(Class cl)   {      Constructor[] constructors = cl.getDeclaredConstructors();      for (Constructor c : constructors)      {         String name = c.getName();         System.out.print("   ");         String modifiers = Modifier.toString(c.getModifiers());         if (modifiers.length() > 0) System.out.print(modifiers + " ");                  System.out.print(name + "(");         // print parameter types         Class[] paramTypes = c.getParameterTypes();         for (int j = 0; j < paramTypes.length; j++)         {            if (j > 0) System.out.print(", ");            System.out.print(paramTypes[j].getName());         }         System.out.println(");");      }   }   /**    * Prints all methods of a class    * @param cl a class    */   public static void printMethods(Class cl)   {      Method[] methods = cl.getDeclaredMethods();      for (Method m : methods)      {         Class retType = m.getReturnType();         String name = m.getName();         System.out.print("   ");         // print modifiers, return type and method name         String modifiers = Modifier.toString(m.getModifiers());         if (modifiers.length() > 0) System.out.print(modifiers + " ");                  System.out.print(retType.getName() + " " + name + "(");         // print parameter types         Class[] paramTypes = m.getParameterTypes();         for (int j = 0; j < paramTypes.length; j++)         {            if (j > 0) System.out.print(", ");            System.out.print(paramTypes[j].getName());         }         System.out.println(");");      }   }   /**    * Prints all fields of a class    * @param cl a class    */   public static void printFields(Class cl)   {      Field[] fields = cl.getDeclaredFields();      for (Field f : fields)      {         Class type = f.getType();         String name = f.getName();         System.out.print("   ");         String modifiers = Modifier.toString(f.getModifiers());         if (modifiers.length() > 0) System.out.print(modifiers + " ");                  System.out.println(type.getName() + " " + name + ";");      }   }}

三、在运行时使用反射分析对象

获取/修改数据域的值

这里写图片描述
这里写图片描述
这里写图片描述

四、调用任意方法

这里写图片描述

package methods;import java.lang.reflect.*;/** * This program shows how to invoke methods through reflection. * @version 1.2 2012-05-04 * @author Cay Horstmann */public class MethodTableTest{   public static void main(String[] args) throws Exception   {      // get method pointers to the square and sqrt methods      Method square = MethodTableTest.class.getMethod("square", double.class);      Method sqrt = Math.class.getMethod("sqrt", double.class);      // print tables of x- and y-values      printTable(1, 10, 10, square);      printTable(1, 10, 10, sqrt);   }   /**    * Returns the square of a number    * @param x a number    * @return x squared    */   public static double square(double x)   {      return x * x;   }   /**    * Prints a table with x- and y-values for a method    * @param from the lower bound for the x-values    * @param to the upper bound for the x-values    * @param n the number of rows in the table    * @param f a method with a double parameter and double return value    */   public static void printTable(double from, double to, int n, Method f)   {      // print out the method as table header      System.out.println(f);      double dx = (to - from) / (n - 1);      for (double x = from; x <= to; x += dx)      {         try         {            double y = (Double) f.invoke(null, x);            System.out.printf("%10.4f | %10.4f%n", x, y);         }         catch (Exception e)         {            e.printStackTrace();         }      }   }}

输出:

public static double MethodTableTest.square(double)    1.0000 |     1.0000    2.0000 |     4.0000    3.0000 |     9.0000    4.0000 |    16.0000    5.0000 |    25.0000    6.0000 |    36.0000    7.0000 |    49.0000    8.0000 |    64.0000    9.0000 |    81.0000   10.0000 |   100.0000public static double java.lang.Math.sqrt(double)    1.0000 |     1.0000    2.0000 |     1.4142    3.0000 |     1.7321    4.0000 |     2.0000    5.0000 |     2.2361    6.0000 |     2.4495    7.0000 |     2.6458    8.0000 |     2.8284    9.0000 |     3.0000   10.0000 |     3.1623

集合框架

这里写图片描述

1、Set、Map区别:

Set继承了Collection接口;而Map没有。
Set是基于Map来实现的,保证了元素的不重复性;Map保存的是键值对。

  • HashSet是基于HashMap实现的,HashSet存储的值作为HashMap的key值存放,从而保证了值的不重复性,因此HashSet的增删改查也是基于HashMap来实现的。**HashMap允许空的键值对,HashSet允许空的值。**HashSet初始化是可以指定内部HashMap的initialCapacity和loadFactor。
  • LinkedHashSet继承HashSet,它是基于LinkedHashMap实现的。通过维护一个运行于所有条目的双向链表,LinkedHashMap保证了元素迭代的顺序;所以LinkedHashSet也保证了元素迭代的顺序。LinkedHashMap允许空的键值对,LinkedHashSet允许空的值。
  • TreeSet实现了SortedSet接口,它是基于TreeMap实现的。TreeMap底层是一个红黑树的数据结构,所以TreeMap保证了键的有序性,TreeSet保证了存储的值的有序性。两者的增删改查操作均与红黑树的高度有关,为O(logn)。两者初始化的时候都可以传入comparator或者使用key默认的比较器来作为排序的基准。因为需要排序比较,所以TreeMap不允许空的key但是value可以为空,TreeSet不允许空的值。

2、Set、List区别

Set保证了元素的不重复性,而List允许重复值的存在。当然,它们底层实现不一样,List是基于数组或者链表的数据结构实现的。List允许允许空的值,而Set根据具体的实现类而定。

3、Arraylist、LinkedList区别

Arraylist是基于数组实现的,而LinkedList是基于链表实现的。两者都保证了元素迭代的顺序。

4、HashSet、LinkedHashSet区别

LinkedHashSet继承了HashSet;内部实现等价于HashMap和LinkedHashMap的区别。

5、HashMap、TreeMap、LinkedHashMap

  • HashMap和LinkedHashMap允许空的键值对,而TreeMap不允许空的key,但是允许空的value。
  • HashMap底层是基于数组和链表实现的。LinkedHashMap在HashMap的基础上又维护了一个运行于所有条目的双向链表(在Node上增加两个指针)来保证元素迭代的顺序(即元素插入的顺序)。
  • TreeMap底层是基于红黑树实现的,因为红黑树是一种自平衡二叉查找树,所以TreeMap保证了key的有序性,在TreeMap上增删改查操作均与红黑树的高度有关,为O(logn)。TreeMap的Key按照自然顺序进行排序或者根据创建映射时提供的Comparator接口进行排序。
  • HashMap与LinkedHashMap保证了以O(1)的时间复杂度进行增、删、改、查,从存储角度考虑,这两种数据结构是非常优秀的。另外,LinkedHashMap还额外地保证了Map的遍历顺序可以与put顺序一致,解决了HashMap本身无序的问题。
  • 如果只需要存储功能,使用HashMap与LinkedHashMap是一种更好的选择;如果还需要保证统计性能或者需要对Key按照一定规则进行排序(取出最值等操作),那么使用TreeMap是一种更好的选择。

HashMap

http://zhangshixi.iteye.com/blog/672697
http://www.admin10000.com/document/3322.html

table的默认初始大小是16。
key为null的结点在table[0]的链表上。
结点是以头插法的形式插入到链表上的。

插入结点

threshold = table.length * 装载因子

void addEntry(int hash, K key, V value, int bucketIndex) {      // 获取指定 bucketIndex 索引处的 Entry       Entry<K,V> e = table[bucketIndex];      // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry      table[bucketIndex] = new Entry<K,V>(hash, key, value, e);      // 如果 Map 中的 key-value 对的数量超过了极限      if (size++ >= threshold)      // 把 table 对象的长度扩充到原来的2倍。          resize(2 * table.length);  }

HashMap的resize(rehash):

当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。

table.length是2的N次幂,index = h & (length - 1)

显然,hashmap里面的元素位置尽量的分布均匀些,尽量使得每个位置上的元素数量只有一个,那么当我们用hash算法求得这个位置的时候,马上就可以知道对应位置的元素就是我们要的,而不用再去遍历链表。
所以,想到用hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比较均匀的。但是,“模”运算的消耗还是比较大的,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法,因此,index = h & (length - 1)

为什么hashmap的数组初始化大小都是2的次方大小时,hashmap的效率最高?
看下图,左边两组是数组长度为16(2的4次方),右边两组是数组长度为15。两组的hashcode均为8和9,但是很明显,当它们和1110“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到同一个链表上,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hashcode的值会与14(1110)进行“与”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
这里写图片描述
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。

e.hash == hash && ((k = e.key) == key || key.equals(k))

判断是否一致的条件是:e.hash == hash && ((k = e.key) == key || key.equals(k)),一定要牢牢记住这个条件。

  1. 必须满足的条件1:hash值一样,hash值的来历就是根据key的hashCode再进行一个复杂的运算,当两个key的hashCode一致的时候,计算出来的hash也是必然一样的。
  2. 必须满足的条件2:两个key的引用一样或者equals相同。

综上所述,HashMap对于key的重复性判断是基于两个内容的判断,一个就是hash值是否一样(会演变成key的hashCode是否一样),另一个就是equals方法是否一样(引用一样则肯定一样)。它依据的是两个条件,所以对于一个Person类,若想在HashMap中以person对象作为key,要满足person1对象和person2对象一样,则我们必须要重写equals方法和hashCode方法。若没有重写hashCode方法,则使用系统默认的本地hashCode方法,不同的对象的hashCode是不一样的,所以HashMap在判断时就会认为person1和person2是不一样,造成了我们事与愿违的结果。

HashMap为什么要多引入key的hash是否一致的判断条件呢?为什么不仅仅判断equals方法是否一样?

  1. 好处1:当这个table数组特别大的时候,如长度为10000,根据hash&length-1这个计算的索引值,便很快的定位某一个链表下,过滤了很大一批数据,不需要一个一个遍历。仅仅依靠equals是无法实现这样的快速过滤的。
  2. 好处2:不同的hash值得出的索引位置很可能是一样的,所以在这个链表下仍要进一步判断,此时就需要一个一个进行遍历。Entry< K,V >对象中hash值是已经保存的数据,新的key的hash也已经计算出来,所以在遍历对比的过程中判断hash值是否一致是相当快的,如果不一致,则认为不相同继续下一个判断,就不会调用费时的equals方法。假如这个链表的数据也特别多,判断过程也是相当快的。也就是说,判断hash是否一致加快了在链表上的遍历的速度,减少了相对费时的equals调用次数(equals是业务需求!当用到HashMap或者HashSet等时必须要重写hashCode方法)。

综上所述,为了实现HashMap的上述高效的存储操作,引入了hash这个重要的东西。同时带给我们的附加操作就是要满足key一致除了equals返回true外,还必须让hashCode一样。所以我们重写equals方法的时候尽量的重写hashCode方法,当用到HashMap或者HashSet等时必须要重写hashCode方法。
hashCode的重写的原则:当equals方法返回true,则两个对象的hashCode必须一样。
如String、Integer等类都重写了equals方法和hashCode方法,都是遵循上述原则。所以我们在重写hashCode时也要遵循上述原则。

modeCount++是用于fail-fast机制的,每次修改HashMap数据结构的时候都会自增一次这个值

fail-fast机制
Java并发编程:同步容器、并发容器和阻塞队列
“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

解决方法:
- 方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
- 方案二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。

HashMap的table为什么是transient的

transient Entry[] table;

看到table用了transient修饰,也就是说table里面的内容全都不会被序列化。
因为HashMap是基于HashCode的,HashCode作为Object的方法,是native的。这意味着的是:HashCode和底层实现相关,不同的虚拟机可能有不同的HashCode算法。再进一步说得明白些就是,可能同一个Key在虚拟机A上的HashCode=1,在虚拟机B上的HashCode=2,在虚拟机C上的HashCode=3。
这就有问题了,Java自诞生以来,就以跨平台性作为最大卖点,好了,如果table不被transient修饰,在虚拟机A上可以用的程序到虚拟机B上可以用的程序就不能用了,失去了跨平台性。
因此,为了避免这一点,Java采取了重写自己序列化table的方法,在writeObject选择将key和value追加到序列化的文件最后面;而在readObject的时候重构HashMap数据结构。
这个例子也告诉了我们:尽管使用的虚拟机大多数情况下都是HotSpot,但是也不能对其它虚拟机不管不顾,有跨平台的思想是一件好事。

HashMap和Hashtable的区别

HashMap和Hashtable是一组相似的键值对集合,它们的区别也是面试常被问的问题之一,我这里简单总结一下HashMap和Hashtable的区别:

  1. Hashtable是线程安全的,Hashtable所有对外提供的方法都使用了synchronized,也就是同步,而HashMap则是线程非安全的。
  2. Hashtable不允许空的key和value,空的将导致空指针异常,而HashMap则无所谓,没有这方面的限制。
  3. 初始容量大小和每次扩充容量大小的不同。HashTable默认的初始大小为11,之后每次扩充为原来的2n+1。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍。如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。
  4. HashTable中有contains(Object value)
  5. HashTable会尽量使用素数、奇数。而HashMap则总是使用2的幂作为哈希表的大小。我们知道当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀(,所以单从这一点上看,HashTable的哈希表大小选择,似乎更高明些。但另一方面我们又知道,在取模计算时,如果模数是2的幂,那么我们可以直接使用位运算来得到结果,效率要大大高于做除法。所以从hash计算的效率上,又是HashMap更胜一筹。HashMap由于使用了2的幂次方,所以在取模运算时不需要做除法,只需要位的与运算就可以了。但是由于引入的hash冲突加剧问题,HashMap在调用了对象的hashCode方法之后,又做了一些位运算在打散数据。
    HashMap 和 HashTable 到底哪不同 ?

散列冲突

拉链法和开放地址法
http://www.nowamagic.net/academy/detail/3008050

Java8之重新认识HashMap

http://www.importnew.com/20386.html


自动拆箱和自动装箱

在自动装箱的时候,Java虚拟机会自动调用Integer的valueOf方法;在自动拆箱的时候,Java虚拟机会自动调用Integer的intValue方法。

Byte、Short、Integer、Long、Char这几个装箱类的valueOf()方法是以128位分界线做了缓存的,假如是128以下且-128以上的值是会取缓存里面的引用的,以Integer为例,其valueOf(int i)的源代码为:

public static Integer valueOf(int i) {    final int offset = 128;    if (i >= -128 && i <= 127) { // must cache         return IntegerCache.cache[i + offset];    }        return new Integer(i);    }

而Float、Double则不会,原因也很简单,因为byte、Short、integer、long、char在某个范围内的整数个数是有限的,但是float、double这两个浮点数却不是。


Forward和Redirect的区别

Forward和Redirect代表了两种请求转发方式:直接转发和间接转发。对应到代码里,分别是RequestDispatcher类的forward()方法和HttpServletResponse类的sendRedirect()方法。

  • 对于间接方式(Redirect),服务器端在响应第一次请求的时候,让浏览器再向另外一个URL发出请求,从而达到转发的目的。它本质上是两次HTTP请求,对应两个request对象。
  • 对于直接方式(Forward),客户端浏览器只发出一次请求,Servlet把请求转发给Servlet、HTML、JSP或其它信息资源,由第2个信息资源响应该请求,两个信息资源共享同一个request对象。

间接请求转发(Redirect)

间接转发方式,有时也叫重定向,它一般用于避免用户的非正常访问。例如:用户在没有登录的情况下访问后台资源,Servlet可以将该HTTP请求重定向到登录页面,让用户登录以后再访问。在Servlet中,通过调用response对象的SendRedirect()方法,告诉浏览器重定向访问指定的URL,示例代码如下:

......//Servlet中处理get请求的方法public void doGet(HttpServletRequest request,HttpServletResponse response){//请求重定向到另外的资源    response.sendRedirect("资源的URL");}........

上图所示的间接转发请求的过程如下:
这里写图片描述
1. 浏览器向Servlet1发出访问请求;
2. Servlet1调用sendRedirect()方法,将浏览器重定向到Servlet2;
3. 浏览器向servlet2发出请求;
4. 最终由Servlet2做出响应。

直接请求转发(Forward)

Web应用程序大多会有一个控制器。由控制器来控制请求应该转发给那个信息资源。然后由这些信息资源处理请求,处理完以后还可能转发给另外的信息资源来返回给用户,这个过程就是经典的MVC模式。

javax.serlvet.RequestDispatcher接口是请求转发器必须实现的接口,由Web容器为Servlet提供实现该接口的对象,通过调用该接口的forward()方法到达请求转发的目的,示例代码如下:

    //Servlet里处理get请求的方法 public void doGet(HttpServletRequest request , HttpServletResponse response){     //获取请求转发器对象,该转发器的指向通过getRequestDisPatcher()的参数设置   RequestDispatcher requestDispatcher =request.getRequestDispatcher("资源的URL");    //调用forward()方法,转发请求         requestDispatcher.forward(request,response);    }......

其实,通过浏览器就可以观察到服务器端使用了那种请求转发方式,当单击某一个超链接时,浏览器的地址栏会出现当前请求的地址,如果服务器端响应完成以后,发现地址栏的地址变了,则证明是间接的请求转发。相反,如果地址没有发生变化,则代表的是直接请求转发或者没有转发。


线程池

1、“讲讲线程池的实现原理”
2、“线程池中的coreNum和maxNum有什么不同”
3、“在不同的业务场景中,线程池参数如何设置”


锁的实现

Synchronized和ReentrantLock的实现原理,读写锁。


ConcurrentHashMap


join()/join(long millis)/wait()/wait(long timeout)

1、 wait()/wait(long timeout)是Object类中的方法

public final void wait() throws InterruptedException {        wait(0);    }

public final native void wait(long timeout) throws InterruptedException;

特别要注意:
The current thread must own this object’s monitor.
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait();
* … // Perform action appropriate to condition
* }
当前线程thread1 调用 对象A.wait(); 时,thread1必须拥有对象A的对象锁!也就是说,wait()/notify()/notifyAll()必须在synchronized语句中执行。
Causes the current thread to wait until another thread invokes the
{@link java.lang.Object#notify()} method or the
{@link java.lang.Object#notifyAll()} method for this object.

注意:当在未获取对象锁的情况下调用wait/notify(不在synchronized语句中),会抛出java.lang.IllegalMonitorStateException

2、join()/join(long millis)

在Thread类中:

public final void join() throws InterruptedException {        join(0); //Waits at most {@code millis} milliseconds for this thread to die. A timeout of {@code 0} means to wait forever.    }
//在 当前线程 调用thread1.join(long millis);//其中synchronized表示当前线程已经获取了thread1对象的对象锁,//所以可以在方法内部调用thread1的wait(0)方法,将前线程挂起!并且将thread1对象的对象锁释放// 当前线程 要等待thread1.notify()的执行。当thread1 terminates时会主动调用this.notifyAll,即唤醒所有等待thread1对象的对象锁的线程,并将其放入等待队列//然后 当前线程 才有机会够继续执行public final synchronized void join(long millis)    throws InterruptedException {        long base = System.currentTimeMillis();        long now = 0;        if (millis < 0) {            throw new IllegalArgumentException("timeout value is negative");        }        if (millis == 0) {            while (isAlive()) {                wait(0); //挂起当前线程,等待thread1.notify唤醒当前线程            }        } else {            while (isAlive()) {                long delay = millis - now;                if (delay <= 0) {                    break;                }                wait(delay);                now = System.currentTimeMillis() - base;            }        }    }

数据类型之间的转换

Java数据类型分为三大类,即布尔型、字符型和数值型。其中数值型又分为整型和浮点型。Java的基本数据类型(8种)为布尔型boolean(1字节);字符型char(2字节);整型byte(1字节)、short(2字节)、int(4字节)、long(8字节);浮点型float(4字节)、double(8字节)。

由低级到高级分别为(byte、short、char)-> int -> long -> float -> double

自动类型转换:低级变量可以直接转换成高级变量,这是自动类型转换。 注意:如果低级类型为char型,向高级类型转换时,会转换成对应的ASCII码值。
对于byte、short、char三种类型而言,他们是相同级别的,因此,不能相互自动转换,但是可以强制类型转换。

强制类型转换:将高级变量转换为低级变量时,需要用到强制类型转换,这种转换可能导致溢出或精度的下降。

int num1 = 100%3.0;  //报错。3.0的类型是double,所以100会自动转换成double类型,计算结果1.0也是double类型,double不会自动转换成int类型        double num2 = 100%3.0;    //通过     1.0        int num3 = 100/3.0;     //报错        double num4 =100/3.0;  //通过           33.333333333333336

Class的newInstance()和new 构造函数创建一个实例的区别

用newInstance与用new是区别的,区别在于创建对象的方式不一样,前者是使用类加载机制。

Java中工厂模式经常使用newInstance来创建对象,因此从为什么要使用工厂模式上也可以找到具体答案。
例如:
Class c = Class.forName(”A”);
factory = (AInterface)c.newInstance();
其中AInterface是A的接口,如果下面这样写,你可能会理解:
String className = “A”;
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
进一步,如果下面写,你可能会理解:
String className = readfromXMlConfig;//从xml 配置文件中获得字符串
Class c = Class.forName(className);
factory = (AInterface)c.newInstance();
上面代码就消灭了A类名称,优点:无论A类怎么变化,上述代码不变,甚至可以更换A的兄弟类B , C , D…等,只要他们继承Ainterface就可以。

从jvm的角度看,我们使用new的时候,这个要new的类可以还没有加载。
但是使用newInstance时候,就必须保证:
1、这个类已经加载;
2、这个类已经连接了。而完成上面两个步骤的正是class的静态方法forName()方法,这个静态方法调用了当前类的类加载器。

有了上面jvm上的理解,那么我们可以这样说,newInstance实际上是把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化。
这样分步的好处是显而易见的。我们可以在调用class的静态加载方法forName时获得更好的灵活性,提供给了我们降耦的手段。例如工程模式的使用。
注:用newInstance的时候必须有一个无参的构造函数,如果没有构造函数,jvm会提供一个无惨的构造函数,如果自己提供了有参的构造函数,jvm则不会提供了,这样的情况下就会报错。所以如果在这种情况下,应该自己再提供一个无参的构造函数。
newInstance: 弱类型。低效率。只能调用无参构造。
new: 强类型。相对高效。能调用任何public构造。

通过反射创建新的类示例,有两种方式:

  1. Class中的newInstance()方法:只能够调用无参的构造函数,即默认的构造函数;构造函数必须是public类型的。
  2. Constructor中的newInstance()方法:可以根据传入的参数,调用任意构造构造函数。
public class A {      private A() {          System.out.println("A's constructor is called.");      }      private A(int a, int b) {          System.out.println("a:" + a + " b:" + b);      }  }
public class B {       public static void main(String[] args) {           B b=new B();           out.println("通过Class.NewInstance()调用私有构造函数:");           b.newInstanceByClassNewInstance();           out.println("通过Constructor.newInstance()调用私有构造函数:");           b.newInstanceByConstructorNewInstance();       }       /*通过Class.NewInstance()创建新的类示例*/       private void newInstanceByClassNewInstance(){           try {/*当前包名为reflect,必须使用全路径*/               A a=(A)Class.forName("reflect.A").newInstance();           } catch (Exception e) {               out.println("通过Class.NewInstance()调用私有构造函数【失败】");           }      }      /*通过Constructor.newInstance()创建新的类示例*/       private void newInstanceByConstructorNewInstance(){           try {/*可以使用相对路径,同一个包中可以不用带包路径*/               Class c=Class.forName("A");               /*以下调用无参的、私有构造函数*/               Constructor c0=c.getDeclaredConstructor();               c0.setAccessible(true);               A a0=(A)c0.newInstance();               /*以下调用带参的、私有构造函数*/               Constructor c1=c.getDeclaredConstructor(new Class[]{int.class,int.class});               c1.setAccessible(true);               A a1=(A)c1.newInstance(new Object[]{5,6});           } catch (Exception e) {               e.printStackTrace();           }       }   } 
通过Class.NewInstance()调用私有构造函数: 通过Class.NewInstance()调用私有构造函数【失败】 通过Constructor.newInstance()调用私有构造函数: A's constructor is called. a:5 b:6 

对Class.getResourceAsStream和ClassLoader.getResourceAsStream方法所使用的资源路径的解释

http://blog.csdn.net/bluishglc/article/details/38753047


数组拷贝方式

效率由高到底依次排序如下:

1、package java.lang;
public final class System类中的

//length:the number of array elements to be copied. public static native void arraycopy(Object src,  int  srcPos,                                        Object dest, int destPos,                                        int length);

2、clone()
对于非基本数据类型的数组,比如数组的内容是类,clone()方法也是复制数组的内容,但是这时候数组的内容是指向对象的引用而不是对象的本身.
对于基本数据类型的数组,clone()方法就是新建一个新的数组,与之前的数组的值的变化没有关系.

3、package java.util;
public class Arrays类中的
它们内部依然是调用System.arraycopy

//newLength:the length of the copy to be returnedpublic static <T> T[] copyOf(T[] original, int newLength) {        return (T[]) copyOf(original, newLength, original.getClass());    }
public static <T> T[] copyOfRange(T[] original, int from, int to) {        return copyOfRange(original, from, to, (Class<? extends T[]>) original.getClass());    }

4、for循环

原创粉丝点击