第04章 面向对象-中
创作日期:2021-11-05
Java 面向对象学习的三条主线:
- Java 类及类的成员:属性,方法,构造器,代码块,内部类
- 面向对象的三大特征:封装性,继承性,多态性,(抽象性)
- 其他关键字:this,super,static,final,abstract,interface,import等
1.面向对象特征之二:继承性
1.1 继承的概念 Object类是所有类的父类
继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
子类继承父类之后,自己也可以声明一些特有的属性和方法,实现功能的拓展。
1.2 类的继承格式
在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:class A extends B{ } ,一旦子类继承了父类以后,子类就获取到了父类中声明的结构:属性,方法。
- A:子类,派生类,subclass B:父类,超类,基类,superclass
class 父类 {
}
class 子类 extends 父类 {
}
1.3 继承的好处
减少了代码的冗余,提高了代码的复用性
便于功能的扩展
为之后多态性的使用,提供了前提
1.4 继承类型
- 需要注意的是 Java 不支持多继承,但支持多重继承。
- 一个子类只能有一个父类,一个父类可以派生出多个子类。
- 直接继承的类称之为:直接父类,间接继承的类称之为:间接父类。
1.5 继承性的实力练习
//测试类
public class KidsTest {
public static void main(String[] args) {
//创建子类对象
Kids someKid = new Kids();
//子类对象继承父类的成员属性和方法进行设置
someKid.setSex(1);
someKid.setSalary(4500);
someKid.manOrman();//man
someKid.employeed();//job
}
}
// ManKind 父类
class ManKind {
private int sex;
private int salary;
public ManKind() {
}
public ManKind(int sex, int salary) {
this.sex = sex;
this.salary = salary;
}
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public void manOrman() {
if (this.sex == 1) {
System.out.println("man");
} else {
System.out.println("woman");
}
}
public void employeed() {
if (this.salary == 0) {
System.out.println("no job");
} else {
System.out.println("job");
}
}
}
//Kids 子类
class Kids extends ManKind {
private int yearsOld;
public int getYearsOld() {
return yearsOld;
}
public void setYearsOld(int yearsOld) {
this.yearsOld = yearsOld;
}
public void printAge() {
System.out.println(yearsOld);
}
}
//测试类
public class CylinderTest {
public static void main(String[] args) {
Cylinder cylinder = new Cylinder();
cylinder.setRadius(5);
cylinder.setLength(5);
double volume = cylinder.findVolume();
System.out.println("底面半径为:"+cylinder.getRadius()+"厘米,\n圆柱体的面积为:"+String.format("%.2f", volume)+"立方厘米");
}
}
//圆
class Circle {
//半径
private double radius;
public Circle() {
}
public Circle(double radius) {
this.radius = 1.0;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea(){
return radius*radius*Math.PI;
}
}
class Cylinder extends Circle{
private double length;
public Cylinder(double length) {
this.length = 1.0;
}
public Cylinder() {
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public double findVolume(){
return findArea()*length;
}
}
2.方法的重写 (override/overwrite)
2.1 定义
在子类中可以根据需要对父类中继承来的方法进行改造,也成为方法的重置,覆盖。在程序执行时,子类的方法将覆盖父类的方法。
子类继承父类以后,可以对父类中同名同参数的方法进行覆盖操作。
2.2 重写的规定
子类重写的方法必须和父类被重写的方法具有相同的方法名称,参数列表
子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型
子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限
子类不能重写父类中声明为private权限的方法
子类方法抛出的异常不能大于父类被重写方法的异常
2.3 注意
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写),因为static方法属于类的,子类无法覆盖父类的方法。
2.4 练习
//在kids类中加入以下重写代码
@Override
public void employeed() {
System.out.println("kids should study and no job!");
}
3.关键字:super
3.1 super的简述
super理解为:父类的,可以用来调用:属性,方法,构造器
3.2 super的使用
我们可以在子类的方法或构造器中,通过使用“super.属性”或“super.方法”的方式,显式的调用父类中声明的属性和方法。但是,通常情况下,我们习惯省略“super.”
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用“super.属性”的方式,表示调用的是父类中声明的属性。
- 特殊情况,当子类重写父类中的方法以后,我们想在子类的方法中调用父类被重写的方法时,则必须显式的使用“super.方法”的方式。表明调用的是父类中被重写的方法。
3.3 super调用构造器
- 我们可以在子类的构造器中显式的使用“super(形参列表)”的方式,调用父类中声明的指定的构造器
- “super(形参列表)”的使用,必须声明在子类构造器中的首行!
- 我们在类的构造器中,针对于“this(形参列表)”或“super.(形参列表)”只能二选一,不能同时出现
- 在构造器的首行,没有显式的声明“this(形参列表)”或“super.(形参列表)”,则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少有一个类的构造器使用了“super.(形参列表)”,调用父类中的构造器
class Person {
public static void prt(String s) {
System.out.println(s);
}
Person() {
prt("父类·无参数构造方法: "+"A Person.");
}//构造方法(1)
Person(String name) {
prt("父类·含一个参数的构造方法: "+"A person's name is " + name);
}//构造方法(2)
}
public class Chinese extends Person {
Chinese() {
super(); // 调用父类构造方法(1)
prt("子类·调用父类"无参数构造方法": "+"A chinese coder.");
}
Chinese(String name) {
super(name);// 调用父类具有相同形参的构造方法(2)
prt("子类·调用父类"含一个参数的构造方法": "+"his name is " + name);
}
Chinese(String name, int age) {
this(name);// 调用具有相同形参的构造方法(3)
prt("子类:调用子类具有相同形参的构造方法:his age is " + age);
}
public static void main(String[] args) {
Chinese cn = new Chinese();
cn = new Chinese("codersai");
cn = new Chinese("codersai", 18);
}
}
4.子类对象实例化过程
从结果上看:(继承性)
子类继承父类以后,就获取了父类中声明的属性和方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。
从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Obejct类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以进行调用。
5.面向对象特征之三:(多态性)
5.1 多态性的概述
多态性,指一个事物的多种形态,是面向对象中最重要的概念,在Java中的体现:
对象的多态性:父类的引用指向子类的对象
可以直接应用在抽象类和接口上
5.2 多态性的使用
Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时变量由实际赋给该变量的对象决定,简称:编译时,看左边,运行时,看右边。若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism),对象的多态性只适应方法,不适应属性。
- 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类持有的方法)
- “看右边“:看的是子类的对象(实际运行的是子类重写父类的方法)
5.3 多态性的使用前提
- 有类的继承
- 有方法的重写
5.4 虚拟方法调用(Virtual Method Invocation)
- 正常的方法调用:
Person e= new Person();
e.getInfo();
- 虚拟方法调用(多态情况下):子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据付给它的不同子类对象,动态调用属于子类的方法。这样的方法调用在编译器是无法确定的。
Person e= new Student();
e.getInfo(); //调用Student类的getInfo()方法
- 编译时类型和运行时类型:编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。 ——动态绑定
5.5 instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。要求x所属的类与类A必须是子类和父类的关系,否则编译错误。如果x属于类A的子类B,x instanceof A值也为true。
public class InstanceTest {
public static void main(String[] args) {
InstanceTest instanceTest = new InstanceTest();
instanceTest.method(new Student());
}
public void method(Person e){
String info = e.getInfo();
System.out.println(info);
if (e instanceof Graduate){
System.out.println("a graduated student");
System.out.println("a student");
System.out.println("a person");
}else if (e instanceof Student){
System.out.println("a student");
System.out.println("a person");
}else {
System.out.println("a person");
}
}
}
5.6 向下转型
5.7 多态性的使用实例
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
/*
* 定义一个测试类GeometricTest,
* 编写equalsArea方法测试两个对象的面积是否相等(注意方法的参数类型,利用动态绑定技术),
* 编写displayGeometricObject方法显示对象的面积(注意方法的参数类型,利用动态绑定技术)。
*
*/
public class GeometricTest {
public static void main(String[] args) {
GeometricTest geometricTest=new GeometricTest();
Circle circle=new Circle(2,"white",1.0);
Circle circle1=new Circle(3,"white",1.0);
boolean isEquals = geometricTest.equalsArea(circle,circle1);
System.out.println(isEquals);
MyRectangle myRectangle= new MyRectangle(2,3,"black",1.0);
System.out.println(geometricTest.displayGeometricObject(myRectangle));
}
public double displayGeometricObject(GeometricObject o) {//GeometricObject o = new Circle(...)
return o.findArea();
}
public boolean equalsArea(GeometricObject o1, GeometricObject o2) {
return o1.findArea() == o2.findArea();
}
}
class GeometricObject {//几何图形
private String color;
private double weight;
public GeometricObject(String color, double weight) {
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public double findArea(){
return 0.0;
}
}
class Circle extends GeometricObject{
private double radius;
public Circle(double radius,String color, double weight) {
super(color, weight);
this.radius=radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
@Override
public double findArea() {
return Math.PI*radius*radius;
}
}
class MyRectangle extends GeometricObject{
private double width;
private double height;
public MyRectangle(double width,double height,String color, double weight) {
super(color, weight);
this.height=height;
this.width=width;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
@Override
public double findArea() {
return height*width;
}
}
5.8 继承成员变量和继承方法的区别:实例说明:
- 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中:编译看左边,运行看右边
- 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
6.Object类的使用
6.1 Object类的概述
Object类是所有Java类的根父类,只声明了一个空参的构造器,如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
6.2 Object类的方法
Object类中的功能(属性,方法)就具有通用性。
属性:无
方法:equal() / toString() / getClass() / hashCode() / clone() / finalize() / wait() / notify() / notifyAll()
6.3 equal()方法的使用
面试题: == 和equals( )的区别
” == “可以使用在基本数据类型变量和引用数据类型变量中
如果比较的是基本数据类型,比较两个变量保存的数据是否相等。(不一定类型要相同)
如果比较的是引用型数据变量,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体(类型要相同)
基本数据类型比较
- 引用型数据变量
- equals( )是一个方法,而非运算符,只能适用于引用数据类型,而String,Date,File,包装类等都重写了Obejct类中的equals( )方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容“是否相同。Object类中的equlas( )的定义:
public boolean equals(Object obj) {
return (this == obj);
}
//说明:Object类中定义的equlas( )和 == 的作用是相同的,比较两个对象的地址值是否相同,即两个引用是否指向同一个对象实体
public class test {
public static void main(String args[]) {
Kids kids1 = new Kids(1);
Kids kids2 = new Kids(1);
boolean equals = kids1.equals(kids2);//false
System.out.println(equals);
String s1 = new String("a");
String s2 = new String("a");
boolean equals1 = s1.equals(s2);//true
System.out.println(equals1);
}
}
6.4 重写equlas( )方法
通常情况下,我们自定义类中使用equlas( )方法也是为了比较”实体内容“是否相同,那么我们就需要对在自定义类中对equlas( )方法进行重写,重写的原则是比较两个对象的实体是否相同。
//自定义类中equals()方法的重写
//Class :自定义类的类名
//a :自定义类中的变量名
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Class cla = (Class) o;
return a == cla.a;
}
6.5 equlas( )方法的练习实例
//练习1
public class OderTest {
public static void main(String[] args) {
Oder oder1 = new Oder(88, "lisi");
Oder oder2 = new Oder(88, "lisi");
System.out.println(oder1.equals(oder2));//结果为:true
}
}
class Oder {
private int orderId;
private String orderName;
public Oder() {
}
public Oder(int orderId, String orderName) {
this.orderId = orderId;
this.orderName = orderName;
}
public int getOrderId() {
return orderId;
}
public void setOrderId(int orderId) {
this.orderId = orderId;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
//重写Object父类的equals()方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Oder oder = (Oder) o;
return orderId == oder.orderId && Objects.equals(orderName, oder.orderName);
}
}
7.Object类下:toString( )方法的使用
7.1 Object类中toString( )方法的简述
- 当我们输出一个对象的引用时,实际上是调用当前对象的toString( )方法
- toString( )方法在Obejct类中定义,其返回值是String类型,返回类名和它的引用地址。
- 在进行String与其它类型数据的连接操作时,自动调用toString( )方法
- 可以根据需要在用户自定义类型中重写toString( )方法
- 基本类型数据转换为String类型时,调用了对应包装类的toString( )方法
7.2 Object类中toString( )方法的定义
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
7.3 Object类中toString( )方法的重写
像String,Date,File,包装类等都重写了Object类中的toString( )方法。使得在调用对象的toString( )时,返回“实体内容”信息。自定义类也可以重写toString( )方法,,当调用此方法时,返回对象的“实体内容”。
//toString方法的重写
//return返回值内容可以根据需求自定义返回值内容
//当前Order 为 类名
//当前orderId orderName 为属性名
@Override
public String toString() {
return "Oder{" +
"orderId=" + orderId +
", orderName='" + orderName + '\'' +
'}';
}
7.4 Object类中toString( )方法的使用实例
//测试类
public class CircleTest {
public static void main(String[] args) {
Circle c1 = new Circle(2.3);
Circle c2 = new Circle("white",2.3,2.3);
System.out.println("颜色是否相等:"+c1.getColor().equals(c2.getColor()));
//颜色是否相等:true
System.out.println("半径是否相等:"+c1.equals(c2));//半径是否相等:true
System.out.println(c1);//Circle{radius=2.3}
System.out.println(c2.toString());//Circle{radius=2.3}
}
}
//父类 GeometricObject
class GeometricObject {
private String color;
private double weight;
public GeometricObject() {
super();
this.color = "white";
this.weight = 1.0;
}
public GeometricObject(String color, double weight) {
super();
this.color = color;
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
}
//子类 Circle
class Circle extends GeometricObject{
private double radius;
public Circle() {
super();
radius = 1.0;
}
public Circle(double radius) {
super();
this.radius = radius;
}
public Circle(String color, double weight, double radius) {
super(color, weight);
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
public double findArea(){
return Math.PI*radius*radius;
}
//重写equals()方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Circle circle = (Circle) o;
return Double.compare(circle.radius, radius) == 0;
}
//重写toString()方法
@Override
public String toString() {
return "Circle{" +
"radius=" + radius +
'}';
}
}
8.Java中的JUnit单元测试
8.1 Java中的JUnit单元测试的使用步骤
- 创建Java类,进行单元测试(此时的Java类的要求:此类必须是public(公共类),此类提供公共的无参的构造器)
- 此类中声明单元测试方法:方法的权限是public,没有返回值,没有形参
- 单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 声明好单元测试以后,就可以在方法体内测试相关代码
- 每个测试类都可以独立运行,进行测试方法体内的需求
8.2 Java中的JUnit单元测试的使用实例
//利用Java单元测试方法进行便捷测试代码
import org.junit.Test;
public class CircleTest {
Circle c1 = new Circle(2.3);
Circle c2 = new Circle("white",2.3,2.3);
//单元测试的使用
@Test
public void CircleEquals(){
System.out.println("颜色是否相等:"+c1.getColor().equals(c2.getColor()));//颜色是否相等:true
System.out.println("半径是否相等:"+c1.equals(c2));//半径是否相等:true
}
//单元测试的使用
@Test
public void CircleToString(){
System.out.println(c1);//Circle{radius=2.3}
System.out.println(c2.toString());//Circle{radius=2.3}
}
}
9.包装类(Wrapper)的使用
9.1 包装类的简述
- 针对八种基本数据类型定义相应的引用类-包装类(封装类),使得基本数据类型的变量具有类的特征
- 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
9.2 基本数据类型,包装类与String类间的转换
基本数据类型转换为 ----> 包装类 :调用包装类的构造器
@Test
public void demo01() {
int num = 10;
Integer integer = new Integer(num);
System.out.println(integer.toString());
Boolean ble = false;
Boolean aBoolean = new Boolean(ble);
System.out.println(aBoolean.toString());
}
- 包装类转换为 ----> 基本数据类型 :调用包装类的xxxValue
@Test
public void demo02() {
Integer integer = new Integer(10);
int i = integer.intValue();
System.out.println(i);
Boolean aBoolean = new Boolean(false);
boolean b = aBoolean.booleanValue();
System.out.println(b);
}
- Java5.0新特性:自动装箱与自动拆箱
//③.包装类的自动装箱和自动拆箱
@Test
public void demo03() {
//自动装箱 基本数据类型 ---> 包装类对象
int num1 = 10;
Integer integer1 = num1; //自动装箱
System.out.println(integer1.toString());
boolean ble1 = false;
Boolean aBoolean1 = ble1; //自动装箱
System.out.println(aBoolean1.toString());
//自动拆箱 包装类对象 ---> 基本数据类型
Integer integer2 = 10;
int num2 = integer2;
System.out.println(num2); //自动拆箱
Boolean aBoolean2 = false;
boolean ble2 = aBoolean2;
System.out.println(ble2); //自动拆箱
}
- 基本数据类型,包装类 ----> String类型
//④.基本数据类型,包装类 ----> String类型 :调用String重载的ValueOf(Xxx xxx)
@Test
public void demo04() {
int num1 = 10;
//方式一:连接运算
String str1 = num1 + " 数字";
System.out.println(str1);
//方式二:调用String重载的ValueOf(Xxx xxx)
int num2 = 10;
String str2 = String.valueOf(num2);
System.out.println(str2+" "+str2.getClass());
}
9.3 包装类的使用实例应用
//如下两个题目输出结果相同吗?各是什么:
@Test
public void demo05() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);//1.0
Object o2;
if (true)
o2 = new Integer(1);
else
o2 = new Double(2.0);
System.out.println(o2);//1
}