欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java笔记一 博客分类: java  

程序员文章站 2024-02-16 19:33:58
...
用java也几年了,各种书籍电子文档也看了不少,java编程思想的电子书也看过,这次准备系统的在学习学习,巩固一下,顺便做个笔记。如有问题或者理解错误请大家指正,谢谢!
以下概括几个重点:
一.对象
对象,我的理解就是一类事务的具体抽象。例如:猪,房子。学习java的都知道一句名言,“万物皆对象”。就是在java的世界里所有的事务都能抽象成对象,抽象的好与坏决定了代码的质量。对象是一个概念,它的作用是用于我们分析需求,而后设计程序。
它的体现,简单点说,猪是一种动物,狗是一种动物,那在定义实体对象的时候,就会定义一个实体类:Pig和Dog。如果我们在深入的想想,猪和狗都是动物啊,他们都有很多相似的特性,比如:有头、眼睛;都会叫、跑等等。(当然如果有需要,可以将头抽象成一个对象,因为眼睛、耳朵等都是头上的属性)那我们抽象一个动物类:Animal,它里边都会定义很多动物的共同属性和行为。然后Pig和Dog就可以继承他们动物特有的属性和方法。
当然,我们“重写”了eat方法,因为猪和狗,吃东西不一样,猪不吃屎,狗会吃屎。额~~~我乱猜的。
java笔记一
            
    
    博客分类: java  
二.“是一个”与“像是一个”
这是设计上的一个争论,例如一个制冷系统。起初这样设计:
java笔记一
            
    
    博客分类: java  
空调“是一个”制冷系统,假设这个空调只能制冷,但是热力泵可以替代空调即“像是一个”制冷系统,而且还可以制热。但是对于制冷系统的调用者来说,它只知道有一个cool方法,而压根不知道有个制热功能。
当然,我们可以做一个通用的设计,系统不叫制冷系统,叫“温度控制系统”,这样做是不是更合理呢。但是又有一个问题,对空调来说,他只有一个制冷功能,但是也必须实现heat这个方法,即实现是空的。

java笔记一
            
    
    博客分类: java  

这两种设计方式的使用,因情况而定,主要还是看需求。
三.上溯造型(向上转型)
继承和接口实现,有一个特点,父类可以当做子类对象来使用,但是只能调用父类定义的方法或属性。一般来讲,只有用到上溯造型时,才会用继承。
这在我们做程序设计的时候经常用到,尤其各种设计模式。这种面向接口的编程方式的好处就是,调用方面对的总是一类接口,而不关心它的实现,易于代码维护。
例如:上边的温度控制系统,一旦空调换成了热力泵,我只需要给他换一个热力泵。而对于调用温度控制系统的地方,不需要做任何的改动。
例如:策略模式
//温度控制系统
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;
	}
	
}
  • java笔记一
            
    
    博客分类: java  
  • 大小: 21 KB
  • java笔记一
            
    
    博客分类: java  
  • 大小: 16.2 KB
  • java笔记一
            
    
    博客分类: java  
  • 大小: 21.6 KB