Java基础知识-第5章-JAVA面向对象解析(中)

196 阅读33分钟

导图

image.png

1、面向对象特征--继承性

1.1、继承性概述

继承是java 面向对象编程技术的一块基石,因为它允许创建分等级层次的类。继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

为什么要有类的继承性?

  • 子类父类代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高,因此继承就减少了代码的冗余,提高了代码的复用性
  • 便于功能的扩展,子类可以对父类没有的方法进行补充或者对父类已有的方法进行重写
  • 为之后多态性的使用,提供了前提

图示:

image.png

1.2、继承性的格式

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
}
 
class 子类 extends 父类 {
}
  • A:子类、派生类、subclass
  • B:父类、超类、基类、superclass
public class Animal { 
    private String name;   
    private int id; 
    
    public Animal(String name, String id) { 
        this.name = name;
        this.id = id;
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
 
public class Penguin  extends  Animal{ 
}

1.3、继承性的注意点

子类继承父类以后有哪些不同?

  • 体现:一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。
  • 特别的,父类中声明为 private 的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只因为封装性的影响,使得子类不能直接调用父类的结构而已 (子类只能通过setget方法)。

image.png

  • 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展
  • 子类和父类的关系,不同于子集和集合的关系。

Java中继承性的说明

  • Java类的继承是单一继承,即一个类可以被多个子类继承。
  • Java中类的单继承性:一个类只能有一个父类,不允许多继承,但允许多重继承
  • 可以通过接口达到实现继承多个父类的效果
  • 子父类是相对的概念。
  • 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类
  • 子类继承父类以后,就获取了直接父类以及所间接父类中声明的属性和方法

图示:

需要注意的是 Java 不支持多继承,但支持多重继承。

img

继承的特性

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

2、方法的重写

定义

在子类中可以根据需要对从父类中继承来的方法进行改造也称为方法的重置、覆盖 。在程序执行时,子类的方法将覆盖父类的方法。重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

应用

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

举例

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
    
   @Override //方法的重写
   public void move(){
      System.out.println("狗可以跑和走");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
       
      //Dog b = new Dog(); // Dog 对象
      Animal b = new Dog(); // Dog 对象。指向Animal类型,多态
 
      a.move();// 执行 Animal 类的方法
 
      b.move();//执行 Dog 类的方法
   }
}

以上实例编译运行结果如下:

动物可以移动
狗可以跑和走

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。这是由于在编译阶段,只是检查参数的引用类型。然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法(多态-晚绑定)。因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

注意点:

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
   public void move(){
      System.out.println("狗可以跑和走");
   }
   public void bark(){
      System.out.println("狗可以吠叫");
   }
}
 
public class TestDog{
   public static void main(String args[]){
      Animal a = new Animal(); // Animal 对象
      Animal b = new Dog(); // Dog 对象,多态
 
      a.move(); // 执行 Animal 类的方法
      b.move(); //执行 Dog 类重写的move方法
      b.bark();
   }
}

以上实例编译运行结果如下:

TestDog.java:30: cannot find symbol
symbol  : method bark()
location: class Animal
                b.bark();
                 ^

该程序将抛出一个编译错误,因为对象 b 的引用类型Animal没有bark方法。解决的办法就是:如下声明 dog 对象即可

Dog b = new Dog(); // Dog 对象

重写的规则:

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

约定俗称:子类中的叫重写的方法,父类中的叫被重写的方法

  • 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
  • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符,例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected
  • 特殊情况:子类和父类在同一个包中,那么子类可以重写父类所有方法,但是子类不能重写父类中声明为private权限的方法
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 构造方法不能被重写,只能重载
  • 如果不能继承一个类,则不能重写该类的方法。
  • 返回值类型:
    • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
    • 父类被重写的方法的返回值类型是 A 类型,则子类重写的方法的返回值类型可以是A类或A类的派生子类
    • 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
  • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
  • 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。 因为 static 方法是属于类的,子类无法覆盖父类的方法。

区分方法的重写和重载?

① 二者的概念:

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

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

引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚绑定,它就不是多态。”

② 重载和重写的具体规则

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

③ 重载:不表现为多态性。重写:表现为多态性。

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

总结

重写是父类与子类之间多态性的一种表现

  • 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
  • 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。

img

3、Super 关键字

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。 Super 可以理解为:父类的 。Super关键字可以用来调用的结构:属性、方法、构造器

1、super调用父类中定义的属性

package com.lanmeix.java3;
​
public class Person {
    String name;
    int age;
    int id = 1001;//身份证号
​
    public void eat(){
        System.out.println("人:吃饭");
    }
}
​
public class Student extends Person{
​
    String major;
    int id = 1002; //学号
​
    @Override
    public void eat() {
        System.out.println("学生:多吃有营养的食物");
    }
​
    public void show(){
        System.out.println("name = " + name + ", age = " + age);//省略了this
        System.out.println("id = " + this.id); //1002, 属性不会覆盖,
        System.out.println("id = " + super.id); //1001
    }
}

2、super调用父类中定义的成员方法:

class Animal{ 
    public void move(){
        System.out.println("动物可以移动");
    }
}
​
class Dog extends Animal{
​
    @override
    public void move(){
        super.move(); //调用父类被重写的方法
        System.out.println("狗可以跑和走");
    }
}
​
public class TestDog{
    public static void main(String args[]){
        Animal b = new Dog(); // Dog 对象
        b.move(); //执行 Dog类的方法
    }
}

以上实例编译运行结果如下:

动物可以移动
狗可以跑和走

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

  • 当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用super.属性的方式,表明调用的是父类中声明的属性。
  • 当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用super.方法的方式,表明调用的是父类中被重写的方法。

3、super调用构造器:

子类是不继承父类的构造器的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则可以在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。

如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。

package com.lanmeix.java3;
​
class Person {
    String name;
    int age;
    int id = 1001;//身份证号
​
    public Person(){
        System.out.println("我无处不在!");
    }
​
    public Person(String name){
        this.name = name;
    }
​
    public Person(String name,int age){
        this(name);
        this.age = age;
    }
​
    public void eat(){
        System.out.println("人:吃饭");
    }
    public void walk(){
        System.out.println("人:走路");
    }
}
​
​
class Student extends Person{
​
    String major;
    int id = 1002;//学号
​
    public Student(){
        super(); //显示的声明,不写的话默认有这一行是这个调用父类的无参构造
    }
    public Student(String major){
        super(); //显示的声明,不写的话默认有这一行是这个调用父类无参构造
        this.major = major;
    }
​
    public Student(String name,int age,String major){
        //this.name = name;
        //this.age = age;
        super(name,age); //因为继承了父类属性,所以可直接super,等同父类构造器在子类构造器里面,造子类对象时候,父类里面的this就是子类对象
        this.major = major;
    }
​
    @Override
    public void eat() {
        System.out.println("学生:多吃有营养的食物");
    }
​
    public void study(){
        System.out.println("学生:学习知识");
        this.eat();//子类重写的eat方法,可以省略this,谁调eat,this就是谁
        super.eat();//父类的eat方法
        walk();
    }
​
    public void show(){
        System.out.println("name = " + name + ", age = " + age);//省略了this
        System.out.println("id = " + this.id);//1002,属性不会覆盖,
        System.out.println("id = " + super.id);//1001
    }
}
  • 我们可以在子类的构造器中显式的使用 super(形参列表) 的方式,调用父类中声明的指定的构造器,不写的话默认会自动调用父类的无参构造
  • super(形参列表) 的使用,必须声明在子类构造器的首行!
  • 我们在类的构造器中,针对于this(形参列表)super(形参列表)只能二选一,不能同时出现
  • 在构造器的首行,没显式的声明this(形参列表)super(形参列表),则默认调用的是父类中空参的构造器:super()
  • 在类的多个构造器中,至少一个类的构造器中使用了super(形参列表),调用父类中的构造器
  • 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错

image.png

this 和 super 的区别

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。

this关键字:指向自己的引用。

class Animal {
  void eat() {
    System.out.println("animal : eat");
  }
}
 
class Dog extends Animal {
  void eat() {
    System.out.println("dog : eat");
  }
  void eatTest() {
    this.eat();   // this 调用自己的方法
    super.eat();  // super 调用父类方法
  }
}
 
public class Test {
  public static void main(String[] args) {
    Animal a = new Animal();
    a.eat();
    Dog d = new Dog();
    d.eatTest();
  }
}

输出结果为:

animal : eat
dog : eat
animal : eat

image.png

4、native 关键字

详见 JVM

使用: native 关键字说明这个方法是原生函数,也就是 这个方法是用 C/C++ 等非Java 语言实现的 ,并且被编译成了 DLL ,由 java 去调用。

为什么要用 native 方法

  • java使用起来非常方便,然而有些层次的任务用 java 实现起来不容易,或者我们对程序的效率很在意时,问题就来了。例如: 有时 java 应用需要与 java 外面的环境交互。这是本地方法存在的主要原因 ,你可以想想 java 需要与一些底层系统如操作系统或某些硬件交换信息时的情况。本地方法正是这样一种交流机制:它为我们提供了一个非常简洁的接口,而且我们无需去了解 java 应用之外的繁琐的细节。

native 声明的方法,对于调用者,可以当做和其他 Java 方法一样使用一个native method 方法可以返回任何 java 类型,包括非基本类型,而且同样可以进行异常控制。

native method的存在并不会对其他类调用这些本地方法产生任何影响,实际上调用这些方法的其他类甚至不知道它所调用的是一个本地方法。 JVM 将控制调用本地方法的所有细节。如果一个含有本地方法的类被继承,子类会继承这个本地方法并且可以用java语言重写这个方法(如果需要的话)。

5、子类对象实例化的过程

image.png

从结果上看:继承性

  • 子类继承父类以后,就获取了父类中声明的属性或方法。
  • 创建子类的对象,在堆空间中,就会加载所父类中声明的属性。

从过程上看:

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

图示:

image.png

强调说明:

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

6、面向对象特征--多态性

多态性的理解

可以理解为一个事物的多种形态。比如一个person类可以有多种对象。多态是同一个行为具有多个不同表现形式或形态的能力。

何为多态性:

多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:

image.png

对象的多态性

父类的引用指向子类的对象(或子类的对象赋给父类的引用)

举例:

Person p = new Man();//man继承了person
Object obj = new Date();

多态存在的三个必要条件

  • 继承
  • 重写
  • 父类引用指向子类对象:Parent p = new Child();

多态性的应用举例

public void func(Animal animal){ //Animal animal = new Dog();
    animal.eat();
    animal.shout();
}
​
public void method(Object obj){ //所有类都是object的子类
}
​
class Driver{
    public void doData(Connection conn){
        //conn = new MySQlConnection(); 
        //conn = new OracleConnection();
​
        //规范的步骤去操作数据
        //    conn.method1();
        //    conn.method2();
        //    conn.method3();
    }
}

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

虚拟方法调用:当调用子父类中同名参数的方法时,实际执行的是子类重写父类的方法,虚函数的存在是为了多态。在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

Java 中其实没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是Java的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。

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

Animal animal = new Dog();
animal.getInfo() //调用 Dog 类的 getInfo 方法

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

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。

多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

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,如果不向下转型,直接 a.work 会报错
    }  
​
    public static void show(Animal a)  { //Animal a = new Cat()/new Dog()
        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();  //抽象方法,没有方法体
    //没有work方法
}  
​
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("看家");  
    }  
}

执行以上程序,输出结果为:

吃鱼
抓老鼠
吃骨头
看家
吃鱼
抓老鼠

多态性使用的注意点:

对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

向上转型与向下转型:

  • 向上转型:多态,Animal a = new Cat(); // 向上转型
  • 向下转型:Cat c = (Cat)a;

为什么使用向下转型:

  • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用(强制调用,如果父类中没有该方法就会报错)。如何才能调用子类特的属性和方法?使用向下转型

如何实现向下转型:

  • 使用强制类型转换符:()

向下转型的几个问题:

//man woman继承于Person-----不想关的两个类使用向下转型会发生错误//问题一:编译时通过,运行时不通过
//举例一:
Person p3 = new Woman(); 
Man m3 = (Man)p3;
​
//举例二:
Person p4 = new Person();
Man m4 = (Man)p4;
​
​
//问题二:编译通过,运行时也通过
Object obj = new Woman();
Person p = (Person)obj;
​
//问题三:编译不通过
Man m5 = new Woman();
​
String str = new Date();
​
Object o = new Date();
String str1 = (String)o;

使用时的注意点:

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

instanceof的使用:

  • a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
  • 如果 a instanceof A 返回true,则 a instanceof B 也返回true。其中,类B是类A的父类。
  • 要求对象a所属的类与类A必须是子类和父类的关系,否则编译错误。

图示:

image.png

面试题:

谈谈你对多态性的理解?

  • 实现代码的通用性。
  • Object类中定义的public boolean equals(Object obj){ }
  • JDBC:使用java程序操作(获取数据库连接、CRUD)数据库(MySQL、Oracle、DB2、SQL Server)
  • 抽象类、接口的使用肯定体现了多态性。(抽象类、接口不能实例化)

多态是编译时行为还是运行时行为?

  • 运行时行为,运行的时候才知道造的是哪个对象
package com.lanmeix.java5;
​
import java.util.Random;
​
//面试题:多态是编译时行为还是运行时行为?
//证明如下:
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();
    }
}

多态的实现方式

方式一:重写:

  • 这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:
  • Java 重写(Override)与重载(Overload)

方式二:接口

  • 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
  • java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。具体可以看 java接口 这一章节的内容。

方式三:抽象类和抽象方法

7、JUnit单元测试

这里只简单介绍一下Java中的 JUnit 单元测试,详见开发者测试内容。引入下列依赖即可

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

实例

public class JUnitTest {
​
   int num = 10;
​
   @Test
   public void testEquals(){
       String s1 = "MM";
       String s2 = "MM";
       System.out.println(s1.equals(s2));
       
       //ClassCastException的异常
       //       Object obj = new String("GG");
       //       Date date = (Date)obj;
​
       System.out.println(num);
       show();
   }
​
   public void show(){
       num = 20;
       System.out.println("show()....");
   }
​
   @Test
   public void testToString(){
       String s2 = "MM";
       System.out.println(s2.toString());
   }
}

8、包装类Wrapper

8.1、概述

为什么要有包装类(或封装类)

为了使基本数据类型的变量具有类的特征,引入包装类。有了 类的特点,就可以调用类中的 方法, Java 才是真正的面向对象,java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征

基本数据类型与对应的包装类:

image.png

需要掌握的类型间的转换:(基本数据类型、包装类、String)

image.png

8.2、基本数据类型与包装类的转换

基本数据类型转化为包装类

  • 通过包装类的构造器
  • 通过字符串参数的构造器
  • JDK 5.0 新特性:自动装箱与自动拆箱

包装类转化为基本数据类型

  • 调用包装类XxxxxxValue()
  • JDK 5.0 新特性:自动装箱与自动拆箱
/*
* JDK 5.0 新特性:自动装箱 与自动拆箱
*/
@Test
public void test3(){
    int num1 = 10;
    //基本数据类型-->包装类的对象
    method(num1); //Object obj = num1,这是由自动装箱实现的
​
    //自动装箱:基本数据类型 --->包装类
    int num2 = 10;
    Integer in1 = num2;//自动装箱
​
    boolean b1 = true;
    Boolean b2 = b1;//自动装箱
​
    //自动拆箱:包装类--->基本数据类型
    System.out.println(in1.toString());
    int num3 = in1; //自动拆箱
}
​
public void method(Object obj){
    System.out.println(obj);
}
​
//包装类--->基本数据类型:调用包装类Xxx的xxxValue()
@Test
public void test2(){
    Integer in1 = new Integer(12);
​
    int i1 = in1.intValue();
    System.out.println(i1 + 1);
​
​
    Float f1 = new Float(12.3);
    float f2 = f1.floatValue();
    System.out.println(f2 + 1);
}
​
//基本数据类型 --->包装类:调用包装类的构造器
@Test
public void test1(){
    
    //int型转换
    int num1 = 10;
    //System.out.println(num1.toString());
    Integer in1 = new Integer(num1);
    System.out.println(in1.toString()); //10
​
    Integer in2 = new Integer("123"); //通过字符串参数
    System.out.println(in2.toString()); //123
​
    //报异常
    //Integer in3 = new Integer("123abc");
    //System.out.println(in3.toString());
​
    //float型转换
    Float f1 = new Float(12.3f);
    Float f2 = new Float("12.3");
    System.out.println(f1);
    System.out.println(f2);
​
    Boolean b1 = new Boolean(true);
    Boolean b2 = new Boolean("TrUe");
    System.out.println(b2);
    Boolean b3 = new Boolean("true123");
    System.out.println(b3);//false
​
​
    Order order = new Order();
    System.out.println(order.isMale);//false
    System.out.println(order.isFemale);//null
}
​
class Order{
    boolean isMale; //基本数据类型
    Boolean isFemale; //包装类型
}

8.3、基本数据类型、包装类转化为 String

//基本数据类型、包装类--->String类型:调用String重载的valueOf(Xxx xxx)
@Test
public void test4(){
​
    int num1 = 10;
    
    //方式1:连接运算
    String str1 = num1 + "";
    
    //方式2:调用String的valueOf(Xxx xxx)
    float f1 = 12.3f;
    String str2 = String.valueOf(f1);//"12.3"
​
    Double d1 = new Double(12.4); //包装类
    String str3 = String.valueOf(d1);
    System.out.println(str2);
    System.out.println(str3);//"12.4"
​
}

8.4、String 转化为基本数据类型、包装类

//String类型 --->基本数据类型、包装类:调用包装类的parseXxx(String s)
@Test
public void test5(){
    String str1 = "123";
    //错误的情况:
    //int num1 = (int)str1;//这种强转是错误的,强转()只涉及基本数据类型
    //Integer in1 = (Integer)str1; //这种强转也是错误的,两者之间没有子父类关系
    //可能会报NumberFormatException
    
    int num2 = Integer.parseInt(str1);
    System.out.println(num2 + 1);
​
    String str2 = "true1";
    boolean b1 = Boolean.parseBoolean(str2);
    System.out.println(b1);
}

8.5、包装类使用举例

package com.lanmeix.java2;
​
import org.junit.Test;
​
/*
* 关于包装类使用的面试题
*/
public class InterviewTest {
​
   @Test
   public void test1() {
       //编译的时候就必须保证类型统一,所以1自动类型提升了
       Object o1 = true ? new Integer(1) : new Double(2.0);
       System.out.println(o1);// 1.0
​
   }
​
   @Test
   public void test2() {
       Object o2;
       if (true)
           o2 = new Integer(1);
       else
           o2 = new Double(2.0);
       System.out.println(o2);// 1
​
   }
​
   @Test
   public void test3() {
       Integer i = new Integer(1);
       Integer j = new Integer(1);
       System.out.println(i == j); //false
​
       //Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
       //保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
       //-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
​
       Integer m = 1;
       Integer n = 1;
       System.out.println(m == n); //true
​
       Integer x = 128;//相当于new了一个Integer对象
       Integer y = 128;//相当于new了一个Integer对象
       System.out.println(x == y);//false
   }
}

9、java.lang.Object 类

9.1、object类概述

  • 如果我们没有使用extends关键字显式的声明一个类的父类的话,则此类继承于java.lang.Object
  • 所的java类(除java.lang.Object类之外都直接或间接的继承于java.lang.Object
  • 意味着,所的java类具有java.lang.Object类声明的功能,可以使用 Object 的所有方法

image.png

Object 类位于 java.lang 包中,编译时会自动导入,我们创建一个类时,如果没有明确继承一个父类,那么它就会自动继承 Object,成为Object 的子类。

public class ObjectTest {
    public static void main(String[] args) {        
        Order order = new Order();
        System.out.println(order.getClass().getSuperclass());//object       
    }
}
class Order{    
}

Object 类可以显示继承,也可以隐式继承,以下两种方式时一样的:

显示继承:

public class Runoob extends Object{
​
}

隐式继承:

public class Runoob {
}

类的构造函数

Object类只声明了一个空参的构造器

序号构造方法 & 描述
1Object() 构造一个新对象。

9.2、Object类的方法

参考地址docs.oracle.com/javase/8/do…

序号方法 & 描述
1protected Object clone()创建并返回一个对象的拷贝
2boolean equals(Object obj)比较两个对象是否相等
3protected void finalize()当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法。
4Class getClass()获取对象的运行时对象的类
5int hashCode()获取对象的 hash 值
6void notify()唤醒在该对象上等待的某个线程
7void notifyAll()唤醒在该对象上等待的所有线程
8String toString()返回对象的字符串表示形式
9void wait()让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
10void wait(long timeout)让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间。
11void wait(long timeout, int nanos)与 wait(long timeout) 方法类似,多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。

1、clone方法

package com.lanmeix.java1;
​
//Object类的clone()的使用
public class CloneTest {
    public static void main(String[] args) {
        Animal a1 = new Animal("花花");
        try {
            Animal a2 = (Animal) a1.clone();
            System.out.println("原始对象:" + a1);
            a2.setName("毛毛");
            System.out.println("clone之后的对象:" + a2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
​
class Animal implements Cloneable{
    private String name;
​
    public Animal() {
        super();
    }
​
    public Animal(String name) {
        super();
        this.name = name;
    }
​
    public String getName() {
        return name;
    }
​
    public void setName(String name) {
        this.name = name;
    }
​
    @Override
    public String toString() {
        return "Animal [name=" + name + "]";
    }
​
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // TODO Auto-generated method stub
        return super.clone();
    }
}

2、equals方法

回顾 == 运算符的使用:

  • 可以使用在基本数据类型变量和引用数据类型变量中
  • 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
  • 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体
  • 补充:== 符号使用时,必须保证符号左右两边的变量类型一致。
//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);//true
System.out.println(i == d);//trueboolean b = true;
//System.out.println(i == b);  编译报错char c = 10;
System.out.println(i == c);//truechar c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);//true//引用类型:
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1 == cust2);//false String str1 = new String("lanmeix"); //字符串常量池放一份,堆里面放一份,内容为常量池的内容
String str2 = new String("lanmeix");
System.out.println(str1 == str2);//false

Object类中equals()的定义:

只能适用于引用数据类型

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

说明:Object类中定义的equals()==的作用是相同的:比较两个对象(引用数据类型)的地址值是否相同,即两个引用是否指向同一个对象实体

像String、Date、File、包装类等都重写Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。

举例

Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
​
String str1 = new String("lanmeix"); //字符串常量池放一份,堆里面放一份,内容为常量池的内容
String str2 = new String("lanmeix");
​
System.out.println(cust1.equals(cust2));//false 没有重写equals
System.out.println(str1.equals(str2));//trueDate date1 = new Date(32432525324L);
Date date2 = new Date(32432525324L);
System.out.println(date1.equals(date2));//true      

equals()方法的重写

通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写,重写的原则:比较两个对象的实体内容是否相同.

手动重写equals()

//重写的原则:比较两个对象的实体内容(即:name和age)是否相同
//手动实现equals()的重写
@Override
public boolean equals(Object obj) {
​
    System.out.println("Customer equals()....");
    if (this == obj) {
        return true;
    }
​
    if(obj instanceof Customer){
        Customer cust = (Customer)obj;
        //比较两个对象的每个属性是否都相同
        if(this.age == cust.age && this.name.equals(cust.name)){
            return true;
        }else{
            return false;
        }
}

开发中如何实现:自动生成的

在类中代码处,右键单击选择Generate ,然后选择Equals() and hashCode() 这个选项,选择相应属性即可

//自动生成的equals()
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Customer customer = (Customer) o;
    return age == customer.age && Objects.equals(name, customer.name);
}
//自动生成的hashcode()
@Override
public int hashCode() {
    return Objects.hash(name, age);
}

3、toString()方法

toString 方法在 Object 类中定义 其返回值是 String 类型,返回类名和它的引用地址 toString()的使用:当我们输出一个对象的引用时,实际上就是调用当前对象的 toString()方法

  1. Object类中toString()的定义:
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

2、像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回"实体内容"信息

public class ToStringTest {
    public static void main(String[] args) {

        Customer cust1 = new Customer("Tom",21);
        System.out.println(cust1.toString());//com.lanmeix.java1.Customer@15db9742-->Customer[name = Tom,age = 21]
        System.out.println(cust1);//com.lanmeix.java1.Customer@15db9742-->Customer[name = Tom,age = 21]

        String str = new String("MM");
        System.out.println(str);//MM,重写了tostring

        Date date = new Date(4534534534543L);
        System.out.println(date.toString());//Mon Sep 11 08:55:34 GMT+08:00 2113

    }
}

3、自定义类也可以重写toString()方法,当调用此方法时,返回对象的"实体内容"

//手动实现
@Override
public String toString() {
    return "Customer[name = " + name + ",age = " + age + "]"; 
}
​
//idea自动实现,方法一样
@Override
public String toString() {
    return "Customer{" +
        "name='" + name + ''' +
        ", age=" + age +
        '}';
}

4、finalize方法

Java 允许定义这样的方法,它在对象被垃圾收集器析构(回收)之前调用,这个方法叫做 finalize( ),它用来清除回收对象。例如,你可以使用 finalize() 来确保一个对象打开的文件被关闭了。在 finalize() 方法里,你必须指定在对象销毁时候要执行的操作。

finalize() 一般格式是:

protected void finalize(){
  // 在这里终结代码
}

关键字 protected 是一个限定符,它确保 finalize() 方法不会被该类以外的代码调用。当然,Java 的内存回收可以由 JVM 来自动完成。如果你手动使用,则可以使用上面的方法。

package com.lanmeix.test2;
​
public class FinalizeTest {
    public static void main(String[] args) {
        Person p = new Person("Peter", 12);
        System.out.println(p);
        p = null;//此时对象实体就是垃圾对象,等待被回收。但时间不确定。
        System.gc();//强制性释放空间
    }
}
​
class Person{
    private String name;
    private int age;
​
    public Person(String name, int age) {
        super();
        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;
    }
    //子类重写此方法,可在释放对象前进行某些操作
    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象被释放--->" + this);
    }
    @Override
    public String toString() {
        return "Person [name=" + name + ", age=" + age + "]";
    }
​
}

垃圾回收机制关键点

  • 垃圾回收机制只回收JVM堆内存里的对象空间。
  • 对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
  • 现在的JVM有多种垃圾回收实现算法,表现各异。
  • 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
  • 可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
  • 程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有一些效果,但是系统是否进行垃圾回收依然不确定。
  • 垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活对象)。
  • 永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用

5、总结

final、finally、finalize的区别?

② == 和 equals() 区别

  • == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
  • equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals是比较值的错误观点。
  • 具体要看自定义类里有没有重写Objectequals方法来判断。
  • 通常情况下, 重写equals方法,会比较类中的相应属性是否都相等。

10、JDK中的主要包介绍

image.png

11、命令行参数的使用

有时候你希望运行一个程序时候再传递给它消息。这要靠传递命令行参数给main()函数实现。命令行参数是在执行程序时候紧跟在程序名字后面的信息。

实例

下面的程序打印所有的命令行参数:

public class CommandLine {
   public static void main(String[] args){ 
      for(int i=0; i<args.length; i++){
         System.out.println("args[" + i + "]: " + args[i]);
      }
   }
}

如下所示,运行这个程序:

$ javac CommandLine.java 
$ java CommandLine this is a command line 200 -100
args[0]: this
args[1]: is
args[2]: a
args[3]: command
args[4]: line
args[5]: 200
args[6]: -100