一.继承
继承:将多个类的共性内容抽取在一个独立的类中,然后独立的类和这些类产生一种“继承”关系. extends
(private私有的变量不能被直接继承,子类无法直接访问,可以通过公共方法间接访问)
Java提供的"继承" 特征 的好处:
1)提高了代码的复用性
2)提高了的代码的维护性
3)让类与类之间产生关系 ----- 是 “多态的前提条件”
Java开发原则:
开闭原则(经常使用到: 对修改关闭,对扩展开放)
迪米特原则
接口分离原则
依赖注入原则
…
开发中,设计原则: 都要遵循 ,"低耦合,高内聚"
耦合:类与类的产生的关系
低耦合:这种关系越少越好! “耦合”----只能避免,不能彻底解决!(Spring框架:IOC 控制反转)
内聚:执行某件事情的能力
高内聚:在一个类中能够完成的事情,不要使用多个类完成事情!
注意事项:
-
子类继承父类,只能继承父类非私有的成员,私有的成员,外界不能访问的,只能间接通过
公共方法访问!子类中的功能:一般都是一些子类的特有功能
-
不要为了使用部分功能而使用继承!
如果A类是B类的一种,或者B类是A类的一种,这个时候使用继承;
继承的体现出的是一种“is a”的关系:xxx是xxx的一种
//父类
class Father{
private int num = 20 ; // num 在 Father 中是 private 访问控制
public int num2 = 30 ;
public void show(){
System.out.println( num) ;
function() ;
}
private void function(){
System.out.println("function father...") ;
}
}
//子类
class Son extends Father{
public void playGame(){
System.out.println("玩游戏...") ;
}
}
//测试类
class ExtendsDemo2{
public static void main(String[] args){
//创建子类对象
Son s = new Son() ;
//System.out.println(s.num) ;
System.out.println(s.num2) ;
s.show() ;
//s.function() ;
s.playGame() ;
}
}
演示代码:
//使用继承的思想:将Student类和Worker类的共性共性内容:抽取到一个Person类中
class Person{ //"父类"
private String name ;
private int age ;
public Person(){}
//构造方法赋值
public Person(String name,int age){
this.name = name ;
this.age = age ;
}
//setXXX(xxx)
public void setName(String name){
this.name = name ;
}
public void setAge(int age){
this.age = age ;
}
public String getName(){
return name ;
}
public int getAge(){
return age ;
}
public void eat(){
System.out.println("饿了就需要eat") ;
}
public void sleep(){
System.out.println("累了就需要sleep") ;
}
}
//学生类和工人类 (子类)
class Student extends Person{}
class Worker extends Person{}
//测试类
class ExtendsDemo{
public static void main(String[] args){
//没有使用继承之前
//创建一个学生类
/*
Student s = new Student("高圆圆",41) ;
System.out.println(s.getName()+"---"+s.getAge()) ;
s.eat() ;
s.sleep() ;
System.out.println("---------------------------") ;
//创建工人类
Worker w = new Worker("程序员",23) ;
System.out.println(w.getName()+"---"+w.getAge()) ;
w.eat() ;
w.sleep() ;
*/
//使用继承了
//方式:通过无参+setXXX()
Student s = new Student() ;
s.setName("高圆圆") ;
s.setAge(41) ;
System.out.println(s.getName()+"---"+s.getAge()) ;
s.eat() ;
s.sleep() ;
Worker w = new Worker() ;
w.setName("雷军") ;
w.setAge(50) ;
System.out.println(w.getName()+"---"+w.getAge()) ;
w.eat() ;
w.sleep() ;
}
}
继承的特点:
- 在Java中,类与类之间的继承关系,只支持单继承,不支持多继承!
在别的语言中,可能支持多继承,class 子类名 extends 父类名1,父类名2,…{} - 在Java中,类与类之间虽然不能 多继承,但是可以 多层继承!
//定义两个父类
class GrandFather{
public void function(){
System.out.println("我是爷爷...") ;
}
}
class Father extends GrandFather{
public void method(){
System.out.println("我是老子...") ;
}
}
/*
class Month{
}
*/
//子类
//class Son extends Father,Monther{} 多继承:Java语言中:类与类之间不支持
//正常的写法:单继承
class Son extends Father{
public void show(){
System.out.println("我是仔仔...") ;
}
}
//测试类
class ExtendsDemo{
public static void main(String[] args){
//创建子类对象
Son son = new Son() ;
son.show() ;
son.method() ;
son.function() ;
}
}
继承中,一个类的组成
1)成员变量
2)构造方法
3)成员方法
在继承中,成员变量访问:遵循一个原则: "就近原则"
- 子类继承父类,如果子类的成员变量名称和父类的成员变量名称不一致的情况: 比较简单,分别访问即可
- 子类继承父类,如果子类的成员变量名称和父类的成员变量名称一致的情况:
a)现在本类的局部位置找,有没有这个变量,如果有就直接使用
b)如果本类的局部位置没有,在本类的成员位置中找,如果有,就使用
c)如果本类的成员位置没有,在它的父类的成员位置找,如果有,就使用
d)父类的成员位置都没有这个变量,压根不存在,就报错!
//父类
class Fu{
int num = 100 ;//父类的成员变量
}
//子类
class Zi extends Fu{
//int num2 = 200 ;
int num = 200 ; //子类的成员变量
//成员方法
public void show(){
int num = 50 ; //就近原则
System.out.println(num) ;
}
}
//测试类
class ExtendsDemo2{
public static void main(String[] args){
//创建子类对象
Zi zi = new Zi() ;
//System.out.println(zi.num) ;
//System.out.println(zi.num2) ;
zi.show() ;
}
}
输出: 50
继承关系中,构造方法的访问
子类继承父类,不能够继承构造方法,但是可以通过super()间接访问父类的构造方法,让父类先进行初始化
子类的所有构造方法都默认访问父类的无参构造方法:子类的每一个构造方法的第一句话:super();
super:代表的是否父类的空间标识(代表父类的对象的地址值引用)
为什么要去让父类初始化呢?
因为子类继承父类,可能会使用到父类的数据!所有必须先让父类初始化,然后子类在初始化(分层初始化)
在写父类的时候:在继承中由于存在默认的访问问题,建议永远给出父类的无参构造方法
为什么必须在第一行?
super();必须让非父类先初始化,防止这个类被初始化多次(构造方法)
//父类
class Fu{
public Fu(){
System.out.println("这是父类的无参构造方法") ;
}
public Fu(String name){
System.out.println("这是父类的有参构造方法") ;
}
}
//子类
class Zi extends Fu{
public Zi(){
//隐藏,可以不写
//super() ;
System.out.println("这是子类的无参构造方法...") ;
//super() ; //要显示 super,必须在第一句话
}
public Zi(String name){
//super() ;
System.out.println("这是子类的有参构造方法") ;
}
}
//测试类
class ExtendsDemo3{
public static void main(String[] args){
//创建子类对象
Zi zi = new Zi() ; //无参构造方法
System.out.println("------------------") ;
Zi zi2 = new Zi("hello") ; //子类有参构造方法
}
}
问题:
子类继承父类,那么如果父类的无参构造方法没有,子类的构造方法会出现什么问题,以及如果存在问题,如何解决?
子类的所有构造方法都会报错:存在默认访问:子类的所有构造方法都默认访问父类的无参(第一句:super())
解决办法:
- 手动给出父类的无参构造方法
- 在子类的所有构造方法中,间接通过super(xx):访问父类的有参构造方法
- 子类的所有构造方法的一个只要能够让父类初始化即可!(不推荐!)
执行子类无参构造方法,先执行本的有参构造方法:this(xx)
然后在通过本类的有参构造方法 :super(xx):间接访问父类的有参构造,完成父类初始化
//父类
class Fu{
/*
public Fu(){
}
*/
public Fu(String name){
System.out.println("这是父类的有参构造方法") ;
}
}
//子类
class Zi extends Fu{
public Zi(){
//super("随便给") ; //访问父类的有参构造
this("hello") ; //本类的有参构造方法
System.out.println("这是子类的无参构造方法...") ;
}
public Zi(String name){
super("随便给") ;//访问父类的有参构造
System.out.println("这是子类的有参构造方法") ;
}
}
//测试类
class ExtendsDemo4{
public static void main(String[] args){
Zi zi = new Zi() ;
System.out.println("-------------------") ;
Zi zi2 = new Zi("javaee") ;
}
}
this和super的区别
this:代表的本类对象的地址值引用
super:代表的父类空间标识(父类对象的地址值引用)
this.变量:访问的本类的成员变量
this.方法名():访问的本类的成员方法
this()/this(xx):访问本类的无参构造方法/访问的本类的有参构造方法
super.变量名:访问的是父类的成员变量
super.方法名():访问的父类的成员方法
super()/super(xx):访问的父类的无参构造方法/访问的父类的有参构造方法
需求:最终输出的结果:30,20,10
考点:继承关系中,成员变量访问问题 :如果子类继承父类,成员变量名称都一致, 就近原则
需要现在子类的局部位置找,有就使用
没有,在子类的成员位置找,有就使用
没有,在父类的成员位置找,有就使用
//父类
class Fu{
int num = 10 ;
}
//子类
class Zi extends Fu{
int num = 20 ;
public void show(){
int num = 30 ;
//补全代码
System.out.println(num) ; //输出30
System.out.println(this.num) ; //输出20
System.out.println(super.num) ; //输出10
}
}
class Test{
public static void main(String[] args){
Zi zi = new Zi() ;
zi.show() ;
}
}
猫狗案例---->继承版
猫存在属性:姓名,年龄,颜色;行为:吃,睡,玩游戏
狗存在属性:姓名,年龄,颜色;行为:吃,睡,看门
将两个事物的共性内容抽取在一个独立的类中:
动物事物
属性:姓名,年龄,颜色 属性私有化
行为:吃,睡
公共访问方法:setXXX()/getXXX()
猫事物 和狗事物分别继承自动物,
在每一个具体的事物中:提供对应的构造方法…
定义动物类,猫类和狗类,分别测试
//定义一个动物类
class Animal{
//姓名,年龄,颜色 属性私有化
private String name ;
private int age ;
private String animalColor ;
//父类的无参构造方法
public Animal(){
}
//有参构造方法
public Animal(String name,int age,String animalColor){
this.name = name ;
this.age = age ;
this.animalColor = animalColor ;
}
//公共访问方法
public void setName(String name){
this.name = name ;
}
public String getName(){
return name ;
}
public void setAge(int age){
this.age = age ;
}
public int getAge(){
return age ;
}
public void setAnimalColor(String animalColor){
this.animalColor = animalColor ;
}
public String getAnimalColor(){
return animalColor ;
}
//其他成员方法:吃,睡
public void eat(){
System.out.println ("动物饿了就需要吃饭...") ;
}
public void sleep(){
System.out.println ("动物困了都需要休息...") ;
}
}
//猫类 继承自动物类
class Cat extends Animal{
//无参构造方法
public Cat(){
//super() ; //省略
}
//子类的有参构造
public Cat(String name,int age,String animalColor){ //"tom",5,"蓝色"
super(name,age,animalColor) ; //访问父类的有参构造方法
}
//子类的特有功能
public void playGame(){
System.out.println("猫完毛线...") ;
}
}
//狗类
class Dog extends Animal{
//无参构造方法
public Dog(){
//super() ; //省略
}
//子类的有参构造
public Dog(String name,int age,String animalColor){ //"小白",3,"白色"
super(name,age,animalColor) ; //访问父类的有参构造方法
}
//特有功能
public void lookDoor(){
System.out.println("狗可以看家...") ;
}
}
//测试类
class ExtendsTest{
public static void main(String[] args){
//测试狗类
//方式1:无参构造+setXXx()
Dog d = new Dog() ;
d.setName("小白") ;
d.setAge(3) ;
d.setAnimalColor("白色") ;
System.out.println("当前狗的姓名是:"+d.getName()+",年龄是:"+d.getAge()+",颜色是:"+d.getAnimalColor()) ;
d.eat() ;
d.sleep() ;
d.lookDoor() ;
System.out.println("----------------------------") ;
//方式2:有参构造+getXXX()
Dog d2 = new Dog("旺财",5,"黑色") ;
System.out.println("当前狗的姓名是:"+d2.getName()+",年龄是:"+d2.getAge()+",颜色是:"+d2.getAnimalColor()) ;
d2.eat() ;
d2.sleep() ;
d2.lookDoor() ;
//猫类自己 测试
}
}
二. 方法重写
方法重写:在继承中,子类出现了和父类一模一样的方法,子类的方法会将父类的方法覆盖掉! (方法重写/方法复写/方法覆盖…)
方法重载overLoad和方法重写override的区别?
方法重载overLoad:
定义方法的时候,如果多个方法的方法名相同,参数列表不同,与返回值无关
参数列表不同:
参数个数不同
参数类型不同
public boolean compare(int a,int b){}
public boolean compare(float a,float b){}
方法重写override:
子类继承父类,出现了和父类一模一样的方法声明(权限,返回值,方法名,参数列表都相同),方法覆盖
为了将父类的功能覆盖掉,使用自己的功能!
class Phone{
//打电话
public void callPhone(){
System.out.println("手机可以打电话了...");
}
}
//子类:newPhone
class NewPhone extends Phone{
//子类和父类出现一模一样的方法:方法名相同,返回值相同,权限修饰符都一样
//@Override :jdk提供的内置注解 ,可以不写
public void callPhone(){
super.callPhone();//访问父类的成员方法
System.out.println("手机可以看天气预报了") ;
}
public void show(){}
}
//测试类
public class ExtendsDemo {
public static void main(String[] args) {
//创建手机类对象
// Phone p = new Phone() ;
//p.callPhone();
//优化之后:测试
NewPhone newPhone = new NewPhone() ;
newPhone.callPhone();
}
}
北方人:属性:姓名,年龄,性别;行为:爱吃面,爱考公
人:属性:姓名,年龄,性别;行为:睡
北方人和南方人都继承Person
//人类
public class Person {
//姓名
private String name ;
//年龄
private int age ;
//性别
private String gender ;
//alt+ins--->Constructor --->select none
//无参构造方法
public Person() {
}
//有参构造
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
//setXXX(xx)/getXXX()
//alt+ins---->gettter and setter---->属性全部选中---就会自动生成
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
//睡觉
public void sleep(){
//sout--->System.out.println();
System.out.println("人困了就需要休息...");
}
}
//北方人
public class NorthPerson extends Person {
public NorthPerson() {
}
public NorthPerson(String name, int age, String gender) {
super(name, age, gender);
}
public void eat(){
System.out.println("北方人爱吃面") ;
}
public void study(){
System.out.println("北方人爱钻研") ;
}
}
//南方人
public class SourthPerson extends Person {
//alt+insrt--->Constructor:将无参/有参全部给出
public SourthPerson() {
}
public SourthPerson(String name, int age, String gender) {
super(name, age, gender);
}
//特有功能
public void eat(){
System.out.println("南方人爱吃米饭");
}
public void business(){
System.out.println("南方人爱经商...");
}
}
//测试类
public class ExtendsTest {
public static void main(String[] args){
//测试南方人:
//第一种方式:无参构造+setXXX()赋值
SourthPerson sp = new SourthPerson();
//赋值
sp.setName("高圆圆");
sp.setAge(41);
sp.setGender("女");
System.out.println(sp.getName()+"---"+sp.getAge()+"---"+sp.getGender());
sp.sleep();
sp.business();
sp.eat();
System.out.println("----------------------");
//方式2:有参构造直接赋值+getxxx()
SourthPerson sp2 = new SourthPerson("赵又廷",45,"男");
System.out.println(sp2.getName()+"---"+sp2.getAge()+"---"+sp2.getGender());
sp2.sleep();
sp2.business();
sp2.eat();
}
}
三. 继承中成员方法的访问问题
继承中成员方法的访问:
如果子类和父类的成员方法名称不一致,分别访问即可!
子类和父类的成员方法名称一致,
- 子类中存在,访问子类的;
- 如果不存在,访问父类,如果父类没有,报错!
//定义一个父类
class Fu{
public void method(){
System.out.println("method fu...");
}
}
//子类
class Zi extends Fu{
public void show(){
System.out.println("show zi...");
}
public void method(){
System.out.println("method zi...");
}
}
//测试类
public class ExtendsDemo {
public static void main(String[] args) {
//创建子类对象
Zi z = new Zi() ;
z.show();
z.method();
}
}
成员方法的分类:是否存在返回值,是否带参
- 带参的,没有返回值;
- 带参的,有返回值;
- 不带参的,没有返回值;
- 不带参的,有返回值;
根据具体需求然后定义相对应的功能!
参数类型: 基本类型/引用类型
返回值类型: 基本类型/引用类型
如果子类和父类的成员方法名称不一致,分别访问即可!
子类和父类的成员方法名称一致,
子类中存在,访问子类的;
如果不存在,访问父类,如果父类没有,报错!
//定义一个父类
class Father{
//带参的,没有返回值类型
public void show(String str){
System.out.println(str);
}
//带参的,有返回值类型的
public String method(int num){
return "helloworld"+num ;
}
}
//子类
class Son extends Father{
//子类的成员方法
//不带参的,没有返回值的
public void function(){
System.out.println("function son...");
}
//不带参的,有返回值的
public String function2(){
return "JavaEE" ;
}
}
//测试类
public class ExtendsDemo2 {
public static void main(String[] args) {
//创建子类对象
Son s = new Son() ;
s.show("helloworld,javaee");
String str = s.method(100);
System.out.println(str);
s.function();
String str2 = s.function2();
System.out.println(str2);
}
}
子类继承的父类的时候,有时候需要将父类的功能覆盖掉,完成的自己的功能!
但是,有的时候不能覆盖
举例:
有一个父类,父类中有一个方法,show().
定义一个子类,子类将父类的show()覆盖掉,直接将父类的功能覆盖了
现在就不想让父类的功能被覆盖掉,Java提供了一个关键字:final: 状态修饰符 (最终的,无法更改的)
可以修饰类,可以修饰变量,可以修饰方法
class Fu{
public final void show(){
System.out.println("这是绝密文件,任何人不能更改");
}
}
class Zi extends Fu {
/*
public void show() {
System.out.println("这是一堆垃圾...");
}
*/
}
//测试类
public class FinalDemo {
public static void main(String[] args) {
Zi zi = new Zi() ;
zi.show();
}
}
四. final关键字
final:状态修饰符,最终的,无法更改的
- 可以修饰类,该类不能被继承
- 可以修饰变量,此时这个变量是一个常量:自定义常量
此时这个变量只能被赋值一次,再次赋值,报错!(成员变量被final修饰,必须先初始化) - 可以修饰方法,此时这个方法不能被重写!
final的成员位置:public static final int xxx = 值;传统方式定义常量(编译时期常量)
public final Student s = new Student();实例化(创建对象)
变量(运行时期变量)
class Fu2{
//成员变量被final:必须先初始化
public final int num = 100 ;
public final void show(){
System.out.println("show fu2...");
}
}
class Zi2 extends Fu2{
int num2 = 20 ;
/*
public void show(){
System.out.println("show zi2...");
}
*/
}
public class FinalDemo2 {
public static void main(String[] args) {
//创建子类对象
Zi2 zi = new Zi2() ;
System.out.println(zi.num);
//zi.num = 200 ;
zi.show();
}
}
final关键字修饰基本类型/引用类型的区别
当final修饰基本数据类型,基本数据类型的值不能再改变,只能赋值一次!
开发中,定义基本数据类型的常量:
在一个类的成员位置:
public static final int xxx = xx ;
当final修饰引用数据类型,引用数据类型的地址值不能再改变!
class Demo{
//传统定义方式
public static final int s1 = 100 ;
public static final int s2 = 200 ;
public static final int s3 = 300 ;
public static final int s4 = 400 ;
public static final String str1 = "FRONT" ;
public static final String str2 = "BEHIND" ;
public static final String str3 = "LEFT" ;
public static final String str4 = "RIGHT" ;
//常量:引用类型
public static final Student s = new Student() ; //静态实例变量
}
enum Demo2{ //枚举类型(JDK5以后的枚举)
//一堆字符串常量
FRONT,BEHIND,LEFT,RIGHT;
}
//定义学生类
class Student{
int age = 20 ; //成员位置
}
//测试类
public class FinalDemo {
public static void main(String[] args) {
//定义一个变量:
int num = 100 ;
System.out.println(num);//这是一个变量
System.out.println("--------------------");
//定义final修饰的变量
final int num2 ;//final修饰的基本数据类型的变量
num2 = 200 ; //只能赋值一次,常驻内存(已经是常量:自定义常量)
System.out.println(num2);
// num2 = 300 ;
System.out.println("---------------------");
int x = 100 ;
x = 1000 ;
System.out.println(Demo.s1) ;
System.out.println(Demo.s2) ;
System.out.println(Demo.s3) ;
System.out.println(Demo.s4) ;
System.out.println("------------------------");
//创建一个学生类对象;
//改变当前类中的成员变量:依然可以更改,但是此时不能在重新new对象了
final Student s = new Student() ;//com.qf.extends_01.Student@1540e19d
System.out.println(s);
s.age = 30 ;
System.out.println(s.age);
s.age = 25 ;
System.out.println(s.age);
// s = new Student() ; 不能重新进行实例化了(final修饰之后,学生对象的地址值无法改变的!)
}
}