Java基础(三)

目录

四、面向对象(中)

1. 继承性

2. 方法的重写

2.1 区分方法的重载与重写

3. 四种访问权限修饰符

4. super关键字的使用

5. 子类对象实例化过程

6. 多态性

6.1 instanceof关键字的使用

7. Object类的使用

7.1 equals()方法

7.2 toString()方法

8. 包装类的使用

9. 单元测试方法


四、面向对象(中)

1. 继承性

一、继承性的好处 :

  1. 减少了代码的冗余,提高了代码的复用性

  2. 便于功能的扩展

  3. 为多态性的使用提供了前提

二、继承性的格式

class A extends B { }

A : 子类 或 派生类 subclass

B : 父类 或 超类 或 基类 superclass

体现 : 一旦子类A继承了父类B后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明的 私有的属性和方法,子类继承父类以后,仍然认为获取了父类中私有的结构,只是由于封装性的影响,使得子 类不能调用父类的结构而已。

public class Person {
    String name;
    private int age;///将其变为私有属性 Student一样可以继承到只是在Student类中不能直接用
​
    public Person() {}
    public Person(String name, int age){
        this.age = age;
        this.name = name;
    }
​
    private void eat(){
        System.out.println("eat ! ");
    }
    public void sleep(){
        this.eat();//在sleep里调用eat方法 当Student类继承了Person类之后
        //eat、sleep方法随之被继承,由于eat方法为private的 所以Student不能直接调用
        //但是可以在sleep方法中间接的调用eat方法 证明了eat方法是被继承了的。
        System.out.println("sleep ! ");
    }
​
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
​
public class Student extends Person{
//    String name;
//    int age;
    String major;
​
    public Student() { super(); }
    public Student(String name, int age, String major){
        super(name, age);
//        this.name = name;
//        this.age = age;
        this.major = major;
    }
​
//    public void eat(){
//        System.out.println("eat ! ");
//    }
//
//    public void sleep(){
//        System.out.println("sleep ! ");
//    }
​
    public void study(){
        System.out.println("study ! ");
    }
}
​
public class ExtendsTest {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.sleep();
​
        Student s1 = new Student();
        s1.name = "Tom";
        s1.setAge(10);
        s1.sleep();
        System.out.println("name : " + s1.name + ", age : " + s1.getAge());
    }
}
​
/*
result :
eat !
sleep !
eat !
sleep !
name : Tom, age : 10
 */

以上示例说明了,继承性和封装性互不影响,继承是继承的所有的属性和方法,但继承之后能不能调用,要看封装性的大小。

子类继承父类之后,子类可以定义一些自己特有的属性和方法,实现功能的扩展,extends : 延展 扩展

三、Java中关于继承性的规定

  1. 一个类可以被多个子类所继承

  2. Java中类的单继承性,一个类只能有一个父类

  3. 子父类是相对的概念,可以多层继承

  4. 子类直接继承的父类称为直接父类,间接继承的父类称为间接父类

  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法

四、如果一个类没有显示的继承一个父类的话,则此类继承于java.lang.Object类

  • 所有的java类 (除 java.lang.Object) ,都直接或间接的继承于java.lang.Object类

  • 意味着,所有的java类都具有java.lang.Object类中声明的功能

2. 方法的重写

  1. override\overwrite : 子类继承父类之后,可以对父类中同名同参的方法,进行覆盖操作

  2. 用法 : 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参的方法时,实际执行的是子类中重写的父类方法。

  3. 重写的规定 :

    方法的声明 : 权限修饰符 返回值类型 方法名 (形参列表) throws 异常的类型 { //方法体 }

    (约定子类中的叫重写方法,父类中的叫被重写的方法)

    • 子类重写的方法的方法名和形参列表与被重写的方法名和形参列表相同

    • 子类重写方法的权限修饰符不小于父类被重写的方法的权限修饰符

      • 特殊规定 : 子类不能重写父类中声明为 private 权限的方法

    • 返回值类型 :

      • 如果父类被重写的方法的返回值类型为void,则子类中重写方法的返回值类型必须为void

      • 如果父类被重写的方法的返回值类型为A类,则子类中重写方法的返回值类型必须为A或A的子类型

      • 如果父类被重写的方法的返回值类型为基本数据类型,则子类中重写方法的返回值类型必须为相同的基本数据类型

    • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型

    • 子类和父类中同名同参数的方法要么都声明为非static的 ( 重写 ),要么都声明为static的 ( 非重写 ) (因为static的方法是随着类的加载而加载的不能被覆盖)

    public class CylinderTest {
        public static void main(String[] args) {
            Cylinder cylinder = new Cylinder();
            cylinder.setRadius(10);
            cylinder.setHeight(2);
    ​
            double area = cylinder.findArea();
            //此处的表面积为重写后的圆柱体表面积 非圆的面积
            System.out.println("表面积为 : " + area);
            double volume = cylinder.findVolume();
            System.out.println("体积为 : " + volume);
        }
    }
    ​
    public class Circle {
        private double radius;
    ​
        public Circle() {}
        public Circle(double radius){
            this.radius = radius;
        }
    ​
        public double getRadius() {
            return radius;
        }
        public void setRadius(double radius) {
            this.radius = radius;
        }
    ​
        public double findArea(){
            return Math.PI * radius * radius;
        }
    }
    ​
    public class Cylinder extends Circle {
        private double height;
    ​
        public Cylinder() {}
        public Cylinder(double radius, double height){
            super(radius);//调用父类的构造器
            this.height = height;
        }
    ​
        public double getHeight() {
            return height;
        }
    ​
        public void setHeight(double height) {
            this.height = height;
        }
    ​
        @Override
        public double findArea() {
            return 2 * super.findArea() + 2 * Math.PI * getRadius() * getHeight();
        }
    ​
        public double findVolume(){
            return super.findArea() * getHeight();
        }
    }
    ​
    /*
    result :
    表面积为 : 753.9822368615504
    体积为 : 628.3185307179587
    314.1592653589793
     */

2.1 区分方法的重载与重写

方法的重写Overriding和重载Overloading是Java多态性的不同表现。重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。

从编译和运行的角度来看 : 重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。

所以,对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为"早绑定"或"静态绑定"而对于多态而言,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,"晚绑定"或"动态绑定"

3. 四种访问权限修饰符

继承和封装没有关系,继承是继承的所有属性和方法,但是由于权限修饰符的不同,属性和方法的可见性大小就不同,以下是代码体现 :

//*****************在自己的类中********************
package com.atguigu.ordertest.order;
​
public class Order {
    private int orderPrivate;
    int orderDefault;
    protected int orderProtected;
    public int orderPublic;
​
    private void methodPrivate(){}
    void methodDefault(){}
    protected void methodProtected(){}
    public void methodPublic(){}
​
    public void myclass(){
        //在自己的类中都可以调用
        orderPrivate = 1;
        orderDefault = 2;
        orderProtected = 3;
        orderPublic = 4;
​
        methodPrivate();
        methodDefault();
        methodProtected();
        methodPublic();
    }
}
//*****************在同一个包中的类中*****************
package com.atguigu.ordertest.order;
​
public class Test {
    public static void main(String[] args) {
        Order order = new Order();
​
        order.orderDefault = 1;
        order.orderProtected = 2;
        order.orderPublic = 3;
​
        order.methodDefault();
        order.methodProtected();
        order.methodPublic();
​
        //private修饰的属性和方法只能在自己类中调用
//        order.orderPrivate = 4;
//        order.methodPrivate();
    }
}
//*****************在不同包的子类中********************
package com.atguigu.ordertest.test;
​
import com.atguigu.ordertest.order.Order;
​
public class ExtendsTest extends Order {
    public void myclass(){
        orderProtected = 1;
        orderPublic = 2;
​
        methodProtected();
        methodPublic();
​
        //缺省修饰的属性和方法不能在包外的子类中调用
//        orderPrivate = 3;
//        orderDefault = 4;
//        methodPrivate();
//        methodDefault();
​
    }
}
//*****************在不同包的非子类中*******************
package com.atguigu.ordertest.test;
​
import com.atguigu.ordertest.order.Order;
​
public class NoExtendsTest {
    public static void main(String[] args) {
        Order order = new Order();
​
        order.orderPublic = 1;
​
        order.methodPublic();
​
        //只有public修饰的属性和方法可以在包外非子类中调用
//        order.orderPrivate = 2;
//        order.orderDefault = 3;
//        order.orderProtected = 4;
//        order.methodPrivate();
//        order.methodDefault();
//        order.methodProtected();
​
    }
}

可见,虽然包外子类中继承了private和缺省的属性和方法,但是由于封装性的限制,在子类中还是无法调用。

  • private 在本类中随便调用

  • 缺省 在同一个包下随便调用

  • protected 在包外子类中随便调用

  • public 同一个工程下随便调用

4. super关键字的使用

  1. super理解为 : 父类的

  2. super可以用来调用属性、方法、构造器

  3. super的使用 :

    • 我们可以在子类的构造器或方法中,通过使用 "super.属性" 或 "super.方法" 的方式,显示的调用父类中声明的属性和方法。但是,通常习惯省略"super."

    • 特殊情况 : 当子类和父类中定义了同名的属性时,要想在子类中调用父类中声明的属性则必须显示的使用"super.属性"的方式,表明调用的是父类中声明的属性。

    • 在方法和构造器中调用的属性和方法,凡是没有加super或this的全都默认为是this,this的寻找的逻辑是先找本类中的,如果没有找到再去父类中找。(子类中声明定义同名的属性不会被覆盖)

    • 特殊情况,当子类中重写了父类的方法以后,我们想在子类中调用父类被重写的方法,则必须显示的使用"super.方法"的方式,表明调用的是父类中被重写的方法。

    • super调用父类构造器,我们可以在子类的构造器中使用 "super(形参列表)"的方式,调用父类中声明的指定的构造器,"super(形参列表)"的使用,必须声明在子类构造器的首行且不能和"this(形参列表)"同时出现

    • 子类的构造器中如果不显示的使用 "super(形参列表)"或 "this(形参列表)" 那么也会在第一行默认有一个 "super ( ) ",默认调用父类中的空参构造器

    • 在类的多个构造器中,至少有一个类的构造器中使用了 "super(形参列表)",调用父类中的构造器

public class SuperTest {
    public static void main(String[] args) {
        SuperStudent superStudent = new SuperStudent("Tom", 18, "计算机科学与技术");
        superStudent.show();
        superStudent.study();
​
        System.out.println("*******************");
        SuperStudent test = new SuperStudent();//会默认调用父类的空参构造器
​
    }
}
​
class SuperPerson{
    String name;
    int age;
    int id = 1001;//身份证号
​
    public SuperPerson() {
        System.out.println("This is a constructor that not have values");
    }
    public SuperPerson(String name){
        this.name = name;
    }
    public SuperPerson(String name, int age){
        this(name);
        this.age = age;
    }
​
    public void eat(){
        System.out.println("SuperPerson eat");
    }
    public void walk(){
        System.out.println("SuperPerson walk");
    }
}
​
class SuperStudent extends SuperPerson{
    String major;
    int id = 1002;//学号
​
    public SuperStudent(){
        //super();//即使不显示的写出也会默认调用super()
    }
    public SuperStudent(String name, int age, String major){
        super(name, age);//调用父类的满参构造器
        this.major = major;
    }
​
    @Override
    public void eat() {
        System.out.println("SuperStudent should eat more healthy");
    }
​
    public void study(){
//        eat();//调用的是重写后的方法
        super.eat();//调用的是父类中的方法
        System.out.println("SuperStudent study");
    }
​
    public void show(){
        System.out.println("name : " + name + ", age : " + age);
        //子类中声明定义与父类同名的属性不会被覆盖
        System.out.println("id : " + id);//此时调用的是子类中另外加的id而不是从父类中继承的id
        System.out.println("super.id : " + super.id);//此时调用的是从父类中继承的id
    }
}

5. 子类对象实例化过程

  1. 从结构上来看 :

    • 子类继承父类以后,就获取了父类中声明的属性或方法。

    • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

  2. 从过程上来看 :

    当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,........,直到调用了 java.lang.Object 类中的空参构造器为止。正因为加载过所有的父类结构,所有才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。

    明确 : 虽然创建子类对象时,调用了父类的构造器,但是自始至终只创建过一个对象,即new的对象。

6. 多态性

面向对象特征之三 : 多态性

理解多态性 : 可以理解为一个事物的多种形态

什么是多态性 : 父类的引用指向子类的对象 ( 子类的对象赋给父类的引用 )

多态的使用 : 虚拟方法调用

  • 有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法。但在运行期,我们实际执行的是子类重写父类的方法。

  • 总结 : 编译看左边,运行看右边。

  • 对象的多态性只适用于方法,不适用于属性。

多态性的使用前提 :

  1. 类的继承关系

  2. 方法的重写

public class DTTest {
    public static void main(String[] args) {
        //对象的多态性 : 父类的引用指向子类的对象
        DTPerson p1 = new DTMan();
        DTPerson p2 = new DTWoman();
​
        //多态的使用
        //当调用子父类同名同参的方法时,实际执行的是子类重写父类的方法--虚拟方法的调用
        p1.eat();//编译的时候看eat方法看的是DTPerson中有没有而不是看DTMan中
        p1.walk();//但执行的时候执行的是子类中重写的方法
​
//        p1.earnMoney();//DTPerson中没有这个方法 所以不能调用
    }
}
​
class DTPerson{
    String name;
    int age;
​
    public void eat(){
        System.out.println("DTPerson eat");
    }
​
    public void walk(){
        System.out.println("DTPerson walk");
    }
}
​
class DTMan extends DTPerson{
    boolean isSmoking;
​
    public void earnMoney(){
        System.out.println("DTMan earnMoney");
    }
​
    @Override
    public void eat() {
        System.out.println("DTMan eat");
    }
​
    @Override
    public void walk() {
        System.out.println("DTMan walk");
    }
}
​
class DTWoman extends DTPerson{
    boolean isBeauty;
​
    public void goShopping(){
        System.out.println("DTWoman goShopping");
    }
​
    @Override
    public void eat() {
        System.out.println("DTWoman eat");
    }
​
    @Override
    public void walk() {
        System.out.println("DTWoman walk");
    }
}

为什么要有多态性 ?

public class DTUseTest {
    public static void main(String[] args) {
        System.out.println("new Dog() : ");
        fun(new Dog());
        System.out.println("new Cat() : ");
        fun(new Cat());
    }
    //利用多态性
    public static void fun(Animal animal){
        animal.eat();
        animal.shout();
    }
    //如果没有多态 那么就要写以下三个方法
/*    public void fun(Animal animal){
        animal.eat();
        animal.shout();
    }
    public void fun(Dog dog){
        dog.eat();
        dog.shout();
    }
    public void fun(Cat cat){
        cat.eat();
        cat.shout();
    }*/
}
​
class Animal{
    public void eat(){
        System.out.println("animal eat");
    }
    public void shout(){
        System.out.println("animal shout");
    }
}
​
class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("dog eat");
    }
​
    @Override
    public void shout() {
        System.out.println("dog shout");
    }
}
​
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("cat eat");
    }
​
    @Override
    public void shout() {
        System.out.println("cat shout");
    }
}
​
//举例二 :
class Order{
    //这里的对象是 Object 的 意味着任何对象都可以放进去
    public void method(Object obj){
        
    }
}

虚拟方法调用 :

子类中定义了与父类同名同参的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

编译时类型和运行时类型 : 编译时为父类类型,而方法的调用是在运行时确定的。所以调用的是子类的方法。

--------动态绑定

多态是编译时行为还是运行时行为?(面试题)

证明见以下代码 : 直观看看不出结果 只有运行才可以看出结果

//面试题:多态是编译时行为还是运行时行为?
//证明如下:
class Animal  {
​
    protected void eat() {
        System.out.println("animal eat food");
    }
}
​
class Cat  extends Animal  {
​
    protected void eat() {
        System.out.println("cat eat fish");
    }
}
​
class Dog  extends Animal  {
​
    public void eat() {
        System.out.println("Dog eat bone");
​
    }
​
}
​
class Sheep  extends Animal  {
​
​
    public void eat() {
        System.out.println("Sheep eat grass");
​
    }
​
​
}
​
public class InterviewTest {
​
    public static Animal  getInstance(int key) {
        switch (key) {
            case 0:
                return new Cat ();
            case 1:
                return new Dog ();
            default:
                return new Sheep ();
        }
​
    }
​
    public static void main(String[] args) {
        int key = new Random().nextInt(3);
​
        System.out.println(key);
​
        Animal  animal = getInstance(key);
​
        animal.eat();
​
    }
}

多态性的练习 :

  1. 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统不可能把父类里的方法转移到子类中 : 编译看左边,运行看右边

  2. 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量 : 编译运行都看左边

public class DTExer {
    public static void main(String[] args) {
        Sub s = new Sub();
        System.out.println(s.count);//20
        s.display();//20
​
        Base b = s;//多态性
        System.out.println(b == s);//地址值相同 true
        System.out.println(b.count);//10
        b.display();//20
    }
}
​
class Base{
    int count = 10;
​
    public void display(){
        System.out.println(this.count);
    }
}
​
class Sub extends Base{
    int count = 20;
​
    public void display(){
        System.out.println(this.count);
    }
}
//考查多态的笔试题目:
public class InterviewTest1 {
​
   public static void main(String[] args) {
      Base base = new Sub();
      base.add(1, 2, 3);//sub_1
​
//    Sub s = (Sub)base;
//    s.add(1,2,3);
   }
}
​
class Base {
   public void add(int a, int... arr) {
      System.out.println("base");
   }
}
​
class Sub extends Base {
    //此方法和可变个数形参的认为是两个相等的方法,构成了重写
   public void add(int a, int[] arr) {
      System.out.println("sub_1");
   }
​
   //打开以下方法以后,这是子类特有的方法,所以多态的时候不能调用,只有强转成子类时,才优先考虑此方法
// public void add(int a, int b, int c) {
//    System.out.println("sub_2");
// }
}

6.1 instanceof关键字的使用

有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。

那么如何才能调用子类特有的属性和方法呢 ?

Person p = new Man();//对象的多态性 既然new了Man()那么就一定有Man的属性和方法
//如何才能调用子类的属性和自己的方法呢 ? 
//向下转型 : 使用强制类型转换符
Man m = (Man)p;
m.isSmoking = true;
m.earnMoney();

但是在强制类型转换时,会出现转换错误的情况 :

不相关的两个类在强制类型转换的编译期不会报错,但是在运行期会报错

Person p = new Man();
Woman w = (Woman)p;//编译器不报错
w.goshopping();//会报ClassCastException的异常 : 类型转换异常

为了避免出现这种问题,引入instanceof关键字 :

  • a instanceof A : 判断对象a是否为类A的实例,如果是返回true,否则返回false

  • 其中,在判断的时候,如果判断 a instanceof A的父类,返回的也是true

  • 使用情境 : 为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型,如果返回false,不进行向下转型。

if (p instanceof Woman){
    Woman w = (Woman)p;
    w.goShopping();
}
if (p instanceof Object){
    Object obj = (Object)p;
}

基本数据类型 :

  • 向下转型 : 强制类型转换(可能损失精度)

  • 向上转型 : 自动类型提升

类 :

  • 向下转型 : 强制类型转换(使用instanceof进行判断)

  • 向上转型 : 对象的多态性

 

7. Object类的使用

  1. java.lang.Object类是所有Java类的根父类

  2. 如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类

    public class ObjectTest {
        public static void main(String[] args) {
            Example example = new Example();
            System.out.println(example.getClass().getSuperclass());
            //class java.lang.Object
        }
    }
    ​
    class Example{
    }
  3. Object类中的功能(属性、方法)就具有通用性

  4. Object类只声明了一个空参构造器,没有属性,有以下方法 :

 

7.1 equals()方法

回顾 "=="运算符的使用 : 可以使用在基本数据类型变量和引用数据类型变量当中

  • 基本数据类型 : 比较的数值(保存的数据)是否相等(不一定数据类型要相同)

  • 引用数据类型 : 比较两个对象的地址值是否相同 即两个引用是否指向同一个对象实体

public class EqualsTest {
    public static void main(String[] args) {
        //基本数据类型
        int i = 10;
        char c = 10;
        System.out.println(i == c);//true 比较的保存的数据
        
        //引用数据类型
        Customer c1 = new Customer("Tom", 21);
        Customer c2 = new Customer("Tom", 21);
        System.out.println(c1 == c2);//false 比较的地址值
​
        String str1 = new String("Tom");
        String str2 = new String("Tom");
        System.out.println(str1 == str2);//false
​
        String str3 = "Tom";
        String str4 = "Tom";
        System.out.println(str3 == str4);//true 表明使用这种声明创建对象的方式,会根据值相等而指向同一个对象实体
    }
}
​
class Customer{
    String name;
    int age;
​
    public Customer() {}
    public Customer(String name, int age){
        this.age = age;
        this.name = name;
    }
​
    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;
    }
}

equals()方法的使用 :

  • 是一个方法而非运算符,所以不能用于基本数据类型,只适用于引用数据类型

  • Object中的equals()方法 :

    public boolean equals(Object obj) {
        return (this == obj);
    }

    可见,Object类中的equals()方法实质和"=="运算符的作用一样,比较的是对象的地址值

  • 像String、Date、File、包装类等都重写了Object中的equals()方法,重写了以后比较的是两个对象的实体内容(最关心的是属性)是否相同。

  • 通常情况下,我们自定义的类如果使用equals()的话,也通常比较两个对象的实体内容(最关心的是属性)是否相同。那么,我们就需要对Object类中的equals()方法进行重写。

  • 重写的时候,首先判断地址值,如果地址值都一样,那么两个对象一定相等。其次,判断Object接收的对象是否是该对象的类型(instanceof 判断),如果不是那么就一定不相等,如果是,那么进一步比较对象内容。

    @Override
    public boolean equals(Object obj) {
        //判断两个对象的地址是否相同
        if (obj == this) return true;
        //判断两个对象是否为一个类型或类型的子类
        if (obj instanceof Customer){
            //比较两个对象的属性是否相同,注意引用数据类型继续用equals方法比较
            Customer customer = (Customer) obj;
            return this.name.equals(customer.name) && this.age == customer.age;
        } else return false;
    }
    ​
    /*
    关于重写equals()方法第二步instanceof的说明 :
    首先将重写equals()方法的类称为规定的类
    由于这个方法用到多态,且需要强制类型转换,所以在转换之前利用instanceof关键字来判断一下
    传入的对象是否为规定的类或其子类(instanceof关键字在当传入对象为要判断的类的子类时,返
    回值为true),若为其规定的类型,那么就直接强制类型转换比较就可以,然后比较对象的内容即可
    如果传入的对象为规定的类的子类,那么可以利用多态再将其提升到和规定的类一样的类型,或者也
    可以利用强制类型转换转换为规定的类,由于不确定性,所以统一利用强制类型转换转换为规定的类
    然后再判断对象的内容。
    */

7.2 toString()方法

  1. 当我们输出一个对象的引用时,实际上就是调用对象的toString()方法(在println方法内部调用了传入对象的toString方法)

  2. Object类中的toString方法 :

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  3. 像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用对象的toString()方法时,返回"实体内容"信息。

  4. 自定义类也可以重写toString()方法,返回"实体内容"信息。

public class ToStringTest {
    public static void main(String[] args) {
        MyDate myDate = new MyDate(2022, 7, 18);
        //当toString()方法没重写的时候,输出的就是地址值
//        System.out.println(myDate);//com.atguigu.oop2.MyDate@1b6d3586
//        System.out.println(myDate.toString());//com.atguigu.oop2.MyDate@1b6d3586
        //当toString()方法重写之后,输出的为自定义字符串
        System.out.println(myDate);//MyDate{year=2022, month=7, day=18}
        System.out.println(myDate.toString());//MyDate{year=2022, month=7, day=18}
    }
}
​
class MyDate{
    private int year;
    private int month;
    private int day;
​
    public MyDate() {}
    public MyDate(int year, int month, int day) {
        this.year = year;
        this.month = month;
        this.day = day;
    }
​
    public int getYear() {
        return year;
    }
​
    public void setYear(int year) {
        this.year = year;
    }
​
    public int getMonth() {
        return month;
    }
​
    public void setMonth(int month) {
        this.month = month;
    }
​
    public int getDay() {
        return day;
    }
​
    public void setDay(int day) {
        this.day = day;
    }
​
    @Override
    public String toString() {
        return "MyDate{" +
                "year=" + year +
                ", month=" + month +
                ", day=" + day +
                '}';
    }
}

8. 包装类的使用

Wrapper Class

  • 针对八种基本数据类型定义相应的引用类型---包装类 (封装类)

  • 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象

    可见,数值型的包装类都继承于 Number 类

  • 基本数据类型、包装类、String三者之间的相互转换

    public class WrapperTest {
        //包装类\基本数据类型 --> String : 调用String重载的 ValueOf(Xxx xxx) 静态方法
        @Test
        public void test05(){
            int num = 10;
            //方式一 :
            String str = num + "";
            //方式二 :
            //基本数据类型 :
            String str1 = String.valueOf(num);
            System.out.println(str1);
            //包装类 :
            Integer in1 = new Integer(10);
            String str2 = String.valueOf(in1);
            System.out.println(str2);
    ​
            Boolean b1 = new Boolean(true);
            String str3 = String.valueOf(b1);
            System.out.println(str3);
        }
    ​
        //String --> 包装类 : 调用包装类的 parseXxx() 静态方法 返回的是基本数据类型的值
        @Test
        public void test04(){
            String str1 = "123";
            int i1 = Integer.parseInt(str1);
            System.out.println(i1);
    ​
            boolean b1 = Boolean.parseBoolean("true");
            System.out.println(b1);
            boolean b2 = Boolean.parseBoolean("true1");
            System.out.println(b2);
        }
    ​
        //JDK 5.0 新特性 : 自动装箱 自动拆箱
        @Test
        public void test3(){
            //自动装箱 基本数据类型 --> 包装类
            int i = 10;
            Integer in1 = i;
            method(in1);
            method(12);//自动装箱
            Integer in2 = 100;
            System.out.println(in1.toString());
            System.out.println(in2.toString());
    ​
            Boolean b1 = true;
            Boolean b2 = false;
            System.out.println(b1.toString());
            System.out.println(b2.toString());
    ​
            //自动拆箱 包装类 --> 基本数据类型
            Integer in3 = new Integer(32);
            int i2 = in3;
            System.out.println(in3);
            Float fl1 = new Float(12.3);
            float f1 = fl1;
            System.out.println(f1);
        }
        public void method(Object obj) {}
    ​
        //包装类 --> 基本数据类型 : 调用 对象.xxxValue() 方法
        @Test
        public void test2(){
            Integer in1 = new Integer("12");
            int i = in1.intValue();
            System.out.println(i + 1);
    ​
            Character ch1 = new Character('b');
            char c1 = ch1.charValue();
            System.out.println((c1));
        }
    ​
        //基本数据类型 --> 包装类 : 调用包装类的构造器
        @Test
        public void test1(){
            int i = 10;
    //        System.out.println(i.toString());
            Integer in1 = new Integer(i);
            System.out.println(in1.toString());
            Integer in2 = new Integer("123");
            System.out.println(in2.toString());
            // 数值型包装类构造器中的字符串要符合对应数的格式
            //否则报NumberFormatException异常
    //        Integer in3 = new Integer("123abc");
    //        System.out.println(in3.toString());
    ​
            Float fl1 = new Float(12.3);//调用的是double的构造器
            Float fl2 = new Float(12.3f);//调用的是float的构造器
            Float fl3 = new Float("12.3");//调用的是String的构造器
            System.out.println(fl1);
            System.out.println(fl2);
            System.out.println(fl3);
    ​
            Boolean b1 = new Boolean("true");
            System.out.println(b1.toString());
            //Boolean包装类使用String构造器时,除了true字符串(大小写可以转换)为true,其余的都为false
            Boolean b2 = new Boolean("true1");
            System.out.println(b2.toString());
    ​
            BooleanTest booleanTest = new BooleanTest();
            System.out.println(booleanTest.isMale);//默认为false
            System.out.println(booleanTest.isFemale);//默认为null(引用数据类型)
        }
    }
    ​
    class BooleanTest{
        boolean isMale;//基本数据类型
        Boolean isFemale;//包装类
    }

关于包装类的面试题 :

public class WrapperInterviewTest {
    @Test
    public void test1(){
        Object o1 = true ? new Integer(1) : new Double(2.0);
        System.out.println(o1);//1.0
        //对于三元运算符后面两个的结构要统一为一个类型,所以编译的时候要自动类型提升为double类型
    }
    @Test
    public void test2(){
        Integer i = new Integer(1);
        Integer j = new Integer(1);
        System.out.println(i == j);//false
​
        Integer m = 1;
        Integer n = 1;
        System.out.println(m == n);//true
​
        Integer x = 128;
        Integer y = 128;
        System.out.println(x == y);//false
    }
​
    /*
    在Integer中定义了一个内部类为 IntegerCache 里面存有一个Integer[]数组 范围是 -128 ~ 127
    在类加载时提前加载好,如果使用自动装箱的方式,且数符合这个范围,就会直接用这里面的数,所以
    对应的地址一样。目的 : 提高效率
    如果用new的方式创建不同的对象,那么其地址一定不一样
     */
}

9. 单元测试方法

Java中的JUnit单元测试

步骤 :

  1. 在工程下添加JUnit4的jar包

  2. 创建Java类,进行单元测试,此时的Java类的要求为 :

    • 此类是public的

    • 此类提供公共的空参构造器

  3. 在此类中声明单元测试方法 : 方法的权限是public且无返回值、无形参

  4. 此单元测试方法上需要声明 @Test,并在单元测试类中导入 : import org.junit.Test

  5. 声明好单元测试方法以后,就可以在方法体内测试相关的代码

  6. 点击直接运行,光标放在哪个单元测试方法里,就执行的那个单元测试方法

import org.junit.Test;
​
public class JUnitTest {
    @Test
    public void testEquals(){
        String str1 = "MM";
        String str2 = "MN";
​
        System.out.println(str1.equals(str2));
​
    }
    @Test
    public void testToString(){
        String str = "MM";
        System.out.println(str.toString());
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

侯静川

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值