Java基础进阶整理

Java学习笔记整理java

         本文档是我我的整理的,首先是想经过完成本文档更加扎实本身的基础增强对java语言的理解,而后就是想给入了门的同志们作下贡献。程序员

         固然,本文档主要是对java语言基础(固然还有不少基础没有涉及到)的进行较全面的理解已经整理,只要充分掌握了基础知识,学习高级部分的知识才会事半功倍犹如轻车熟路通常容易上手。算法

 

 

正文:数组

 

CLASSPATH的设置:咱们知道运行java必需要设置CLASSPATH环境变量,可是sun  java 1.4以后改进了设计,JRE会自动搜索当前路径下的类文件,并且使用java的编译和运行工具时,系统会自动加载dt.jar和tools.jar文件中的类,所以再也不须要设置该环境变量。缓存

         固然,若是你设置了CLASSPATH环境变量就必须设置全,好比包含当前路径,不然将会出现找不到类的问题。安全

Java程序的源文件必须与public类的类(接口)名相同,若是源文件全部类都没有使用public修饰,则文件名能够是任意合法的文件名,所以,一个Java源文件里最多自能定义一个public类(接口)。闭包

 

运算符的结合性和优先级ide

         只有 单目运算符,赋值运算符和三目运算符是从右向左结合的,其他全是左结合的。函数

         运算符优先级工具

                                     运算符说明                                                             java运算符

                                      分隔符                                                                    . [] () {} , ;

                                      单目运算符                                                          ++ -- ~ !

                                      强制类型转换运算符                                        (type)

                                      四则运算符和求余运算

                                      移位运算符                                                          << >> >>>

                                      关系运算符                                                          < <= > >= instanceof

                                      等价运算符                                                          == !=

                                      按位与                                                                    &

                                      按位异或                                                               ^

                                      按位或                                                                    |

                                      条件与                                                                    &&

                                      条件或                                                                    ||

                                      三目运算符                                                          ?:

                                      赋值运算符                                                          = += -= /= *= &= |= ^\ <<= >>= >>>= %=

 

break continue 后跟标签,能够直接结束其外层循环。 return 用于结束整个方法。

 

数组

         定义数组时不能指定数组的长度。

 

foreach

         当使用foreach 来迭代访问数组元素时,foreach中的循环变量至关于一个临时变量,整个临时变量并非数组元素,它只是保存了数组元素的值(深复制),所以若是但愿改变数组元素的值,则不能使用这种foreach循环。

栈内存和堆内存之分

         当一个方法执行是,每一个方法都会创建本身的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将被销毁,所以,全部在方法定义的变量都是放在栈内存中的。

         当咱们在程序中建立一个对象时,这个对象将被保存到运行时数据区中,这个运行时数据区就是堆内存。只有当一个对象没有任何引用变量引用它时,才会在合适的时机回收它。

 

面向对象

this能够表明任何对象,当this出如今某个方法体中时,它所表明的对象是不肯定的,但它的类型是肯定的,它所表明对象只能是当前类;只有当这个方法被调用时,他所表明的对象才能被肯定下来。

static 修饰的方法中不能使用this引用,因此static修饰的方法中不能访问没有使用static修饰的普通成员。static 用来修饰方法和属性等成员。局部成员的上已经程序单元是方法不是类,使用static修饰它们是没有意义的,因此局部成员都不能使用static修饰

在构造器中使用this引用时,this老是引用该构造器正在初始化的对象。

方法自己是指令的操做码部分,保存在stack中,方法内部变量做为指令的操做数部分,跟着指令的操做码以后,保存在stack中(实际是简单类型保存在stack中,引用类型在stack中保存地址,在heap中保存值)。

对象实例以及非静态属性保存在heap中的,而heap必须经过stack中的地址指针才可以被指令(类的方法)访问到。静态属性保存在stack中(这是网上找到的说,笔者认为应该是静态属性的地址保存在stack中)。

         非静态方法有一个隐含的传入参数,该参数是JVM给的,和咱们的代码无关,这个隐含参数就是对象实例在stack中的地址指针(this)。所以非静态方法(在stack中的指令代码)老是能够找到本身专用数据(在heap中的对象属性值)。固然非静态方法也必须得到该隐含参数,所以在调用非静态方法以前,必须先new一个对象实例,得到stack中地址的指针,不然JVM将没法将隐含参数传给非静态方法。而静态方法无需此隐含参数,所以不须要new对象,只要class文件被ClassLoader load进入JVM的stack,该方法便可被调用,固然此时静态方法存取不到heap中的对象属性的。

 

造成长度可变的方法

         public void test(int a, String… books){}

能够传入多个字符串参数做为参数值,实际上是能够看着参数数组。但一个方法只能有一个长度可变的造成且位于形参列表的最后。

        

方法重载

         方法的重载要求:两同(同一个类中的方法名相同),一不一样(参数列表不一样)(不建议使用长度可变的形参重载方法),跟方法的其余部分(返回值类型(有时咱们调用方法时不须要返回值,就不能根据返回值类型来肯定究竟是调用哪一个方法),修饰符等)没有任何关系。

 

         系统在第一次使用类加载类,并初始化类。类属性从这个类准备阶段起开始存在。系统不会为局部变量执行初始化,局部变量在访问以前必定要肯定是已经初始化。

 

         模块设计追求高内聚(尽量把模块的内部数据,功能实现细节隐藏在模块内部独立完成,不容许外部直接干预),低耦合(仅暴露少来的方法给外部使用)

 

构造器

         若是咱们提供了自定义的构造器,系统再也不提供默认的构造器,一般咱们都保留无参数的默认构造器。

 

多态性

         当编译时类型和运行时类型不一致,就会出现所谓的多态。

         当把子类对象赋给父类引用变量时,被称为向上转型,这个老是能够成功的,但把一个父类对象赋给一个子类引用变量时,就须要强制类型转换,并且还可能在运行时产生ClassCastException异常(当这个父类对象编译时类型为父类类型,运行时类型是子类类型才是正确的),使用instanceof运算符可让强制类型转换更安全。

 

Instanceof 运算符

         a instanceof B       a必须是具体的实例,B是一种类(或接口),能够是数组

         若是A是编译时可以肯定具体的类型,那么Instanceof 运算符前面操做数的编译时类型要么与后面类(接口,抽象类)相同,要么是后面类的父类,(便可以经过类型转换(B)a到B的,不能经过(B)a转换到B的能够,转换到它们共有的父类如(Object)a,但仍返回false),不然会引发编译错误。若是要运行时才肯定类型(如何调用函数getObject()返回对象等,但new String()这个在编译时就会肯定下类型,由于String Integer等类被final修饰,因此编译时就肯定类型),没有这样的限定。

         B不能是肯定的泛型参数的泛型,能够是 List或者 List<?>

         Instanceof 运算符前面的对象是不是后面的类,或者其子类。实现类的实例。若是是,就返回true,不然返回false,若a是null则返回false。

         通过instanceof 运算符判断一个对象是否能够强制类型转换,而后在使用(type)运算符进行强制转换,从而保证程序不会出现错误。

 

初始化块,构造器质性顺序

         当Java建立一个对象时,系统先为该对象的全部实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始化,其初始化顺序是:先执行初始化块或声明属性是指定的初始化值,而后执行构造器里指定的初始值。

         类初始化阶段:(类初始化只执行依次,而后会在虚拟机一直存在)

                   先执行最顶层父类的静态初始化块,依次向下,最后执行当前类的静态初始化块

         对象初始化阶段:(每次建立实例对象是都要进行)

                   先执行最最顶层父类的初始化块,构造器,依次向下,最后执行当前类初始化块。

 

构造器。

         初始化块和声明指定初始化值,他们的执行顺序与源代码中的排列顺序相同。

 

基本类型之间的转换

         Xxx parseXxx(String s) 将字符串类型转换成基本类型(除了Character以外)

         String 中的valueOf() 将基本类型转换成字符串。

 

==和equals

         == 判断两个变量是否相等是,若是2个变量是基本类型的变量,且都是数值型(不必定要求数据类型严格形同),则只要两个变量值相等,就返回true;但当判断两个引用类型的变量,必须它们指向同一个对象是,== 判断才会返回true。

       equals 在比较字符串是只要两个字符串引用变量的指向内容相等就返回true(String类已经重写了Object的equals方法),自定义的类要想达到这样的效果,必须重写Object的equals方法(自定义标准),不然用这个方法判断两个对象相等的标准与 == 符号是没有区别的。

 

单例类(Singleton)

         一个类只能建立一个实例

eg:

         class Singleton

         {

                   private static Singleton instance;

                   //隐藏构造器

                   private Singleton(){}

                   //提供一个静态方法,用于返回Singleton实例

                   //该方法能够加入自定义的控制,保证只产生一个Singleton对象

                   public static Singleton getInstance()

                   {

                            if(instance==null)

                            {

                                     Instance=new Singleton();

                            }

                            return instance;

                   }

}

这样用 getInstance返回的对象始终都是同一个。

 

final 修饰符

         当final修饰引用类型变量时,final只保证这个引用所引用的地址不会改变即一直引用同一个对象,但这个对象能够发生改变。

         若是final修饰的变量是基本数据类型,且在编译时就能够肯定该变量的值,因而能够把该变量当成常量处理,若是是修饰引用数据类型,就没法在编译时就得到值,而必须在运行时才能获得值。

         成员变量是随着类初始化或对象初始化而初始化的。当类初始化时,系统会为该类的类属性分配内存,并分配默认值;当建立对象时,系统会为该对象的和私立属性分配内存,并分配默认值。

                   类属性:可在静态初始化块中,声明该属性时指定初始值

                   实例属性:可在非静态初始化块中,声明该属性、构造器中指定初始值

并且上面初始化操做要其只能被初始化一次。(必须由程序员显示初始化)

若是在构造器,初始化块中对final成员变量进行初始化,则不要在初始化以前访问final成员变量的值,不然会出现“可能未初始化变量”的错误。(系统不会进行隐式初始化)。

         final 修饰的方法只是不能被重载,并非不能被重写。

 

抽象类

         抽象方法不能有方法体     abstract void String getName();

         只要含有抽象方法(直接定义了一个抽象方法;继承了一个抽象父类,但没有彻底实现父类包含的抽象方法;以及实现一个接口,但没有彻底实现接口包含的抽象方法)的类就是抽象类

         抽象类能够包含普通类的成分,可是必须有抽象方法。

         abstract 不能用于修饰属性和构造器,private 和 abstract 不能同时使用。

 

更完全的抽象:接口

         修饰符能够是public或者省略,省略了public 访问控制符,则只有在相同包结构下才能够访问该接口。

         一个接口能够有多个直接父接口,但不能继承类。

         一个接口不能包含构造器和初始化块定义,能够包含属性(只能是常量),方法(只能是抽象方法),内部类,内部接口,和枚举类定义。

         定义接口成员是,能够省略访问控制修饰符,若是指定访问修饰符,只能使用public修饰符。在接口定义属性时,无论是否使用 public static final修饰符,接口里的属性老是使用者三个修饰符来修饰。一样接口老是使用public abstract 来修饰。接口不可以使用static修饰接口里定义的方法。同理接口老是使用public static对内部类(静态内部类),内部接口和枚举类进行修饰。

 

内部类

         内部类至关外部类定义的一个普通方法,能够访问外部类的私有数据和方法。当内部类和外部类的成员或者方法出现重名时,可使用this和外部类名.this来区分。外部类就至关一个命名空间。

非静态内部类

         非静态内部类对象必须寄存在外部类对象里,故要建立一个非静态内部类必定要有经过外部了对象。在静态方法中不能直接经过new 内部类了建立内部类对象(没有外部类对象)(相似静态方法不能调用非静态方法以及访问非静态属性)。

         非静态内部类里不能有静态方法,静态属性,静态初始化块,能够有普通初始化块。

 

静态内部类

         静态内部类至关于一个外部类的一个静态方法。

        

        

内部类的使用

         Out.In in=new Out().new In();

         当继承内部类是必定要有外部类对象来调用内部类的构造器。

        

局部内部类

         局部内部类无需使用访问控制阀和static修饰符修饰

        

匿名内部类

         匿名内部了不能是抽象类,不能定义构造器,由于匿名内部类没有类名,因此没法定义构造器,但能够定义实例初始化块。

 

枚举类(省略)

 

泛型

 

         当使用泛型接口、父类时不能再包含类型形参,如继承带泛型的父类、接口要传入世界参数类型。 如 public class A extends B<String>。

         ArrayList<String>并无生产新的类,系统不会生产真正的泛型类,如应用 instance instanceof List<String>编译时引发错误:instanceof 运算符后不能使用泛型类。

         List<String>对象不能被当成List<Object>对象使用,也就是说List<String>类并非List<Object>类的子类。如 ArrayList<Number> nList=new ArrayList<Integer>(); 会引发编译错误。

         这点与数组不一样,若是Apple 是 Fruit 的子类,则 Apple[] 依然是 Fruit[]的子类

 

类型通配符

         为了表示各类List的父类,使用类型通配符“?”,可是并不能把元素加入到其中(不然会引发编译错误),由于咱们并不知道元素的类型,惟一例外的是null,它是全部引用类的实例。其次,咱们使用get()方法返回List<?>集合指定索引的元素,其返回值是一个未知类型,当能够确定的是,它是一个Object类型。前面咱们使用List<E> 使用add 的参数必须是E类的对象或者是子类的对象。

 

设定通配符的上限

         List<? Extends Fruit> 一样此处咱们使用add参数类型是 ?extends Fruit 它表示 Fruit未知的子类,咱们没法准确知道这个类型是什么,因此咱们没法将任何对象添加到这种集合中。

设定类型形参的上限

         class A <T extends Fruit>

更极端  class A<T extends Number &java.io.Serializable>

泛型方法

         public <T >void add (T a, List<T> list)

         {

                   t.add(a);

         }

若是无需向泛型集合添加元素,或者修改泛型集合的元素时,可使用类型通配符,无需使用泛型方法。

 

设定通配符的下限

         List<? super Type> 它表示它必须是Type自己或者Type的父类。

 

泛型的擦除和转换

         擦除,当把一个具备泛型形象的对象赋给另外一个没有泛型信息的变量时,则全部在尖括号直接的类型信息都被扔掉,如List<String> 类型被转换成List,则该List对几乎元素的类型检查变成了类型变量的上限(即Object),当再次 String s =list.get(0);将会引发编译错误(要强制转换)。

         转换,

                            List<Integer> li=new ArrayList<Integer>();

                            li.add(6);

//编译运行正常,只是引发“未经检查的转换”的警告

                            List list=li;     //泛型擦除

                            List<String> ls=list;   

                            System.out.println(ls.get(0));    //将引发运行时异常

                            //一样会引发运行时异常,不能进行类型转换

                            System.out.println((String)li.get(0));

                            List<?> lis=li;

                            System.out.println(li.get(0));     //OK

                            Lis.add(new Integer(6));    //error

 

泛型数组

         数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。

                   List<String>[] la=new List<String>[10];  //这是不容许的

         但能够

                   List<String> la=new ArrayList[10];  //编译时有“未经检查的转换”警告

使用时候注意不要 引发“ClassCastException”异常。

 

异常处理

         进行异常捕获的时候必定要先捕获小的异常,而后在捕获大的异常。

         在finally块中使用了return或throw的语句,将会致使try块、catch块中的return,throw 语句失效。

         当java程序执行try块、catch块时遇到return throw语句(没有System.exit(1);语句),这两个语句或致使该方法当即结束,因此系统并不会当即执行这个两个语句,而是去寻找该异常处理流程中是否包含finally块,若是有finally块,只有当finally块执行完毕,系统才会再次跳回来执行try块、catch块里的return或throw语句。

 

 

 

数组与内存控制

 

数组静态初始化:

         String[] books=new String[]{“wo shi “,”zhongshan”,”daxue”};

         String[] books={“wo shi “,”zhongshan”,”daxue”};

数组动态初始化:

         String[] books=new String[5];

         执行动态初始化时系统将为数组元素分配默认初始值。

不要同时使用静态初始化和动态初始化。

经过其余数组赋值初始化:

         String[] books1=books;

数组变量是一种引用类型的变量(存储的是数组的引用),数组变量自己存储在栈区,指向的内容是指向堆区。在Java中对象的引用自己不须要进行初始化,而对象自己才须要进行初始化。

全部局部变量都是放在各自的方法栈内存里保存的,但引用类型变量所引用的对象则老是存储在堆内存中的。只要程序经过引用类型变量访问属性或者调用方法,则该引用类型将被其所引用的对象代替,即要进行初始化。

         int[] sArra=null;

         System.out.println(sArra)               //这是没有问题的,输出的是null

 

对象与内存控制

 

实例变量和类变量

         static不能修饰外部类,局部变量,局部内部类

         //下面代码将提示:非法向前引用

         int num1=num2+1;

         int num2=3;

         上面代码变量同时用 static 修饰一样会出现上述问题。

         //下面代码正常

         int num1=num2+1;

         static int num2=3;        

这说明类变量的初始化时间老是比实例变量以前。

使用javap –c 类名 能够获得该类初始化的过程,当咱们队一个类初始化过程不是很清楚的时候不妨尝试下该命令。

 

实例变量的初始化时机

每次建立Java对象的时都会为实例变量分配内存空间,并对实例变量执行初始化

实例变量初始化有三种方式

         定义实例变量时指定初始值;

         非静态初始化块中对实例变量指定初始值;

         构造器中对实例变量指定初始值

其实前两种初始化执行顺序和它们在源代码中位置排列相同,也就拥有想多等级初始化时机,但都比第三种初始化方式更早。

实际中实例变量初始化 指定初始值和初始化块 当通过编译器处理后,它们都会被提取到构造器中。

例如:

         double a=24;

实际上会被分红以下2步执行:

         (1)double a;   建立Java对象时系统根据该语句为该对象分配内存

         (2)a=24;  这条语句是被提取到Java类的构造器中执行

public class JavapToolTest

{

              //初始化快中为count实例变量指定初始值

              count = 12;

     }

     int count = 20;

     //定义构造器

     public JavapToolTest()

     {

              System.out.println(count);

     }

使用javap –c 命令能够获得以下代码:结果就一目了然了

 

C:\Users\D.S.Qiu\Desktop\codes\codes\02\2.1>javap -c JavapToolTest

Compiled from "JavapToolTest.java"

public class JavapToolTest {

  int count;

 

  public JavapToolTest();

    Code:

       0: aload_0

       1: invokespecial #1                  // Method java/lang/Object."<init>":

()V

       4: aload_0

       5: bipush        12

       7: putfield      #2                  // Field count:I

      10: aload_0

      11: bipush        20

      13: putfield      #2                  // Field count:I

      16: getstatic     #3                  // Field java/lang/System.out:Ljava/

io/PrintStream;

      19: aload_0

      20: getfield      #2                  // Field count:I

      23: invokevirtual #4                  // Method java/io/PrintStream.printl

n:(I)V

      26: return

 

类变量初始化时机

类变量初始化的方法

         定义类变量时指定初始值;

         静态初始化块中对类变量指定初始值

初始化时机与实力变量初始化时机相似。

对没有给出初始化的变量系统总分配默认值。

 

父类构造器

当建立Java对象时,程序总会先依次调用每一个父类的非静态初始化块,父类构造器执行初始化,最后才调用本类的非静态初始块,构造器执行初始化。

隐式调用和显示调用

         若是子类构造器中没有使用super调用父类构造器,将会隐式调用父类无参数的构造器。

super 和this调用构造器最多只能使用其中一者,并且必须做为构造器代码的第一行。

访问子类对象的实例变量

         下面看一个极端的例子

class Base

{

         //定义了一个名为i的实例变量

         private int i = 2;

         public Base()

         {

                   this.display();

         }

         public void display()

         {

                   System.out.println(i);

         }

}

//继承Base的Derived子类

class Derived extends Base

{

         //定义了一个名为i的实例变量

         private int i = 22;

         //构造器,将实例变量i初始化为222

         public Derived()

         {

                   i = 222;

         }

         public void display()

         {

                   System.out.println(i);

         }

}

public class Test

{

         public static void main(String[] args)

         {

                   //建立Derived的构造器建立实例

                   new Derived();                      

         }

}

最终输出的竟是 0 。实际上,构造器只是负责对Java对象实例变量执行初始化(赋初始值),但在执行构造器以前,该对象所占的内存已经被分配下来了,只是默认初始值(空值)。

         程序在执行Derived类构造器以前首先会执行父类Base构造器(固然Object类在此以前是必定会被执行),此时Base中的  i 已经指定初始值2,而后执行构造器中的 this.display();  关键是此处this 表明谁,是Base仍是Derived

         为此咱们能够在Base类构造器中添加以下代码:

                            System.out.println(this.i);

                            System.out.println(i);

其实结果都是输出 2,但在Derived的display()函数中添加上述代码,输出结果都是0,这代表访问成员变量只有所处的类有关,但为何调用this.display();却输出 0 。

         其实咱们知道:this在构造器中表明正在初始化的Java对象,而咱们易知此时正在初始化对象时Derived对象——是Derived()构造器隐式调用了Base构造器的代码,故得出this表明Derived对象,而不是Base对象。咱们能够用System.out.println(this.class)来验证咱们的结论。

         但因为上述this位于Base的构造器中的,因此该this虽然表明的是Derived对象可是它的编译类型仍然是Base,只是实际引用了一个Derived对象(运行时类型)。

         咱们知道当变量编译时类型和运行时类型不一致时,经过该变量访问其引用对象的实例属性(成员变量)时,该实例属性的值由访问该属性的所处的类决定(不管是否使用this),但调用实例方法时,该方法行为是由它的实际引用的对象类型(运行时类型)决定。

         此外,若是咱们在Derived类中定义了一个getI()方法 而后在Base类的构造器中调用this.getI()方法时会报错,由于this的编译类型是Base类型不能调用getI()方法而没法经过编译。

 

调用子类重写的方法

         参考下面程序代码

class Animal

{

         //desc实例变量保存对象toString方法的返回值

         private String desc;

         public Animal()

         {

                   //调用getDesc()方法初始化desc实例变量

                   this.desc = getDesc();                                         //(3)

         }

         public String getDesc()

         {

                   return "Animal";

         }

         public String toString()

         {

                   return desc;

         }

}

public class Wolf extends Animal

{

         //定义name、weight两个实例变量

         private String name;

         private double weight;

         public Wolf(String name , double weight)

         {

                   //为name、weight两个实例变量赋值

                   this.name = name;                                //(3)

                   this.weight = weight;

         }

         //重写父类的getDesc()方法

         @Override

         public String getDesc()

         {

                   return "Wolf[name=" + name + " , weight="

                            + weight + "]";

         }

         public static void main(String[] args)

         {

                   System.out.println(new Wolf("灰太郎" , 32.3));                             //(1)

         }

}

固然是先初始化Wolf对象,在执行(2)以前先调用父类的无参数构造函数,this.desc=getDesc(); 执行getDesc()函数根据前面,咱们知道调用的是子类Wolf的方法,但此时Wolf的name和weight尚未执行初始化(只是默认空值),故会输出 Wolf[name=null,weight=0];

         为了不这种问题的发生,咱们能够这样定义Animal的toString()方法

                   class Animal

                   {

                            public String getDesc()

                            {

                                     return “Animal”;

                            }

                            public String toString()

                            {

                                     return getDesc();

                            }

                   }

这样就子类Wolf调用父类toString()的方法时,只要重写getDesc()方法就能够获得想要的结果。

父子实例的内存控制

继承成员变量和继承方法的区别参照以下代码

         class Base

{

         int count = 2;

         public void display()

         {

                   System.out.println(this.count);

         }

}

class Derived extends Base

{

         int count = 20;

         @Override

         public void display()

         {

                   System.out.println(this.count);

         }

}

public class FieldAndMethodTest

{

         public static void main(String[] args)

         {

                   Derived d=new Derived();

                   //声明一个Base变量,并将Derived对象赋给该变量

                   Base bd = new Derived();

                   //直接访问count实例变量和经过display访问count实例变量

                   System.out.println(bd.count);

                   bd.display();

                   //让d2b变量指向原d变量所指向的Dervied对象

                   Base d2b = d;

                   //访问d2b所指对象的count实例变量

                   System.out.println(d2b.count);

                   System.out.println(((Derived)d2b).count);

                   d2b.display();

         }

}

输出结果是 2  20  2  20  20

其实我知道 d ,db, d2b实际指向的是一个Derived对象,而这个对象分别存储了父类和本身的count的值。

         此外 若是咱们敲下:Derived dd=(Derived)new Base();运行时会出现类型转换错误。(编译能够经过,运行时会报错)。

         另外,咱们经过javap –c 命令能够知道 编译器在处理(继承下来的)方法和变量时的区别,对于父类的变量子类不会有保留,这使得子类能够拥有和父类同名的实例变量(经过对象名直接访问属性时是由编译类型决定,在函数内访问与属性所处的类名有关),但若是子类重写了父类的方法,就意味着子类定义的方法完全覆盖了父类的同名方法了(调用函数是由运行时类型决定)。

 

内存中子类的实例

         上面程序中  Derived d=new Derived();

                                     Base d2b=d;

其实就只建立了一个Derived对象,可是这个Derived对象不只保存了在Derived类中定义的全部实例变量,还保存了它全部父类所定义的所有实例变量。

         对于this,我想总结一个更为深入的结论就是,this到底表明什么对象跟运行时哪一个对象执行this所在的方法,而与this所在的方法位置没有直接的关系。Java能够经过return this 返回调用当前方法的Java对象(子类调用父类的方法(若方法返回值是return this,则返回的是子类对象),但不容许直接return super 来返回父类对象。Super关键字自己没有引用任何对象,不容许将super直接当作变量使用如 super==a 是会引发编译错误,在子类使用super只是做为调用父类方法的限定(当子类重写了父类的相应方法)。

父、子类的类变量

         因为类变量属于类自己,所以不会像实例变量那样复杂。

final修饰符(必定要显式指定初始值,不能默认空值)

         final 修饰的实例变量必须是显式指定初始值,只能在3个位置指定初始值

                   定义final实例变量时指定初始值

                   在非静态初始化块中为final实例变量指定初始值

                   在构造器中为final实例变量指定初始值

本质上final实例 变量只能在构造器中初始值(用javap –c 能够查看响应的原理)

         对于final 修饰的类变量只能在 定义时和静态初始化块指定初始值,其实 通过编译器的处理,这2种方式都会被抽取到静态初始化块去赋予初始值。

         final修饰局部变量必定要在定义时指定初始值。

         final 修饰引用类型变量时,引用变量自己不能改变,但其引用的对象能够改变。

执行“宏替换”的变量

         final变量在指定初始值以后本质是已经再也不是一个变量,而是至关一个直接量。

        

public class Test

{

         public static void main(String[] args)

         {

                   //定义一个普通局部变量

                   int a = 5;

                   System.out.println(a);

         }

}

使用javap  -c Test 编译

 

C:\Users\D.S.Qiu\Desktop\codes\codes\02\2.4>javap -c Test

Compiled from "FinalLocalTest.java"

public class Test {

  public Test();

    Code:

       0: aload_0

       1: invokespecial #1                  // Method java/lang/Object."<init>":

()V

       4: return

 

  public static void main(java.lang.String[]);

    Code:

       0: iconst_5

       1: istore_1

       2: getstatic     #2                  // Field java/lang/System.out:Ljava/

io/PrintStream;

       5: iload_1

       6: invokevirtual #3                  // Method java/io/PrintStream.printl

n:(I)V

       9: return

}

 

若是使用final修饰变量a,使用java –c分析新产生的class文件发现缺乏了 istore_1 和iload_1两行代码,这说明用final修饰的变量a以后,变量a就彻底消失了。当定义final变量时指定了初始值,而这初始值能够在编译时就肯定下来了,那这个final变量本质上就是一个“宏变量”,编译器会把程序中全部用到该变量的地方直接替换成该变量的值。

         Java会缓存全部曾经用过的字符串直接量

                   String s1=”疯狂”;

                   String s2=”Java”;

                   String s3=”疯狂”+”Java”;

                   String s4=s1+s2;

                   System.out.println(s1==s2);                //输出的是false

由于编译器没法在编译时肯定s2的值,不会让s3指向字符串池中的缓存中的“疯狂Java”,故s3==s4输出false。

若是咱们将s1,s2用final去修饰,s1,s2就变成“宏变量”这样在编译时就能够肯定s4的值,就能够得到true的输出。

         值得注意的是,final变量只有在定义该变量指定初始值,才会出现“宏替换”的效果。

         对于类变量的final修饰相似。

        

         Java要求因此内部类访问的局部变量都要使用final修饰那是有缘由的:对于局部变量而言,做用域就只停留在该方法内,当方法执行结束,该局部变量也要随着消失,但若是内部类若是产生隐式的“闭包”,闭包将使得局部变量脱离它的所在方法的继续存在(以下面的程序)。是故若是不可避免要使用该变量能够将该变量声明成全局变量。

public class ClosureTest

{

         public static void main(String[] args)

         {

                   //定义一个局部变量

                   final String str = "Java";

                   //在内部类里访问局部变量str

                   new Thread(new Runnable()

                   {

                            public void run()

                            {

                                     for (int i = 0; i < 100 ; i++ )

                                     {

                                               //此处将一直能够访问到str局部变量

                                               System.out.println(str + " " + i);

                                               //暂停0.1秒

                                               try

                                               {

                                                        Thread.sleep(100);

                                               }

                                               catch (Exception ex)

                                               {

                                                        ex.printStackTrace();

                                               }

                                     }

                            }

                   }).start();

                   //执行到此处,main方法结束

         }

}

 

常见Java集合的实现细节

 

Set和Map  参看源代码

HashMap和HashSet

         对于HashSet而言,系统采用Hash算法决定集合元素的存储位置,而HashMap 系统将value当成key的附属物。

HashMap类有一个重要的内部接口Map.Entry,每一个Map.Entry其实就是一个key-value对。当两个Entry对象的key的hashCode()返回值相同时,将有key经过eqauls()比较值决定是采起覆盖行为(返回true)仍是产生Entry链。

         当建立hashMap是将initialCapacity参数值指定为2的n次方,这样能够减小系统的计算开销。

         对于HashSet而言,它是基于HashMap实现的。HashSet底层采用HashMap来保存全部的元素,而其的value则存储了一个PRESENT,它是一个静态的Object对象。重写equals()和hashCode()方法必须保持返回值的一致性,全部计算hashCode返回值的关键属性,都应该用于做为equals比较的标准。

TreeMap和TreeSet

         TreeSet底层采用的一个 NavigableMap来保存TreeSet集合的元素。但实际上,因为NavigableMap只是一个接口,所以底层依然是使用TreeMap来包含set集合中的全部元素。

 

Map和List

Map的values()方法

HashMap的values()方法代表上返回一个Values集合对象,但这个对象不能添加元素,主要用于遍历HashMap里的全部value。

ArrayList 和LinkedList

         ArrayList和ArrayQeque(是Deque集合的实现类)底层都是基于Java数组实现的,Vector(还有一个Stack子类)其实就是ArrayList的线程安全版本,绝大部分实现方法都是相同的,只是在方法上增长了synchronized修饰。

         LinkedList是一种链式存储的线性表,本质就是双向链表,不只实现了List接口,还实现了Deque(双端队列)接口。

Java的内存回收

         都说Java不会内存泄露,可是在必定状况仍是会发生的。例如咱们对一个Stack结构按以下方式定义一个pop方法

                   修改Stack的size属性,也就是记录栈内元素减1

                   返回elementData数组中索引为size-1的元素

这样是会致使内存泄露的(没有作相应赋null处理)。

 

内存管理小技巧

         尽可能使用直接量

         使用StringBuilder和StringBuffer进行字符串链接

         尽早释放无用对象的引用

         尽可能少用静态变量

         避免在常常调用的方法、循环体重建立java对象(每次建立都要分配内存空间,执行初始化操做,而后就是回收操做,影响程序的性能)。

       缓存常用的对象

         尽可能不要使用finalize方法(将资源清理放在finalize方法中完成是很是拙劣的选择,垃圾回收机制工做量比较大)。

 

表达式中的陷阱

Java程序建立对象的常规方式有

                   经过new调用构造器建立Java对象

                   经过Class对象的newInstance()方法调用构造器来创造Java对象

                   经过Java反序列化机制从IO流中回复Java对象

                   经过Java对象提供的clone方法复制一个新的对象

String s=”疯狂”+”Java”+10;

String s1=”疯狂Java10”;

s==s1;  //true

上述表达式中若是运算数都是字符串直接量,整数直接量,没有变量参与(有“宏替换”例外),没有方法调用,JVM在编译时就肯定了该字符串链接的表达式的值。

       String s=”疯狂”+”Java”+”DSQiu”;

上述字符串只建立了一个对象,由于str的值能够在编译时肯定下来,JVM会在编译时就计算出s的值为”疯狂JavaDSQiu”,而后将该字符串直接量放入字符串池中,并不存在所谓的”疯狂”等字符串对象。

表达式类型的陷阱

表达式的类型自动提高

当表达式中有多个基本类型的知识,整个算术表达式的数据类型自动提高与表达式中最高级别操做数一样

          char  

            int  long  float  double  从左向右提高类型

byte  short 

       short sVa=5;

            sVa=sVa-2;    //没法经过编译表达式右边提高成 int类型

但若改成 sVa-=2;则没有问题,由于复合赋值运算符包含了隐式类型转换。但隐式转换类型可能会出现“截断”偏差(数据溢出) 如 short st=5;     st+=90000;  st却获得24479.

用 +=链接字符串是 +=左边的变量只能是String类型,不能是String的父类型。

 

            int a=23;

           int b=a/2;               //除不尽,可是没有问题,因为a是int类型的,a/2表达式也依然保持int类型。

 

输入法的陷阱 

         当咱们输入空格时,使用的是中文输入法的空格,编译的时候会报出“非法字符”的错误。

注释字符必须合法

如    G:\codes\uinits5\Hello.java   该句注释会报出“非法转义字符”的错误,Java容许直接使用\uxxxx的形式表示字符,必须后是0~F的字符。

转义字符的陷阱

         如  System.out.println(“abc\u000a”.length());

\u000a是一个换行符,而字符串中是不容许直接使用换行符(要转移)或者说字符串不能分红两行来书写故编译时报出错误。

一样//\u000a也会引发错误

然而  http://www.sysu.edu.cn   这个直接出如今代码中是没有错误的,由于能够当作http:(标签 goto)和//www.sysu.edu.cn(注释)

 

泛型可能引发的错误

         看代码

                   List list=new ArrayList();

                   list.add(“sb”); 

                   List<Integer> inList=list;                 //编译时只会有warning

                   System.out.println(inList.get(0));             //没有问题能正常输出字符串,访问集合元素是不关心集合实际类型

                   Integer in=inList.get(0);                       //编译时正常,运行时访问集合元素当实际类型与集合所带的泛型信息不匹配时抛出ClassCastException

                   String s=inList.get(0);                  //编译不能经过,不能兼容类型

 

此外,当把一个带泛型信息的Java对象付给不带泛型信息的变量是,Java程序不只会擦除传入的类型参数的泛型信息,还会擦除其余泛型信息(如 public List<String> getValues()中返回值及括号的信息。

Class Apple<t extends Number> 擦除信息后编译器只能知道上限是Number但不知道具体是哪一个子类。

不能建立泛型数组 如 List<String>[] list=new List<String>[10]; //不被容许的

        

面向对象的陷阱

 

Instanceof运算符的陷阱

         Instanceof运算符前面操做数的编译时类型必须是一下3中状况

                   与后面的类相同

                   是后面的类父类

                   是后面的类子类

若是它实际引用的对象是第二个操做数的实例,或者是第二个操做数的子类,实现类(接口等)的实例,运算结果返回true。

         在编译阶段,强制转换要求被转换变量的编译时类型必须是如下3中状况

                   与目标的类相同

                   是目标的类父类  返回false

                   是目标的类子类(自动向上转型) 返回true

     运行阶段,被转型变量所引用对象的实际类型必须是目标类型的实例,或者是目标类型的子类,实现类的实例,不管是否强制转换或者编译时类型是什么:

                   Base base=new Derived();

                   Derived d=new Derived();

                   Base base1=new Base();

                   //Base是Derived的父类

                   System.out.println(base1 instanceof Derived);   //false

                   System.out.println(d instanceof Base);     //ture

                   System.out.println((Base)base instanceof Derived);     //true

                   System.out.println((Derived)base instanceof Base);      //true

null能够做为因此引用类型的变量的值,因此在使用instanceof以前要排除null的影响。

构造器的陷阱

         反序列化机制恢复的对象虽然和原来对象具备彻底相同的实例变量值,可是系统中会将会禅师两个对象。使用反序列化机制对单例类机制的破坏,能够为单例类提供readResolve方法(直接返回静态实例变量)就能够解决。

       经过重写clone 方法实现对Java对象的复制也须要调用构造器。

无限递归的构造器

防止构造器的递归调用

                   尽可能不要在定义实例变量是指定实例变量的值为当前类的实例

                   尽可能不要初始化块中建立当前类的实例

                   尽可能不要在构造器内调用本构造器建立Java对象

调用哪一个重载方法

         一个方法被屡次重载后,传入参数可能被向上转型,使之符合调用方法的须要;JVM会选择一个最精确匹配的一个方法。

看代码

         public void info(Object obj,int count)

         public void info(Object[] obj, double count)

当调用 info(null,5)会引发“引用不明确”的异常,null与Object[]更匹配,5与int更匹配。

非静态内部类的陷阱

         系统在编译阶段总会为非静态内部类的构造器增长一个参数(第一个参数老是外部类)(能够经过javap –c分析得到),所以调用非静态内部类的构造器是必须传入一个外部类对象做为参数,不然程序将引起运行时异常(不能经过反射调用newInstance()来建立内部类)。

        非静态内部类(自己至关于一个非静态上下文)固然不能拥有静态成员。

         this.super()  不能正常使用能够用   类名.this.super()来解决。该类名不是当前类的类名

 

异常捕捉的陷阱

         对于Check异常,要么声明抛出,要么使用try…catch进行捕捉。

         finally回收资源安全模式

                            finally

                            {

                                     If(os!=null)

                                     {

                                               try{

                                                        os.close();

                                               }catch(Excepiton e){}

                                     }

                            }

        

学过Java基础的人都能很容易理解上面的代码和多态的原理,可是仍有一些关键的地方须要注意的,算是本身对多态的一个小结:
1.Java中除了static和final方法外,其余全部的方法都是运行时绑定的。在我另一篇文章中说到private方法都被隐式指定为final的,所以final的方法不会在运行时绑定。当在派生类中重写基类中static、final、或private方法时,实质上是建立了一个新的方法。 2.在派生类中,对于基类中的private方法,最好采用不一样的名字。 3.包含抽象方法的类叫作抽象类。注意定义里面包含这样的意思,只要类中包含一个抽象方法,该类就是抽象类。抽象类在派生中就是做为基类的角色,为不一样的子类提供通用的接口。 4.对象清理的顺序和建立的顺序相反,固然前提是本身想手动清理对象,由于你们都知道Java垃圾回收器。 5.在基类的构造方法中当心调用基类中被重写的方法,这里涉及到对象初始化顺序。 6.构造方法是被隐式声明为static方法。 7.用继承表达行为间的差别,用字段表达状态上的变化。

 

 

 

我一直有个这样的想法:若是咱们每一个人都乐于分享本身学习经验,那么咱们在自学的时候可以少走不少弯路,顺利找到捷径。所以我但愿可以有一套这样的分享机制:每一个人均可以帮本身的学习经验整理出来,供你们学习交流,并能作进一步补充,不断完善,最终趋于完美。

相关文章
相关标签/搜索