深入面向对象

一:内部类

内部类:在某些情况下,我们把一个类存放在另一个类的内部定义,这个定义在其他类内部的类就叫做内部类(嵌套类)。包含内部类的类叫外部类(宿主类)。

内部类成员可以直接访问外部类的私有数据,因为内部类被当成其他外部类成员,同一个类成员可以相互访问。但外部类不能访问内部类的属性。

内部类分为:非静态内部类,静态内部类,局部内部类,匿名内部类。

非静态内部类:

不使用static关键字修饰的成员内部类叫非静态内部类,反之叫静态内部类。

内部类可以使用private,protected,public等访问控制符修饰。

内部类可以是直接访问外部类的私有属性,是因为在非静态内部类对象里,保存了一个它寄存的外部类对象的引用(当调用非静态内部类的实例方法时,必须有一个非静态内部类的实例,而非静态内部类实例必须寄存在外部类实例里)。在非静态内部类方法中访问某个变量时,系统优秀在该方法内部查找,依次是内部类中查找,外部类中查找,如果还是没找到,则编译错误。

如果外部类属性,内部类属性与内部类里方法中的局部变量名相同,则可以通过this,外部类类名.this来进行区分。

非静态内部类可以访问外部类的private成员,但反过来就不行,非静态内部类的成员只在非静态内部类范围内可知的,并不能被外部类直接使用,如果外部类需要访问非静态内部类成员,则必须显示创建非静态内部类对象来调用访问其实例成员。

经典实例:

public class OutClass {

	private int outNum = 10;

	class InnerClass {
		private int inNum = 5;

		public void acessOutProp() {
			System.out.println("外部类的outNum属性值=" + outNum);
		}
	}

	public void accessInnerProp() {
		// 访问内部类的实例属性,必须显示创建内部类对象
		InnerClass ic = new InnerClass();
		System.out.println("内部类的inNum属性值=" + ic.inNum);
		ic.acessOutProp();
	}

	public static void main(String[] args) {
		OutClass oc = new OutClass();
		oc.accessInnerProp();

	}
}


经典问题:非静态内部类对象和外部类对象的关系是怎么样的?

解答:非静态内部类对象必须寄存在外部类对象里,而外部类对象则不一定有非静态内部类对象寄存在其中。简单的说,如果存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象。但外部类对象存在时,外部类对象里就不一定寄存了非静态内部类对象,因此外部类对象访问非静态内部类成员时,可能非静态内部类对象根本不存在,而非静态内部类对象访问外部类成员时,外部类对象一定是存在的。


根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量,创建对象实例等。

经典实例:

public class Test5 {
	private class Inner{
		
	}
	public static void main(String[] args) {
		new Inner();//代码编译错误,静态成员无法访问非静态成员。
	}
}
public class Test5 {
	private class InnerClass{
		
		/*下面三个静态声明都将引发编译错误,非静态内部类不能有静态声明
		static{
			System.out.println("我是内部类中的静态初始化块");
		}
		private static int inNum;
		private static void test(){
			System.out.println("test方法");
		}
	}
}
总结:非静态内部类里不可以有静态初始化块,但可以有普通的初始化块,非静态内部类普通初始化块的作用和普通类相同的。


静态内部类:

使用static关键字修饰的成员内部类叫静态内部类。该内部类属于整个外部类,而不是单独属于外部类的某个对象。

静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规定,所以静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。

静态问题:为什么静态内部类实例方法也不访问外部类的实例属性呢?

解答:因为静态内部类是外部类的类相关,而不是外部类的对象相关,也就是说,静态内部类的对象不是寄存在外部类对象里,而是寄存在外部类的类本身中,也就是说,当静态内部类的对象存在时,并不存在一个被它寄存的外部类对象,静态内部类的对象里值有外部类的类引用,没有外部类对象的引用。如果允许静态内部类的实例方法访问外部类的实例成员时,但找不到被寄存的外部类对象,这将引发错误。

经典实例:

public class Test5 {
	private int outNum=5;
	private static int outNum2=10;
	static class StaticInner{
		//静态内部类里可以包含静态成员
		private static int age;
		public void accessOutProp(){
			//下面这个代码编译错误,静态内部类无法访问外部类的实例成员
			System.out.println(outNum);
			
			System.out.println("outNum2");
		}
	}
}

除上面介绍的之外,Java还允许在接口中定义内部类,接口中定义的内部类默认使用public static修饰,接口内部类只能是静态内部类。但是,定义接口内部类的意义不大,因为接口的作用是定义一个公共的规范(暴露出来供大家使用)。

内部类的使用:

外部类以外使用非静态内部类:

经典实例:

public class Test5 {
	
	public static void main(String[] args) {
		
		//Out.In in=new Out().new In("Java程序员");和下面的代码功能完全一样
		Out.In in;
		Out out=new Out();
		in=out.new In("Android程序员");
	}
}

class Out{
	class In{
		public In(String msg){
			System.out.println(msg);
		}
	}
}

外部类以外使用静态内部类

经典实例:

public class Test5 {
	
	public static void main(String[] args) {
		
		//StaticOut.StaticIn in=new StaticOut.StaticIn();和下面的代码功能相同
		StaticOut.StaticIn in;
		in=new StaticOut.StaticIn();
	}
}

class StaticOut{
	static class StaticIn{
		public StaticIn(){
			System.out.println("静态内部类的构造器");
		}
	}
}

经典提问:既然内部类是外部类的成员,是否可以为外部类定义子类,在子类中再定义一个内部类来重写其父类中内部类?

解答:不可以!内部类的类名不再是简单的由内部类的类名组成,它实际上还是外部类名作为一个命名空间,作为内部类类名的限制,因此子类中的内部类和父类中的累不累不可能完全同名,即使二者所包含的内部类的类名相同,但因为他们所处的外部类空间不同,所以他们不肯能是同一个类,也就不可能重写。


局部内部类:

把一个内部类定义在外部类的方法中,这样的内部类就局部内部类。局部内部类只能在该方法中有效。局部内部类也不能使用访问控制符和static修饰符修饰。

经典实例:

public class Test5 {
	
	public static void main(String[] args) {
		
		//定义局部内部类
		class InnerBase{
			int a;
		}
		//定义局部内部类的子类
		class InnerSub extends InnerBase{
			int b;
		}
		//创建局部内部类的对象
		InnerSub is=new InnerSub();
		is.a=5;
		is.b=8;
		System.out.println("InnerSub对象的a和b变量的值是:"+is.a+", "+is.b);
	}
}

匿名内部类:适合用于创建那些仅需要一次使用的类。创建匿名内部类时会立即创建一个该类的实例。这个类定义立即消失,匿名内部类不能重复使用。

匿名内部类规则:

(1)匿名内部类不能是抽象类,因为系统在创建匿名内部类时,会立即创建一个该类的实例。

(2)匿名内部类不能定义构造器,因为匿名内部类没有类名。匿名内部类可以定义初始化块。

经典实例:

public class Test5 {
	
	public void test(Product p){
		System.out.println("购买了一个"+p.getName()+",花掉了"+p.getPrice());
	}
	public static void main(String[] args) {
		Test5 test=new Test5();
		//调用test方法时,需要传入一个Product参数,此处传入其匿名实现类的实例
		test.test(new Product() {
			
			@Override
			public double getPrice() {
				// TODO Auto-generated method stub
				return 500.0;
			}
			
			@Override
			public String getName() {
				// TODO Auto-generated method stub
				return "希捷500G硬盘";
			}
		});
	}
}

interface Product{
	public double getPrice();
	public String getName();
}

二.闭包和回调:

Java并不能显式地支持闭包,但对非静态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类对象的引用,并且可以直接调用外部类的private成员,可以把非静态内部类当成面向对象领域的闭包。

当某个方法一旦获得了内部类对象的应用后,就可以在合适时候反过来调用外部类实例的方法。所谓回调,就是是运行客户类通过内部类引用来调用其外部类的方法。

经典实例:

public class Test5 extends Programmer{

	public Test5() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Test5(String name) {
		super(name);
		// TODO Auto-generated constructor stub
	}
	
	private void teach(){
		System.out.println(getName()+" 教师在讲台上讲课");
	}
	
	private class Closure implements Teachable{
		/*
		 * 非静态内部类回调外部类实现work方法,非静态内部类引用的作用仅仅是
		 * 向客户类提供一个回调外部类的途径
		 * 
		 */
		public void work(){
			teach();
		}
	}
	//返回一个非静态内部类引用,允许外部类通过该非静态内部类引用来回调外部类的方法
	public Teachable getCallbackReference(){
		return new Closure();
	}
	
	public static void main(String[] args) {
		Test5 test=new Test5("屌丝");
		test.work();
		//表面上调的是Closure的work方法,实际还是回调Test5的work方法
		test.getCallbackReference().work();
	}
	
}
interface Teachable{
	void work();
}

class Programmer{
	private String name;
	
	public Programmer() {
		super();
		// TODO Auto-generated constructor stub
	}

	public Programmer(String name) {
		super();
		this.name = name;
	}

	public void work(){
		System.out.println(name+"在等下认真的工作");
	}
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

三:枚举类

枚举是一种特殊的类,它一样可以有自己的方法和属性,可以实现一个或多个接口,也可以定义自己的构造函数。
枚举类与普通类的区别:

(1)枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类。其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口。

(2)枚举类的构造器只能使用private访问控制符,系统默认使用private修饰。

(3)枚举类的所有实例必须在枚举类中显示列出(枚举值直接用逗号隔开),否则这个枚举类将永远都不能产生实例。列出这些实例时,系统会自动添加public  static final修饰。

(4)所有枚举类都提供了一个values方法,该方法可以很方便地遍历所有的枚举值。

简单的枚举类格式:

public enum SeasonEnum {
	SPRING,SUMMER,FALL,WINTER;
}
枚举类的属性,方法和构造器


经典实例:

public enum Gender {

	MALE,FEMALE;
	private String name;
	public void setName(String name){
		switch (this) {
		case MALE:
			if(name.equals("男")){
				this.name=name;
			}else{
				System.out.println("参数错误");
				return;
			}
			break;
		case FEMALE:
			if(name.equals("女")){
				this.name=name;
			}else{
				System.out.println("参数错误");
				return;
			}
			break;

		default:
			break;
		}
	}
	
	public String getName(){
		return this.name;
	}
}
public static void main(String[] args) {
		Gender g=Enum.valueOf(Gender.class,"MALE");
		g.setName("男");
		System.out.println(g+"代表:"+g.getName());
		//此时设置name属性时将会提示参数错误。
		g.setName("女");
		System.out.println(g+"代表:"+g.getName());
	}

枚举的构造方法:

public enum Gender {

	//此处的枚举值必须调用对应的构造函数来创建
	MALE("男"),FEMALE("女");
	private String name;
	//枚举类的构造函数只能使用private修饰
	private Gender(String name){
		this.name=name;
	}
	public String getName(){
		return this.name;
	}
}

public static void main(String[] args) {
	Gender g=Enum.valueOf(Gender.class,"MALE");
	System.out.println(g.getName());//打印男	
}

枚举类实现接口和继承抽象类的相关知识和普通类完全相同,此处就不再详细总结。

四:对象与垃圾回收

垃圾回收:当程序创建对象,数组等应用类型实体时,系统都会在堆内存中为之分配一块内存空间,对象就保存在这块内存空间里,当这块内存不再被任何引用变量引用时,这块内存空间就变成了垃圾,等待垃圾回收机制进行回收。
特点:
(1)垃圾回收机制只负责回收堆内存中对象,不会回收任何物理资源(比如数据库连接,网络IO等资源)
(2)程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候运行。当对象永久性地失去引用后,系统就会在适合的时候回收它所占的内存。
(3)垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
当一个对象在堆内存中运行时,可以将对象所处的状态分为一下三种:
(1)激活状态:当一个对象被创建后,有一个以上的引用变量引用它,则这个对象在程序中处于激活状态,程序可以通过引用变量来调用该对象的属性和方法。
(2)去活状态:如果程序中某个对象不再被任何引用变量引用它,它就进入了去活状态。在这个状态下,系统的垃圾回收机制准备回收该对象所占的内存,回收之前,系统会调用去活状态对象的finalize方法进行资源清理,如果系统在调用finalize方法重新让一个引用变量引用该对象,则这个对象会再次变为激活状态,否则该对象将进入死亡状态。
(3)当对象与所有引用的关联都被切断,其系统已经调用所有对象的finalize方法依然没有使该对象变为激活状态,这个对象将永久的失去引用,最后变为死亡状态,只有当对象处于死亡状态时,系统才会真正回收该对象所占的资源。

强制垃圾回收:
程序是无法精确控制Java垃圾回收的时间,但我们依然可以强制系统进行垃圾回收:只是这种强制只是通知系统进行垃圾回收,但系统是否进行垃圾回收这个还是不确定。当使用强制系统垃圾回收后还是会有一点效果的。
强制垃圾回收:(1)调用System类的gc()静态方法:System.gc();
(2)调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc();
经典实例:

public class Test {
	public static void main(String[] args) {
		for(int i=0;i<3;i++){
			new Test();
			//下面两行diamond的作用完全相同,强制系统进行垃圾回收
			//Runtime.getRuntime().gc();
			System.gc();
		}
	}
	public void finalize(){
		System.out.println("系统正在处理Test兑现的的资源...");
	}
}
注意:强制垃圾回收仅仅只是建议系统立即进行垃圾回收,系统完全有可能并不立即进行垃圾回收,只是通知垃圾回收机制,让其尽快的进行垃圾回收。
finalize方法详解:
当垃圾回收机制回收某个对象所占用的内存空间之前,通常要求程序调用适当的方法来清理资源,在没有明确指定资源清理的情况下,Java提供了默认机制来清理该对象的资源,这个方法总是finalize。该方法是定义在Object类的实例方法,方法原型:protected void finalize() throws Throwable。
任何Java类都可以覆盖Object类的finalize方法,在该方法中清理该对象占用的资源,如果程序终止之前始终没有进行垃圾回收,则不会调用失去引用对象的finalize方法来清理资源。垃圾回收机制什么时候调用对象的finalize方法是完全透明的,只有当程序认为需要更多额外的内存时,垃圾回收机制才会进行垃圾回收。因此,有可能出现,当某个失去引用的对象只占用了少量内存,而且系统没有产生严重的内存需求,因此垃圾回收机制并没有试图回收该对象所占用的资源,所以该对象的finalize方法也不会调用。
finalize方法特点:
(1)永远不要主动调用某个对象的finalize方法,该方法是由垃圾回收机制调用。
(2)finalize方法何时被调用,是否被调用具有不确定性,不要把finalize方法当成一定会执行的方法。
(3)当虚拟机执行去活对象的finalize方法时,可能使该对象或系统中其他对象重新变为激活状态。
(4)当虚拟机执行finalize方法时出现了异常,垃圾回收机制不会报告异常,程序继续执行。
经典实例:
public class Test {
	private static Test test=null;
	public static void main(String[] args) throws Exception {
		new Test();
		//通知系统进行垃圾回收
		System.gc();
		//让程序暂停2秒
		Thread.sleep(2000);
		test.testInfo();
	}
	public void finalize(){
		//让test引用试图回到去活状态
		test=this;
	}
	public void testInfo(){
		System.out.println("测试资源清理的finalize方法");
	}
}
如果把程序中的Thread.sleep(2000);注释掉,运行结果将报空指针异常。从这点可以看错出,当程序调用了System.gc()方法后,系统并没有立即执行垃圾回收,否则将会先执行finalize方法,也就不会报空指针异常。
为了避免这样的问题。System和Runtime类中都提供了一个叫runFinalization方法,该方法可以强制垃圾回收机制调用finalize方法。这样就避免了上面报空指针的错误。


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值