耽搁挺久了,只记了草稿,没整理出来发表,本着楼主不能太监的原则,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)
returntrue
.非空
x
andy
,x.equals(y)
returntrue
if and only ify.equals(x)
returnstrue
.- 非空
x
,y
, andz
, ifx.equals(y)
returnstrue
andy.equals(z)
returnstrue
, thenx.equals(z)
returntrue
.
注意:按照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()
3. public String 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修饰的方法,经常用来生成动态代理,可用于工厂模式。
Class:public 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方法进行函数回调,使用接口进行回调会使得代码运行速度更快,更易维护。
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加载
}
}
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。