一. 面向对象继承
- 什么是继承
只是描述一种关系,跟现实中的父子关系一样;
孩子继承父亲嘛,继承的特性。父亲有的东西,儿子可以直接拿来用!而儿子有的,父亲不一定可以用!
2.怎么来的继承
我们先设计两个类
学生和工人
现在再来老师类,医生类,每个类中都有姓名,年龄字段,这样是不是很麻烦,那么姓名年龄就是他们的共性部分,我们可以把他们的共性部分提取出来单独有一个类来描述
用一个人类来描述,是人都有姓名和年龄。然后让我们的学生类,工人类,跟这个Person类相关联一下;这里的关系就是继承关系。让学生,工人继承人类。然后学生和工人就相当于也拥有了人类的姓名,年龄字段;儿子可以用父亲的嘛
3.提取共性的好处:
1.首先子类里边可以不用写name,age字段,直观地来说,代码变少了;
2.他们之间有一种继承关系(至于这种关系的好处,以后慢慢会遇到)
3.子类可以使用父类的属性-直观地描述了我们现实中的关系。
思考:
因为学生类和工人类都有姓名,年龄。那么可不可以让工人类继承学生类?
不能,因为代码来源于生活!咱们生活中学生和工人有继承关系吗?没有!而工人和学生都有一个共性的部分就是都属于人类!那么可以把共性的属性提取到人类里边,让学生类,工人类都继承这个Person类!
所以注意:千万不要为了获取其他类的功能,或简化代码而继承。必须是类与类之间有所属关系才可以用继承。
我们举个例子:比如说来了一帅哥,手拿iphone7,你现在很想玩一把,那么这个时候你不可能为了了一个iphone7就继承他吧。总不能为了一手机叫人家爹吧。所以注意,必须要像我们现实中有那种所属关系的;
4.什么时候用继承
例:车—-小汽车,大卡车,摩托车。 他们都属于车,才能继承车
用代码来演示一下:我现在方便大家观看,就把类写在一个文件中。你们别这么干就行。
class A{
void a1(){}
void a2(){}
}
class B{
void a1(){}
void b(){}
}
这时候如果B如果继承A 那么B里边可以少写一个a1的方法,他可以用A里面的方法。但是现在B里边有3个方法了 a1 a2 b 可以父类的所有方法!这样就违背了我们定义类的时候的一个特性,我们定义的B里边只有两个方法 不科学!
那么这个时候我们就把共性的方法a1提取出来放在他们的父类中
class A{
void a2(){}
}
class B{
void b(){}
}
class C{
void a1(){}
}
现在是提取出来了,而且A和B里面的代码少了,但是他们有所属关系吗?没有,java中表示继承关系会用一个关键字
5.extends关键字
现在我们要的是A继承C B继承C那么
class A extends C{
void a2(){}
}
class B extends C{
void b(){}
}
这时候A,B和C就有继承关系,相当于C就有了两个孩子。我们来看一下可不可以用父类的成员;
A a = new A();
B b = new B();
a.a1();
b.a1();
可以访问的!
思考一下:先有父类还是先有子类?
现实生活中是先有父亲,再有孩子。Java中呢,也是先有父类哈。虽然我们演示的时候是先有子类然后提取共性到父类。其实我们写代码的时候是先定义父类的,然后在扩展功能定义他的子类强化父类的功能。
比如说:我现在要定义一个生物类,首先先定义所有生物最共性的东西,比如吃,动!现在生物的功能就只能吃,动。后来慢慢有了人类。人类可以吃,动,还能跑,跳。功能多了,这时候就直接添加到生物类的子类。不管以后出现什么新物种都可以添加上去!
6.多继承
多继承就是C extendsA,B 就是一个儿子有多个爹。这是不允许的,所以java中不支持多继承。C++里边就支持多继承,这个不靠谱!演示
请问这时候java运行的是哪个show方法。不确定,所以java不支持这样的,C++在这方面有安全隐患!
Java不支持多继承。但是java支持多层继承。
爷—-父—–子 子继承父,父继承爷。多层继承
所以说:java跟现实中一样啊:一个父亲可以有多个儿子,一个儿子不能有多个父亲;
7.如何使用一个继承体系的功能。比如以后我们查API使用java内库的时候
查阅父类功能,创建子类对象调用方法。
我们先通过父类的共性描述可以了解整个体系中的基本功能,因为父类是最共性的描述。
为什么要创建子类对象呢1.有一些父类可能是抽象类,不能创建对象。2.子类的方法比父类多,可以用自己独有的方法,也可以用所有父类的方法。
接下来我们通过代码来看一下子类继承父类的规律
class Fu{
inti = 1;
}
class Zi extends Fu{
inti = 5;
publicvoid show(){
System.out.println(i);
}
}
new Zi().show();
继承的时候记得要写extends,别只看Fu Zi 通过名字就可以说他们是继承关系吗,一定要看关键字哈
首先调用子类的成员,现在子类中找有没有,如果有就直接用子类的,如果没有就用父类的;
思考:如果现在我就想访问父类的i呢?
这时候就要引入一个关键字
8.super关键字
super:超级的意思,父类也称为超类。所以我们要告诉虚拟机,我们访问的是哪个i
java有个就近原则。我们刚才的i先在离自己最近的去找,肯定是i=5离他最近。在一个类中,比不在一个类中近。
现在我们要访问父类的:
System.out.println(super.i);
加上super就去父类中去找i变量;
如果父类中没有呢?
class Fu{
}
就报错呗!
9.重写(复写)
刚才我们子类父类有相同的变量名,现在我们来看一下有相同的方法名
class Fu{
publicvoid show(){
System.out.println(“我是父类的show”);
}
}
class Zi extends Fu{
publicvoid show(){
System.out.println(“我是子类的show”);
}
}
现在我们用子类调用show的时候基本上跟父类的show没什么关系,他有没有都不影响。相当于我子类的show覆盖了父类的show 这一现象用专业的说法就叫重写。等于把父类的方法重新改写了;
刚才可以用super. 的方式访问父类的变量那现在我们怎么访问父类的show方法呢?
没办法,除非new一个父类对象 用父类对象来调用show
重写的实用场景:
class Phone2010{
publicvoid tell(){
System.out.println(“打电话”);
}
}
2010年的手机,就打个电话完事,现在是2017年,以前的手机已经过时了,以前的tell方法也是过时了,我现在可以打视频电话。那么现在是不是要把这个tell方法改了!可以把原来的Phone2010的tell方法改了吗?不能啊,2010年我们写的代码,现在去改,首先我们不知道它关联了多少类,而且实际开发中,改源码是最不建议干的事情;改一处可能要动很多处。那么现在我们可以用继承的重写来改
class Phone2017 extends Phone2010{
publicvoid tell(){
System.out.println(“打视频电话”);
}
}
这就是实际中的应用。
重写还能扩展功能。比如说:以前的手机只能打电话,我们现在还能聊微信,约陌陌,打游戏等。一样的复写;
class Phone2017 extends Phone2010{
publicvoid tell(){
System.out.println(“打电话”);
System.out.println(“聊微信”);
System.out.println(“约陌陌”);
System.out.println(“打游戏”);
}
}
嘿!我们发现打电话,其实以前的手机也可以打电话,这里父类帮我们做了打电话这个事情,我们可以直接调用父类的方法简化代码!
class Phone2017 extends Phone2010{
publicvoid tell(){
super.tell();
System.out.println(“聊微信”);
System.out.println(“约陌陌”);
System.out.println(“打游戏”);
}
}
他说:这也没简化什么代码啊,但是其实方法中肯定有很多代码,我这就一句,如果有很多代码就可以体会到它的好处了;
10.重写的注意事项:
1.子类重写父类方法时,必须保证子类的权限大于或等于父类权限,否则报错!
- 静态只能重写静态;
11.重载和重写的区别
重载:只看同名函数的参数列表;
重写:子类父类的方法要一模一样(包括返回值类型和参数列表)
重载:
publicclass Person {
public Person(){
}
public Person(String name){
}
publicvoid show(){
}
publicvoid show(inti){
}
}
只要方法名一样,参数列表不一样的,就叫重载;
重写:就是有继承关系,子类重写父类的方法。方法名和参数列表包括返回值都得一模一样
思考下面的运行结果:
publicclass Person {
publicint show(){
return 1;
}
}
class Student extends Person{
publicvoid show(){
}
}
new Student().show();
首先子类复写了父类的show方法没有?没有,因为返回值类型不一样,不是复写。
这个时候Student类中有两个方法,int show()和void show(),那么java虚拟机知道要调用哪一个show方法吗?java虚拟机都不知道,所以必须报错!一个类当中能有两个同名并且同参数列表的方法吗?不行哈
二. 构造函数的继承
直接上代码:
class Fu{
Fu(){
System.out.println(“父类的构造函数”);
}
}
class Zi extends Fu{
Zi(){
System.out.println(“子类的构造函数”);
}
}
这个时候new Zi(); 我们发现不仅调用了子类的构造函数,还执行了父类的构造函数,而且父类的还先执行!为什么会出现这个情况呢?
因为我们的子类构造函数中默认第一行有一条隐式的语句super();
Super()的意思是调用父类的无参构造函数。我们之前的this();就是调用自己的构造函数。其实super和this的用法一致,一个代表自己,一个代表父类;
注意是第一行哈;放下来就不行!跟this();一样都必须是第一行。
如果现在我们把父类的无参构造函数改成有参的构造函数;
Fu(inti){
System.out.println(“父类的构造函数”);
}
这样super();就访问不到了吧,就报错!这时候怎么办,我们可以手动写出调用父类的有参构造函数
Zi(){
super(1);
System.out.println(“子类的构造函数”);
}
接下来,继续变魔术了啊!
class Fu{
public String name;
Fu(String name){
this.name = name;
}
}
class Zi extends Fu{
Zi(String name){
}
}
这时候报错了,所以我们要加上super(“”);随便传一个字符串,我们这是不是也可以给字段赋值,this.name= name;子类继承父类,那么子类相当于也有了name 字段,所以这个this.name= name;是对的哈,有的可能不理解。这时候我们发现父类也做了赋值动作,我们子类没必要再做一次,那么就可以调用父类的这个有参构造函数。诶!我们子类本来就要调用父类的构造函数super(“”);已经调用了嘛,那么直接把name传过去让父类赋值就行了!
Zi(String name){
super(name);
}
跟this(name);差不多嘛!
现在又改一下:
class Fu{
public String name;
Fu(){}
Fu(String name){
this.name = name;
}
}
class Zi extends Fu{
Zi(){}
Zi(String name){
this();
super(name);
}
}
我们发现不管怎么弄都报错,因为this语句和super语句调用构造函数都必须在第一句,这儿就矛盾了,你要第一句,我也要第一句。他们就打架了。那么这时候又怎么办?随便去掉一句。去掉super(name);为什么不报错!我们子类的构造函数必须要访问到父类的构造函数!不然就报错,那这里为什么没报错。因为这里this();调用了自己的无参构造函数,
而无参构造函数里面有一句隐式的super();所以也拿到父类的构造函数!
总结就是:子类的构造函数必须拿到父类的任意一个构造函数!
三. final关键字
1.final:最终的意思
可以修饰类,函数,变量
被final修饰的类不可以被继承
我们之前用继承的时候,其实继承是破坏了封装性的.
class Person{
privatevoid show(){
System.out.println(“Person的show”);
}
}
class Student extends Person{
void show(){
System.out.println(“子类的show”);
}
}
虽然Person把show方法给私有化了,但是我们子类只要权限大于等于它就可以复写这个方法.我们私有化的目的就是不让外界访问,不让外界来修改,可是通过继承复写的方式打破了它的封装性!如果说是java底层的方法,我们随随便便就继承来复写了,那java就玩完了!那么这时候就引入一个关键字
2.final关键字
当一个方法用final修饰的时候,此方法不能被子类复写.
class Person{
publicfinalvoid show(){
System.out.println(“Person的show”);
}
}
想要方法不被复写就用final,跟权限没关系,跟你用private,public都没关系.
3.final修饰类
当final修饰类的时候,这个类是最终的,不能被继承
finalclass Person{
publicfinalvoid show(){
System.out.println(“Person的show”);
}
}
这个没什么可多说的就这么一个用处!实际的时候用得比较少,也不排除会有这个需求!比如说,我定义一个类,我不需要扩展功能了,他有很少的关联.以后一般也不会去改变了.可以用final修饰!(实际开发一般不用)
4.final修饰变量
被final修饰的变量只能赋值一次
finalinti;
i = 1;
说明一赋值就不能改变了!用在什么地方呢?一般用在全局常量.PI圆形里面的PI,跟static一样搭配使用.
publicstaticfinaldoublepi = 3.14;
注意:修饰成员变量的时候,定义的时候就要赋值.修饰局部变量的时候可以先定义,再赋值!
publicstaticfinaldoublepi;
5.总结
final修饰类:不能被继承
final修饰方法:不能被复写
final修饰变量:只能赋值一次(成员变量定义和赋值,局部变量可以先定义再赋值!)
四.Object
咱们说过每一个子类的构造函数,至少要调用super();那么我们平时自己写的一个类也有super();语句,那么这个super();是调用谁的构造函数,我们写的这个类的父类是谁呢?
class Person{
Person(){
// super();
}
}
说明我们每创建一个对象都是有一个父类的.那么他们的父类就是Object
查看API中的Object,通过API描述我们知道他是java中的上帝.我们用到的所有的类,包括自己创建的也好,还是之前的String 数组啊都是这个类的直接或间接子类!
那么Object是所有类的超类,那么他里边的方法应该是所有类都具备的方法.所以类都可以调用他的方法.也可以复写他的方法(有一些用final修饰了的不能被复写,说明这些方法的规矩是定死了的)
来一个假设:如果Object在底层不小心被删除了.那么整个java就崩了,我们不能定义类,不能创建对象.什么事都不能干了!
Object中有一个无参的构造方法.我们每创建一个类都是调用的这个方法!那么思考一下:Object中的构造方法有super()语句吗?没有啊!你啥时候见过上帝还有父亲的?
1.toString()方法
上帝认为:所有对象都具备一个把对象变成字符串的形式!
什么意思呢?这里要提一下
System.out.println(1);
我们以前打印一个int类型的值,打印出来的这个1并不是把int类型打印出来了.他的底层是用这个int类型的1调用了自己的toString方法.把它转成字符串打印出来.所以我们看到的所有打印出来的都是字符串形式.并不是直接把int的值打印出来.
接下来我们打印一个对象
Person p = new Person();
System.out.println(p);
打印出来的这个是个什么东西呢?cn.zhm.asd.Person@46205df9
前面我们能看懂吧.全限定名:包名+类名 @:应该是一个分隔符 46205df9:16进制的地址值,在内存中的位置的标记!咱们创建的所有对象在内存中都有一个地址值,那么这个返回地址值方法也应该在Object里边.hashCode();返回该对象的哈希码值;
它是一个哈希码值,可以理解成底层的一种特殊算法得出来的数值;那么我们打印一下这个哈希值,看是不是跟之前一样!
Person p = new Person();
System.out.println(p);
System.out.println(p.hashCode());
诶!不一样.因为一个是16进制,一个是10进制.
2.进制的转换
这里我提供几个方法:10进制转2进制 转16进制 转8进制
转换得到是字符串!
把其他进制的字符串转换成10进制的int值
Person p = new Person();
String he = Integer.toHexString(p.hashCode());
inti = Integer.parseInt(he,16);//多少进制转就写一个int值
System.out.println(i);
3.复写toString方法
Person p = new Person();
System.out.println(p);
System.out.println(Integer.toHexString(p.hashCode()));
所以地址值就是这个hashCode()在控制;回来我们的toString方法.返回地址值等字符串,这对我们来说,没什么意义!看也看不懂.一般来说:比如人,我们要打印这个人,我们要看到的是他的属性信息,姓名,年龄,等.所以Object中的toString满足不了我们的需求.那么我们可以复写他的方法!
class Person{
private String name;
privateintage;
Person(String name,intage){
this.name = name;
this.age = age;
}
}
Person p = new Person(“张三”,20);
System.out.println(p);
我们打印出来是地址值.现在我们要复写Object中的toString方法
public String toString(){
returnname+”:”+age;
}
注意啊:为什么这里我没有调用p.toString()就可以打印出姓名,年龄!
我们的打印语句System.out.println();打印一个什么东西,就自动调用他的toString方法,就相当于我们不写他也给我们加上.
System.out.println(p.toString());
加上的效果也一样.