java笔记一 博客分类: java
程序员文章站
2024-02-16 19:47:16
...
用java也几年了,各种书籍电子文档也看了不少,java编程思想的电子书也看过,这次准备系统的在学习学习,巩固一下,顺便做个笔记。如有问题或者理解错误请大家指正,谢谢!
以下概括几个重点:
一.对象
对象,我的理解就是一类事务的具体抽象。例如:猪,房子。学习java的都知道一句名言,“万物皆对象”。就是在java的世界里所有的事务都能抽象成对象,抽象的好与坏决定了代码的质量。对象是一个概念,它的作用是用于我们分析需求,而后设计程序。
它的体现,简单点说,猪是一种动物,狗是一种动物,那在定义实体对象的时候,就会定义一个实体类:Pig和Dog。如果我们在深入的想想,猪和狗都是动物啊,他们都有很多相似的特性,比如:有头、眼睛;都会叫、跑等等。(当然如果有需要,可以将头抽象成一个对象,因为眼睛、耳朵等都是头上的属性)那我们抽象一个动物类:Animal,它里边都会定义很多动物的共同属性和行为。然后Pig和Dog就可以继承他们动物特有的属性和方法。
当然,我们“重写”了eat方法,因为猪和狗,吃东西不一样,猪不吃屎,狗会吃屎。额~~~我乱猜的。
二.“是一个”与“像是一个”
这是设计上的一个争论,例如一个制冷系统。起初这样设计:
空调“是一个”制冷系统,假设这个空调只能制冷,但是热力泵可以替代空调即“像是一个”制冷系统,而且还可以制热。但是对于制冷系统的调用者来说,它只知道有一个cool方法,而压根不知道有个制热功能。
当然,我们可以做一个通用的设计,系统不叫制冷系统,叫“温度控制系统”,这样做是不是更合理呢。但是又有一个问题,对空调来说,他只有一个制冷功能,但是也必须实现heat这个方法,即实现是空的。
这两种设计方式的使用,因情况而定,主要还是看需求。
三.上溯造型(向上转型)
继承和接口实现,有一个特点,父类可以当做子类对象来使用,但是只能调用父类定义的方法或属性。一般来讲,只有用到上溯造型时,才会用继承。
这在我们做程序设计的时候经常用到,尤其各种设计模式。这种面向接口的编程方式的好处就是,调用方面对的总是一类接口,而不关心它的实现,易于代码维护。
例如:上边的温度控制系统,一旦空调换成了热力泵,我只需要给他换一个热力泵。而对于调用温度控制系统的地方,不需要做任何的改动。
例如:策略模式
//温度控制系统
在调用方法中,显而易见,我们只需要将new AirConditioner()换成new HeatPump ()就完成了我们冰箱与热力泵的兑换。
问题:为什么c = new Context(new AirConditioner());中传入的2个对象(虽然他们是实现的同一个接口),而确调用了正确的实现类的方法呢?
原因是,java中有一个机制叫做“绑定”。程序执行前的绑定叫“前期绑定”如:static,final,private(private也就是final的)。运行时绑定叫“动态绑定”。反正想一下就知道,对象转换过程中肯定绑定了真实对象的类型信息。
四.对象的生命周期
说对象的生命周期,先要讲对象创建。
当我们创建一个对象的时候,内存堆中会分配一些动态内存空间给他。对象创建常见方式2种:
int s=”111”;
Person p = new Person();
其中“s”和“p”都是对象的引用,他们是存放在栈内存中的,基本类型对象也都是存放在栈内存中的。“111”也是存放在栈内存中的。只有new出来的对象是存放在堆内存中的。
当一个对象的引用变为0的时候,意味着jvm可以清理它了。
堆栈详解见:http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html
五.初始化
初始化顺序
用new一个类来说明比较容易,比如:
为什么m的输出会是0呢?因为子类重写了父类的p()方法,而创建子类对象之前,先要创建父类对象,但在父类对象的构造器中又调用了,被子类重写的方法p(),所以调用的是子类的p()方法,而这个时候子类Test2中的m还没有执行到被赋值的地方,所以编译器给了个初始值0。
统一类型的初始化都是按排列顺寻执行。
1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。(解释m为什么是0)
2.静态属性、静态块初始化
3.成员变量、基本块初始化
4.构造器初始化
5.局部变量初始化
六.垃圾回收机制
Java中的垃圾回收机制是一个神器,他帮助我们清理不用的对象,而我们不用手动的释放他们。
Jvm的垃圾回收机制只与堆内存打交道,即只管理new出来的对象。当jvm内存不够用时,垃圾回收机制才会运行,清理无用的垃圾对象。
垃圾回收方法原理
对于垃圾回收的方法有很多,常用的是“引用计数”和“标记清零”。这些知识只能帮助我们理解垃圾回收的机制,让我们更好的管理代码。实际的java虚拟机可能用的其他牛逼的方法。
引用计数法
每个对象都有一个引用计数器,当对象引用增加是加1,引用去除时或被置为null时减1。垃圾回收器在所有对象上遍历,当对象引用变为0时,就释放其占用的空间。这种方法的缺陷是,如果对象循环引用,则会出现“对象该被释放,但是引用不为0的情况”。这种方式用来说明垃圾回收的工作方式。
标记清理
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。并且,由于它只是清除了那些未标记的对象,而并没有对标记对象进行压缩,导致会产生大量内存碎片,从而浪费内存
标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另一半空间中。GC运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。并且对于指定大小堆来说,需要两倍大小的内存,因为任何时候都只使用其中的一半。
增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾,也可理解为把堆栈分成一小块一小块,每次仅对某一个块进行垃圾收集。这会造成较小的应用程序中断时间,使得用户一般不能觉察到垃圾收集器正在工作。
分代收集器
复制收集器的缺点是:每次收集时,所有的标记对象都要被拷贝,从而导致一些生命周期很长的对象被来回拷贝多次,消耗大量的时间。而分代收集器则可解决这个问题,分代收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象(非短命对象)将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
finalize的用法
finalize方法是Object类的一个方法,多有的类都继承了它可以重写它。
finalize方法只在两种情况下调用,一种是jvm准备垃圾回收,会先调用它,还有就是手动调用System.gc()调用它;但是垃圾回收只和内存有关系,只有内存不够用的时候才会执行回收机制,而finalize或者将对象应用置为null都只是说明此对象可以被回收,但不一定就会被回收。对象引用置为null只是释放了在栈中的内存,并没有释放在堆中new的内存,堆中的内存由jvm垃圾回收控制。
使用垃圾收集器注意的地方
(1)每个对象只能调用finalize( )方法一次。如果在finalize( )方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。
(2)垃圾收集器跟踪每一个对象,收集那些不可触及的对象(即该对象不再被程序引用 了),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用该对象的finalize( )方法(如果有)。如果在finalize()方法中,又使得该对象被程序引用(俗称复活了),则该对象就变成了可触及的对象,暂时不会被垃圾收集了。但是由于每个对象只能调用一次finalize( )方法,所以每个对象也只可能 "复活 "一次。
(3)Java语言允许程序员为任何方法添加finalize( )方法,该方法会在垃圾收集器交换回收对象之前被调用。但不要过分依赖该方法对系统资源进行回收和再利用,因为该方法调用后的执行结果是不可预知的。
(4)垃圾收集器不可以被强制执行,但程序员可以通过调研System.gc方法来建议执行垃圾收集。记住,只是建议。一般不建议自己写System.gc,因为会加大垃圾收集工作量。
七.异常
异常是java的又一个牛逼特点。它可以帮助我们在可能发生错误的地方“捕获”异常,防止出现代码卡死的情况;或者处理异常情况,保证代码的“异常流程”正确执行。
异常处理的方式改变了代码的编写方式:
代码一:
在程序处理中,我们更愿意采用第二种方式,因为它可以很清楚的把后台错误“抛送”上来,以供前台处理。
注意:
如果在异常上抛过程中,有一层代理,那么就需要再代理中处理异常,然后再向上抛送一次,否则前台获取的错误会是代理异常,而不是底层的异常信息。
例如:
比如,此代理处理的是代码二中的excute2方法,那么在catch中,用throw e.getCause()方法将底层信息在抛送一次。这样在前台的异常信息就会是“"出错了"+e”;而不是java.lang.reflect.xxx的异常信息。
八.shis关键字
this关键字代表了对象本身,在一个类中是隐式存在的。就说说几个常用的形式。
shis构造器
2.传递当前对象
//将当前对象传递给其他方法,为了显示我不是闲的蛋疼,在传递过程中对此对象做了一些处理,给x赋值。
3.返回对象本身引用
4.javabean规范
以下概括几个重点:
一.对象
对象,我的理解就是一类事务的具体抽象。例如:猪,房子。学习java的都知道一句名言,“万物皆对象”。就是在java的世界里所有的事务都能抽象成对象,抽象的好与坏决定了代码的质量。对象是一个概念,它的作用是用于我们分析需求,而后设计程序。
它的体现,简单点说,猪是一种动物,狗是一种动物,那在定义实体对象的时候,就会定义一个实体类:Pig和Dog。如果我们在深入的想想,猪和狗都是动物啊,他们都有很多相似的特性,比如:有头、眼睛;都会叫、跑等等。(当然如果有需要,可以将头抽象成一个对象,因为眼睛、耳朵等都是头上的属性)那我们抽象一个动物类:Animal,它里边都会定义很多动物的共同属性和行为。然后Pig和Dog就可以继承他们动物特有的属性和方法。
当然,我们“重写”了eat方法,因为猪和狗,吃东西不一样,猪不吃屎,狗会吃屎。额~~~我乱猜的。
二.“是一个”与“像是一个”
这是设计上的一个争论,例如一个制冷系统。起初这样设计:
空调“是一个”制冷系统,假设这个空调只能制冷,但是热力泵可以替代空调即“像是一个”制冷系统,而且还可以制热。但是对于制冷系统的调用者来说,它只知道有一个cool方法,而压根不知道有个制热功能。
当然,我们可以做一个通用的设计,系统不叫制冷系统,叫“温度控制系统”,这样做是不是更合理呢。但是又有一个问题,对空调来说,他只有一个制冷功能,但是也必须实现heat这个方法,即实现是空的。
这两种设计方式的使用,因情况而定,主要还是看需求。
三.上溯造型(向上转型)
继承和接口实现,有一个特点,父类可以当做子类对象来使用,但是只能调用父类定义的方法或属性。一般来讲,只有用到上溯造型时,才会用继承。
这在我们做程序设计的时候经常用到,尤其各种设计模式。这种面向接口的编程方式的好处就是,调用方面对的总是一类接口,而不关心它的实现,易于代码维护。
例如:上边的温度控制系统,一旦空调换成了热力泵,我只需要给他换一个热力泵。而对于调用温度控制系统的地方,不需要做任何的改动。
例如:策略模式
//温度控制系统
public interface TemperatureControlSystem { public void helloWorld(); } //空调实现 public class AirConditioner implements TemperatureControlSystem { @Override public void helloWorld() { System.out.println("hello~~~~~AirConditioner"); } } //热力泵实现 public class HeatPump implements TemperatureControlSystem { @Override public void helloWorld() { System.out.println("hello~~~~~ HeatPump "); } } //调用方法 public class Context { private TemperatureControlSystem strategy; public Context(TemperatureControlSystem strategy) { this.strategy = strategy; } public void doMethod(){ strategy.helloWorld(); } public static void main(String[] args) { Context c ; c = new Context(new AirConditioner()); c.doMethod(); c = new Context(new HeatPump ()); c.doMethod(); } }
在调用方法中,显而易见,我们只需要将new AirConditioner()换成new HeatPump ()就完成了我们冰箱与热力泵的兑换。
问题:为什么c = new Context(new AirConditioner());中传入的2个对象(虽然他们是实现的同一个接口),而确调用了正确的实现类的方法呢?
原因是,java中有一个机制叫做“绑定”。程序执行前的绑定叫“前期绑定”如:static,final,private(private也就是final的)。运行时绑定叫“动态绑定”。反正想一下就知道,对象转换过程中肯定绑定了真实对象的类型信息。
四.对象的生命周期
说对象的生命周期,先要讲对象创建。
当我们创建一个对象的时候,内存堆中会分配一些动态内存空间给他。对象创建常见方式2种:
int s=”111”;
Person p = new Person();
其中“s”和“p”都是对象的引用,他们是存放在栈内存中的,基本类型对象也都是存放在栈内存中的。“111”也是存放在栈内存中的。只有new出来的对象是存放在堆内存中的。
当一个对象的引用变为0的时候,意味着jvm可以清理它了。
堆栈详解见:http://www.cnblogs.com/whgw/archive/2011/09/29/2194997.html
五.初始化
初始化顺序
用new一个类来说明比较容易,比如:
public class Initialize { private static Test t = new Test(); { System.out.println("{}"); } private int n=2; static{ System.out.println("static{}"); } public Initialize() { System.out.println("initialize:Constructor"); System.out.println(n); } static{ System.out.println("static2{}"); } public void f(){ System.out.println("f():execute"); } public static void main(String[] args) { new Initialize().f(); new Test2(); } } class Test{ public Test() { System.out.println("Test:Constructor"); p(); } public void p(){ System.out.println("Test:m"); } } class Test2 extends Test{ private int m=1; public Test2(){ System.out.println("Test2:Constructor"); } public void p(){ System.out.println("Test2:m:"+m); } } /* Test:Constructor static{} static2{} {} initialize:Constructor 2 f():execute Test:Constructor Test2:m:0 Test2:Constructor *///:~
为什么m的输出会是0呢?因为子类重写了父类的p()方法,而创建子类对象之前,先要创建父类对象,但在父类对象的构造器中又调用了,被子类重写的方法p(),所以调用的是子类的p()方法,而这个时候子类Test2中的m还没有执行到被赋值的地方,所以编译器给了个初始值0。
统一类型的初始化都是按排列顺寻执行。
1.在其他任何事物发生之前,将分配给对象的存储空间初始化成二进制的零。(解释m为什么是0)
2.静态属性、静态块初始化
3.成员变量、基本块初始化
4.构造器初始化
5.局部变量初始化
六.垃圾回收机制
Java中的垃圾回收机制是一个神器,他帮助我们清理不用的对象,而我们不用手动的释放他们。
Jvm的垃圾回收机制只与堆内存打交道,即只管理new出来的对象。当jvm内存不够用时,垃圾回收机制才会运行,清理无用的垃圾对象。
垃圾回收方法原理
对于垃圾回收的方法有很多,常用的是“引用计数”和“标记清零”。这些知识只能帮助我们理解垃圾回收的机制,让我们更好的管理代码。实际的java虚拟机可能用的其他牛逼的方法。
引用计数法
每个对象都有一个引用计数器,当对象引用增加是加1,引用去除时或被置为null时减1。垃圾回收器在所有对象上遍历,当对象引用变为0时,就释放其占用的空间。这种方法的缺陷是,如果对象循环引用,则会出现“对象该被释放,但是引用不为0的情况”。这种方式用来说明垃圾回收的工作方式。
标记清理
这种收集器首先遍历对象图并标记可到达的对象,然后扫描堆栈以寻找未标记对象并释放它们的内存。这种收集器一般使用单线程工作并停止其他操作。并且,由于它只是清除了那些未标记的对象,而并没有对标记对象进行压缩,导致会产生大量内存碎片,从而浪费内存
标记-压缩收集器
有时也叫标记-清除-压缩收集器,与标记-清除收集器有相同的标记阶段。在第二阶段,则把标记对象复制到堆栈的新域中以便压缩堆栈。这种收集器也停止其他操作。
复制收集器
这种收集器将堆栈分为两个域,常称为半空间。每次仅使用一半的空间,JVM生成的新对象则放在另一半空间中。GC运行时,它把可到达对象复制到另一半空间,从而压缩了堆栈。这种方法适用于短生存期的对象,持续复制长生存期的对象则导致效率降低。并且对于指定大小堆来说,需要两倍大小的内存,因为任何时候都只使用其中的一半。
增量收集器
增量收集器把堆栈分为多个域,每次仅从一个域收集垃圾,也可理解为把堆栈分成一小块一小块,每次仅对某一个块进行垃圾收集。这会造成较小的应用程序中断时间,使得用户一般不能觉察到垃圾收集器正在工作。
分代收集器
复制收集器的缺点是:每次收集时,所有的标记对象都要被拷贝,从而导致一些生命周期很长的对象被来回拷贝多次,消耗大量的时间。而分代收集器则可解决这个问题,分代收集器把堆栈分为两个或多个域,用以存放不同寿命的对象。JVM生成的新对象一般放在其中的某个域中。过一段时间,继续存在的对象(非短命对象)将获得使用期并转入更长寿命的域中。分代收集器对不同的域使用不同的算法以优化性能。
finalize的用法
finalize方法是Object类的一个方法,多有的类都继承了它可以重写它。
finalize方法只在两种情况下调用,一种是jvm准备垃圾回收,会先调用它,还有就是手动调用System.gc()调用它;但是垃圾回收只和内存有关系,只有内存不够用的时候才会执行回收机制,而finalize或者将对象应用置为null都只是说明此对象可以被回收,但不一定就会被回收。对象引用置为null只是释放了在栈中的内存,并没有释放在堆中new的内存,堆中的内存由jvm垃圾回收控制。
使用垃圾收集器注意的地方
(1)每个对象只能调用finalize( )方法一次。如果在finalize( )方法执行时产生异常(exception),则该对象仍可以被垃圾收集器收集。
(2)垃圾收集器跟踪每一个对象,收集那些不可触及的对象(即该对象不再被程序引用 了),回收其占有的内存空间。但在进行垃圾收集的时候,垃圾收集器会调用该对象的finalize( )方法(如果有)。如果在finalize()方法中,又使得该对象被程序引用(俗称复活了),则该对象就变成了可触及的对象,暂时不会被垃圾收集了。但是由于每个对象只能调用一次finalize( )方法,所以每个对象也只可能 "复活 "一次。
(3)Java语言允许程序员为任何方法添加finalize( )方法,该方法会在垃圾收集器交换回收对象之前被调用。但不要过分依赖该方法对系统资源进行回收和再利用,因为该方法调用后的执行结果是不可预知的。
(4)垃圾收集器不可以被强制执行,但程序员可以通过调研System.gc方法来建议执行垃圾收集。记住,只是建议。一般不建议自己写System.gc,因为会加大垃圾收集工作量。
public class FinalizeTest { @Override protected void finalize() throws Throwable { System.out.println("finalize called"); } public static void main(String[] args) { new FinalizeTest(); System.gc(); System.gc(); } } /*output:finalize called*///:~
七.异常
异常是java的又一个牛逼特点。它可以帮助我们在可能发生错误的地方“捕获”异常,防止出现代码卡死的情况;或者处理异常情况,保证代码的“异常流程”正确执行。
异常处理的方式改变了代码的编写方式:
代码一:
/** * 1:正常;0:失败;-1:异常 * @return */ public int excute(){ try{ if(true){ return 1; }else{ return 0; } }catch(Exception e){ return -1; } }代码二: public void excute2(){ try{ System.out.println("execute"); }catch(Exception e){ throw new RuntimeException("出错了"+e); } }
在程序处理中,我们更愿意采用第二种方式,因为它可以很清楚的把后台错误“抛送”上来,以供前台处理。
注意:
如果在异常上抛过程中,有一层代理,那么就需要再代理中处理异常,然后再向上抛送一次,否则前台获取的错误会是代理异常,而不是底层的异常信息。
例如:
/** *通用的限制适配器 */ public <T> T getProxy(Class<T> intf, final T obj) { return (T) Proxy.newProxyInstance(obj.getClass().getClassLoader(), new Class[] { intf }, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try{ Object result = method.invoke(obj, args); return result; }catch(Exception e){ throw e.getCause(); } } }); }
比如,此代理处理的是代码二中的excute2方法,那么在catch中,用throw e.getCause()方法将底层信息在抛送一次。这样在前台的异常信息就会是“"出错了"+e”;而不是java.lang.reflect.xxx的异常信息。
八.shis关键字
this关键字代表了对象本身,在一个类中是隐式存在的。就说说几个常用的形式。
shis构造器
/*这个类说明了2个问题,一个是this构造器,一个是创建子类对象的时候,必然先创建父类对象, * 即在this(1),方法前边默认插入了一个super(); * 用IDE生成构造器的时候会自动加入,不知道大家注意过没有。*/ public class ExtemdThis extends Father{ int x; public ExtemdThis() { this(1);//这种调用必须在此方法中的第一样,否则报错。 super.p(); } public ExtemdThis(int x) { this.x = x; } public static void main(String[] args) { System.out.println(new ExtemdThis().x); } } class Father{ protected void p(){ System.out.println("Father"); } } /* Father 1 *///:~
2.传递当前对象
//将当前对象传递给其他方法,为了显示我不是闲的蛋疼,在传递过程中对此对象做了一些处理,给x赋值。
public class PersonEatApple { public void eat(Apple apple){ Apple peeled = apple.getPeeled(); System.out.println(peeled.x); } public static void main(String[] args) { new PersonEatApple().eat(new Apple()); } } class Peeler{ static Apple peel(Apple apple){ apple.x=1; return apple; } } class Apple{ int x; public int getX() { return x; } public void setX(int x) { this.x = x; } Apple getPeeled(){ return Peeler.peel(this); } } /*Output: * 1 * *///:~
3.返回对象本身引用
//返回对象本身的引用,可以做到非常有趣的事情。 public class ReturnThis { int i; public ReturnThis increment(){ i++; return this; } public static void main(String[] args) { System.out.println(new ReturnThis().increment().increment().increment().i); } } /*output:3 *///:~
4.javabean规范
//javabean中用于区分数据成员和参数名的区别,这算是一种规范。 public class JavaBean { int x; public int getX() { return x; } public void setX(int x) { this.x = x; } public JavaBean(int x) { super(); this.x = x; } }
推荐阅读
-
3. Java 7与伪共享的新仇旧恨[转载] 博客分类: J2SE硬件,操作系统 java7伪共享
-
java7 新特性 博客分类: java知识总结 java7jdk
-
学习java7的fork/join 博客分类: java语言 java7
-
静态工厂方法VS构造器 博客分类: Effective Java java7设计模式
-
java笔记一 博客分类: java
-
linux java sh 博客分类: linux
-
Java泛型 博客分类: JavaSE Java
-
Java 7提供的多异常捕捉 博客分类: java Java7多异常
-
Java7中的switch支持String的实现细节 博客分类: Java 基础 javajava7
-
java笔记一 博客分类: java