Thinking in Java 笔记(三)

耽搁挺久了,只记了草稿,没整理出来发表,本着楼主不能太监的原则,Here am I ~


一、继承

     面向对象设计是对现实世界特定关系的一种模拟,而继承是现实生活中一种很常见的关系,具有某些相同的属性,可以使用相同的方法去执行,这些类可以继承自一个基础类,类本身可以有自己所继承方法的独特实现,也可以新增自己的方法,这也很符合对添加开放对修改关闭的原则,能够很好的做到代码的重用。

    1. derived class  A extends Super class B, 代表 Ais aB , 派生类继承超类最好在物理世界中关系也是如此。在派生类的构造方法必须中添加超类的构造方法,因为可能派生类需要用到超类的一些特性,所以要先初始化超类,编译器会在派生类的默认构造方法中添加这句话 super(); //调用超类的构造方法。若超类无默认构造方法,则必须在派生类构造方法中第一句显式调用超类的构造方法,super(Object ...);

    2. super 关键字代表着当前类的所有超类,可以使用它来调用超类的除private之外的方法, 如 super.getClass().geSimpletName(); super.getSalary();

    3. 子类可以增加域或方法,也可以重写父类方法,推荐在方法之前添加注解 @Override  ,这样编译器检查的时候会更容易发现代码的错误。

    4.动态绑定:编译器只有在运行时才能知道调用的方法是哪一个。 方法的名字和参数列表构成方法的签名(返回类型不属于签名的一部分),虚拟机在程序运行一次后会维护一个方法表,列出来所有的方法签名和实际调用的方法。Overload 和Override 都属于动态绑定。子类可以直接转换成父类,但父类必须强制转换成子类,因为子类更具体,有一些域父类可能没有,所以在强转之前若不知道要转的类型,使用A instanceof B 判断一下。通过将子类upcasting 到父类,然后通过父类调用相应的方法,在子类中呈现不同的特性这叫多态。多态是建立在动态绑定的基础上,子类不能够覆盖private修饰的方法,但是写一个一模一样的函数是可以通过编译的,只是这不是叫做Override,而是写了一个新方法。在子类的构造函数中,应尽可能简单地使成员变量都初始化,尽可能地避免方法的调用,要调用就调用父类的final方法.

//动态绑定,运行时才决定调用的是哪个方法

public class Polymorphism{



	public static void main(String args[]){

		new Child(3);

	}

}



class Parent {

	public void show(){System.out.println("parent show");}

	public Parent(){

		System.out.println("before show");

		show();

		System.out.println("after show");

	}

}



class Child extends Parent{

	private int age=1;

	public void show(){

		System.out.println("child show, age="+age);

	}

	public Child(int ag){

		this.age=ag;

		System.out.println("child age="+age);

	}

}

/**

*Results:

*before show

*child show, age=0 //构造父类的时候,子类还未来得及初始化

*after show

*child age=3

**/


    5. 值得一提的关键字: final

     final 可以用来阻止本类被其他类所继承,该关键字主要分成三方面使用:

    修饰变量: 编译时常量。基本类型变量的值不可变,引用变量的指向不能变,但是指向的变量可以变,其实很好理解,就认为引用变量是一个存储内存地址的               变量,地址不能变,但是以该首地址开头的整块存储区存储的内容是可以随意更改的。

    修饰方法:方法不能被重写

    修饰类: 类不能该被继承,类中的所有方法自动变成了final (其实这也没多大意思,都不能被继承怎么可能重写方法呢)

   以前程序员使用final是为了减少动态绑定带来的系统开销,这个过程叫内联,可提高效率,现在编译器优化做得足够好了,没有必要为了效率而使用它了。

  ps: 类中private 修饰的变量和方法隐含添加了final关键字

    6. 抽象类

    抽象类是使用继承进行高层次抽象的利器,用 abstract 关键字修饰的较高层次的超类叫做抽象类。抽象类有以下特性:

    1. 抽象类不能被实例化,只能被继承,可以作为可实例化的子类的引用变量。

    2. 抽象类可以有 0-n抽象方法,可以包含非抽象方法和成员变量。

    3.只要包含了1个抽象方法,这个类就必须声明为抽象类

    4.抽象类的子类要想被实例化必须实现所有的抽象方法,抽象方法起到了占位的作用。

继承设计总结:

1. 尽量将通用的方法(不管抽象与否)和域放在父类(不管抽象与否)中

2. 尽量不要使用protected 修饰域,一是子类的种类是无限制的,访问该域很可能破坏封装性,二是同包的所有类都可以访问protected 修饰的域,不安全

3.继承尽量是现实中的 “is-a" 关系,

4.覆盖方法不要改变预期的行为

5.反射适合编写系统程序,不大适合应用程序,不要过多的使用反射!!(很难发现错误,只有运行时才有异常)


二、 详解 Object

    Object类是Java 中所有类的父类,被称为Base Class 基类, 这是Java “ 万物皆对象 ” 的思想。也就是说Java中所有类(包括Class)都继承自Object,因此研究这个类对于我们理解其它类和继承很有意义。


1. public boolean equals(Object obj)

判断两个对象是否相等。此方法具有自反性、对称性和可传递性。
  • 非空 x.equals(x)  return true.
  • 非空 x and yx.equals(y) return true if and only if y.equals(x) returns true.
  • 非空 xy, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) return true.
只要在equals 方法中比较的内容没有任何改变,equals 的返回值就必须一致。任何非空的对象equals(null) 都return false。此方法要区别于" ==" ,''=="判断的是两个对象的引用指向的是否是同一个对象,即内存中同一块地址,而equals 对象多用于判断两个对象某个受业务关注的内容是否相等(前提是要覆盖equals 方法)。

 注意:按照hashCode方法的协定,最好在覆盖equals方法的时候也覆盖hashCode方法(非强制)。

例如: String 类覆盖了hashCode方法,

    int hash=0;

    for(int i=0;i<length();i++)

hash=30*hash+charAt(i); //对每个字符都加码

 

public class StringEquals{
	public static void main(String[] args){
		String s1="thinking ";
		String s2="in java";
		String s3="thinking in java";
		String s4="thinking "+"in java";
		String s5=s1+s2;
		String s6=new String("thinking in java");
		
		System.out.println(s3=s4);	//thinking in java ;普通的赋值返回s3
		System.out.println(s3==s4);//true	;常量池中已经存在
		System.out.println(s5==s4);//false	;s5需要等到运行时才知道他的值,不在常量池中
		System.out.println(s6==s5);//false	;两个对象引用不一致
		System.out.println(s6.equals(s5));//true	;改写了equals方法,内容一致返回true
		System.out.println(s6.equals(s4));//true	;改写了equals方法,内容一致
		
		System.out.println(s1.hashCode());// -692772752
		System.out.println(s2.hashCode());//1880620093
		System.out.println(s3.hashCode());//736263629 , String 类重写了hashCode,内容一样哈希码值即一样
		System.out.println(s4.hashCode());//736263629
		System.out.println(s5.hashCode());//736263629
		System.out.println(s6.hashCode());//736263629
	}
}
/**
Java 没有运算符重载,String x = "a1" + "a2"
其实在编译后,代码变为
String x = (new StringBuilder(String.valueOf("a1"))).append("a2").toString();
这就是为什么在操作String时建议采用StringBuffer了,上面的操作显然对多次String的相加不利
建议直接采用append();
*/


2. public int hashCode()

返回当前对象的哈希码值(默认用内存中该对象的地址来实现)。
根据协定,每一个对象有一个唯一的哈希码值。
1.  在一个程序中,只要这个类不覆盖equals 函数,无论何时调用此方法,都返回一个相同的整数。在同一个应用不同程序之间,可以返回不同的整数。
2. 如果两个对象用equals 方法判断是相等的,则这两个对象 必须返回一样的哈希码值
3. 不要求两个通过equals不等的对象返回的哈希码值不相等。但是推荐程序员返回两个不等的值以提高效率。


ps: 数组的散列值可以使用Arrays.hashCode(type [] a) ; 来计算


3. public String toString()

以人类可以阅读的方式代表这个对象,System.out.println(Object o); //默认调用toString() ; 方法打印对象
 getClass().getName() + '@' + Integer.toHexString(hashCode())

4. 对象克隆

protected Object clone() throws CloneNotSupportedException
浅复制, 只会复制常量域和不变的引用, 不会将是成员变量的引用所指向的对象也拷贝,只是拷贝一个对象的引用。
 x.clone() != x  //true,按照约定应该这样
 x.clone().getClass() == x.getClass()//true , 按照约定应该这样
 x.clone().equals(x)//true , 按约定应该这样

(1)按约定,要想克隆的对象独立于创建者,一般需要修改super.clone() 所返回的域和值。

 (2)一个类若要使用clone 方法,必须要implements Cloneable 接口,并覆盖Object.clone()方法,否则会抛出 CloneNotSupportedException 异常。所有数组都实现了这个接口。


public class CloneTest implements Cloneable{
	private A a;
	public CloneTest(){
		a=new A();
	}
	
	public A getA(){
		return a;
	}
	
	@Override
	public CloneTest clone() throws CloneNotSupportedException{
		CloneTest copy=(CloneTest)super.clone();//浅复制 shallow copy
		System.out.println("original a "+this.getA().hashCode());//original a 	15293014
		System.out.println("copy a "+copy.getA().hashCode());	 //copy 	a 	15293014
		//要想深拷贝 deep copy , do sth with a here...
		return copy;
	}
	
	public static void main(String args[]) throws CloneNotSupportedException{
		CloneTest original=new CloneTest();
		CloneTest copy=original.clone();
		System.out.println(original==copy);			//false
		System.out.println(original.equals(copy));	//false
	}
}
//随便声明一个类
class A{}

/**
* Cloneable 只是个标记接口,它没有任何方法,只是向程序员声明该类将要用到Object.clone()
* 这里覆盖的不是Cloneable 接口的方法,而是Object 的方法
* clone 方法会抛出CloneNotSupportedException, 覆盖它的方法都得抛出或者捕捉。
* clone 方法访问权限是protected , 要想在外部调用它得声明为public
* clone 方法进行的是浅拷贝,对于不变的对象来说这就足够了,对于有可变的对象引用就要深拷贝
*/

三、 反射

    反射是Java独特的性质之一,它可以获得正在运行的对象,对类进行自省,从而分析类的能力。

    通过反射我们可以判读对象所属的类Class, 成员变量 Fields, 方法 Methods. 通过反射可以调用到Private修饰的方法,经常用来生成动态代理,可用于工厂模式。

  Classpublic final classClass<T> extendsObject implementsSerializable,GenericDeclaration,Type,AnnotatedElement

Meta Class 元类。它用来描述类的信息,如类名、构造器、成员变量、方法等。 Class extends Object.  Class 的实例代表着类或接口。枚举是一种类,注解是一种接口,他们都属于Class的实例,8个基本类型和void 都是 Class 的对象。

    可以有三种方式获得:

        Class c1=int.class;  Class c2=Class.forName("java.util.ArrayList"); Class c3=new ArrayList().getClass();

常用方法:

       getDeclaredConstructors(); //获得所有构造器  getDeclaredFields();  //获得所有成员变量 getDeclaredMethods();//获得所有方法 

Class.forName(String className).newInstance() ; //根据类名来加载一个类,并实例化

    Field:   public final classFieldextends AccessibleObjectimplements Member 。成员变量,可以通过getDeclaredField(String fieldName); 或 getDeclaredFields(); 获得, 然后对这个域进行读写。 getInt(), setDouble()  

    Method:public final classMethod extendsAccessibleObject implementsGenericDeclaration,Member 。类中的方法描述。可以通过

getDeclaredMethod(String name, Class<?> ... parameterType)来获得。当获得这个对象之后,可以使用invoke(Object obj, Object... args)激活此方法。

ps:  建议不要使用Method的invoke方法进行函数回调,使用接口进行回调会使得代码运行速度更快,更易维护。
ClassLoader: 类加载器是将类的二进制字节码加载到JVM中的工具,它会根据类名找到对应的字节码,把这些字节码转化成java 中 Class 类的实例,每个实例都表示一个Java类。类加载采用全盘委托制,当某个类被类加载器加载成功后,这个类对应的所有引用的类都必须由这个类加载器加载。
系统有三个类加载器:Bootstrap ClassLoader,Extension ClassLoader , App ClassLoader. JVM启动Bootstrap ClassLoader,初始化sun.misc.Laucher,然后由sun.misc.Laucher启动Extension ClassLoader 和App ClassLoader 。
Bootstrap ClassLoader 是用C++ 写的, 它负责加载基础的Java类,主要是%JRE_HOME%/lib 目录下的基础类,如rt.jar,resources.jar , charsets.jar 等。
Extension ClassLoader是用Java写的,继承自URLClassLoader,负责加载Java的扩展类,主要是%JRE_HOME%/lib/ext 目录下的扩展类。
App ClassLoader是Extension ClassLoader的子类,负责加载java应用的 classpath 下的类,是Java类的默认加载器。下面是类的加载过程:
class loading process
可以用代码来看一下:
import java.util.Date;
public class ClassLoaderTest {
	private Date day=new Date();
	public Date getDay(){return day;}
	
	public static void main(String[] a){
		ClassLoader loader=ClassLoaderTest.class.getClassLoader();
		System.out.println(loader);				//sun.misc.Launcher$AppClassLoader@1c0ec97
		System.out.println(loader.getParent());			//sun.misc.Launcher$ExtClassLoader@ecb281
		System.out.println(loader.getParent().getParent());	//null //Bootstrap ClassLoader 不是Java类
		ClassLoaderTest clazz=new ClassLoaderTest();
		System.out.println(clazz.getClass().getClassLoader());	//sun.misc.Launcher$AppClassLoader@1c0ec97
		System.out.println(clazz.getDay().getClass().getClassLoader());	//null//Date 由Bootstrap ClassLoader加载
	}
}


由于一些原因例如防止反编译逆向工程等,我们得对类的字节码文件进行加密,当要用到这些类文件时就可以用含有解密算法类加载器来加载,使得只有密钥才能正确使用代码。这时需要自己写类加载器。自定义类加载器可以继承字URLClassLoader也可以自己写,加载的字节码可以是本地二级制文件中的,也可以是二进制字节流,还可以是网络上的资源,这也许是为什么类两个高层的类加载器都继承自URLClassLoader的原因。这时字节码是以字节数组的形式传递,就要使用   defineClass  将字节数组转化成Class 实例 .然后通过 newInstance 转化成该类的对象。必须得先定义该类,再加载该类。通过此类的ClassLoader.loadClass 方法可以将所有被此类构造函数引用的类都加载入JVM。通过网络构造类可以用下面的方法:
ClassLoader loader = new NetworkClassLoader(host, port);
  	Object main = loader.loadClass("Main", true).newInstance();
	class NetworkClassLoader extends ClassLoader {
         String host;
         int port;

         public Class findClass(String name) {
             byte[] b = loadData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadData(String name) {
             // load the class data from the connection
              . . .
         }
    	}


     Java 字节代码(.class)文件存放在服务器上,客户端通过网络的方式获取字节代码并执行。当有版本更新的时候,只需要替换掉服务器上保存的文件即可。类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。
 
  ClassLoader 经常用到的方法:URL getResource(String resName) //获取在二进制类文件同目录下的文件,默认以“/”开头 
 
InputStream getResourceAsStream(String resName) //获取文件,以流的形式返回。
JVM认为只有两个类的类名和类加载器一模一样,这两个类才是相等的,否则如果将类名相同的类进行赋值会抛出ClassCastException。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值