面向对象上

1类和对象

1修饰符

2类,Field,方法三类的修饰符。

类:public,final,abstract,或者完全省略这三个修饰类。

Field:修饰符可以省略,也可以是public,protected,private,static,final,其中public,protected,private三个最多只能出现其中之一,可以与static,final组合起来修饰Field。

方法:修饰符可以省略,也可以是public,protected,private,static,final,abstract,其中public,protected,private三个最多只能出现其中之一;abstract和final最多只能出现其中之一,它们可以与static组合起来修饰方法。

3构造器即不能定义返回值类型,也不能使用void定义构造器没有返回值,如果为构造器定义了返回值类型,或使用void声明构造器没有返回值,编译时不会出错,但Java会把这个所谓的构造器当成方法来处理。

代码例子:

public class Test
{
	public Test()
	{
		System.out.println("构造器");
		System.out.println(Test());
	}
	/*public void Test()
	{
		System.out.println("无返回值方法");
	}*/
	public String Test()
	{
		return "有返回值方法";
	}
}
public class Test
{
	public Test()
	{
		System.out.println("构造器");
		Test();
	}
	public void Test()
	{
		System.out.println("无返回值方法");
	}
	/*public String Test()
	{
		return "有返回值方法";
	}*/
}
4对象的数据是存放在堆里,而引用变量是存在栈中。

5对于static修饰的方法而言,则可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象(因为this代表对象,类直接通过类名调用方法而没有创建实例的话,还不存在对象的this引用)。所以,static修饰的方法中不能使用this引用,由于static修饰的方法不能使用this引用,所以static修饰的方法不能访问不使用static修饰的普通成员,因此Java语法规定:静态成员不能直接访问非静态成员。

2方法详解

1Java方法里面的参数传递有两种情况:一种是基本类型传递,另一种是引用传递。学过C语言知道基本类型传递,那就是实参传递给方法里的形参是不会影响实参的值,Java的基本类型传递也是这样的,但Java引用传递则不同,下面结合代码例子来分析。

代码例子:

class DataWrap
{
	public int a;
	public int b;
}
public class ReferenceTransferTest
{
	public static void swap(DataWrap dw)
	{
		//下面三行代码实现dw的a、b两个Field值交换。
		//定义一个临时变量来保存dw对象的a Field的值
		int tmp = dw.a;
		//把dw对象的b Field值赋给a Field
		dw.a = dw.b;
		//把临时变量tmp的值赋给dw对象的b Field
		dw.b = tmp;
		System.out.println("swap方法里,a Field的值是"
			+ dw.a + ";b Field的值是" + dw.b);
		//把dw直接赋为null,让它不再指向任何有效地址。
		dw = null;
	}
	public static void main(String[] args) 
	{
		DataWrap dw = new DataWrap();
		dw.a = 6;
		dw.b = 9;
		swap(dw);
		System.out.println("交换结束后,a Field的值是" 
			+ dw.a + ";b Field的值是" + dw.b);
	}
}
结果:

接下来我截·图分析:

main方法中创捷了DataWrap对象后存储示意图:

main方法中的dw传入swap方法后存储示意图:

在前面类和对象的第四点提到过,对象的数据是存放在堆里,而引用变量是存在栈中,首先引用变量dw指向一个DataWrap对象在堆中分配的内容,当把dw传入给swap时,其实把该堆中的地址也传给了Swap形参的dw,从而在Swap栈区开辟一个dw引用变量也指向这个对象的内容(即实参和形参指向同一个堆中的对象分配空间),故dw对对象的内容所做出的改变会影响到实参dw的所指向的对象的内容。

2长度可变的形参只能处于形参列表的最后,一个方法中最多只能包含一个长度可变的形参,调用包含一个长度可变形参的方法时,这个长度可变的形参即可以传入多个参数,也可以传入一个数组。

代码例子:

public class Varargs
{
	//定义了形参个数可变的方法
	public static void test(int a , String... books)
	{
		//books被当成数组处理
		for (String tmp : books)
		{
			System.out.println(tmp);
		}
		//输出整数变量a的值
		System.out.println(a);
	}
	public static void main(String[] args) 
	{
		//调用test方法
		test(5 , "scau" , "beyondboy");
	}
}
3方法重载的要求就是两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法返回值类型,修饰符等,与方法重载没有任何关系。有一种特殊的情况,就是重载的方法里包含了长度可变的形参。

代码例子:

public class Overload
{
	//下面定义了两个test方法,但方法的形参列表不同
	//系统可以区分这两个方法,这种被称为方法重载
	public void test()
	{
		System.out.println("无参数");
	}
	public void test(String msg)
	{
		System.out.println("重载的test方法 " + msg);
	}
	public static void main(String[] args)
	{
		Overload ol = new Overload();
		//调用test时没有传入参数,因此系统调用上面没有参数的test方法。
		ol.test();
		//调用test时传入了一个字符串参数,
		//因此系统调用上面有一个字符串参数的test方法。
		ol.test("hello");
	}
}

3成员变量和局部变量

1当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值,与成员变量不同的是,局部变量除了形参之外,都必须显示初始化,也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它。

public class BlockTest
{
	public static void main(String[] args) 
	{
		{
			//定义一个代码块局部变量a
			int a;
			//下面代码将出现错误,因为a变量还未初始化
			//System.out.println("代码块局部变量a的值:" + a);
			//为a变量赋初始值,也就是进行初始化
			a = 5;
			System.out.println("代码块局部变量a的值:" + a);
		}
		//下面试图访问的a变量并不存在
		//System.out.println(a);
	}
}

2如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例Field)或类名(对于类Field)作为调用者来限定访问成员变量。

代码例子:

public class VariableOverrideTest
{
	//定义一个name实例Field
	private String name = "scau";
	//定义一个price类Field
	private static double price = 78.0;
	//主方法,程序的入口
	public static void main(String[] args) 
	{
		//方法里的局部变量,局部变量覆盖成员变量
		int price = 65;
		//直接访问price变量,将输出price局部变量的值:65
		System.out.println(price);
		//使用类名作为price变量的限定,
		//将输出price类Field的值:78.0
		System.out.println(VariableOverrideTest.price);
		//运行info方法
		new VariableOverrideTest().info();
	}
	public void info()
	{
		//方法里的局部变量,局部变量覆盖成员变量
		String name = "孙悟空";
		//直接访问name变量,将输出name局部变量的值:"孙悟空"
		System.out.println(name);
		//使用this来作为name变量的限定,
		//将输出price实例Field的值:"scau"
		System.out.println(this.name);
	}
}
3局部变量定义后,必须经过显示初始化后才能使用,系统不会为局部变量执行初始化,这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值,系统才会为局部变量分配内存,并将初始值保存到这块内存中。

4相对于局部变量,成员变量的两个劣势:

增大了变量的生存时间,这将导致更大的内存开销。

扩大了变量的作用域,这不利于提高程序的内聚性。

4隐藏和封装

1一个类常常就是一个小的模块,我们应该只让这个模块公开必须让外界知道的内容,而隐藏其他一切内容,进行程序设计时,应尽量避免一个模块直接操作和访问另一个模块的数据,模块设计追求高内聚(尽可能把模块的内部数据,功能实现细节隐藏在模块内部独立完成,不允许外部直接干预),低耦合( 仅暴露少量的方法给外部使用).

2当虚拟机要装载beyondboy.Hello类时,它会依次搜索Classpath环境变量所指定的系列路径,查找这些路径下是否包含beyondboy路径,并在beyondboy路径下查找是否包含Hello.class文件。如果找不到,会在当前路径去寻找。

3同一个包中的类不必位于相同的目录下,例如,有beyondboy.Person和beyondboy.PersonTest两个类,他们完全可以一个位于C盘下某个位置,一个位于D盘下某个位置,只要让classpath环境变量里包含这路径即可,虚拟机会自动搜索classpath下的子路径,把它们当成同一个包下的类来处理。

4import语句中的星号(*)只能代表类,不能代表包,因此使用import beyondboy.*;语句时,它表明导入beyondboy包下的所有类,但不会导入beyondboy下的子包的所有类。

5静态导入使用import static语句,静态导入有两种语法。

导入指定类的单个静态Field,方法。(import static package.subpackage...ClassName.fieldName|methodName;)

导入全部Field,方法。(import static package.subpackage...ClassName.*;)

代码例子:

public class StaticImportTest
{
	public static void main(String[] args) 
	{
		//out是java.lang.System类的静态Field,代表标准输出
		//PI是java.lang.Math类的静态Field,表示π常量
		out.println(PI);
		//直接调用Math类的sqrt静态方法
		out.println(sqrt(256));
	}
}

5构造器

1构造器最大的用处就是在创建对象时执行初始化,当创建一个对象时,系统为这个对象的Field进行默认初始化,这种默认的初始化把所有基本类型的Field设为0(对数值型Field)或false(对布尔型Field),把所有引用类型的Field设为null。

2使用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句。

6类的继承

1继承的特点。

子类扩展了父类,将可以获得父类的全部Field和方法。但不能获得父类的构造器。java.lang.Object是所有类的父类。

2重写父类的方法,遵循"两同两小一大"规则.

两同:方法名相同,形参列表相同。

两小:子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出抛出的异常类应比父类方法声明抛出的异常类更小或相等。

一大:子类的方法访问权限应比父类方法的访问权限更大或相等。

注意:覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法,如果父类被子类覆盖方法不可以被private访问权限修饰。

3super限定

super和this一样,不能出现在static修饰的方法中,因为static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,因而super限定也就失去了意义。

在子类中构造器中,不能让this和super同时出现在构造器执行体中。

当存在变量同名的情况下,系统查找顺序如图:


系统不仅会为该类中定义的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。

7多态

1引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。
通过引用变量来访问其包含的实例Field时,系统总是试图访问它编译时类型所定义的Field,而不是它运行时类型所定义的Field。

2如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时引发ClassCastException异常。

8继承与组合

1继承的注意点:

继承严重的破坏了父类的封装性,因子类而可以直接访问父类的Field和方法,从而造成子类和父类的严重耦合。

尽量不要在父类构造器中调用将要被子类重写的方法。

代码例子:

class Base
{
	public Base()
	{
		test();
	}
	public void test()           //①号test方法
	{
		System.out.println("将被子类重写的方法");
	}
}
public class Sub extends Base
{
	private String name;
	public void test()        //②号test方法
	{
		System.out.println("子类重写父类的方法,"
			+ "其name字符串长度" + name.length());
	}
	public static void main(String[] args)
	{
		//下面代码会引发空指针异常
		Sub s = new Sub();
	}
}
2利用组合实现复合

1组合则是把旧类对象作为新类的Field嵌入,用以实现新类的功能,用户看到的是新类的方法,而不能看到被嵌入对象的方法。

代码例子:

class Animal
{
	private void beat()
	{
		System.out.println("心脏跳动...");
	}
	public void breath()
	{
	beat();
		System.out.println("吸一口气,吐一口气,呼吸中...");
	}
}
class Bird
{
	//将原来的父类嵌入原来的子类,作为子类的一个组合成分
	private Animal a;
	public Bird(Animal a)
	{
		this.a = a;
	}
	//重新定义一个自己的breath方法
	public void breath()
	{
		//直接复用Animal提供的breath方法来实现Bird的breath方法。
		a.breath();
	}
	public void fly()
	{
		System.out.println("我在天空自在的飞翔...");
	}
}
class Wolf
{
	//将原来的父类嵌入原来的子类,作为子类的一个组合成分
	private Animal a;
	public Wolf(Animal a)
	{
		this.a = a;
	}
	//重新定义一个自己的breath方法
	public void breath()
	{
		//直接复用Animal提供的breath方法来实现Wolf的breath方法。
		a.breath();
	}
	public void run()
	{
		System.out.println("我在陆地上的快速奔跑...");
	}
}
public class CompositeTest
{
	public static void main(String[] args)
	{
		//此时需要显式创建被嵌入的对象
		Animal a1 = new Animal();
		Bird b = new Bird(a1);
		b.breath();
		b.fly();
		//此时需要显式创建被嵌入的对象
		Animal a2 = new Animal();
		Wolf w = new Wolf(a2);
		w.breath();
		w.run();		
	}
}
3对于实现复用来说,采用继承和组合的系统内存开销是差不多的。

9初始化块

1当Java创建一个对象时,系统先为该对象的所有实例Field分配内存(前提是该类已经被加载过了),接着程序开始对这些实例变量执行初始化,其初始化顺序是:先执行初始化或声明Field时指定的初始值,在执行构造器里指定的初始值。

代码例子:

public class InstanceInitTest
{
	//先执行初始化块将a Field赋值为6
	{
		a = 6;
	}
	//再执行将a Field赋值为9
	int a = 9;
	public static void main(String[] args) 
	{
		//下面代码将输出9。
		System.out.println(new InstanceInitTest().a);
	}
}

2静态初始化快也被称为类初始化,也属于类的静态成员,同样遵循静态成员不能访问非静态成员,当JVM第一次主动使用某个类时,系统会在类准备阶段为该类的所有静态Field分配内存(只在第一次加载,第二次不加载)

3创建一个Java对象时,不仅会执行该类的普通初始化快和构造器,而且系统会一直上溯到java.lang.Object类,先执行java.lang.Object类的初始化快,开始执行java.lang.Object的构造器,依次向下执行其父类的初始化快,然后开始执行其父类的构造器.............最后才执行该类的初始化快和构造器,放回该类的对象。

代码例子:

class Root
{
	static{
		System.out.println("Root的静态初始化块");
	}
	{
		System.out.println("Root的普通初始化块");
	}
	public Root()
	{
		System.out.println("Root的无参数的构造器");
	}
}
class Mid extends Root
{
	static{
		System.out.println("Mid的静态初始化块");
	}
	{
		System.out.println("Mid的普通初始化块");
	}
	public Mid()
	{
		System.out.println("Mid的无参数的构造器");
	}
	public Mid(String msg)
	{
		//通过this调用同一类中重载的构造器
		this();
		System.out.println("Mid的带参数构造器,其参数值:"
			+ msg);
	}
}
class Leaf extends Mid
{
	static{
		System.out.println("Leaf的静态初始化块");
	}
	{
		System.out.println("Leaf的普通初始化块");
	}	
	public Leaf()
	{
		//通过super调用父类中有一个字符串参数的构造器
		super("scau");
		System.out.println("beyondboy");
	}
}
public class Test
{
	public static void main(String[] args) 
	{
		new Leaf();
		new Leaf();
	}
}
运行结果:

这篇博客参考资料:

Java疯狂讲义2




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值