继承
首先什么是继承呢?
继承就是使用一个类的定义,复制并扩展出一个新的类型,那么新的类型可以使用原来类型的属性和功能,也就是不劳而获。
当然新类型也可以扩展出,自己个性化的属性和功能,这就叫长江后浪推前浪,一代更比一代强。
那为什么要使用继承呢?
当然是为了省事呗,这和现实中是一个道理,现实中所谓人生何处不拼爹,父母拥有的儿女都能自动享用,所以java中的继承和现实中都是一个道理,为了省事。
什么是重写呢?
但是子类也不是照单全收父类的属性和方法,如果子类觉得从父类继承来的方法不爽,它完成可以利用重写来推翻父类的定义,自己定义一个新的方法实现。
重写是JAVA当中非常非常重要的一个功能,利用重写可以实现JAVA中最强大的功能之一“动态方法调度”。
接下来让我们进一步的理解继承和重写
概念(什么是继承)
继承就是使用一个类的定义,复制并扩展出一个新的类定义。
子类( Sub class )可以继承父类( Super class )的成员变量及成员方法,同时也可以定义自己的成员变量和成员方法;
Java语言不支持多重继承,-个类只能继承一-个父类 ,但一个父类可以有多个子类。
泛化的过程
泛化:从多个类中,抽取相同部分, 生成父类的过程叫泛化
设计时,从子类泛化出公共父类,再让子类继承父类。
例如:电商网站,用户分为买家和卖家。其中:
-买家信息:姓名,地址,手机号,等级等
-卖家信息:姓名,地址,手机号,粉丝数,服务态度等
另外,买家和卖家拥有的功能,分别是:
-买家功能:查询商品,查询订单,评价商品等
-卖家功能:查询商品,查询订单,修改订单,修改商品等
JAVA的继承的实现和生活中的实现有点颠倒,现实中大家都是先有父母后有孩子,而JAVA在设计时,却是先有子类,后有父类。
接下来我们跟着买家和卖家 设计出两个类的定义
我们可以发现红色标记出来的属性和功能,都是两个类相同的部分,甚至这两个相同的部分,占了类定义的绝大部分,如果今后在代码中实现这两个类,这些重复部分则会重复定义两次,这种重复不但影响了开发的效率,还会给今后的修改带来巨大的隐患。
开发人员以后在开发中,应该时刻记住: 依次定义,处处使用的效果,既能避免重复代码,又能会今后的修改提高遍历。
既然发现了重复,应该怎么避免呢?
这就要采用泛化,把相同的部分提取到一个公共的类中。
扫描二维码关注公众号,回复: 12367016 查看本文章
比如说我们知道买家和卖家,其实在需求中都称为用户,所以我们就需要就定义一个用户类,用来包含两个类公共的部分。
首先用户类中包含,属性和功能:
此时大家观察一下,买家和卖家类中 只剩下各自个性化的部分,由此我们得出结论:
泛化过程就是将 共性的部分,提取到父类中,而子类仅包含个性的部分。
下面,我们在代码中按照泛化的结果,定义三个类
程序中先定义父类,在定义子类
extends关键字
编码时,必须先定义父类,再定义子类,然后才能继承父类
子类可以通过extends关键字 ,继承父类中的属性和功能:
语法: public class子类extends父类{… …}
例如:
- public class Customer extends 'ier
-表示Customer类的对象可以继承得到User类的属性和功能- public class Seller extends User
-表示Seller类的对象可以继承得到User类的属性和功能
首先我们来创建一个 封装买家和卖家公共属性和功能的父类
package day06;
/**
* 封装买家和卖家公共属性和功能的父类
*/
public class User {
/*公共属性*/
String name; //姓名
String address; //地址
String mobile; //手机号
/*公共的功能*/
//1.查询商品的功能
public void searchProduct(){
System.out.println(name+"查询商品...");
}
//2.查询订单
public void searchOrders(){
System.out.println(name+"查询订单...");
}
}
创建封装买家属性和个性功能的子类
package day06;
/**
* 封装买家属性和个性功能的类
*/
public class Customer extends User{
/*买家独有的属性 等级*/
int lv1;
/*买家独有的功能 评价商品*/
public void evaluate(){
System.out.println(name+"评价商品");
}
}
创建封装卖家属性和个性功能的子类
package day06;
/**
* 封装卖家属性和个性功能的类
*/
public class Seller extends User{
/*卖家独有的属性 粉丝的数量*/
int fans;
/*卖家独有的属性 服务态度的评分*/
double service;
/*卖家独有的功能 修改订单*/
public void modifyOrder(){
System.out.println(name+"修改订单... ");
}
/*卖家独有的功能 修改商品*/
public void modifyProduct(){
System.out.println(name+"修改商品...");
}
}
接下来让我们测试子类能否调用父类的公共属性与方法呢?
package day06;
public class Test {
public static void main(String[] args) {
User user = new User();
Customer customer = new Customer();
Seller seller = new Seller();
//给客户取一个名字
customer.name="白富美";
seller.name="店小二";
//客户 白富美查询订单
customer.searchOrders();
//客户 白富美查询商品
customer.searchOrders();
}
}
运行结果:
调用成功,说明customer 和 seller 两个对象都通过继承,实现对User父类的调用
子类之间能否互相使用对方的属性或功能呢?
接下来我们看一下:
我们可以看到,程序报错了,说customer 不是 fans的方法,说明了子类之间,是毫无关系的,各自为栈。
这就好像生活中,你兄弟有钱,和你家没关系,这就是好比亲兄弟明算账。
继承中的传递性
多级继承:将一个子类作为另一个类的父亲。
传递性:子类可以使用自己所有父类的属性和功能。反之则不能使用。
例如:
继承中构造方法
・Java中 ,实例化对象,都会调用构造方法。作用是初始化成员变量的值。
・继承中,实例化子类对象同时,既要初始化父类成员变量的值,又要初始化子类成员变量的值。所以,实例化子类对象时, 一定会先调用父类的构造方法,再调用子类的构造方法。
・子类的构造方法中,第一句话必须要使用super关键字调用父类的构造方法。
・如果子类的构造方法中没有调用父类的构造方法,编译器会自动加入一行代码: super() ,强行首先调用父类无参构造方法。
・如果父类找不到无参构造方法,则编译错误。
接下来让我们看案例:
package day06;
public class Test {
public static void main(String[] args) {
}
}
class IPad{
double screen; //屏幕大小
int battery; //电池容量
//模拟编译器自动生成
IPad(){
this.screen=0.0;
this.battery=0;
}
}
class IPhone extends IPad{
String mobile; //手机号
//模拟编译器自动生成
//因为现在IPhone作为子类出现了,所以在初始化自己的成员变量之前
//必须先调用super()方法,就算你不加编译器也会加上
IPhone(){
super();//表示调用父类的构造方法
this.mobile=null;
}
}
为Ipad和IPhone添加带参构造方法
package day06;
public class Test {
public static void main(String[] args) {
/*继承中的构造方法测试*/
IPad air2=new IPad(9.7,8759);//初始化一个IPad,9.7寸 8759毫安
System.out.println("屏幕:"+air2.screen+" 电池:"+air2.battery);
IPhone if6=new IPhone(5.2,1810,"123456789");
System.out.println("屏幕:"+if6.screen+" 电池:"+if6.battery+" 手机号:"+if6.mobile);
}
}
class IPad{
double screen; //屏幕大小
int battery; //电池容量
public IPad(double screen, int battery) {
this.screen = screen;
this.battery = battery;
}
}
class IPhone extends IPad{
String mobile; //手机号
public IPhone(double screen, int battery, String mobile) {
super(screen, battery);
this.mobile = mobile;
}
}
运行结果:
父类的引用指向子类的对象
一个子类的对象可以向上转型,为父类的类型。即,父类型的变量,可以指向子类的对象。
package day06;
public class Test {
public static void main(String[] args) {
/*继承中的构造方法测试*/
IPad air2=new IPad(9.7,8759);//初始化一个IPad,9.7寸 8759毫安
System.out.println("屏幕:"+air2.screen+" 电池:"+air2.battery);
IPhone if6=new IPhone(5.2,1810,"123456789");
System.out.println("屏幕:"+if6.screen+
" 电池:"+if6.battery+
" 手机号:"+if6.mobile);
/*向上转型测试*/
IPad charger;
charger=air2;//编译正确:air本身加上IPad类型
charger=if6;//编译正确:
charger(charger);
charger(air2);
}
public static void charger(IPad charger){
System.out.println(charger.toString()+"正在充电");
}
}
class IPad{
double screen; //屏幕大小
int battery; //电池容量
public IPad(double screen, int battery) {
this.screen = screen;
this.battery = battery;
}
}
class IPhone extends IPad{
String mobile; //手机号
public IPhone(double screen, int battery, String mobile) {
super(screen, battery);
this.mobile = mobile;
}
}
方法的重写(Override)
子类可以重写继承自父类的方法,即方法名和参数列表与父类的方法相同;但方法的实现不同。
当调用子类对象的重写方法时,运行的只能是子类重写后的新实现。
第一种情况,典型的仅继承
package day06;
public class override {
public static void main(String[] args) {
double fee=480; //实际学费480元
double money=500; //家长给了500元
Parent p = new Parent(); //家长类型的对象
Child1 c1 = new Child1(); //大儿子类型的对象
Child2 c2 = new Child2(); //小儿子类型的对象
//家长的要求
p.pay(money,fee);
//大儿子的做法
c1.pay(money,fee);
//小儿子的做法
c2.pay(money,fee);
}
}
/**
* 封装家长功能的类
*/
class Parent{
/**
* 交学费的方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
}
}
/**
* 封装大儿子的子类
*/
class Child1 extends Parent{
}
/**
* 封装小儿子的类
*/
class Child2 extends Parent{
}
运行结果:大儿子和小儿子仅继承了家长的方法
第二种情况 完全重写
package day06;
public class override {
public static void main(String[] args) {
double fee=480; //实际学费480元
double money=500; //家长给了500元
Parent p = new Parent(); //家长类型的对象
Child1 c1 = new Child1(); //大儿子类型的对象
Child2 c2 = new Child2(); //小儿子类型的对象
//家长的要求
p.pay(money,fee);
//大儿子的做法
c1.pay(money,fee);
//小儿子的做法
c2.pay(money,fee);
}
}
/**
* 封装家长功能的类
*/
class Parent{
/**
* 交学费的方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
}
}
/**
* 封装大儿子的子类
*/
class Child1 extends Parent{
/**
* 大儿子重写的交学费方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
System.out.println("大儿子调皮,没交学费都买了变形金刚"+money+"元的变形金刚");
}
}
/**
* 封装小儿子的类
*/
class Child2 extends Parent{
}
运行结果:大儿子重写了父类的方法
第三种情况:在父类的基础上,再加工。
子类在重写父类方法时,可以通过super关键字调用父类的方法。
package day06;
public class override {
public static void main(String[] args) {
double fee=480; //实际学费480元
double money=500; //家长给了500元
Parent p = new Parent(); //家长类型的对象
Child1 c1 = new Child1(); //大儿子类型的对象
Child2 c2 = new Child2(); //小儿子类型的对象
//家长的要求
p.pay(money,fee);
//大儿子的做法
c1.pay(money,fee);
//小儿子的做法
c2.pay(money,fee);
}
}
/**
* 封装家长功能的类
*/
class Parent{
/**
* 交学费的方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
System.out.println("家长让交:"+fee+"元学费,剩下 "+(money-fee)+"当零花钱");
}
}
/**
* 封装大儿子的子类
*/
class Child1 extends Parent{
/**
* 第二种情况,重写
* 大儿子重写的交学费方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
System.out.println("大儿子调皮,没交学费都买了变形金刚"+money+"元的变形金刚");
}
}
/**
* 封装小儿子的类
*/
class Child2 extends Parent{
/**
* 第三种情况
* 小儿子重写的交学费方法
* @param money 找家长要了多少钱?
* @param fee 实际交的学费
*/
public void pay(double money,double fee){
//第一件事:按照妈妈的要求,先交学费
super.pay(money,fee);//使用super关键字调用了父类的方法
//第二件事:将剩余的钱给了爸爸
System.out.println("小儿子把零花钱给了爸爸");
}
}
运行结果:调用完父类的方法,在重写
动态方法调度
动态方法调度:在运行时,父类变量根据指向子类对象的不同,动态判断调用何种重写方法。
依据:当调用子类对象的重写方法时,运行的只能是子类重写后的新实现
例如: -位爸爸带着两个孩子去参加爸爸去哪儿。爸爸只知道要为两个孩子做饭。而孩子们都得到一张自己的任务卡。任务卡上写着具体要做什么饭。
让我们先创建好需要的类和方法
package day06;
public class override {
public static void main(String[] args) {
double fee=480; //实际学费480元
double money=500; //家长给了500元
Parent p = new Parent(); //家长类型的对象
Child1 c1 = new Child1(); //大儿子类型的对象
Child2 c2 = new Child2(); //小儿子类型的对象
}
}
/**
* 封装家长功能的类
*/
class Parent{
/**
* 爸爸做饭的任务
*/
public void cook(){
System.out.println("爸爸的任务: 做饭");
}
}
/**
* 封装大儿子的子类
*/
class Child1 extends Parent{
/**
* 大儿子任务卡,要做蛋炒饭
* 继承了父类的方法,并重写了它
*/
public void cook(){
System.out.println("帮大儿子做蛋炒饭");
}
}
/**
* 封装小儿子的类
*/
class Child2 extends Parent{
/**
* 小儿子的任务卡是:包饺子
* 继承了父类的方法,并重写了它
*/
public void cook() {
System.out.println("帮小儿子包饺子");
}
}
接下来,这位爸爸怎么帮儿子完成做法的任务呢?
首先爸爸先去帮大儿子完成蛋炒饭
爸爸在去帮小儿子完成包饺子
请大家记住:new的是谁,就调用谁的方法
package day06;
public class override {
public static void main(String[] args) {
double fee=480; //实际学费480元
double money=500; //家长给了500元
Parent p = new Parent(); //家长类型的对象
Child1 c1 = new Child1(); //大儿子类型的对象
Child2 c2 = new Child2(); //小儿子类型的对象
/*动态方法调度*/
Parent dad=null; //定义一个父类型的变量引用
Child1 son1=new Child1();
Child2 son2=new Child2();
dad=son1; //爸爸带着大儿子一起做蛋炒饭
dad.cook();
dad=son2; //爸爸又帮小儿子一起包饺子
dad.cook();
dad=new Parent(); //只有父类型对象自己,可以调用父类中的方法
dad.cook();
}
}
/**
* 封装家长功能的类
*/
class Parent{
/**
* 爸爸做饭的任务
*/
public void cook(){
System.out.println("爸爸的任务: 做饭");
}
}
/**
* 封装大儿子的子类
*/
class Child1 extends Parent{
/**
* 大儿子任务卡,要做蛋炒饭
* 继承了父类的方法,并重写了它
*/
public void cook(){
System.out.println("帮大儿子做蛋炒饭");
}
}
/**
* 封装小儿子的类
*/
class Child2 extends Parent{
/**
* 小儿子的任务卡是:包饺子
* 继承了父类的方法,并重写了它
*/
public void cook() {
System.out.println("帮小儿子包饺子");
}
}
运行结果:
总结一下:
重写和重载的区别(鄙视题)
现在就当你自己是编译器
凡是new的代码都给我先屏蔽掉,因为编译器谁给你创建对象去,所以现在所有的new都是干扰:
然后我们直接看最后一句话就行了:
顺着g方法,我们找到goo类中,g方法的定义:
今后凡是看见重载,先看参数的差别
一个是Super类型的参数,一个是Sub类型的参数
我们发现刚才调用g方法的时候,传入的obj变量是Super类型,显然编译器绑定的是第一个g方法
下面,我们人格分裂一下,在扮演一下JVM,看看运行时程序怎么执行第一个g方法的
运行JVM就要创建对象了,我们把刚抹掉的new,拿回来:
new的是谁就执行谁的方法,我们可以看见这里new的是Sub对象,所以这里obj.f()和new的左边没有半点关系
这道题中父类Super如果没有f方法的定义,就是编译错误,因为如果父类Super没有方法的定义,这就不是一个重写,如果不是重写,父类类型的变量这个变量obj就永远无法访问到子类 类型的非重写方法!
final关键字
1,概述
最终的,final的本意是用来, 控制子类重写的现象.
如果父类的某些方法,不许子类修改. 只需要把父类的方法修饰成最终的
2,特点
可以修饰类,不能被继承
可以修饰方法,不能被重写
可以修饰变量,值不能被修改,是一个常量
3,测试
package cn.tedu.oop;
//测试 final关键字
public class Test1_Final {
public static void main(String[] args) {
Zi zi = new Zi();
zi.eat();//使用了父类的方法
// zi.name = "张三" ;
// System.out.println( zi.name );//继承了父类的属性
System.out.println( Fu.NAME );//继承了父类的属性
}
}
//1, 可以修饰类,不能被继承The type Zi cannot subclass the final class Fu
//final class Fu{
class Fu {
//2, 可以修饰方法,只能被继承不能被重写 Cannot override the final method from Fu
final public void eat() {
System.out.println("吃肉");
}
//3, 可以修饰变量,值不能被修改,是一个常量 The final field Fu.name cannot be assigned
// final String name = "tony" ;
public static final String NAME = "tony" ;//常量的 标准写法
}
class Zi extends Fu{
//重写: 方法声明和父类一样 + 有权限
// @Override
// public void eat() {
// System.out.println("喝汤");
// }
}