Java基础 之三 继承

2021-03-15 16:38

阅读:656

标签:result   ble   报告   字符串   值类型   manage   point   name   空间   

1.子类

1) 定义子类

//假设父类(也叫做超类)是Employee类,用extends来表示继承
public class Manager extends Employee{
    //域和方法
    private double bonus;	//子类的域
    ...
    private void setBonus(double bonus){	//子类的方法
        this.bonus = bonus;
    }
}
//子类继承了父类的非private的域和方法,而父类对象不能使用子类具有域和方法
//通常把通用的方法放在父类中,具有特殊用途的方法放在子类中

2) 覆盖(重写)方法 super

//在基础之二中 说明了重载和重写的区别
//父类中的一些方法可能并不适合子类的对象使用,子类就可以重写父类的方法
public double getSalary(){	//重写父类的方法
    //父类中有一个private的salary域,子类不能直接访问,只能通过父类的getSalary方法获取
    //使用super关键字访问父类的方法
    double baseSalary = super.getSalary();
    return baseSalary+binus;
}
//注意 只有方法才会被重写,属性不会!

3) 子类构造器

//子类构造器
public Manager(String name,String salary,int year,int month,int day){
    super(name,salary,year,month,day);
    bonus = 0;
}
//这里super是调用父类的含有name、salary、year、month、day
//由于子类自动继承超类的域,如果子类构造器没有显示调用超类的构造器,则将自动调用超类默认(无参)构造器
//如果超类没有不带参数的构造器,并且在子类的构造器中没有显式地调用超类的其他构造器,则会编译报错

4) 父类 子类的引用说明

Employee e;
//虽然e被声明为Employee类型,但是实际上e既可以引用Employee类型的对象,也可以引用Manager类型的对象
//1.e引用Employee类型的对象
	Employee e = new Employee();
	e.getSalary();	//此处调用的是Employee对象的方法

//2.e引用Manager类型的对象
	Employee e = new Manager();
	e.getSalary();	//此处调用的是Manger对象的方法
//jvm的知识
Employee e = new Manager();
//左边的Employee称为静态类型,右边的Manager成为实际类型
//静态类型编译器可知
//实际类型变化的结果再运行期才确定,编译时并不知道对象的实际类型是什么
//一个口决:成员变量,静态方法看左边;非静态方法:编译看左边,运行看右边
//前提是子类重写了父类方法,否则调用e无法调用子类自己独有的方法

//成员变量,静态方法看左边(静态类型):如父子都有num,值不一样,则值为静态类型对应的值
//非静态方法,编译看静态类型,运行看动态类型

2.多态

//is-a规则,表明子类的每个对象也是超类的对象。
//因此,将经理的都是雇员,但是雇员不都是经理
//is-a规则的另一种表述法是置换法则,它表明程序中出现超类对象的任何地方都可以用子类对象置换
//例如,可以将一个子类对象赋给超类变量
Employee e;
e = new Employee(...);
e = new Manager(...); 
e.setBonus();//错误,此方法是Manager类中的,不是Employee的方法,所以不能调用
//在Java中,对象变量是多态的。
//一个Employee变量既可以引用一个Employee类对象,也可以引用一个Employee类的任何一个子类对象。
//但是不能将一个超类引用赋给子类变量。
Manager m = new Employee();//错误,不是所有的雇员都是经理

3.方法调用过程

假设调用x.f(args),隐式参数x声明为类C的一个对象。C x = new C(); 

1) 方法f可能存在重载的方法,如f(int)、f(String)。编译器将会一一列举所有C类中名为f的方法和其超类中访问属性为public且名为f的方法

2) 然后,编译器查看调用方法时提供的参数类型。如果在所有名为f的方法中存在一个与参数类型完全匹配,就选择这个方法。这个过程称为重载解析。加入是x.f("hello"),编译器将会挑选f(String)。这个过程允许类型转换,所以可能很复杂。若编译器没找到与参数类型匹配的方法,或者发现经过类型转换后有多个方法与之匹配,就会报告一个错误。

至此,编译器以及获得需要调用的方法名字和类型参数。

注:方法名字与参数列表称为方法签名。如果子类中定义了一个与超类签名相同的方法,那么子类中的方法就覆盖了超类中这个相同签名的方法。

但是返回类型不是签名的一部分,所以在覆盖方法时,允许子类方法的返回类型是原方法返回类型的子类型。

3)  如果是private方法、static方法、final方法或者构造器,那么编译器将可以准确地知道应该调用哪个方法,我们将这中调用方式称为静态绑定。与之对应,调用方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定。

4) 当程序运行,并且采用动态绑定调用方法时,虚拟机一定调用与x所引用对象的实际类型最合适的哪个类的方法。假设x的实际类型是D,即C x = new D()。他是C类的子类。如果D定义了方法f(String),就直接调用它;否则,将在D类的超类中寻找f(String),以此类推。
    
总结:
    //如果f()不是private、static、final类型的方法
    1.zi x = new zi(); 
		x.f("hello")会找本类中的f(String)方法,找不到再找父类继承来的public f(String)方法
    2.fu x = new zi();//zi是fu的子类
		x.f("hello")会更具实际类型(zi)找f(String),如果没有则找父类的f(String)并调用
    //如果f()是 static、final类型的方法
    1.zi x = new zi(); 
		x.f("hello")会找本类中的f(String)方法,找不到就报错
    2.fu x = new zi();
		x.f("hello")会找静态类型(fu)中的f(String)方法,找不到就报错
     //如果f()是private方法
    1.zi x = new zi(); 
		x.f("hello")会找本类中的f(String)方法,找不到就报错    
    2.fu x = new zi();
		不管f()是父类的方法还是子类的方法,x.f("hello")直接报错,
     

注意:在覆盖一个方法时,子类方法不能低于超类方法的可见性。特别是如果超类方法是public,子类方法一定要声明为public。

4.阻值继承:final类和方法

//类上加final关键词就不能进行继承
public fina class Executive extends Manager{
    ...//此类无子类
}

//方法上加上final则子类覆盖不能
public final String fun(){	//此方法子类不能覆盖
    ...
}

//如果将一个类声明为final 只有其中的方法自动成为final 而不包括域

5.强制类型转换

//在基础1中讲了数值类型的
//将一个子类的引用赋值给父类的变量,编译器允许
Employee e = new Manager();
//但是将一个超类的引用赋值给子类变量,需要进行强制类型转换,才能通过运行时编译
//但是前提是:此父类对象为子类对象强转的结果 例如:
Father father = (Father)son;
//当这种情况时,可以用instanceof判断是否是子类类型(实际)然后强转回去
  if(father instanceof Son)
     Son son =(Son)father;
//除此之外,不行。
/*
	往深了讲。子类强制转换为父类对象时,并没有实际丢失它原有内存空间(比父类多的那些部分)只是暂时不可访问。所以能再转回来。
  另:父类对象可接受任何子类对象--此时发生自动转型--》转为父类类型--》所以能够再转回来。用instanceof 是防止错误的一种方式。
*/
boolean result = obj instanceof Class
/* 其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。*/

6.抽象类

//抽象类是对事物的高层次的抽象,按照逻辑来说只需要提供这个类别应该具有的属性和功能而不需要实现
//抽象类还可以包含具体数据与具体方法,并不是只可以有抽象方法
public abstract class Person{
    private String name;
    public Person(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }
    public abstract  String getDescription();
}
//类中即使不包含抽象方法,也可以将类声明为抽象类
//子类不一定要实现抽象父类的abstract方法
//抽象类不能被实例化,但是抽象类的变量可以引用子类的实例
Person p = new Student();

7.Object类

java中所有类都继承自object类
在Java中,只有基本类型不是对象,例如数值、字符和布尔类型的值都不是对象
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了Object类

1) equals方法

在Object类中,这个方法判断两个对象是否具有相同的引用,因为Object中equals方法就是写的==
equals和==的比较
1.若变量是数值类型,==比较值是否相等;若变量是引用类型,==比较这两个变量引用的对象的地址是否相同,即是否指向同一个对象
2.equals没有重写之前和==是一样的,重写之后看你自己的逻辑,一般是比两个对象的内容是否相等
如:String a ="hello";
	String b = "world";
	a.equals(b);//false
这里String类已经重写了equals方法,比较的是内容。

java规范equals应该具有的一些性质

1)自反性:对于任何非空引用x,x.equals(x)应该返回true;
2)对称性:对于任何引用x、y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true
3)传递性:对于任何引用x、y、z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true
4)对于任何非空引用x,x.equals(null)应该返回false

2) hashCode方法

//Object类
public native int hashCode();

/*
	散列码hash code是由对象导出的一个整形值散列码是没有规律的。如果x,y是两个不同的对象,
	x.hashCode() y.hashCode() 基本不会相同
	每个对象都有一个默认的散列码,值为对象存储地址(这个地址是在哈希表中的存储地址,不是物理地址)
	通过散列的规则,比如有5个位置来存储
	1	2	3	4	5
	|	|	|	|	|
	q	w	e	r	t
	a	s	d	f	g
	如上是散列之后的哈表,1-5就是哈希值,就是在哈希表中的存储地址
	q和a的哈希值相等,此时就不能根据hashCode方法比较他们是否相等,用equals来比较
	为什么不直接用equals?还要用hashCode?
	因为,假如有1w个对象,equals你要一个一个比较,效率太低;而先用hashCode确定他的存储区域,比如1,再在1中比较所有的对象,肯定小于1w个,大大提高了效率。
	也就是hash code相等对象不一定相等,equals为true,一定相等
*/

3) toString方法

//重写Object类中的toString方法
public String toString(){
    return getClass().getName()
        +"[name"+name
        +",salary="+salary
        +",hireDay="+hireDay
        +"]"
}
//如果x是任意一个对象,
System.out.println(x);
//println方法直接调用x.toString
//如果对象的类中没有重写toString方法,则返回的是对象所属类的类名和散列码

8.泛型数组列表

//使用泛型数组列表可以动态更改数组
Employee[] staff = new Employee[1000];	//数组容量定死了
//1.构建数组列表
ArrayList staff = new ArrayList();
//java SE7 之后可以省区右边的参数类型
ArrayList staff = new ArrayList();

//2.添加
staff.add(new Employee());
staff.add(n,e); 	//在索引为n出添加元素e
//使用ensureCapacity()方法,预先设置Arraylist的大小,这样可以大大提高初始化速度。 
staff.ensureCapacity(100);
//还可以初始化的时候初始容量
ArrayList staff = new ArrayList(100);

//3.获取数组列表当前元素数量
staff.size();
//若确认列表大小不发生变化,调用trimToSize方法,他将存储区域大小调整为当前元素数量所需要的存储空间数目。垃圾回收器将回收多余空间。

//4.get set
staff.get(i);//获取索引为i的元素
staff.set(i,harry);//替换索引为i的元素

//5.toArray
Employee[] a = new Employee[staff.size()];	
staff.toArray[a];	//list 转换为数组

//6.移除
Employee e = staff.remove(n);

9.对象包装器与自动装箱

所有基本类型都有一个与之对应的类,这些类称为包装器。
Integer、Long、Float、Double、Short、Byte、Character、Void 和 Boolean
前6个类派生于公共的超类Number
同时,对象包装器类是final,不允许改变包装在里面的值,也不能定义子类

1) 自动装箱

ArrayList list = new ArrayList();
list.add(3);
//上面的调用自动转换为
list.add(Integer.valueOf(3));
//这种操作被称为自动装箱

2) 自动拆箱

//将一个Integer对象赋值给一个int时,会自动拆箱
int n = list.get(i);
//上面的语句会自动转换成
int n = list.get(i).intValue();

3) 例子

//1.算术表达式自动装箱拆箱
Integer n = 3;	//自动装箱
n++;	//自动拆箱

//2.变量引用null
Interger n  = null;
System.out.println(2*n);	//报空指针异常 NullPointException

//3.混合使用
Integer n = 1;	//自动装箱
Double x = 2.0;	//自动装箱
Double y = n;	//n先拆箱为int,再转换提升为double,再自动装箱为Double

4) 一些方法

//将一个数字字符串转换为数值
int x = Integer.parseInt(s);

10.可变参数数量的方法

public class PrintStram{
	public PrintStram printf(String fmt,Object...args){
		return format(fmt,args);
	}
}
//这里的省略号...是Java代码的一部分,它表明这个方法可以接收任意数量的对象

11.枚举类

/*
  1.定义,这是一个类,刚好有4个实例,尽量不要构造新对象
	所以,比较两个枚举类型的值不需要用equals,而直接使用“==”就可以
	还可以添加构造器,方法,域
*/
public enum Size{
    SMALzL("S"),MEDIUM("L"),LARGE("L"),EXTRA_LARGE("XL")
};
Size a = Size.SMALL;

//2.所有枚举类都是Enum的子类,继承了许多方法,如toString方法返回枚举常量名
Size.SMALL.toString(); //返回字符串"SMALL"

//3.toString的逆方法是valueOf
Size s = Enum.valueOf(Size.class,"SMALL");//将s设置成Size.SMALL

//4.所有枚举类型都有一个静态的values方法,他将返回一个包含全部枚举值的数组
Size[] values = Size.values();

//5.ordinal方法返回enum声明中枚举常量的位置,从0开始计数。
Size.MEDIUM.ordinal();	//返回1

Java基础 之三 继承

标签:result   ble   报告   字符串   值类型   manage   point   name   空间   

原文地址:https://www.cnblogs.com/tyycdc/p/14000785.html


评论


亲,登录后才可以留言!