类设计技巧:
1、保证数据私有化
2、对数据进行显式的初始化
3、不要过多使用基本数据类型(相关的基本数据可以封装成一个新的类)
4、将类职责进行分解
5、名字尽量体现类和方法的职责
继承
extends表示新类派生于一个已存在的类,已存在的类称为超类、父类,新类称为子类、派生类。
在子类中新添加的方法不属于父类,父类不能使用。
子类继承父类的所有域和方法
子类访问父类的私有域:
子类可以调用父类中的public方法来设置私有域,但是不能直接访问,也不能访问父类的private方法。只有父类能直接访问
子类要访问必须借助公有的接口。
public double getSalary(){ //子类重写方法
return 10+getSalary(); //调用父类接口获得父类私有域、名称错误
}
这里希望·调用父类的getSalary,需要加上super.getSalary()子类可以增加域、增加方法、覆盖方法,但是不能删除继承的域和方法
子类构造函数
子类执行构造函数时,必须调用父类的一个构造器(一个,不是0或2)
可以使用super(param),显式地在子类构造函数中调用父类构造函数,显式调用父类的构造函数,这一句话必须放在子类构造函数的第一句。
不显式声明,则采用父类的默认构造函数
不能将父类对象赋给子类,反之可以(经理是雇员,但雇员不一定是经理)
Manager m=new Manager();
Employee e=new Employee();
Employee e2=new Manager();
m.getSalary();
e.getSalary();
e2.getSalary();
这里Java能自动识别出,e2本身是Manager对象,调用子类的方法,而不是父类的多态:一个变量对象可以可以指向多种实际类型
动态绑定:在运行时能够自动选择调用哪一种方法,
如果不希望哪一个方法被继承,可以设为final
Manager m=new Manager();
Employee e=new Manager();
m.newfun(); //Manager对象执行子类中的新方法
e.newfun(); //错误,虽然e是一个Manager,但是赋给了Employee变量,只能执行声明类型Employee中存在的函数
这样也不行
Manager boss=new Manager();
Employee e=boss;
boss.newfun(); //可以
e.newfun(); //不可以
Manager boss=new Manager();
Employee e=boss;
Manager m;
m=boss; //正确
m=e; //错误
Employee boss=new Manager();
System.out.println(boss.getClass());//输出class main.Manager
public static void main(String[] args){
Manager boss=new Manager();
p(boss);
}
public static void p(Employee e){
System.out.println(11111);
}
public static void p(Manager e){
System.out.println(22222);//不报错,会执行这一方法,将该方法注释,会找到上一个方法执行
}
Employee boss=new Manager();
System.out.println(boss.getClass());//输出class main.Manager
public static void main(String[] args){
Manager boss=new Manager();
p(boss);
}
public static void p(Employee e){
System.out.println(11111);
}
public static void p(Manager e){
System.out.println(22222);//不报错,会执行这一方法,将该方法注释,会找到上一个方法执行
}
动态绑定
对象执行方法:x.func(param), x的声明类型为C1、编译器查看对象的声明类型和方法名,获取所有的可选方法(C类中的、超类中的可访问的f方法)
2、编译器查看参数,如果存在一个参数完全匹配的就选择执行(由于参数允许类型转换,int转double,manager转employee等,这个过程比较复杂,如果没有可以匹配的或者有多个,则报错)
3、如果是private(只能调用自己的)、static(只有一个)、final(无法继承)方法,那就是静态绑定,如果调用的方法依赖于隐式参数的实际类型,在运行时实现动态绑定
4、动态绑定时,虚拟机调用与调用方法的那个对象的实际类型最匹配的那个方法,否则向上到超类中寻找
每次调用都要进行搜索开销太大,虚拟机会每个类创建一个方法表,列出了所有方法的签名和实际调用的方法,这样真正调用方法的时候,虚拟机查找这个表就行了。
动态绑定的一大好处,无需对现存的代码进行修改,既可以对程序进行拓展.比如新继承出一个子类CEO,还是可以用Employee e来调用它,不需要对e.getSalary进行重新编译,就会动态绑定到CEO.getSalary()
覆盖一个方法时,子类方法不能低于超类方法的可见性(会报错)
阻止继承:
阻止继承类:final class Executive extends Manager,所有方法称为fianl,但不包括域(域的fianl是不可改,方法和类是不能继承+修改)
阻止继承方法: public final String func()
如果一个方法没有被覆盖而且很短,编译器会进行优化处理变成一个内联函数比如e.getName()会直接访问e.name,因为调用方法的指令,使用的分支转移会扰乱预取指令的策略。如果方法在子类中被覆盖了,编译器不知道覆盖的代码是什么操作,无法进行内联处理。所以过去有的人建议将不需要继承的方法全部设为final。但是现在的虚拟机强大很多,编译器可以准确地知道类之间的继承关系,如果一个类简短、频繁调用、没有被真正地覆盖,则即时编译器能够对该方法进行内联处理。如果加入了新的子类,包含对内联方法的覆盖,那会取消对方法的内联。
强制类型转换
每一个对象变量都属于一个类型,类型描述了变量所引用的,以及可以引用的对象类型。将一个子类的引用赋给一个超类的变量是允许的,但是将超类的引用赋给子类的变量,必须进行强制转换才能通过类型检查。
double x=2.1; int y=(int) x;//舍弃小数点部分
比如之前的Employee e=boss;虽然可以用动态绑定,调用Manager中覆盖的方法,但是Manager独有的方法,通过e无法调用
在进行类型转换前应检查是否能够成功转换
Employee boss=new Manager();
Manager m1=((Manager) boss);
Employee empl=new Employee();
Manager m2=((Manager) empl); //编译时没有错,运行时报ClassCastException
用instanceof检查Employee boss=new Manager();
if(boss instanceof Manager){
Manager m1=((Manager) boss);
}
Employee empl=new Employee();
if(empl instanceof Manager){
Manager m2=((Manager) empl);
}
应尽量少用类型转换,可以考虑将子类特有的方法也在超类中声明抽象类
从继承层次来看,祖先更加通用,有时只是想将它作为派生其他类的基类,而不是作为使用的实例类。
可以将姓名、获取姓名等方法继承层次更高的通用类中。
超类对于子类的实现细节一无所知,方法可以实现一些空操作,但是更好的办法是使用abstract关键字,这样就不需要实现这个方法了
包含一个或多个抽象方法的类必须声明为抽象的,抽象类不能实例化。
抽象类中除了抽象方法,还可以包含具体数据和具体方法,虽然抽象类主要起到占位的作用,但是还是将通用的方法(不管是不是抽象)加入到超类(不管是不是抽象类)中
子类必须实现抽象父类的所有抽象方法,才能使自己不再是 抽象类
abstract public class Employee
public abstract String getName();//抽象方法,不能有实现
public abstract void print(int i,String n);//正确,在子类中实现该抽象方法时,参数要一样
public abstract void print(int); //错误
抽象类虽然不能实例化,但是抽象类的对象变量可以引用非抽象子类的对象。Employee m=new Manager();//Employee是抽象类
m.print();
因为抽象类不能实例化,所以抽象类变量引用的必然是具体子类(只要编译通过就说明没问题,子类可以实例化),可以用抽象类变量实现多态性作用域:
public:可以被任意类使用
private:只能被定义它的类使用,对于子类也适用,子类不能访问超类的私有域
默认:包作用域,这个部分的类、方法、变量可以被同一个包下面的所有方法访问
protected:对本包和子类可见
private设置以后,其他类包括子类都无法调用,如果想要子类能够访问超类中的方法或者域,声明为protected
public class Employee {
private String name;
public void print(){System.out.println("im a Employee");}
}
class Manager extends Employee{
public void print(){
System.out.println("im a manager");
System.out.println(name);//name不可见
}
}
将name从private改为protected,就可以了但是protected也对本包可见,所以:
Employee m=new Employee();
System.out.println(m.name);
这是有点奇怪的,感觉protected不应该设置这么宽的权限下面将继承层次里的权限及方法再过一遍:
public class Employee {
public void print(){System.out.println("im a Employee");}
public String get(){
return "Employee";
}
}
class Manager extends Employee{
public void print(){
System.out.println(get());
}
}
这是可以的,因为Manager继承了父类的public方法public class Employee {
public void print(){System.out.println("im a Employee");}
public String get(){
return "Employee";
}
}
class Manager extends Employee{
public void print(){
System.out.println(get());
}
public String get(){
return "Manager";
}
}
重写了get()方法,动态绑定可以判断用子类还是父类的方法,public void print(){
System.out.println(get());//重写以后,不加任何说明,当然是调用自己的方法
System.out.println(super.get());//调用父类的方法要加super
}
class Manager extends Employee{
public void print(){
System.out.println(get());
System.out.println(super.get());
}
}
只有父类有get()方法,子类没有,这时get()和super.get()都是可以的,都调用父类的方法如果将父类的get()方法声明为private,两者都不可以——The method get() from the type Employee is not visible
将private改为protected,又都可以了
总的来说,权限问题是编程语言(至少是C++、Java)里最基本、简单的问题了,但可能是我脑子不会转弯,对xx可见总是钻牛角尖
因为,不管是private还是public方法、域,最终调用它的肯定都是它的对象啊——objectName.func,什么叫可见不可见
public void print(){
gg();
}
private void gg(){
System.out.println("gg");
}
这里public方法调用了一个private方法,如果在main函数里调用print()函数就相当于:main——print()——gg(),最终不是还是把一个private的结果返回给别人(main)了么,但是这不叫gg()对main可见?现在,我是这么理解的,你得看调用的函数的外面一层方法(从纯代码上,用眼睛看),
public static void main(String[] args){
Employee m2=new Manager();
m2.print();
//dem2.gg();
}
在main里,我们可以直接看出,有print()函数,但是我们看不到print()里面的具体实现(里面还有个gg(),且gg()可能涉及保密操作和数据),对于对象m2的调用者(另一个类里的static main)来说,他只能调用print,却不能自己操作gg,这就叫做不可见,最后,我们再来钻一钻牛角尖:
public class Employee {
private void print(){
System.out.println("im a Employee");
}
}
class Manager extends Employee{
public void print(){
System.out.println("im a Manager");
}
}
父类里重写了print,并将可见性改为public,看它还能不能允许动态绑定Employee m=new Manager();
m.print();
Manager m2=new Manager();
m2.print();
答案是声明为Employee的m,即使本身是一个Manager,在编译时也会因为Employee.print()没有可见性而报错《Java核心技术》写道,为了防止受保护机制的滥用,Manager类中的方法只能访问Manager对象中的protected域,而不能访问其他Employee类中的
public class Employee {
protected int i=2;
}
class Manager extends Employee{
public void print(Employee e){
System.out.println(i);
System.out.println(e.i);
}
}
这样的结果是两者都是可以的,这个e属于不属于Manager类,怎么就可以在Manager类中访问了呢,原来是前面所说的protected还附带了一个包可见性,所以即使不是Manager,其他类也是可以访问的(所以我前面觉得这玩意设置不太合理)现在我把Manager的代码放到另外一个包中,果然e.i就不可见了
说到这你以为问题结束了?问题是越挖越多的,我又想到了另一个bug的场景
public class Employee {
private int i=2;
public void get(){
System.out.println(i);
}
}
class Ceo extends Employee{
private int i=4;
}
我们一般只会覆盖方法,那域可以覆盖么,答案是可以!这种情况下,Employee和Ceo的对象调用get(),输出的都是2public class Employee {
private int i=2;
public void get(){
System.out.println(i);
}
}
class Ceo extends Employee{
private int i=4;
public void get(){
System.out.println(i);
}
}
但是我给Ceo也加上get()方法,厉害了,这次输出4了,居然在继承层次里可以同时存在两个同样的域由于Employee的i是private,我在Ceo里自然不能直接访问,我将它改为protected
public class Employee {
protected int i=2;
public void get(){
System.out.println(i);
}
}
class Ceo extends Employee{
private int i=4;
public void get(){
System.out.println(super.i+" "+i+" "+this.i);输出 2 4 4
}
}
1是,从protected到private没给我报“可见性下降的错”,2是原来域和方法是一样的,也可以用super访问父类的域,只要有可见性,但是我又试着将两个方法分别改成protected和private,他就告诉我“Cannot reduce the visibility of the inherited method from Employee”了更好玩的来了,既然出了个"可以下降域的可见性的bug",那动态绑定的时候咋办?
Employee ee=new Ceo();
System.out.println(ee.i);
这里本身ee的i是protected,但是Ceo的是private,根据ee的声明类型,应该可以输出,但是根据动态绑定。。。。会报错?答案是它在Ceo里“看不见”i,它就往上找,输出了Employee的i=2