JavaSE_day08【继承、初始化】
今日内容
- 继承
- 方法重写
- this关键字
- super关键字
- final修饰符
- 类初始化
- 实例初始化
学习目标
- 能够写出类的继承格式
- 能够说出继承的特点
- 能够说出方法重写的概念以及和重载的区别
- 能够使用this关键字解决问题
- 能够使用super关键字解决问题
- 掌握final的使用
- 能够分析类初始化过程(为面试服务)
- 能够分析实例初始化过程(为面试服务)
第六章 面向对象基础–中(续)
6.4 继承
6.4.1 继承的概述
生活中的继承
-
财产:富二代
-
样貌:如图所示:
- 才华:如图所示:
继承的由来
如图所示:
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
其中,多个类可以称为子类,也叫派生类;多个类抽取出来的这个类称为父类、超类(superclass)或者基类。
继承描述的是事物之间的所属关系,这种关系是:is-a
的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
继承的好处
-
提高代码的复用性。
-
提高代码的扩展性。
-
类与类之间产生了关系,是学习多态的前提。
6.4.2 继承的格式
通过 extends
关键字,可以声明一个子类继承另外一个父类,定义格式如下:
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
继承演示,代码如下:
/*
* 定义动物类Animal,做为父类
*/
class Animal {
// 定义name属性
String name;
// 定义age属性
int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
/*
* 定义猫类Cat 继承 动物类Animal
*/
class Cat extends Animal {
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
System.out.println("抓老鼠");
}
}
/*
* 定义测试类
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
cat.name = "Tom";
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫的catchMouse()方法
cat.catchMouse();
// 调用该猫继承来的eat()方法
cat.eat();
}
}
演示结果:
抓老鼠
2岁的Tom在吃东西
6.4.3 继承的特点一:成员变量
1、父类成员变量私有化(private)
- 父类中的成员,无论是公有(public)还是私有(private),均会被子类继承。
- 子类虽会继承父类私有(private)的成员,但子类不能对继承的私有成员直接进行访问,可通过继承的get/set方法进行访问。如图所示:
代码如下:
/*
* 定义动物类Animal,做为父类
*/
class Animal {
// 定义name属性
private String name;
// 定义age属性
public int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
/*
* 定义猫类Cat 继承 动物类Animal
*/
class Cat extends Animal {
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
System.out.println("抓老鼠");
}
}
/*
* 定义测试类
*/
public class ExtendDemo01 {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
//cat.name = "Tom";// 编译报错
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫的catchMouse()方法
cat.catchMouse();
// 调用该猫继承来的eat()方法
cat.eat();
}
}
如图所示:
eclipse中Debug查看对象成员变量值的情况截图如下:
idea中Debug查看对象成员变量值的情况截图如下:
2、父子类成员变量重名
我们说父类的所有成员变量都会继承到子类中,那么如果子类出现与父类同名的成员变量会怎么样呢?
父类代码:
public class Father {
public int i=1;
private int j=1;
public int k=1;
public int getJ() {
return j;
}
public void setJ(int j) {
this.j = j;
}
}
子类代码:
public class Son extends Father{
public int i=2;
private int j=2;
public int m=2;
}
现在想要在子类Son中声明一个test()方法,并打印这些所有变量的值,该如何实现?
public class Son extends Father{
public int i=2;
private int j=2;
public int m=2;
public void test() {
System.out.println("父类继承的i:" + super.i);
System.out.println("子类的i:" +i);
// System.out.println(super.j);
System.out.println("父类继承的j:" +getJ());
System.out.println("子类的j:" +j);
System.out.println("父类继承的k:" +k);
System.out.println("子类的m:" +m);
}
}
结论:
(1)当父类的成员变量私有化时,在子类中是无法直接访问的,所以是否重名不影响,如果想要访问父类的私有成员变量,只能通过父类的get/set方法访问;
(2)当父类的成员变量非私有时,在子类中可以直接访问,所以如果有重名时,就需要加“super."进行区别。
使用格式:
super.父类成员变量名
以上test()调用结果:
public class TestSon{
public static void main(String[] args){
Son s = new Son();
s.test();
}
}
父类继承的i:1
子类的i:2
父类继承的j:1
子类的j:2
父类继承的k:1
子类的m:2
eclipse中Debug查看对象的成员变量的值截图如下:
idea中Debug查看对象的成员变量的值截图如下:
说明:虽然我们可以区分父子类的重名成员变量,但是实际开发中,我们不建议这么干。
6.4.4 继承的特点二:成员方法
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)
1、方法重写
比如新的手机增加来电显示头像的功能,代码如下:
class Phone {
public void sendMessage(){
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
public void showNum(){
System.out.println("来电显示号码");
}
}
//智能手机类
class NewPhone extends Phone {
//重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
//调用父类已经存在的功能使用super
super.showNum();
//增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class ExtendsDemo06 {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
小贴士:这里重写时,用到super.父类成员方法,表示调用父类的成员方法。
注意事项:
1.@Override:写在方法上面,用来检测是不是有效的正确覆盖重写。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留
2.必须保证父子类之间方法的名称相同,参数列表也相同。
3.子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
注意:如果返回值类型是基本数据类型和void,那么必须是相同
4.子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > 缺省 > private
5.几种特殊的方法不能被重写
- 静态方法不能被重写
- 私有等在子类中不可见的方法不能被重写
- final方法不能被重写
2、方法的重载
(1)同一个类中
class Test{
public int max(int a, int b){
return a > b ? a : b;
}
public double max(double a, double b){
return a > b ? a : b;
}
public int max(int a, int b,int c){
return max(max(a,b),c);
}
}
(2)父子类中
class Father{
public void print(int i){
System.out.println("i = " + i);
}
}
class Son extends Father{
public void print(int i,int j){
System.out.println("i = " + i ",j = " + j);
}
}
对于Son类,相当于有两个print方法,一个形参列表是(int i),一个形参列表(int i, int j)
6.4.5 继承的特点三:构造方法
当类之间产生了关系,其中各类中的构造方法,又产生了哪些影响呢?
首先我们要回忆两个事情,构造方法的定义格式和作用。
-
构造方法的名字是与类名一致的。
所以子类是无法继承父类构造方法的。
-
构造方法的作用是初始化实例变量的,而子类又会从父类继承所有成员变量
所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个
super()
,表示调用父类的实例初始化方法,父类成员变量初始化后,才可以给子类使用。代码如下:
class Fu {
private int n;
Fu(){
System.out.println("Fu()");
}
}
class Zi extends Fu {
Zi(){
// super(),调用父类构造方法
super();
System.out.println("Zi()");
}
}
public class ExtendsDemo07{
public static void main (String args[]){
Zi zi = new Zi();
}
}
输出结果:
Fu()
Zi()
如果父类没有无参构造怎么办?
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//其他成员方法省略
}
public class Student extends Person{
private int score;
}
此时子类代码报错。
解决办法:在子类构造器中,用super(实参列表),显示调用父类的有参构造解决。
public class Student extends Person{
private int score;
public Student(String name, int age) {
super(name, age);
}
public Student(String name, int age, int score) {
super(name, age);
this.score = score;
}
//其他成员方法省略
}
结论:
子类对象实例化过程中必须先完成从父类继承的成员变量的实例初始化,这个过程是通过调用父类的实例初始化方法来完成的。
- super():表示调用父类的无参实例初始化方法,要求父类必须有无参构造,而且可以省略不写;
- super(实参列表):表示调用父类的有参实例初始化方法,当父类没有无参构造时,子类的构造器首行必须写super(实参列表)来明确调用父类的哪个有参构造(其实是调用该构造器对应的实例初始方法)
- super()和super(实参列表)都只能出现在子类构造器的首行
6.4.6 继承的特点四:单继承限制
- Java只支持单继承,不支持多继承。
//一个类只能有一个父类,不可以有多个父类。
class C extends A{} //ok
class C extends A,B... //error
- Java支持多层继承(继承体系)。
class A{}
class B extends A{}
class C extends B{}
顶层父类是Object类。所有的类默认继承Object,作为父类。
-
子类和父类是一种相对的概念。
例如:B类对于A来说是子类,但是对于C类来说是父类
-
一个父类可以同时拥有多个子类
6.4.7 继承练习
练习1
(1)父类Graphic图形
包含属性:name(图形名),属性私有化,不提供无参构造,只提供有参构造
包含求面积getArea():返回0.0
求周长getPerimeter()方法:返回0.0
显示信息getInfo()方法:返回图形名称、面积、周长
(2)子类Circle圆继承Graphic图形
包含属性:radius
重写求面积getArea()和求周长getPerimeter()方法,显示信息getInfo()加半径信息
(3)子类矩形Rectange继承Graphic图形
包含属性:length、width
重写求面积getArea()和求周长getPerimeter()方法
public class Graphic {
private String name;
public Graphic(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getArea() {
return 0.0;
}
public double getPerimeter() {
return 0.0;
}
/*
* this对象:调用当前方法的对象,如果是Graphic对象,那么就会执行Graphic的getArea()和getPerimeter()
* this对象:调用当前方法的对象,如果是Circle对象,那么就会执行Circle的getArea()和getPerimeter()
* this对象:调用当前方法的对象,如果是Rectangle对象,那么就会执行Rectangle的getArea()和getPerimeter()
*/
public String getInfo() {
return "图形:" + name + ",面积:" + getArea() + ",周长:" + getPerimeter();
}
}
public class Circle extends Graphic {
private double radius;
public Circle(String name, double radius) {
super(name);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override//表示这个方法是重写的方法
public double getArea() {
return Math.PI * radius * radius;
}
@Override//表示这个方法是重写的方法
public double getPerimeter() {
return Math.PI * radius * 2;
}
/*@Override//表示这个方法是重写的方法
public String getInfo() {
return super.getInfo() + ",半径:" + radius;
}*/
}
public class Rectangle extends Graphic {
private double length;
private double width;
public Rectangle(String name, double length, double width) {
super(name);
this.length = length;
this.width = width;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
@Override
public double getArea() {
return length*width;
}
@Override
public double getPerimeter() {
return 2*(length + width);
}
}
public class TestGraphicExer3 {
public static void main(String[] args) {
Graphic g = new Graphic("通用图形");
System.out.println(g.getInfo());
Circle c = new Circle("圆", 1.2);
System.out.println(c.getInfo());//调用getInfo()方法的对象是c
Rectangle r = new Rectangle("矩形", 3, 5);
System.out.println(r.getInfo());
}
}
练习2
(1)声明一个银行储蓄卡类,
包含属性:账户,余额
包含取款 public void withdraw(double money)
存款 pubic void save(double money)
获取账户信息: public String getInfo() 可以返回账户和余额
(2)声明一个银行信用卡类,继承储蓄卡类
增加属性:可透支额度,最多可透支金额
重写存款 public void withdraw(double money),可透支
存款 pubic void save(double money),需要恢复可透支额度
(3)在测试类中,分别创建两种卡对象,测试
练习3
1、声明父类:Person类
包含属性:姓名,年龄,性别
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男
2、声明子类:Student类,继承Person类
新增属性:score成绩
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,成绩:89
3、声明子类:Teacher类,继承Person类
新增属性:salary薪资
属性私有化,get/set
包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,薪资:10000
public class Person {
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
public Person() {
super();
}
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 char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男
public String getInfo(){
return "姓名:" + name + ",年龄:" + age +",性别:" + gender;
}
}
public class Student extends Person {
private int score;
public Student() {
}
public Student(String name, int age, char gender, int score) {
setName(name);
setAge(age);
setGender(gender);
this.score = score;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,成绩:89
public String getInfo(){
//方式一:
// return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
//方法二:
return super.getInfo() + ",成绩:" + score;
}
}
public class Teacher extends Person {
private double salary;
public Teacher() {
}
public Teacher(String name, int age, char gender, double salary) {
setName(name);
setAge(age);
setGender(gender);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
//包含getInfo()方法:例如:姓名:张三,年龄:23,性别:男,薪资:10000
public String getInfo(){
return super.getInfo() + ",薪资:" + salary;
}
}
public class TestPersonExer2 {
public static void main(String[] args) {
Person p = new Person("张三", 23, '男');
System.out.println(p.getInfo());
Student s = new Student("陈琦", 25, '男', 89);
System.out.println(s.getInfo());
Teacher t = new Teacher("柴林燕", 18, '女', 11111);
System.out.println(t.getInfo());
}
}
6.5 final关键字
final:最终的,不可更改的,它的用法有:
1、修饰类
表示这个类不能被继承,没有子类
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
2、修饰方法
表示这个方法不能被子类重写
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
3、声明常量
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,即常量,常量名建议使用大写字母。
如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
public class Test{
public static void main(String[] args){
final int MIN_SCORE = 0;
final int MAX_SCORE = 100;
}
}
class Chinese{
public static final String COUNTRY = "中华人民共和国";
private String name;
public Chinese( String name) {
super();
this.name = name;
}
public Chinese() {
super();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//final修饰的没有set方法
public static String getCountry() {
return COUNTRY;
}
}
6.6 成员变量初始化
6.6.1 成员变量初始化方式
1、成员变量有默认值
类别 | 具体类型 | 默认值 |
---|---|---|
基本类型 | 整数(byte,short,int,long) | 0 |
浮点数(float,double) | 0.0 | |
字符(char) | ‘\u0000’ | |
布尔(boolean) | false | |
数据类型 | 默认值 | |
引用类型 | 数组,类,接口 | null |
我们知道类中成员变量都有默认值,但是现在我们要为成员变量赋默认值以外的值,我们该怎么办呢?
2、显式赋值
public class Student{
public static final String COUNTRY = "中华人民共和国";
private static String school = "尚硅谷";
private String name;
private char gender = '男';
}
显式赋值,一般都是赋常量值
3、代码块
如果成员变量想要初始化的值不是一个硬编码的常量值,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值,该怎么办呢?
- 静态初始化块:为静态变量初始化
【修饰符】 class 类名{
static{
静态初始化
}
}
- 实例初始化:为实例变量初始化
【修饰符】 class 类名{
{
实例初始化块
}
}
静态初始化块:在类初始化时由类加载器调用执行,每一个类的静态初始化只会执行一次,早于实例对象的创建。
实例初始化块:每次new实例对象时自动执行,每new一个对象,执行一次。
public class Student{
private static String school;
private String name;
private char gender;
static{
//获取系统属性,这里只是说明school的初始化过程可能比较复杂
school = System.getProperty("school");
if(school==null) {
school = "尚硅谷";
}
}
{
String info = System.getProperty("gender");
if(info==null) {
gender = '男';
}else {
gender = info.charAt(0);
}
}
public static String getSchool() {
return school;
}
public static void setSchool(String school) {
Student.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getGender() {
return gender;
}
public void setGender(char gender) {
this.gender = gender;
}
}
4、构造器
我们发现,显式赋值和实例初始化块为每一个实例对象的实例变量初始化的都是相同的值,那么我们如果想要不同的实例对象初始化为不同的值,怎么办呢?此时我们可以考虑使用构造器,在new对象时由对象的创建者决定为当前对象的实例变量赋什么值。
注意:构造器只为实例变量初始化,不为静态类变量初始化
为实例变量初始化,再new对象时由对象的创建者决定为当前对象的实例变量赋什么值。
5、setter方法
一般用于修改成员变量的值。
6.6.2 类初始化
1、类初始化的目的:为类中的静态变量进行赋值。
2、实际上,类初始化的过程时在调用一个()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化()方法体中。
(1)静态类成员变量的显式赋值语句
(2)静态代码块中的语句
3、整个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。
示例代码1:单个类
public class Test{
public static void main(String[] args){
Father.test();
}
}
class Father{
private static int a = getNumber();//这里调用方法为a变量显式赋值的目的是为了看到这个过程
static{
System.out.println("Father(1)");
}
private static int b = getNumber();
static{
System.out.println("Father(2)");
}
public static int getNumber(){
System.out.println("getNumber()");
return 1;
}
public static void test(){
System.out.println("Father:test()");
}
}
运行结果:
getNumber()
Father(1)
getNumber()
Father(2)
Father:test()
示例代码2:父子类
public class Test{
public static void main(String[] args){
Son.test();
System.out.println("-----------------------------");
Son.test();
}
}
class Father{
private static int a = getNumber();
static{
System.out.println("Father(1)");
}
private static int b = getNumber();
static{
System.out.println("Father(2)");
}
public static int getNumber(){
System.out.println("Father:getNumber()");
return 1;
}
}
class Son extends Father{
private static int a = getNumber();
static{
System.out.println("Son(1)");
}
private static int b = getNumber();
static{
System.out.println("Son(2)");
}
public static int getNumber(){
System.out.println("Son:getNumber()");
return 1;
}
public static void test(){
System.out.println("Son:test()");
}
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son:test()
-----------------------------
Son:test()
结论:
每一个类都有一个类初始化方法()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。一个类,只会初始化一次。
6.6.3 实例初始化
1、实例初始化的目的:为类中非静态成员变量赋值
2、实际上我们编写的代码在编译时,会自动处理代码,整理出一个()的类初始化方法,还会整理出一个或多个的(…)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。
实例初始化方法的方法体,由四部分构成:
(1)super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()
(2)非静态实例变量的显示赋值语句
(3)非静态代码块
(4)对应构造器中的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
3、执行特点:
- 创建对象时,才会执行,
- 调用哪个构造器,就是指定它对应的实例初始化方法
- 创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)
示例代码1:单个类
public class Test{
public static void main(String[] args){
Father f1 = new Father();
Father f2 = new Father("caomingyang");
}
}
class Father{
private int a = getNumber();
private String info;
{
System.out.println("Father(1)");
}
Father(){
System.out.println("Father()无参构造");
}
Father(String info){
this.info = info;
System.out.println("Father(info)有参构造");
}
private int b = getNumber();
{
System.out.println("Father(2)");
}
public int getNumber(){
System.out.println("Father:getNumber()");
return 1;
}
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father()无参构造
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father(info)有参构造
示例代码2:父子类
public class Test{
public static void main(String[] args){
Son s1 = new Son();
System.out.println("-----------------------------");
Son s2 = new Son("caomingyang");
}
}
class Father{
private int a = getNumber();
private String info;
{
System.out.println("Father(1)");
}
Father(){
System.out.println("Father()无参构造");
}
Father(String info){
this.info = info;
System.out.println("Father(info)有参构造");
}
private int b = getNumber();
{
System.out.println("Father(2)");
}
public static int getNumber(){
System.out.println("Father:getNumber()");
return 1;
}
}
class Son extends Father{
private int a = getNumber();
{
System.out.println("Son(1)");
}
private int b = getNumber();
{
System.out.println("Son(2)");
}
public Son(){
System.out.println("Son():无参构造");
}
public Son(String info){
super(info);
System.out.println("Son(info):有参构造");
}
public static int getNumber(){
System.out.println("Son:getNumber()");
return 1;
}
}
运行结果:
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father()无参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son():无参构造
-----------------------------
Father:getNumber()
Father(1)
Father:getNumber()
Father(2)
Father(info)有参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son(info):有参构造
示例代码3:父子类,方法有重写
public class Test{
public static void main(String[] args){
Son s1 = new Son();
System.out.println("-----------------------------");
Son s2 = new Son("caomingyang");
}
}
class Father{
private int a = getNumber();
private String info;
{
System.out.println("Father(1)");
}
Father(){
System.out.println("Father()无参构造");
}
Father(String info){
this.info = info;
System.out.println("Father(info)有参构造");
}
private int b = getNumber();
{
System.out.println("Father(2)");
}
public int getNumber(){
System.out.println("Father:getNumber()");
return 1;
}
}
class Son extends Father{
private int a = getNumber();
{
System.out.println("Son(1)");
}
private int b = getNumber();
{
System.out.println("Son(2)");
}
public Son(){
System.out.println("Son():无参构造");
}
public Son(String info){
super(info);
System.out.println("Son(info):有参构造");
}
public int getNumber(){
System.out.println("Son:getNumber()");
return 1;
}
}
运行结果:
Son:getNumber() //子类重写getNumber()方法,那么创建子类的对象,就是调用子类的getNumber()方法,因为当前对象this是子类的对象。
Father(1)
Son:getNumber()
Father(2)
Father()无参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son():无参构造
-----------------------------
Son:getNumber()
Father(1)
Son:getNumber()
Father(2)
Father(info)有参构造
Son:getNumber()
Son(1)
Son:getNumber()
Son(2)
Son(info):有参构造
6.6.4 类初始化与实例初始化
类初始化肯定优先于实例初始化。
类初始化只做一次。
实例初始化是每次创建对象都要进行。
public class Test{
public static void main(String[] args){
Son s1 = new Son();
System.out.println("----------------------------");
Son s2 = new Son();
}
}
class Father{
static{
System.out.println("Father:static");
}
{
System.out.println("Father:not_static");
}
Father(){
System.out.println("Father()无参构造");
}
}
class Son extends Father{
static{
System.out.println("Son:static");
}
{
System.out.println("Son:not_static");
}
Son(){
System.out.println("Son()无参构造");
}
}
运行结果:
Father:static
Son:static
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造
----------------------------
Father:not_static
Father()无参构造
Son:not_static
Son()无参构造
6.7 this和super关键字
6.7.1 this关键字
1、this的含义
this代表当前对象
2、this使用位置
- this在实例初始化相关的代码块和构造器中:表示正在创建的那个实例对象,即正在new谁,this就代表谁
- this在非静态实例方法中:表示调用该方法的对象,即谁在调用,this就代表谁。
- this不能出现在静态代码块和静态方法中
3、this使用格式
(1)this.成员变量名
- 当方法的局部变量与当前对象的成员变量重名时,就可以在成员变量前面加this.,如果没有重名问题,就可以省略this.
- this.成员变量会先从本类声明的成员变量列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员变量列表中查找
(2)this.成员方法
- 调用当前对象的成员方法时,都可以加"this.",也可以省略,实际开发中都省略
- 当前对象的成员方法,先从本类声明的成员方法列表中查找,如果未找到,会去从父类继承的在子类中仍然可见的成员方法列表中查找
(3)this()或this(实参列表)
-
只能调用本类的其他构造器
-
必须在构造器的首行
-
如果一个类中声明了n个构造器,则最多有 n - 1个构造器中使用了"this(【实参列表】)",否则会发生递归调用死循环
6.7.2 super关键字
1、super的含义
super代表当前对象中从父类的引用的
2、super使用的前提
- 通过super引用父类的xx,都是在子类中仍然可见的
- 不能在静态代码块和静态方法中使用super
3、super的使用格式
(1)super.成员变量
在子类中访问父类的成员变量,特别是当子类的成员变量与父类的成员变量重名时。
public class Person {
private String name;
private int age;
//其他代码省略
}
public class Student extends Person{
private int score;
//其他成员方法省略
}
public class Test{
public static void main(String[] args){
Student stu = new Student();
}
}
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test(30);
}
}
class Father{
int a = 10;
}
class Son extends Father{
int a = 20;
public void test(int a){
System.out.println(super.a);//10
System.out.println(this.a);//20
System.out.println(a);//30
}
}
(2)super.成员方法
在子类中调用父类的成员方法,特别是当子类重写了父类的成员方法时
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
}
}
class Father{
public void method(){
System.out.println("aa");
}
}
class Son extends Father{
public void method(){
System.out.println("bb");
}
public void test(){
method();//bb
this.method();//bb
super.method();//aa
}
}
(3)super()或super(实参列表)
在子类的构造器首行,用于表示调用父类的哪个实例初始化方法
super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
6.7.3 就近原则和追根溯源原则
1、找变量
-
没有super和this
- 在构造器、代码块、方法中如果出现使用某个变量,先查看是否是当前块声明的局部变量,
- 如果不是局部变量,先从当前执行代码的本类去找成员变量
- 如果从当前执行代码的本类中没有找到,会往上找父类的(非private,跨包还不能是缺省的)
-
this :代表当前对象
- 通过this找成员变量时,先从当前执行代码的本类中找,没有的会往上找父类的(非private,跨包还不能是缺省的)。
-
super :代表父类的
- 通过super找成员变量,直接从当前执行代码所在类的父类找
- super()或super(实参列表)只能从直接父类找
- 通过super只能访问父类在子类中可见的(非private,跨包还不能是缺省的)
注意:super和this都不能出现在静态方法和静态代码块中,因为super和this都是存在与对象中的
2、找方法
-
没有super和this
- 先从当前对象(调用方法的对象)的本类找,如果没有,再从直接父类找,再没有,继续往上追溯
-
this
- 先从当前对象(调用方法的对象)的本类找,如果没有,再从父类继承的可见的方法列表中查找
-
super
- 直接从当前对象(调用方法的对象)的父类继承的可见的方法列表中查找
3、找构造器
- this()或this(实参列表):只从本类中,不会再往上追溯
- super()或super(实参列表):只从直接父类找,不会再往上追溯
4、练习
(1)情形1
class Father{
int a = 10;
int b = 11;
}
class Son extends Father{
int a = 20;
public void test(){
//子类与父类的属性同名,子类对象中就有两个a
System.out.println("父类的a:" + super.a);//10 直接从父类局部变量找
System.out.println("子类的a:" + this.a);//20 先从本类成员变量找
System.out.println("子类的a:" + a);//20 先找局部变量找,没有再从本类成员变量找
//子类与父类的属性不同名,是同一个b
System.out.println("b = " + b);//11 先找局部变量找,没有再从本类成员变量找,没有再从父类找
System.out.println("b = " + this.b);//11 先从本类成员变量找,没有再从父类找
System.out.println("b = " + super.b);//11 直接从父类局部变量找
}
public void method(int a){
//子类与父类的属性同名,子类对象中就有两个成员变量a,此时方法中还有一个局部变量a
System.out.println("父类的a:" + super.a);//10 直接从父类局部变量找
System.out.println("子类的a:" + this.a);//20 先从本类成员变量找
System.out.println("局部变量的a:" + a);//30 先找局部变量
}
public void fun(int b){
System.out.println("b = " + b);//13 先找局部变量
System.out.println("b = " + this.b);//11 先从本类成员变量找
System.out.println("b = " + super.b);//11 直接从父类局部变量找
}
}
public class TestInherite2 {
public static void main(String[] args) {
Son son = new Son();
System.out.println(son.a);//20
System.out.println(son.b);//11
son.test();
son.method(30);
son.fun(13);
}
}
(2)情形2
public class Test{
public static void main(String[] args){
Son s = new Son();
System.out.println(s.getNum());//10 没重写,先找本类,没有,找父类
Daughter d = new Daughter();
System.out.println(d.getNum());//20 重写了,先找本类
}
}
class Father{
protected int num = 10;
public int getNum(){
return num;
}
}
class Son extends Father{
private int num = 20;
}
class Daughter extends Father{
private int num = 20;
public int getNum(){
return num;
}
}
(3)情形3
public class Test{
public static void main(String[] args){
Son s = new Son();
s.test();
Daughter d = new Daughter();
d.test();
}
}
class Father{
protected int num = 10;
public int getNum(){
return num;
}
}
class Son extends Father{
private int num = 20;
public void test(){
System.out.println(getNum());//10 本类没有找父类
System.out.println(this.getNum());//10 本类没有找父类
System.out.println(super.getNum());//10 本类没有找父类
}
}
class Daughter extends Father{
private int num = 20;
public int getNum(){
return num;
}
public void test(){
System.out.println(getNum());//20 先找本类
System.out.println(this.getNum());//20 先找本类
System.out.println(super.getNum());//10 直接找父类
}
}