一、基本特征
1、访问控制修饰符作用
访问控制修饰符的作用是控制类中的成员可以在哪些范围内被访问到
2、访问控制修饰符
用的最多的有2个
-
public 公共的意思, 访问范围最大, 可以任意范围内访问
-
private 私有的意思, 访问范围最小, 只能在本类中被访问
二、如何封装
1、成员
类中的要素, 属性, 方法, 构造器, 代码块, 成员内部类, 这些要素都隶属于类, 对于本类而言, 这些要素就是互为成员, 既然是互为成员, 那么它们之间的互相访问是没有任何障碍.
2、成员私有化
只需要把类中的成员使用private修饰就是私有化了, 而一旦成员被私有化, 这个成员也称为被封装了
注意 : 代码块不能封装
public class Teacher {
private String name; // 对象属性
private int age;
private String gender;
// 对象方法
public void lesson() {
System.out.println(name + “老师在上课”);
}
public void eat(String something) {
System.out.println("老师在吃" + something);
}
// 描述对象的详细信息, 把所有的属性值串接成一个字符串
public String say() {
String str = "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
return str;
}
}
3、封装的问题
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher();
// 下面的语句都不能执行了, 因为在测试类中,不能直接跨类访问别的类中的私有成员
//t.name = “张三”;
//t.age = 30;
//t.gender = “男”;
//System.out.println(t.name);
//System.out.println(t.age);
//System.out.println(t.age);
}
}
(1)get/set方法
因为成员一旦私有化, 就不可以在其他类中直接访问了, 但是在其他类中仍然有访问的需求, 成员之间是可以互相访问的, 所以解决这个问题的方法就是在本类中添加一些公共方法, 通过方法间接访问类中的私有成员.
public class Teacher {
private String name = "某老师"; // 实例变量, 私有化属性, 封装
private int age = 30; // 当属性被私有化时, 只能在本类中使用
private String gender = "男";
// 用于间接设置name属性, 所以方法必须公共的
public void setName(String n) {
name = n;
}
// 用于间接获取name属性, 所以方法必须公共的
public String getName() {
return name;
}
public void setGender(String g) {
gender = g;
}
public String getGender() {
return gender;
}
public void setAge(int age) {
this.age = age;
}
// get方法有返回值, 没有参数
public int getAge() {
return age;
}
// 实例方法
public void lesson() {
System.out.println(name + "老师在上课"); // 成员可以互访
}
public void eat(String something) {
System.out.println("老师在吃" + something);
}
// 描述对象的详细信息, 把所有的属性值串接成一个字符串
public String say() {
String str = "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
return str;
}
}
(2)间接访问属性
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher();
t.setName("张老师");// 通过方法设置属性
t.setAge(30);
t.setGender("男");
System.out.println(t.getName());
System.out.println(t.getAge());// 通过方法获取属性
System.out.println(t.getGender());
}
}
(3)为什么要封装
如果不使用封装而直接访问对象的属性, 就可以给属性赋上一些不合理的数据值, 比如
t.age = 50000; // 显然这个操作是应该失败的, 因为年龄不合理
但是如果通过方法来赋值, 就可以把这种错误拦截住
相同的道理, 如果要获取某个属性, 但是需要在内部再处理一下, 也可以通过方法来实现
// 保护数据
public void setAge(int a) {
if (a > 120 || a < 0) {
// 当参数中传入的数据不合理时, 方法直接返回, 不给对象的属性赋值
return;
}
age = a;
}
三、构造器
1、什么是构造器
(1)什么是初始化1
对象在刚创建时进行的工作就称为初始化, 初始化的主要工作是针对对象的属性的.
(2)构造器概念
当对象创建时, 希望对象的属性值被正确赋值, 此时就需要用到了构造器, 构造器也称为构造方法, 本质上构造器就是一个方法, 是一个特殊的方法.
(3)如何定义构造器
构造器(构造方法)的定义
修饰符 类名(参数类型1 形参1, 参数类型2 形参2…) {
代码块;
}
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
public Teacher() {
// 无参构造器
name = "张三";
age = 35;
gender = "男";
}
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 setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
}
注意 : 类中如果没有提供任何的构造器, 编译器也会自动添加一个缺省的构造器
缺省的构造器就是 修饰符和类一致, 无参, 并且方法体中没有代码.
2、构造器的特点
特点1:
构造器没有返回值, 也不可以声明void
特点2:
构造器名称必须和类名一致
特点3:
构造器不可以被final, static, abstract等修饰
特点4:
构造器不像普通方法可以随意调用, 只能在创建对象时调用一次
3、构造器调用
(1)如何调用构造器
使用new关键字在创建对象时就会自动调用了构造器
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher(); // 调用无参构造方法
System.out.println(t.say()); // 输出”姓名:张三,年龄:35,性别:男”
}
}
4、构造器重载
(1)什么是构造器重载
当一个类中有多个构造器, 并且参数不同就形成构造器重载
构造器重载的目的是可以使用多种方式创建不同的对象
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
public Teacher() {
// 无参构造器
name = "佟刚";
age = 35;
gender = "女";
}
// 有参构造器
public Teacher(String n, int a) {
name = n;
age = a;
gender = "女";
}
// 有参构造器
public Teacher(String n, int a, String g) {
name = n;
age = a;
gender = g;
}
public String say() {
return "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
}
}
public class TeacherTest {
public static void main(String[] args) {
Teacher t1 = new Teacher(); // 调用无参构造方法
System.out.println(t1.say());
Teacher t2 = new Teacher(“李四”, 40); // 调用有参构造方法
System.out.println(t2.say());
Teacher t3 = new Teacher(“王五”, 50, “男”); // 调用有参构造方法
System.out.println(t3.say());
}
}
(2)this()调用
在无参构造器中, 给对象的3个属性分别完成赋值, 而这个操作可以通过调用另外一个构造器来完成,调用的语法this(实参列表);
注意 :
this(…) 调用必须放在构造器的第一行!
this(…) 调用时,必须保证有一个构造器是没有this(…)的, 否则会形成构造器递归调用.
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
public Teacher() {
// 无参构造器
// 注释不算行
this("张三", 35, "男"); // 通过调用第3个构造器完成属性赋值
}
// 有参构造器
public Teacher(String n, int a) {
name = n;
age = a;
gender = "女";
}
public Teacher(String n, int a, String g) {
name = n;
age = a;
gender = g;
}
public String say() {
return "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
}
}
四、this关键字和对象关系
1、this关键字含义
(1)this代表什么
this代表对象, 代表的是当前对象, this里保存的是对象的地址.
谁是当前对象? 比如方法调用
t1.say() 在这个方法执行时
执行以下代码 :
public String say() {
return "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
}
这段代码可以写成
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
效果是一样的, 当前对象是哪个对象呢? 就是调用这个方法的对象, t1.say()中的t1就是当前对象,显然这个当前对象在变化, 因为t2.say()调用时,this就是指的是t2了.
(2)为什么使用this
this强调了使用当前对象, 有的时候属性如果和方法中的局部变量重名时, 为了区别必须使用this,在构造器重载时, 调用别的构造器也需要使用this. 两者的语法不一样.
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
public Teacher() {
// 无参构造器
this("佟刚", 35, "女"); // 调用其他构造器
}
public Teacher(String name, int age, String gender) {
this.name = name; // this表示对象, 右侧的name是形参, 是局部变量
this.age = age;
this.gender = gender;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name; // 加上this也可以, 但是没有必须, 它暗含了this
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
}
(3)使用this的优点
1. 使用this可以提高代码的可读性, 强调要使用的是当前对象.
2. 在方法中, 如果局部变量和属性重名, 必须使用this, 用以区分属性和局部变量, 并且这样局部变量的含义也更清晰.
3. this(…)调用可以简化构造器调用, 并有利于维护. 如果有修改的需要, 只要修改被调用的构造器就可以了.
2、对象的传递
(1)为什么要传递对象
在有些方法中, 功能的完成除了需要一些基本数据外, 还需要复杂类型, 比如在Teacher类中方法
public void lesson() {
// 老师上课的代码, 老师上课时, 需要一台电脑, 这时就可以通过参数传递一个电脑对象
}
修改为
public void lesson(Computer computer) {
// 老师上课的代码, 老师上课时, 需要一台电脑, 这时就可以通过参数传递一个电脑对象
// 这样在老师上课的代码中, 就可以直接使用传入的Computer对象.
}
(2)对象如何传递
在方法调用时, 由调用者传实参时,负责把对象传给方法.
public class Computer {
private double cpu;
private int memory;
public Computer() {
}
public Computer(double cpu, int memory) {
this.cpu = cpu;
this.memory = memory;
}
public void setCpu(double cpu) {
this.cpu = cpu;
}
public double getCpu() {
return cpu;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getMemory() {
return memory;
}
public String say() {
return "CPU:" + cpu + "GHz, 内存:" + memory + "G";
}
}
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
public Teacher() {
// 无参构造器
this("佟刚", 35, "女");
}
public Teacher(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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 setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public void lesson(Computer com) {
// 这个方法应该需要一台电脑才能上课
System.out.println("[" + this.name + "] 老师使用电脑[" + com.say() + "]在上课");
}
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
}
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher("李四", 25, "女");
Computer com = new Computer(3.5, 4); // 要传递的对象先创建好
t.lesson(com); // 通过实参传递对象
// 以上代码打印输出为”[李四] 老师使用电脑[CPU:3.5GHz,内存:4G]在上课"
}
}
(3)传递的是什么
在方法调用中的实参com是在main方法中声明的引用变量, 但是这个引用指向了在main中创建的新的Computer对象, 所以在lesson方法执行时, 接收到的是对象的引用, 通过引用使用了对象.
(4)对象传递的优点
即使对象很大, 属性很多, 在对象传递时, 并不是把对象本身复制了一份, 而是只把对象的引用传递了, 优点就是速度快.
(5)对象传递的危险性
危险性就在于因为传递了对象的引用, 这样通过引用一方面可以读取对象的属性或调用对象的方法, 同时如果通过引用直接修改对象的属性, 也会导致对象属性的变化, 所以当把对象传递给方法时, 如果在方法内部有对于对象的修改操作, 对于调用者来说, 这个修改也是同步的.
public class Teacher {
private String name;
public void lesson(Computer com) {
// 这个方法应该需要一台电脑才能上课
System.out.println("[" + this.name + "] 老师使用电脑[" + com.say() + "]在上课");
com.setMemory(8);
}
}
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher();
Computer com = new Computer(3.5, 4); // 要传递的对象先创建好
t.lesson(com); // 通过实参传递对象
System.out.println(com.getMemory()); // 输出的不是原来的4而是在方法中被修改后的8
}
}
3、对象关联
(1)什么是对象关联
对象关联就是一个对象关联了另外一个对象, 前者拥有了后者, 是一种包含关系.
(2)为什么要关联对象
public void lesson(Computer com) {
// 这个方法应该需要一台电脑才能上课
System.out.println("[" + this.name + "] 老师使用电脑[" + com.say() + "]在上课");
}
这个方法中老师对象要想上课, 需要通过参数传入一个对象. 方法结束后, 这个对象对于老师对象来说就会消失, 显然老师对象最好是应该完全拥有一个电脑对象, 这样再执行上课方法时,就不需要再传入对象, 而是使用自己拥有的对象, 甚至还可以再添加一些别的方法使用此对象.
(3)如何关联对象
在当前类中把要关联的对象作为属性即可.
也可以在构造器中添加参数为关联的对象赋值, 也可以添加对应的get/set方法,用以处理这个关联的对象
先写被关联的类
public class Computer {
private double cpu;
private int memory;
public Computer() {
}
public Computer(double cpu, int memory) {
this.cpu = cpu;
this.memory = memory;
}
public void setCpu(double cpu) {
this.cpu = cpu;
}
public double getCpu() {
return cpu;
}
public void setMemory(int memory) {
this.memory = memory;
}
public int getMemory() {
return memory;
}
public String say() {
return "CPU:" + cpu + "GHz, 内存:" + memory + "G";
}
}
在Teacher类中关联Computer类的对象
public class Teacher {
private String name;
private int age = 30;
private String gender = "女";
// 对象关联, 把被关联的对象声明成属性
private Computer computer;
// 可以通过构造器完成对关联对象的初始化
public Teacher(String name, int age, String gender, Computer computer) {
this.name = name;
this.age = age;
this.gender = gender;
this.computer = computer;
}
// 同时应该再提供相应的get/set方法
public void setComputer(Computer computer) {
this.computer = computer;
}
public Computer getComputer() {
return computer;
}
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 setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
// 在上课方法中, 不必再在每次调用时传参了.
public void lesson() {
System.out.println("[" + this.name + "] 老师使用电脑[" + computer.say() + "]在上课");
}
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
}
public class TeacherTest {
public static void main(String[] args) {
Computer com = new Computer(3.5, 4); // 要关联的对象先创建好
Teacher t = new Teacher("李四", 25, "女", com); // 通过构造器完成对象关联
t.lesson(); // 通过实参传递对象
// 以上代码打印输出为”[李四] 老师使用电脑[CPU:3.5GHz,内存:4G]在上课"
}
}
(4)对象关联的作用
对象关联的作用非常广泛, 当一个类完成某种功能时, 有的时候如果有现成的其他对象可以直接使用, 并且在本类中要多次多方法中使用同一个功能时, 对象关联更显得必要. 这样使得对象之间的关系更加紧密.
4、javabean
-
JavaBean是一种Java语言写成的可重用组件。
-
所谓javaBean,是指符合如下标准的Java类:
-
类是公共的
-
有一个无参的公共的构造器
-
有属性,且有对应的get、set方法
-
用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
-
public class TestJavaBean{
private String name; //属性一般定义为private
private int age;
public TestJavaBean(){
}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
五、包
1、package语句
(1)为什么要使用包
当程序中涉及的类越来越多时, 类的功能含义以及如果有同名类该如何处理的问题就出现了.
我们希望把功能类似的一组类放在一起, 有利于管理和维护, 在java中使用了"包"的概念来分类管理
包和目录非常接近, 但是虽然文件系统中是以目录的形式出现的, 但是在程序中包不是目录,目录也不是包.
(2)如何使用包
在程序的第一行添加语句
package 包名.子包名.子子包名…;
包目录深度没有限制, 但也不宜太深
package的作用就是让编译器编译代码时, 把生成的class文件放置到指定的包目录中, 并在类中记录包信息.
包名及子包结构通常符合下列规范,由四个部分组成:
机构类型名称.机构名称.项目名称.模块名称
(3)使用包带来的问题
一旦源文件中使用了package语句, 那么这个源文件中的所有类都会被放置到指定的包中,
带来2个问题
-
包中的所有的类如果被别的包中的类访问时, 不能再使用简单类名(simple name), 必须使用全限定名称(qualified name), 全限定类名 就是所有的包名.类名.
-
如果类是主类, 那么在执行主类时, 必须使用全限定类名.
-
编译源文件时必须加上选项-d 生成的包目录及其中的类文件的目标目录, 通过这个目录是当前目录, 在eclipse中这个目录是bin
package com.atguigu.javase.javabean;
public class Teacher {
// Teacher类在其他包的类中使用时,必须使用全限定名称
private String name;
private int age = 30;
private String gender = "女";
// 可以通过构造器完成对关联对象的初始化
public Teacher(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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 setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public String say() {
return "姓名:" + this.name + ",年龄:" + this.age + ",性别:" + this.gender;
}
}
package com.atguigu.javase.test;
public class TeacherTest {
public static void main(String[] args) {
//Teacher t = new Teacher("李四", 25, "女"); // 编译时这行语句就会报错!!!
com.atguigu.javase.javabean.Teacher t = new com.atguigu.javase.javabean.Teacher(“李四”,25,”女”);
System.out.println(t.say());
}
}
并且在命令行中执行此主类时必须使用全限定类名
java com.atguigu.javase.test.TeacherTest
2、import语句
(1)import作用是什么
跨包使用类时, 必须使用全限定类名, 所以当一个类中使用别的包的类太多时, 或者一个类的包名特别冗长时, 使用全限定名称代价就会非常高, 即使是一个类已经使用过了全限定, 下次再使用时,仍然还得使用全限定类名. 为了解决这个问题, 使用import导入语句, 把本类中要使用的其他包中的类导入进来, 这样, 在本类中再使用别的包的类就不必再使用全限定类名了, 可以直接使用简单类名, 这样就可以简化开发.
package com.atguigu.javase.test;
import com.atguigu.javase.javabean.Teacher;
public class TeacherTest {
public static void main(String[] args) {
Teacher t = new Teacher("李四", 25, "女"); // 虽然Teacher类不在本包, 因为已经导入可以使用
System.out.println(t.say());
}
}
(2)import注意事项
-
import 也可以一次性导入某个包的所有类, 使用.*通配符即可
-
import导入时,必须要保证要导入的类确实存在
六、基本特征二 继承
1、继承
(1)继承的概念
一个类从另外一个类继承所有成员, 包括属性和方法, 但是不包括构造器和语句块
从现有类创建子类, 现有类就称为父类, 基类, 超类.
*Person* |
---|
+name : String +age : int+birthDate : Date |
+getInfo() : String |
public class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo()
{
...}
}
*Student* |
---|
+name : String +age : int+birthDate : Date+school : String |
+getInfo() : String |
public class Student {
public String name;
public int age;
public Date birthDate;
public String school;
public String getInfo()
{
...}
}
(2)为什么要继承
-
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
-
此处的多个类称为子类,单独的这个类称为父类(基类或超类)。可以理解为:“子类 is a 父类”
当一个类和另外一个类的属性和方法完全相同, 并且同时又比另一个类多一些特有的属性和方法时, 前者就可以作成后者的子类, 子类继承父类的所有成员, 这样, 在子类中就可以省略从父类继承的成员, 便于代码的复用, 以及更统一的类型管理.
(3)继承语法
class 子类 extends 父类 {}
从语法意思来看, 子类是扩展自父类, 也可以理解为子类是在以父类为基础的前提下, 进一步扩展一些属性和方法, 所以子类大于父类, 或者也可以说, 子类包含父类.
public class Person {
public String name;
public int age;
public Date birthDate;
public String getInfo() {
...}
}
public class Student extends Person{
public String school;
}
//Student类继承了父类Person的所有属性和方法,并增加了一个属性school。
//Person中的属性和方法,Student都可以使用。
(4)继承的特点
①子类继承父类的所有成员(构造器除外), 就意味着父类的私有成员也会被子类继承, 但是因为私有成员只能被本类访问, 所以即使是在子类中也不能直接访问从父类继承的私有成员, 可以通过从父类继承的公共的get/set方法间接访问.
public class Person {
private String name;
private int age;
private Date birthDate;
public void setName(String name) {
this.name = name;
}
public String getInfo() {
...}
}
public class Student extends Person{
public String school;
public void test() {
//name = “小明”; // 不可以, 因为是私有的
setName(“小明”);// 可以, 通过公共方法间接访问
}
}
②单继承
Java只支持单继承,不允许多重继承
一个子类只能有一个直接父类
一个父类可以派生出多个子类
class SubDemo extends Demo{
} //ok
class SubDemo extends Demo1,Demo2...//error
在继承关系中, 一个类Person可以被另外的类Soldier和Student和Officer继承, 然后Student又被Graduate类继承, 这是允许的, 在这样的体系中, Graduate类也称为Person类的子类, Person类也称为Graduate类的父类, 这样的继承我们称之为多层继承. 在这样的体系中, Student类称为Graduate类的直接父类, 显然地, Person类称为Graduate类的间接父类, 在java中, 多层继承是被允许的.
Java中不允许的多继承是指多重继承, 也就是说一个类不允许有多个直接父类, 间接父类并没有个数限制. 称之为单继承.
2、方法覆盖(override)
(1)什么是方法覆盖
定义:
在子类中可以根据需要对从父类中继承来的方法进行改造,
也称方法的重写、重置。在程序执行时,子类的方法将覆盖父类的方法。
要求:
覆盖方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型。
覆盖方法不能使用比被重写方法更严格的访问权限。
覆盖和被覆盖的方法必须同时为非static的。
子类方法抛出的异常不能大于父类被重写方法的异常
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
int age;
public String getInfo() {
//重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2 执行的是子类重写过的方法
}
}
(2)为什么要方法覆盖
有的时候子类继承的父类方法并不能满足子类的需要, 而方法名称又要保持一致, 此时用到方法覆盖.
(3)方法覆盖的特殊性
子类一旦把父类的方法覆盖, 那么在测试类中再无法通过子类对象调用父类的被覆盖方法了, 因为子类已经把父类的方法重写了, 如果有调用父类方法的需求, 只能通过再创建一个父类对象来调用.
(4)@Override注解的使用
有的时候, 在子类中写重写方法时, 容易出现一些低级的拼写错误, 或其他错误, 导致方法不能正确覆盖时, 程序的运行就会出乎意外, 所以为了防止这种现象, 在子类的覆盖方法上添加修饰符@Override注解.
注解 : 本质上一种类, 也是一种特殊的注释, 所以一般情况下, 程序不执行注解, 但是会被编译器, 运行时所识别和处理(通过反射的方式).注解也有很多.
@Override注解的作用是告诉编译器, 在编译程序时, 必须先检查此方法是否满足方法覆盖的条件, 如果不满足, 则编译出错, 这样强制程序员通过排查, 提前检查方法覆盖的问题.
public class Student extends Person {
public String school;
int age;
@Override // 加上注解, 也能提高代码可读性
public String getInfo() {
//重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
}
3、四种访问权限
(1)为什么需要访问权限
有的时候, 类中的一些成员需要控制可以被哪些范围的其他类所访问, 就可以使用访问控制修饰符来控制.
(2)访问权限
class Parent{
private int f1 = 1;
int f2 = 2;
protected int f3 = 3;
public int f4 = 4;
private void fm1() {
System.out.println("in fm1() f1=" + f1);}
void fm2() {
System.out.println("in fm2() f2=" + f2);}
protected void fm3() {
System.out.println("in fm3() f3=" + f3);}
public void fm4() {
System.out.println("in fm4() f4=" + f4);}
}
class Child extends Parent{
//设父类和子类在同一个包内
private int c1 = 21;
public int c2 = 22;
private void cm1(){
System.out.println("in cm1() c1=" + c1);}
public void cm2(){
System.out.println("in cm2() c2=" + c2);}
public static void main(String args[]){
int i;
Parent p = new Parent();
// i = p1.f1; // 不可以访问其他类的私有成员
i = p.f2;// i = p.f3; i = p.f4; p.fm2();// p.fm3(); p.fm4();
Child c = new Child();
i = c.f2; // i = c.f3; i = c.f4;
i = c.c1; // i = c.c2;
//c.fm1(); // 不能访问
c.cm1(); // c.cm2(); c.fm2(); c.fm3(); c.fm4() 都能访问
}
}
3. 访问权限的使用注意
protected是被保护的, 容易从字面意思理解成它的范围会比较小, 但是其实被protected修饰的成员的访问范围是仅次于public的, 所以要小心.
七、super关键字
1、super关键字含义
(1)super关键字
作用:
在Java类中使用super来调用父类中的指定操作:
super可用于访问父类中定义的属性
super可用于调用父类中定义的成员方法
super可用于在子类构造方法中调用父类的构造器
注意:
尤其当子父类出现同名成员时,可以用super进行区分
super的追溯不仅限于直接父类
super和this的用法相像,this代表本类对象的引用,super代表父类的内存空间的标识
super关键字表示在当前类中特别指定要使用父类的成员时使用super限定.
这里的父类不仅包括直接父类, 也包括间接父类.
(2)super使用场景
1. 方法中使用
class Person {
protected String name="张三";
protected int age;
public String getInfo() {
return “Name: ” + name + “\nage: ” + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool(){
return school;
}
@Override
public String getInfo() {
return super.getInfo() +"\nschool: " +school;
// 在方法中使用super可以调用父类中的被覆盖方法
}
}
public class TestStudent{
public static void main(String[] args){
Student st = new Student();
System.out.println(st.getInfo());
} }
2. 构造器中使用
-
子类中所有的构造器默认都会访问父类中空参数的构造器
-
当父类中没有空参数的构造器时,子类的构造器必须通过**this(参数列表)或者super(参数列表)**语句指定调用本类或者父类中相应的构造器,且必须放在构造器的第一行
-
如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
// ……
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age); // 直接显式调用父类有参构造器
school = s;
}
public Student(String name, String s) {
super(name); // 直接显式调用父类有参构造器
school = s;
}
public Student(String s) {
// 直接隐式调用父类无参构造器
school = s;
}
}
(3)子类对象实例化过程
1. 执行顺序
6.7.4 super和this的区别
No | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用直接父类构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 无此概念 |
八、基本特征三 多态
1、什么是多态
(1)本态
一个对象的本类形态就是本态.
(2)多态
一个对象的多种父类形态就是多态
2、多态的使用
(1)多态引用
将子类对象赋值于父类类型的引用变量就是多态引用, 在这里对象其实还是子类对象, 只不过是被看作是一个父类类型的对象.
Person person = new Student();
-
多态性,是面向对象中最重要的概念,在java中有两种体现:
-
方法的重载(overload)和重写(overwrite)。
-
对象的多态性 ------可以直接应用在抽象类和接口上。
-
-
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。
注意:若编译时类型和运行时类型不一致,就出现多态(Polymorphism)
(2)多态带来的问题
- 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,
因而编译错误。
3、虚拟方法调用(Virtual Method Invocation)
(1)什么是虚拟方法
正常的方法调用:
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用(多态情况下):
Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法
编译时类型和运行时类型:
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。
------动态绑定
(2)多态小结
前提:
需要存在继承或者实现关系
要有覆盖操作
成员方法:
编译时:要查看引用变量所属的类中是否有所调用的方法。
(编译时检查父类类型)
运行时:调用实际对象所属的类中的重写方法。
(运行时执行子类类型)
成员变量:
不具备多态性,只看引用变量所属的类。
4、多态的应用场景
(1)多态数组
当创建多个不同的子类对象, 而又想统一处理这批对象时, 就可以使用多态数组.
Person[] arr = {
new Student(), new Teacher()};
public class Person {
private String name;
private int age;
private String gender;
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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 setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
public String say() {
return "姓名:" + name + ",年龄:" + age + ",性别:" + gender;
}
public void sayHello() {
System.out.println("打个招呼");
}
}
public class Chinese extends Person {
private String shuxiang;
public Chinese(String name, int age, String gender, String shuxiang) {
super(name, age, gender);
this.shuxiang = shuxiang;
}
public void setShuxiang(String shuxiang) {
this.shuxiang = shuxiang;
}
public String getShuxiang() {
return shuxiang;
}
public void spring() {
System.out.println("过大年");
}
@Override
public String say() {
return super.say() + ",属相:" + shuxiang;
}
@Override
public void sayHello() {
System.out.println("吃了吗?");
}
}
public class American extends Person {
private boolean hasGun;
public American() {
}
public American(String name, int age, String gender, boolean hasGun) {
super(name, age, gender);
this.hasGun = hasGun;
}
public void setHasGun(boolean hasGun) {
this.hasGun = hasGun;
}
public boolean isHasGun() {
return hasGun;
}
public void christmas() {
System.out.println("Merry Christmas!!");
}
@Override
public void sayHello() {
System.out.println("How are you?");
}
}
public class PersonTest {
public static void main(String[] args) {
Person[] ps = new Person[5]; // 多态数组, 可以保存任意类型的子类对象
ps[0] = new Chinese("张三", 30, "男", "牛");
ps[1] = new American("Jack", 25, "male", true);
ps[2] = new Person("某人", 15, "未知");
ps[3] = new American("Rose", 32, "female", false);
ps[4] = new Chinese("李四", 40, "女", "羊");
for (Person p : ps) {
System.out.println(p.say());
}
System.out.println("-------------------------");
// 因为在同一个数组中, 并且所有对象都有age属性, 所以就可以对所有元素进行冒泡排序
for (int i = 0; i < ps.length - 1; i++) {
for (int j = 0; j < ps.length - 1 - i; j++) {
if (ps[j].getAge() > ps[j + 1].getAge()) {
Person tmp = ps[j];
ps[j] = ps[j + 1];
ps[j + 1] = tmp;
}
}
}
for (Person p : ps) {
System.out.println(p.say());
}
}
}
(2)多态参数
在设计方法时, 有的方法内部需要用到另外的类的对象, 但是在设计方法时, 又不需要关心究竟是哪个对象, 只关心这些实参对象都是某个类型的, 此时, 方法的参数的类型就可以写成父类类型的参数,这样的方法的参数就是多态参数.
public void test(Person p) {
// 这个方法的参数p的具体类型未知
System.out.println(p.say());
}
main() {
test(new Chinese()); // 在实际调用多态参数方法时, 可以传入任意本类或子类类型的对象
test(new American());
}
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
-
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
-
如果x属于类A的子类B,x instanceof A值也为true。
public class Person extends Object {
…}
public class Student extends Person {
…}
public class Graduate extends Person {
…}
-------------------------------------------------------------------
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
-
基本数据类型的Casting:
- 自动类型转换:小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
- **强制类型转换:**可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
-
对Java对象的强制类型转换称为造型
-
从子类到父类的类型可以自动进行
-
从父类到子类的类型转换必须通过造型(强制类型转换)实现
-
无继承关系的引用类型间的转换是非法的
-
在造型前可以使用instanceof操作符测试一个对象的类型
-
class PersonTest5 {
public static void test(Person p) {
// 多态参数, 提高了兼容性!!
p.sayHello(); //方法中父类中定义,所以直接调用,
//p.spring(); // 多态副作用, 子类特有成员不能访问
// 必须对对象的真实身份进行检测
if (p instanceof Chinese) {
// 造型有风险, 使用须谨慎!!!
Chinese ch = (Chinese)p; // 造型: 对象是什么类型还原成什么类型
ch.spring(); // 子类特有的成员的访问,必须要经过造型.
} else if (p instanceof American) {
((American)p).christmas();
} else if (p instanceof Person){
System.out.println("普通人一枚");
}
}
public static void main(String[] args) {
Person[] ps = new Person[5]; // 多态数组, 可以保存任意类型的子类对象
Chinese c1 = new Chinese("张三", 30, "男", "牛");
American a1 = new American("Jack", 25, "male", true);
Person p1 = new Person("某人", 15, "未知");
American a2 = new American("Rose", 32, "female", false);
Chinese c2 = new Chinese("李四", 40, "女", "羊");
ps[0] = c1;
ps[1] = a1;
ps[2] = p1;
ps[3] = a2;
ps[4] = c2;
System.out.println("-------------------------");
for (Person p : ps) {
test(p); // 不同的子类对象作为实参调用方法, 方法的执行也不一样.
}
}
}