Thinking In Java琐碎知识点摘要(二)

26、访问权限
    private->default->protected->public分别对应类内部访问权限、包访问权限、不同包的子类访问权限(相同包同之前的级别)、全局访问权限。类的绝大多数属性使用private修饰,此外有些方法只是用于辅助实现该类的其他方法,这些"工具"方法也应该使用private修饰。  如果某个类主要用作其他类的父类,该类包含的绝大数方法可能仅仅希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰。希望暴露出来给其它类自由调用的方法应该使用public修饰,顶级类通常给其它类自由使用,所以绝大数顶级类使用public修饰。类的构造器通过使用public修饰暴露给其他类以创建该类的对象。

27、理解封装(面向对象三大特征之一,另外两个是继承和多态)
    封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作访问。

28、控制对成员的访问权限有两个原因。第一是为了使用户不要碰那些他们不该碰的部分这些部分对于类的内部操作是必要的,但是她并不属于客户端程序员所需接口的一部分。因此将方法和域指定成private,对客户端程序员而言是一种服务。因为这样他们可以很清楚地看到什么对他们重要,什么是他们可以忽略的,这简化了他们对类的理解。第二,这也是最重要的原因,为了让类库的设计者可以更改类的内部工作方式,而不必担心这样会给客户端程序员造成重大影响。访问权限控制确保不会有任何客户端程序员依赖于某个类的底层实现的任何部分。

29、可以为每个类创建一个main方法,这可以使每个类的单元测试都变得简便易行。即使是一个程序中含有多个类,也只有命令行所调用的那个类的main() 方法会被调用。

30、由于子类对象在初始化时会调用父类构造器,所以如果没有默认的基类构造器,或者想调用一个带参数的构造器,就必须使用super关键字显式地编写调用基类构造器的语句,并配以适当的参数列表。
class Art {
	Art(int a) {
		print("Art constructor "+a );
	}
	Art() {
		print("Art constructor " );
	}
}

class Drawing extends Art {
	Drawing(String aa) {
		super(1);
		print("Drawing constructor "+ aa);
	}
	Drawing() {
		print("Drawing constructor ");
	}
}


public class Cartoon extends Drawing {
	public Cartoon() {
		//super("zpc");//没有这句话则是一层一层调用默认的无参构造器,取消注释则调用指定的父类构造器
		print("Cartoon constructor");
	}

	public static void main(String[] args) {
		Cartoon x = new Cartoon();
	}
}

31、向上转型:从一个较专用的类型向较通用的类型的转换(子类型转换成父类型),这是安全的,因为导出类是基类的一个超集(子类继承了父类的成员)。

32、final关键字(见45条)
    带有恒定初始值即编译时常量的final static 类型全用大写字母命名。一个既是static又是final的域只占据一段不能改变的存储空间。对于基本类型final使得数值恒定不变;而对于对象引用,final使引用恒定不变。所以不能认为某个变量声    明为final的就不能改变它的值了,只是一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象,然而对象自身的值却是可以改变的。
    final类不能被继承,final类中的所有方法都隐式的指定为final,因为无法覆盖他们。你当然可以显式添加final关键字,但没有任何意义。

33、null

34、类中方法重载:一个类中方法名相同,参数列表不同,至于其他的如返回值类型、修饰符与方法重载没关系。

35、成员变量如果没有显式初始化,则会在类加载或者对象创建时执行默认初始化,但是局部变量(除了形参)都要显式初始化,否则不能使用该变量。局部变量保存在其所在方法的栈内存中。可以通过构造器改变成员变量的默认初始化。

36、系统在执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在构造器中通过this来引用它。当构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另一个引用类型变量,从而让外部程序可以访问该对象。

37、子类覆盖(重写)父类的方法时要注意以下“三同一小一大”规则
    三同:方法名相同、形参列表相同、返回值类型相同(方法重载并没有要求返回值类型相同)
    一小:子类抛出的异常类应比父类小或相等
    一大:子类方法的访问权限应该父类方法更大或者相等(子类不能缩小父类的访问权限)
    覆盖的方法和被覆盖的方法要么都是类方法要么都是实例方法,不能一个是类方法,一个是实例方法。
例程:
public class WithFinal extends MyFinal {
	private final void test(){
		print("WithFinal.test");
	} 
	/*上面之所以能够"覆盖"test()方法只是因为private修饰的成员对子类是隐藏的,
	这里不是覆盖,而是子类中创建的一个新的方法*/
	
	/*public final void test2(){
		print("WithFinal.test2");
	} final修饰的方法不能被子类覆盖
	*/
	public void test3(){
		print("WithFinal.test3");
	}//这里是方法覆盖,子类的修饰符要大于等于父类
}


class MyFinal{
	private final void test(){
		print("MyFinal.test");
	} 
	public final void test2(){
		print("MyFinal.test2");
	}
	protected void test3(){
		print("MyFinal.test3");
	}
}

38、继承时,子类不会获得父类的构造器。子类构造器总会调用父类构造器,要么显式,否则就是隐式(调用无参构造器),从Object类的构造器一直调用到当前类。

39、假如两种类型之间没有继承关系,即不在继承树的同一个继承分支上,那Java编译器不允许进行强制类型转换。

40、多态的三个必要条件:要有继承、要有覆盖、父类引用指向子类对象。多态源于Java引用变量有两个类型:编译时类型(声明的类型)、运行时类型(实际赋给该变量的对象决定)。如果编译时类型和运行时类型不一致就会出现多态现象。

41、在运行时环境中,通过引用类型变量来访问所引用的对象的方法和属性时,JVM采用下列绑定规则:
    实例方法与实际引用的对象的方法绑定,这是动态绑定,程序运行时由JVM动态决定。
    static(静态)方法与所声明的类型的方法绑定,这是静态绑定,编译时就做了绑定。
    成员变量(包括静态变量和实例变量)与所声明的类型的成员变量绑定,这是静态绑定,编译时就做了绑定。

42、instanceof不是关键字,而是运算符
    A instanceof B :判断前一个引用变量A所指的对象是否是B类(或其子类)的实例,保证强制类型转换不会出错。
    注意:instanceof运算符前面操作数的编译时类型要么和后面的类相同,要么是后面类的父类,否则编译出错。


43、设计父类遵循的原则:尽量隐藏父类的内部数据(父类属性设成private类型)。
    父类中的辅助方法设成private,父类中需要被外部类调用的方法以public修饰,但是如果又不希望子类重写该方法,则再用final修饰。如果希望父类中的某个方法被子类重写,但不希望被其它类访问,则使用protected修饰。
    注:尽量不要在父类构造器中调用即被子类覆盖的方法,以免引起麻烦。
//下面的代码引起空指针异常,因为先执行父类构造器,父类构造器中调用test方法实际却是调的子类覆盖的test方法,此时
//子类还没初始化,name值为null,把父类test方法改成private就行了,此时没有构成方法重写,父类构造器中test()直接调用Base类的test方法
public class Sub extends Base {
	String name="zpc";
	public void test(){
		System.out.println("子类重写父类的方法,name字符串的长度"+name.length());
	}
	public static void main(String[] args){
		Sub s=new Sub();
	}
}
class Base{
	public Base(){
		test();
	}
	public void test(){
		System.out.println("将被子类覆盖的方法");
	}
}

//下面这个例子可以清晰展示初始化顺序,能更好地理解上面那个例子的过程
public class Sub extends Base {
	{
		System.out.println("Sub的普通初始化块");
	}
	static {
		System.out.println("Sub的静态初始化块");
	}
	String name="zpc";
	public Sub(){
		System.out.println("Sub()的构造函数");
	}


	public static void main(String[] args){
		Sub s=new Sub();
		System.out.println("**************");
		Sub s2=new Sub();
	}
}
class Base{
	static {
		System.out.println("Base的静态初始化块");
	}
	{
		System.out.println("Base的普通初始化块");
	}
	public Base(){
		System.out.println("Base()的构造函数");
		test();
		
	}
	public void test(){
		System.out.println("将被子类覆盖的方法");
	}
}

44、下面的程序演示了创建一个单例类(如果一个类始终只能创建一个实例,则称这个类为单例类): 
 class  Singleton{
	//使用一个变量来缓存曾经创建的实例
	private static Singleton instance;
	//隐藏构造器
	private Singleton(){}
	//提供一个静态方法返回Singleton的实例
	public static Singleton getInstance(){
		if(instance==null){
			instance=new Singleton();
		}
		return instance;
	}
}
public class TestSingleton {
	public static void main(String[] args) {
		Singleton s1=Singleton.getInstance();
		Singleton s2=Singleton.getInstance();
		System.out.println("s1=s2?"+(s1==s2));//s1、s2指向同一个对象
	}
}
45、final总结
    (1)final变量
    与普通成员变量不同的是,final成员变量(包括类属性和实例属性)必须由程序员显式初始化,系统不会对final成员进行隐式初始化。
    final修饰的类属性和实例属性能指定初始值的地方如下:
    类属性:可在静态初始化块、声明该属性时指定初始值
    实例属性:可在非静态初始化块、声明该属性、构造器中指定初始值。
   public void fuzhi(final int d){
		d=9;//非法,由调用方法时传入的参数完成初始化,不能再赋值
	}
    (2)final方法
     final修饰的方法不能被重写,例程见(37),不希望子类重写父类某个方法可以使用final修饰该方法。
    (3)final类

     final修饰的类不可以被继承。


46、不可变类(未提供set方法,属性用private修饰)、重写equals方法、hashcode方法

   //比较两个地址是否相等
    public class Address {
	private final String detail;
	private final String postcode;
	public Address() {
		this.detail = "";
		this.postcode = "";
	}
	public Address(String detail, String postcode) {
		this.detail = detail;
		this.postcode = postcode;
	}
	public String getDetail(){return this.detail;}
	public String getPostcode(){return this.postcode;}
	public boolean equals(Object obj){
		//重写Address类的equals方法
		if(obj instanceof Address){
			Address ad=(Address)obj;
			if(this.getDetail().equals(ad.getDetail())&&this.getPostcode().equals(ad.getPostcode())){
				return true;
			}
		}
		return false;
	}
	public int hashCode(){
		return detail.hashCode()+postcode.hashCode();
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Address ad1=new Address("南京","210046");
		Address ad2=new Address("南京","210046");
		System.out.println("a1==a2?"+(ad1==ad2));
		System.out.println("a1.equals(a2)?"+ad1.equals(ad2));
		System.out.println("ad1.hashcode:"+ad1.hashCode());
		System.out.println("ad2.hashcode:"+ad2.hashCode());
	}
}

47、abstract类和方法、接口
    有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。 抽象类、接口不能被实例化。
    public abstract void test();//是一个抽象方法
    public abstract void test(){}//是一个空方法
    public abstract class SpeedMeter{}//定义抽象类
    接口interface是一种更彻底的抽象
    接口的定义和继承(和类不一样,接口支持多继承)public interface extends interface1,interface2{}
    接口里可以包含属性(只能是常量,并且自动加了static final修饰)、方法(只能是抽象方法)、内部类(包括内部结构)和枚举类
    一个类只可以继承一个类但可以实现多个接口
    public class zpc extends z implements interface1,interface2{}
    接口不包含构造器,抽象类可以有构造器(抽象类的构造器不是用来初始化对象的,是让其子类调用的)
    
48、一个命令模式的例子
/*
 命令模式测试*/
public class TestCommand {
	public static void main(String[] args) {
		ProcessArray pa=new ProcessArray();
		int[] target={1,2,3,4};
		pa.process(target, new PrintCommand());
		pa.process(target, new SumPrintCommand());
	}
}
class ProcessArray{
	public void process(int[] target,Command cmd){
		cmd.process(target);
	}
} 

interface Command{
	void  process(int[] target);
}

class PrintCommand implements Command{
	public void process(int[] target) {
		for(int temp:target){
			System.out.println("迭代输出目标数组元素:"+temp);
		}
	}
}

class SumPrintCommand implements Command{
	public void process(int[] target) {
		int sum=0;
		for(int t:target){
			sum+=t;
		}
		System.out.println("目标数组元素之和:"+sum);
	}
}

49、内部类实例
   public class InnerClassTest {
	private String prop="外部类属性";
	private  class InnerClass{
		private String prop="内部属性";
		public void info(){
			String prop="局部变量";
			System.out.println("外部类属性值:"+InnerClassTest.this.prop);
			System.out.println("局部变量:"+prop);
			//先查局部变量、再到内部类找、再到外部类找
			System.out.println("内部属性:"+this.prop);
			recall();//注意查找recall的顺序
			InnerClassTest.this.recall();
		}
	}
	public void info(){
		new InnerClass().info();
	}
	public void recall(){
		System.out.println("内部类回调外部类方法");
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new InnerClassTest().info();
	}
}

50、非静态的内部类的构造器必须通过其外部类的对象来调用,在外部类以外的地方创建非静态内部类实例的语法是:OuterInstance.new InnerConstructor()
class Out{
	class In{
		public In(String msg){
			System.out.println(msg);
		}
	}
	//在外部类内部访问
	In in=new In("外部类内部访问");
}
public class CreateInnerInstance {
	public static void main(String[] args){
		Out.In in=new Out().new In("测试");
		/*等效于
		 * Out.In in;
		 * Out out=new Out();
		 * in=out.new In("测试");
		 */
	}
}
/*输出:
外部类内部访问
测试*/

51、匿名内部类适合创建只需一次使用的类,创建匿名内部类时会立即创建一个该类的实例,这个匿名类的定义立即消失,不能重复使用
    匿名内部类定义的格式:new 父类构造器(参数列表)|实现接口(){匿名内部类的类体}
    匿名内部类必须继承或实现一个接口
interface Product{
	public double getPrice();
	public String getName();
}
public class TestAnonymous {
	public void test(Product p){
		System.out.println("购买了一个"+p.getName()+"花了"+p.getPrice()+"元");
	}
	public static void main(String[] args) {
		TestAnonymous t=new TestAnonymous();
		t.test(new Product() {
			public double getPrice() {
				// TODO Auto-generated method stub
				return 223.5;
			}
			
			public String getName() {
				// TODO Auto-generated method stub
				return "路由器";
			}
		});
//上面使用匿名内部类的方式也可以换成如下实现方式
//		class AnonymousProduct implements Product{
//			public String getName() {
//				// TODO Auto-generated method stub
//				return "路由器";
//			}
//			public double getPrice() {
//				// TODO Auto-generated method stub
//				return 223.5;
//			}
//		}
//		t.test(new AnonymousProduct());
	}
}











你可能感兴趣的:(Thinking In Java琐碎知识点摘要(二))