第十二天 面向对象-构造方法&继承&this&super【悟空教程】 第12天 面向对象 构造方法是正常开发中不可或缺的一部分,是语法上必须存在的。是一个具有特殊格式的方法,且有特殊的调用方式。 用来创建实例对象的方法。 给对象的数据进行初始化 Person p = new Person(); 调用构造方法 Person() 只是我们没有写这个方法,而是Java自动补齐了这个方法,是方法就可以传入参数。 创建对象时,可以直接为成员变量赋值,无需再在对象产生后再赋值。 核心功能创建对象 只要方法被调用,就已经创建了对象 为成员变量赋值,在创建对象的同时,通过构造方法的方法体逻辑,为成员变量赋值,此时可以为带参构造 一般定义在其他方法前,成员变量后。 奇怪的方法定义格式:没有返回值类型定义,方法名与类名相同,参数依据需求而定。 public 类名(参数类型 参数1,参数类型 参数2){ //创建对象时要执行的逻辑,经常为成员变量赋值 } 如: public class Person{ private String name; private int age; //两个构造方法为重载关系 public Person() {} //空参的构造方法 public Person(String name,int age) { //带参的构造方法 this.name = name; this.age = age; } } package cn.javahelp; /* * 自定义类型Person 类 * * name age * * 吃 睡 * * 构造方法 * * 核心功能创建对象 只要方法被调用,就已经创建了对象 * 为成员变量赋值,在创建对象的同时,通过构造方法的方法体逻辑,为成员变量赋值,此时可以为带参构造 * * 定义格式 一般定义在其他方法前,成员变量后。 * * public 类名(参数类型 参数1,参数类型 参数2){ * //创建对象时要执行的逻辑,经常为为成员变量赋值 * } * * 如果没有手动给出构造方法 java会为我们自动补齐一个空参的构造方法 * 如果手动给出任意构造方法 java将不会为我们补齐空参构造 * * * public 代表公共的 任何包下都可以访问 * 默认什么都不写 代表默认权限 当前包下可以访问 * private 代表私有 本类中可以访问 * */ public class Person { private String name; private int age; //定义无参构造方法 Person(){ //方法逻辑 System.out.println("我是Person的无参构造"); } //定义带参构造一般都是为了给成员变量赋值 public Person(String name ,int age){ System.out.println("我是Person带参构造给成员变量赋值"); this.name = name; this.age = age; } public Person(int number){ System.out.println("我是Person的带参构造,每次调用我打印n次i love java"); for (int i = 0; i < number; i++) { System.out.println("i love java"); } } public void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("吃"); } 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; } } package cn.javahelp; /* * 测试构造方法constructor */ public class Test { public static void main(String[] args) { Person p = new Person(); System.out.println(p.getName()); System.out.println(p.getAge()); System.out.println("--------------------"); Person p2 = new Person("柳岩",18); System.out.println(p2.getName()); System.out.println(p2.getAge()); System.out.println("--------------------"); Person p3 = new Person(3); System.out.println(p3.getName()); System.out.println(p.getAge()); } } 构造方法是必须存在的,如果没有手动给出构造方法,Java会自动为我们补齐一个空参的构造方法。这个过程我们是看不到的。 如果手动给出了构造方法,java则不会再自动补齐任何构造。 学习阶段一般我们会定义至少两个构造方法:空参构造与带参构造。 空参构造是习惯上存在的。后期一些情况要求我们必须提供空参构造。 带参的构造方法通常是为了给属性赋值的。 构造方法在定义时,有时还会完成一些初始化动作。 同其他方法相同,构造方法也是先定义再使用。而不同的是,其他方法的调用是为了这个方法对应的功能,而构造方法的功能就是创建对象,同时可以为属性赋值。我们在此之前创建对象的方式均是通过构造方法。 new 类型(实际参数); //其结果就是产生了一个该类型的对象,具备一个地址值。 //可以赋值给一个这种类型的变量。 如: Person p = new Person(); //产生了Person的对象 System.out.println(p); //打印结果为一个对象地址值,如0x9a64 System.out.println(p.getName()); //打印结果为null Person p2 = new Person(“AngelaBaby”,18); //产生了一个Person对象 System.out.println(p2); //打印结果为一个对象地址值,如0252b2 System.out.println(p2.getName()); //打印结果为AngelaBaby 构造方法不能被继承。 父类叫person 子类叫student 继承构造方法分不清对象 继承的构造方法是public person 要求构造方法和类名相同 所以和类名冲突了 构造方法的public并不是固定不变的,我们在学习完四种访问权限后,可以使用其他格式修饰构造方法。 (看上面代码) 接口没有构造方法,抽象类具有构造方法。 构造方法是为了在创建的同时直接为属性赋值。 set方法时在创建对象之后,再重新为成员变量赋值,是修改值的过程。 显示初始化会将每个对象都提供相同的初始化值,这样的需求并不常见。 package cn.javahelp2; /* * 自定义类型 Animal 用来辨析 构造方法给成员变量赋值 set方法给成员变量赋值 显示初始化赋值 */ public class Animal { private String name = "柳岩"; private int age; //无参 public Animal(){ } /** * 带参的构造方法 用来给成员变量赋值 * @param name 第一个参数需要一个名字 * @param age 第二个参数需要一个年龄 */ public Animal(String name,int age){ this.name = name; this.age = age; } 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; } } package cn.javahelp2; /* * 测试 * 构造方法给成员变量赋值 set方法给成员变量赋值 显示初始化赋值 * 显示初始化 所有对象一旦创建只要没有对成员变量重新赋值 就是显示初始化的值 一般不这样使用 * * 构造方法为成员变量赋值 当对象一创建就为成员变量赋值了 * set方法赋值 当创建好了一个对象后调用set方法赋值 */ public class Test { public static void main(String[] args) { Animal a = new Animal(); System.out.println(a.getName()); System.out.println(a.getAge()); System.out.println("--------------"); Animal a2 = new Animal("刘柳",20); a2.setName("刘艳"); System.out.println(a2.getName()); System.out.println(a2.getAge()); } } 类 成员变量 构造方法 无参构造方法 带参构造方法 成员方法 getXxx() setXxx() 给成员变量赋值的方式 无参构造方法+setXxx() 带参构造方法 练习 学生类 public class Student { //成员变量 private String name; private int age; //构造方法 public Student() {} public Student(String name,int age) { this.name = name; this.age = age; } //成员方法 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 class DemoStudent { public static void main(String[] args) { //无参+setXxx() Student s = new Student(); s.setName("柳岩"); s.setAge(18); System.out.println(s.getName()+"---"+s.getAge()); //带参构造 Student s2 = new Student("赵丽颖",18); System.out.println(s2.getName()+"---"+s2.getAge()); } } 实际上在面向对象第一天的讲解当中,我们对内存方面做了一些隐瞒。因为除了Object类,所有的类都是有父类的。但是我们在考虑内存图时忽略了这点,现在,我们来简单描述加入了子父类关系后的对象内存图。 以Person类为例: //定义父类 public class Person { private String name; private int age; public Person(){} public Person(String name,int age) { this.name = name; this.age = age; } //get/set方法 } //定义子类 public class Chinese extends Person{ private Stirng address; public Chinese(){} public Chinese(String name,int age,String address) { super(name,age); this.address = address; } //对address的get/set } //定义测试类,使用子类创建对象 public class Test{ Chinese c = new Chinese(“AngelaBaby”,18,”北京海淀区上地7街晋福公寓”); } 对象内存图 /* * 自定义类型Person 类 * * name age * * 吃 睡 */ public abstract class Person { private String name; int age; //定义无参构造方法 public Person(){ //方法逻辑 System.out.println("我是Person的无参构造"); } //定义带参构造一般都是为了给成员变量赋值 public Person(String name ,int age){ System.out.println("我是Person带参构造给成员变量赋值"); this.name = name; this.age = age; } private void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("睡"); } 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; } } /* * 自定义Studnet继承Person类 */ public class Student extends Person{ private String number; /* * 无参构造 */ public Student() { System.out.println("Studnet的无参构造"); } //带参构造 public Student(String number,String name,int age) { System.out.println("Studnet的带参构造"); this.number = number; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } } /* * 测试有继承关系的构造方法 */ public class Test { public static void main(String[] args) { //Student s = new Student(); Student s2 = new Student("2016"); } } 在每次创建子类对象时,我们均会先创建父类对象,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类对象,便可以包含其父类对象的成员,如果父类成员非private修饰,则子类可以随意使用父类成员。 反之,如果没有先创建父类对象就使用了子类对象,则子类无法使用父类的成员。抽象类包含构造方法的原因就在于其仅仅是为了给成员变量赋值,供子类使用。 这里我们需要注意的是,内存当中实际是存在抽象类的对象空间的,我们无法直接创建抽象类对象,但是子类可以,在子类的内存空间中包括了这个抽象父类对象。 super代表本类对象中包含的父类对象空间的引用。 当有了继承关系后,创建一个子类对象时,会先在子类中创建其父类对象,则子类对象包含了父类的所有方法与属性,而其非私有的方法一般都可以访问 (在完成访问权限的学习后,会有进一步认识) 。 在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。 /* * 自定义类型Person 类 * * name age * * 吃 睡 */ public abstract class Person { private String name; int age; //定义无参构造方法 public Person(){ //方法逻辑 System.out.println("我是Person的无参构造"); } //定义带参构造一般都是为了给成员变量赋值 public Person(String name ,int age){ System.out.println("我是Person带参构造给成员变量赋值"); this.name = name; this.age = age; } private void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("睡"); } 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; } } /* * 自定义Studnet继承Person类 * * super访问父类构造方法 * 在子类的所有构造方法的第一行 都默认调用了父类的无参构造 super() * 我们通过super(参数)调用父类的带参构造 给父类的成员变量赋值 * super访问普通成员 * 在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。 * */ public class Student extends Person{ private String number; /* * 无参构造 */ public Student() { super(); System.out.println("Studnet的无参构造"); } //带参构造 public Student(String number,String name,int age) { super(name,age); System.out.println("Studnet的带参构造"); this.number = number; } public void method(){ //super.属性名可以访问父类的非私有成员变量 //System.out.println(super.name); System.out.println(super.age); //super.方法名()可以访问父类的非私有成员方法 // super.eat(); super.sleep(); } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } } /* * 测试有继承关系的构造方法 */ public class Test { public static void main(String[] args) { // Student s = new Student(); Student s2 = new Student("2016","柳岩",38); System.out.println(s2.getNumber()); System.out.println(s2.getName()); System.out.println(s2.getAge()); } } 在子类的每个构造方法中,第一行具有默认调用父类空参构造代码,即super().所以在每次创建子类对象时,会先创建父类的构造。 使用super(参数)可以访问父类任意参数的构造,当手动调用父类任意的构造方法后,Java将不再提供默认调用父类空参的构造方法。 /* * 自定义类型Person 类 * * name age * * 吃 睡 */ public abstract class Person { private String name; Private int age; //定义无参构造方法 public Person(){ //方法逻辑 System.out.println("我是Person的无参构造"); } //定义带参构造一般都是为了给成员变量赋值 public Person(String name ,int age){ System.out.println("我是Person带参构造给成员变量赋值"); this.name = name; this.age = age; } Public void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("睡"); } 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; } } /* * 自定义Studnet继承Person类 * * super访问父类构造方法 * 在子类的所有构造方法的第一行 都默认调用了父类的无参构造 super() * 我们通过super(参数)调用父类的带参构造 给父类的成员变量赋值 * super访问普通成员 * 在子类的任意位置,均可以使用super.属性名或者super.方法名()的方式访问父类空间的非私有成员。 * */ public class Student extends Person{ private String number; /* * 无参构造 */ public Student() { super(); System.out.println("Studnet的无参构造"); } //带参构造 public Student(String number,String name,int age) { super(name,age); System.out.println("Studnet的带参构造"); this.number = number; } public String getNumber() { return number; } public void setNumber(String number) { this.number = number; } } /* * 测试有继承关系的构造方法 */ public class Test { public static void main(String[] args) { // Student s = new Student(); Student s2 = new Student("2016","柳岩",38); System.out.println(s2.getNumber()); System.out.println(s2.getName()); System.out.println(s2.getAge()); } } this代表本类一个对象的引用,当创建了一个子类对象时,子类自己的空间可以使用this访问到。 在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。 使用this(参数)可以访问子类任意其他参数的构造方法,当手动调用子类任意的构造方法后,Java将不再提供默认调用父类空参的构造方法。 this调用构造方法与super调用构造方法不能同时出现。 无论以哪种方式完成构造方法的定义,均会先创建父类对象,再创建子类对象。 package cn.javahelp3; /* * 自定义类型Person 类 * * name age * * 吃 睡 */ public abstract class Person { private String name; int age; //定义无参构造方法 public Person(){ //方法逻辑 System.out.println("我是Person的无参构造"); } //定义带参构造一般都是为了给成员变量赋值 public Person(String name ,int age){ System.out.println("我是Person带参构造给成员变量赋值"); this.name = name; this.age = age; } private void eat(){ System.out.println("吃"); } public void sleep(){ System.out.println("睡"); } 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; } } package cn.javahelp3; /* * 自定义教师 * * this * * this调用本类其他构造 * this(参数)可以调用本来当中其他构造方法 * * this访问本来的普通成员 * 在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。 * */ public class Teacher extends Person{ //id private String id; public Teacher() { //this(参数)可以调用本来当中其他构造方法 this("90213"); } public Teacher(String name, int age,String id) { super(name, age); this.id = id; } public Teacher(String id){ super(); this.id = id; } public void method(){ //在子类的任意位置,均可以使用this.属性名或者this.方法名()的方式访问子类自身空间的成员。 System.err.println(this.id); this.teach(); } public void teach(){ System.out.println("教学生的方法"); } public String getId() { return id; } public void setId(String id) { this.id = id; } } 访问子类区域的成员使用this,访问父类区域的成员使用super。 this: 访问本类对象成员变量 this.变量名 调用本类普通方法 this.方法名(参数) 本类构造方法调用本类其他构造 本类构造方法第一行this(参数) super: 访问本类对象当中的父类对象成员变量 super.变量名 调用本类对象当中的父类普通方法 super.方法名() 本类构造方法调用父类构造 本类构造方法第一行super(参数) 变量访问的就近原则: 当多个位置出现相同名称的变量时,访问时会根据就近原则依次访问。其先后顺序为: 局部位置>本类成员位置>父类成员位置 >父类的父类成员位置 … package cn.javahelp4; /* * 变量的就近访问原则 */ public class Fu { String name = "父类名字"; } package cn.javahelp4; public class Zi extends Fu { String name = "子类名字"; public void method(){ String name = "局部名字"; System.out.println(name); System.out.println(this.name); System.out.println(super.name); } } package cn.javahelp4; /* * 当多个位置出现相同名称的变量时,访问时会根据就近原则依次访问。其先后顺序为: 局部位置 > 本类成员位置 > 父类成员位置 > 父类的父类成员位置 … */ public class Test { public static void main(String[] args) { Zi zi = new Zi(); zi.method(); } } 注意: this与super在调用构造方法时,均必须在第一行,只能调用其中的一个。 父类多个构造,子类调用父类某个参数的构造时,必须保证父类有这个构造,否则报错。 package cn.javahelp5; /* * 自定义Person类 * * name age */ public class Person { private String name; private int age; public Person(){ } public Person(String name, int age) { super(); this.name = name; this.age = age; } public Person(int age){ this.age = age; } 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; } } package cn.javahelp5; /* * 学生类 * *this与super在调用构造方法时,均必须在第一行,只能调用其中的一个。 * 父类多个构造,子类调用父类某个参数的构造时,必须保证父类有这个构造,否则报错。 */ public class Student extends Person{ public Student() { super(); } public Student(String name, int age) { super(name, age); } public Student(int age){ super(age); } } 向上转型: 如图所示,当出现多态时,引用为Person类型,对象为Chinese对象,此时,由于Chinese中包含了父类所有成员,所以可以访问父类非私有的一切。对外表现的就”像个父类对象一样”。仅仅在调用方法时,会调用子类重写后的方法。 向下转型: 当出现多态后,父类Person引用指向子类对象,当强转为子类引用时,由于堆内存当中存储的仍为子类对象,包含子类的一切成员。所以可以转型成功。 但是,如果没有出现多态,仅仅创建父类对象(如果父类不是抽象类的话),则为父类Person的引用指向Person的对象,没有子类的对象。此时如果强转为子类对象,则不包含子类的一些属性与功能,所以强转失败。 思考: 当子父类中有相同名称的成员变量时,强转前与强转后访问的是相同的属性值么? /* * 变量的就近访问原则 */ public class Fu { String name = "父类名字"; } package cn.javahelp4; public class Zi extends Fu { String name = "子类名字"; public void method(){ String name = "局部名字"; System.out.println(name); System.out.println(this.name); System.out.println(super.name); } } public class Test1 { public static void main(String[] args) { Fu fu = new Zi(); System.out.println(fu.name); Zi zi = (Zi)fu; System.out.println(zi.name); } } 重新完成超市购物小票案例或者随机点名案例,在定义类时,加入空参和带参的构造方法。在所有使用构造方法创建对象的地方,均由原来的set方法赋值修改为构造方法直接赋值。 /** * 自定义类型 学生类 * 姓名 年龄 学号 * */ public class Student { private String name; private int age; private String number; /* * 无参构造 */ public Student(){ } /* *满参构造 */ public Student(String name,int age,String number){ this.name = name; this.age = age; this.number = number; } 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 getNumber() { return number; } public void setNumber(String number) { this.number = number; } } import java.util.ArrayList; import java.util.Random; /** * * 随机点名器 用集合存储所有同学 总览全班同学姓名 随机点名其中一人,打印到控制台 * * 1.定义一个集合用来存储所有同学 * 导包 * 创建对象 * 存储的是Person 调用方法 * * 2.向集合中添加学生对象 * * 3.遍历集合,依次获取每个学生对象,打印每个学生的名字 * * 4.随机点名 * 生成一个最大不超过集合最大索引的随机数 0 - 集合最大索引的一个随机数 * 依据随机数得到集合中相对应的人,打印其名字 * */ public class RandomName { public static void main(String[] args) { ArrayList<Student> list = new ArrayList<Student>(); // 初始化集合数据 init(list); // ArrayList<Student> addList = addList(); // 调用危机和遍历方法 showList(list); // 调用随机点名方法 Student s = RandomName(list); System.out.println(s.getName()); } // 4.定义一个随机点名的方法 /* * 生成一个最大不超过集合最大索引的随机数 0 - 集合最大索引的一个随机数 依据随机数得到集合中相对应的人,打印其名字 */ public static Student RandomName(ArrayList<Student> list) { Random r = new Random(); int index = r.nextInt(list.size()); Student student = list.get(index); return student; } // 3.定义一个为学生集合遍历的方法 需要的数据 ArrayList<Student> 返回值 无 public static void showList(ArrayList<Student> list) { for (int i = 0; i < list.size(); i++) { Student student = list.get(i); System.out.println("姓名: " + student.getName()); System.out.println("年龄: " + student.getAge()); System.out.println("学号: " + student.getNumber()); System.out.println("--------------------------"); } } // 1.定义一个为集合初始化数据的方法 需要的数据 ArrayList<Student> 返回数据 无 public static void init(ArrayList<Student> list) { Student s1 = new Student("刘备", 40, "201701"); Student s2 = new Student("关羽", 35, "201702"); Student s3 = new Student("张飞", 30, "201703"); //2.向集合中添加元素 list.add(s1); list.add(s2); list.add(s3); } //定义一个为集合初始化数据的方法 需要的数据 ArrayList<Student> 返回数据 无 // public static ArrayList<Student> addList(){ // ArrayList<Student> list = new ArrayList<Student>(); // Student s1 = new Student("刘备",40,"201701"); // Student s2 = new Student("关羽",35,"201702"); // Student s3 = new Student("张飞",30,"201703"); // // list.add(s1); // list.add(s2); // list.add(s3); // return list; // } } class Father{ public int num = 10; } class Zi extends Father{ public int num = 20; public void show(){ int num = 30; System.out.println("num="+num); System.out.println("this.num="+this.num); System.out.println("super.num="+super.num); } } public class ExtendTest { public static void main(String[] args) { Zi zi = new Zi(); zi.show(); } } 答案:num=30 this.num=20 super.num=10 1:完成课后代码重构综合案例 2:什么是构造方法?构造方法有什么功能? 3:构造方法的定义格式和调用格式有哪些特殊? 4:该类有三个成员变量,学习阶段一般定义几个构造方法,有什么功能?定义一个参数的构造方法可以么? 5:抽象类与接口有构造方法么?试解释为什么Java这样设计。 6:什么是this与super? 7:this与super的使用方式都有哪些?(提示:共三种) 8:就近原则指什么,试举一例。 9:根据继承后的内存图,解释多态的向上向下转型。 10:完整回顾前四天面向对象内容,定义出完整的类,并创建对象使用。可以将前边所有案例中使用自定类型的地方重构为带构造方法的自定义类型,重构代码。 要求: 1.定义老手机类,有品牌属性,且属性私有化,提供相应的getXxx与setXxx方法,提供无返回值的带一个String类型参数的打电话的方法,内容为:“正在给xxx打电话...” 2.定义新手机类,继承老手机类,重写父类的打电话的方法,内容为2句话:“语音拨号中...”、“正在给xxx打电话...”要求打印“正在给xxx打电话...”这一句调用父类的方法实现,不 能在子类的方法中直接打印;提供无返回值的无参数的手机介绍的方法,内容为:“品牌为:xxx的手机很好用...” 3.定义测试类,创建新手机对象,并使用该对象,对父类中的品牌属性赋值; 4.使用新手机对象调用手机介绍的方法; 5.使用新手机对象调用打电话的方法; 答案: /* * 老手机类: * 属性: * 品牌 * 方法: * getXxx与setXxx * 打电话 */ public class LaoShouJi { //属性 private String name; //getXxx与setXxx public String getName() { return name; } public void setName(String name) { this.name = name; } //打电话 public void daDianHua(String ren){ System.out.println("正在给"+ren+"打电话..."); } } /* * 新手机类: * 方法: * 重写父类的打电话的方法 * 手机介绍的方法 */ public class XinShouJi extends LaoShouJi{ //按要求重写父类中的方法 public void daDianHua(String ren) { System.out.println("语音拨号中..."); //使用super关键字调用父类的daDianHua的方法 super.daDianHua(ren); } //手机介绍的方法 public void jieShao(){ System.out.println("品牌为:"+super.getName()+"的手机很好用..."); } } /* * 测试类 */ public class Test { public static void main(String[] args) { //1、创建新手机对象 XinShouJi xin = new XinShouJi(); //2、对父类中的品牌属性赋值 xin.setName("诺基亚"); //3、调用手机介绍和打电话的方法 xin.jieShao(); xin.daDianHua("小丽"); } } |
|