5 面向对象编程(上)

5.1 类和对象

  • 对象:对象是类的一个实例,有状态和行为。例如,一条狗是一个对象,它的状态有:颜色、名字、品种;行为有:摇尾巴、叫、吃等。

  • :类是一个模板,它描述一类对象的行为和状态。

对象是根据类创建的。在Java中,使用关键字 new 来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。

  • 实例化:使用关键字 new 来创建一个对象。

  • 初始化:使用 new 创建对象时,会调用构造方法初始化对象。

public class Input {
    public static void main(String[] args) {
        // cat1是一个对象
        Cat cat1 = new Cat();
        cat1.name = "小白";
        cat1.age = 3;
        cat1.color = "白色";
        // cat2是一个对象
        Cat cat2 = new Cat();
        cat2.name = "小花";
        cat2.age = 100;
        cat2.color = "花色";
    }
}
​
// 定义一个猫类 Cat: 自定义的数据类型
class Cat {
    String name; 
    int age; 
    String color; 
}
​

1) 类是抽象的,概念的,代表一类事物,比如人类,猫类.., 即它是数据类型. 2) 对象是具体的,实际的,代表一个具体事物, 即是实例. 3) 类是对象的模板,对象是类的一个个体,对应一个实例

5.2 类和对象的内存分配机制

5.2.1 Java 内存的结构分析

1) 栈: 一般存放基本数据类型(局部变量) 2) 堆: 存放对象(Cat cat , 数组等) 3) 方法区:常量池(常量,比如字符串), 类加载信息

5.2.2 Java 创建对象的流程简单分析

Person p = new Person();
p.name = “jack”;
p.age = 10

1) 先加载Person 类信息(属性和方法信息, 只会加载一次) 2) 在堆中分配空间, 进行默认初始化(看规则) 3) 把地址赋给p , p 就指向对象 4) 进行指定初始化, 比如p.name =”jack” p.age = 10

5.3 成员方法

在某些情况下,我们要需要定义成员方法(简称方法)。比如人类:除了有一些属性外( 年龄,姓名..),我们人类还有一些行为比如:可以说话、跑步..,通过学习,还可以做算术题。这时就要用成员方法才能完成。

5.3.1 快速入门

public class Method01 {
    //编写一个main 方法
    public static void main(String[] args) {
        //方法使用
        //1. 方法写好后,如果不去调用(使用),不会输出
        //2. 先创建对象,然后调用方法即可
        Person p1 = new Person();
        p1.speak(); //调用方法
        p1.cal01(); //调用cal01 方法
        //调用getSum 方法,同时num1=10, num2=20
        //把方法getSum 返回的值,赋给变量returnRes
        int returnRes = p1.getSum(10, 20);
        System.out.println("getSum 方法返回的值=" + returnRes);
    }
}
​
class Person {
    String name;
    int age;
    //方法(成员方法)
    public void speak() {
        System.out.println("我是一个好人");
    }
    
    //添加cal01 成员方法,可以计算从1+..+1000 的结果
    public void cal01() {
        int res = 0;
        for(int i = 1; i <= 1000; i++) {
            res += i;
        }
        System.out.println("cal01 方法计算结果=" + res);
    }
    
    //添加getSum 成员方法,可以计算两个数的和
    public int getSum(int num1, int num2) {
        int res = num1 + num2;
    return res;
}
}

5.3.2 方法的调用机制原理

  • 当程序执行到方法时,就会开辟一个独立的栈空间

  • 当方法执行完毕,或者执行到return语句时,就会返回到调用方法的地方,回收栈空间

  • 返回后,继续执行方法后面的代码

  • 当main方法执行完毕,整个程序退出

5.3.3 注意事项和使用细节

  1. 一个方法最多有一个返回值

  2. 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)

  3. 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为return 值;而且要求返回值类型必须和return 的值类型一致或兼容

  4. 如果方法是void,则方法体中可以没有return 语句,或者只写return ;

5.4 成员方法传参机制

5.4.1 基本数据类型的传参机制

基本数据类型 , 传递的是值 ( 值拷贝 ) , 形参的任何改变不影响实参 !

5.4.2 引用数据类型的传参机制

引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!

5.5 方法递归调用

递归就是方法自己调用自己,每次调用时传入不同的变量.递归有助于编程者解决复杂问题,同时可以让代码变得简洁

5.5.1 距离

求一个数的阶乘

//factorial 阶乘
public int factorial(int n) {
    if (n == 1) {
    return 1;
} else {
    return factorial(n - 1) * n;
}

5.5.2 递归重要规则

  • 执行一个方法时 ,就创建一个新的受保护的独立空间(栈空间)

  • 方法的局部变量是独立的 ,不会相互影响 ,比如n变量

  • 如果方法中使用的是引用类型变量( 比如数组,对象), 就会共享该引用类型的数据

  • 递归必须向退出递归的条件逼近,否则就是无限递归 , 出现StackOverfIowError )

  • 当一个方法执行完毕,或者遇到 return, 就会返回 ,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行

5.6 方法重载(OverLoad)

java 中允许同一个类中,多个同名方法的存在,但要求形参列表不一致!例如

//一个整数,一个double 的和
public double calculate(int n1, double n2) {
	return n1 + n2;
}
//一个double ,一个Int 和
public double calculate(double n1, int n2) {
	System.out.println("calculate(double n1, int n2) 被调用..");
	return n1 + n2;
}
//三个int 的和
public int calculate(int n1, int n2,int n3) {
    return n1 + n2 + n2;
}
  • 方法名:必须相同

  • 形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求)

  • 返回类型:无要求

5.7 可变参数

java 允许将同一个类中多个同名同功能但参数个数不同的方法,封装成一个方法。就可以通过可变参数实现

访问修饰符返回类型方法名(数据类型... 形参名) {
}

例如

public int sum(int... nums) {
    //System.out.println("接收的参数个数=" + nums.length);
    int res = 0;
    for(int i = 0; i < nums.length; i++) {
    	res += nums[i];
	}
	return res;
}
  • 可变参数的实参可以为0个或任意多个

  • 可变参数的实参可以为数组

  • 可变参数的本质就是数组

  • 可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后

  • 一个形参列表中只能出现一个可变参数

5.8 作用域

变量的范围是程序中该变量可以被引用的部分。方法内定义的变量被称为局部变量。局部变量的作用范围从声明开始,直到包含它的块结束。局部变量必须声明才可以使用。方法的参数范围涵盖整个方法。参数实际上是一个局部变量。for循环的初始化部分声明的变量,其作用范围在整个循环。

  • 属性和局部变量可以重名,访问时遵循就近原则

  • 在同一个作用域中,两个局部变量不能重名

  • 属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量的生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法调用过程中

  • 全局变量/属性可以被本类使用,或其他类使用(通过对象调用)。而局部变量只能在本类中对应的方法中使用

  • 全局变量/属性可以加修饰符。局部变量不可以加修饰符

5.9 构造方法

当一个对象被创建时候,构造方法用来初始化该对象。构造方法和它所在类的名字相同,但构造方法没有返回值。

通常会使用构造方法给一个类的实例变量赋初值,或者执行其它必要的步骤来创建一个完整的对象。

不管你是否自定义构造方法,所有的类都有构造方法,因为 Java 自动提供了一个默认构造方法,默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;类改为 protected,构造函数也改为 protected)。

一旦你定义了自己的构造方法,默认构造方法就会失效。

1) 构造器的修饰符可以默认, 也可以是public protected private 2) 构造器没有返回值 3) 方法名和类名字必须一样 4) 参数列表和成员方法一样的规则 5) 构造器的调用, 由系统完成。在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。

public class Constructor01 {
    //编写一个main 方法
    public static void main(String[] args) {
        //当我们new 一个对象时,直接通过构造器指定名字和年龄
        Person p1 = new Person("smith", 80);
        System.out.println("p1 的信息如下");
        System.out.println("p1 对象name=" + p1.name);//smith
        System.out.println("p1 对象age=" + p1.age);//80
    }
}
​
class Person {
    String name;
    int age;
    //1. 构造器没有返回值, 也不能写void
    //2. 构造器的名称和类Person 一样
    //3. (String pName, int pAge) 是构造器形参列表,规则和成员方法一样
    public Person(String pName, int pAge) {
        System.out.println("构造器被调用~~ 完成对象的属性初始化");
        name = pName;
        age = pAge;
    }
}
  • 一个类可以定义多个不同的构造器,即构造器重载

  • 构造器名和类名要相同

  • 构造器没有返回值

  • 构造器是完成对象的初始化,并不是创建对象

  • 在创建对象时,系统自动的调用该类的构造方法

  • 如果程序没有定义构造器,系统会给类生产一个默认无参构造器,也叫默认构造器,可以使用javap指令反编译查看

  • 一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下

class Dog{
    //构造器
    public Dog(String dName){
        // ......
    }
    Dog() {
        // 显示的定义一下,无参构造器
    }
}

5.10 this 关键字

1) this 关键字可以用来访问本类的属性、方法、构造器 2) this 用于区分当前类的属性和局部变量 3) 访问成员方法的语法:this.方法名(参数列表);

class Dog{ //类
    String name;
    int age;
    public Dog(String name, int age){//构造器
        //this.name 就是当前对象的属性name
        this.name = name;
        //this.age 就是当前对象的属性age
        this.age = age;
        System.out.println("this.hashCode=" + this.hashCode());
    }
    
    public hashCode(){
        // ......
    }
}

5.11 包

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

5.11.1 包的作用

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

  • 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

  • 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

例如:

// net/java/util/Something.java 
package net.java.util;
public class Something{
   ...
}

package(包) 的作用是把不同的 java 程序分类保存,更方便的被其他 java 程序调用。

5.11.2 常用的包

一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。

以下是一些 Java 中的包:

1) java.lang.* lang 包是基本包,默认引入,不需要再引入. 2) java.util.* util 包,系统提供的工具包, 工具类,使用Scanner 3) java.net.* 网络包,网络开发 4) java.awt.* 是做java 的界面开发,GUI

开发者可以自己把一组类和接口等打包,并定义自己的包。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

由于包创建了新的命名空间(namespace),所以不会跟其他包中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。

5.11.3 引入包

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能

在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1[.package2…].(classname|*);

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

5.12 修饰符

5.12.1 访问控制修饰符

Java语言提供了很多修饰符,主要分为以下两类:

  • 访问修饰符

  • 非访问修饰符

修饰符用来定义类、方法或者变量,通常放在语句的最前端。

public class ClassName {
   // ...
}
private boolean myFlag;
static final double weeks = 9.5;
protected static final int BOXWIDTH = 42;
public static void main(String[] arguments) {
   // 方法体
}
  • default (即默认,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。

  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

私有访问修饰符是最严格的访问级别,所以被声明为 private 的方法、变量和构造方法只能被所属类访问,并且类和接口不能声明为 private。声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。Private 访问修饰符的使用主要用来隐藏类的实现细节和保护类的数据。

  • public : 对所有类可见。使用对象:类、接口、变量、方法

被声明为 public 的类、方法、构造方法和接口能够被任何其他类访问。如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。由于类的继承性,类所有的公有方法和变量都能被其子类继承。

  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)

    • 子类与基类在同一包中:被声明为 protected 的变量、方法和构造器能被同一个包中的任何其他类访问;

    • 子类与基类不在同一包中:那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。

5.12.2 非访问修饰符

为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

  • static 修饰符,用来修饰类方法和类变量。

    • 静态变量:static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。

    • 静态方法:static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。

  • final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。

    • final 变量:final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。final 修饰符通常和 static 修饰符一起使用来创建类常量。

    • final 方法:父类中的 final 方法可以被子类继承,但是不能被子类重写。声明 final 方法的主要目的是防止该方法的内容被修改。

    • final 类:final 类不能被继承,没有类能够继承 final 类的任何特性。

  • abstract 修饰符,用来创建抽象类和抽象方法。

    • 抽象类:抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。抽象类可以包含抽象方法和非抽象方法。

    • 抽象方法:抽象方法是一种没有任何实现的方法,该方法的具体实现由子类提供。抽象方法不能被声明成 final 和 static。任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。抽象方法的声明以分号结尾,例如:public abstract sample();

  • synchronized 和 volatile 修饰符,主要用于线程的编程。synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。

  • transient 修饰符:序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。

  • volatile 修饰符:volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

5.13 面向对象编程三大特征

面向对象编程有三大特征:封装、继承和多态。

5.13.1 封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。

封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。

要访问该类的代码和数据,必须通过严格的接口控制。

封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。

适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

实现封装的步骤:

  • 修改属性的可见性来限制对属性的访问(一般限制为private)

  • 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问

public class Person{
    private String name;
    private int age;
    public int getAge(){
      return age;
    }
    
    public String getName(){
      return name;
    }
    
    public void setAge(int age){
      this.age = age;
    }
    
    public void setName(String name){
      this.name = name;
    }
}

采用 this关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

5.13.2 继承

5.13.2.1 继承的介绍

继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

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

class 父类 {
}
 
class 子类 extends 父类 {
}

5.13.2.2 继承的特点

  • 子类拥有父类非 private 的属性、方法。

  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

  • 子类可以用自己的方式实现父类的方法。

  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。

  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

5.13.2.3 继承关键字

  • extends关键字

在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。

public class Animal { 
    private String name;   
    private int id; 
    public Animal(String myName, int myid) { 
        //初始化属性值
    } 
    public void eat() {  //吃东西方法的具体实现  } 
    public void sleep() { //睡觉方法的具体实现  } 
} 
 
public class Penguin  extends  Animal{ 
}
  • implements关键字

使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。

public interface A {
    public void eat();
    public void sleep();
}
 
public interface B {
    public void show();
}
 
public class C implements A,B {
}
  • super 与 this 关键字

    • 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();
  }
}
  • final 关键字

final 可以用来修饰变量(包括类属性、对象属性、局部变量和形参)、方法(包括类方法和对象方法)和类。

使用 final 关键字声明类,就是把类定义定义为最终类,不能被继承,或者用于修饰方法,该方法不能被子类重写:

  1. 声明类:

final class 类名 {//类体}
  1. 声明方法:

修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}

注: final 定义的类,其中的属性、方法不是 final 的。

5.13.2.4 细节问题

  1. 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

2) 子类必须调用父类的构造器, 完成父类的初始化 3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过.

4) 如果希望指定去调用父类的某个构造器,则显式的调用一下: super(参数列表) 5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用) 6) super() 和this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器 7) java 所有类都是Object 类的子类, Object 是所有类的基类. 8) 父类构造器的调用不限于直接父类!将一直往上追溯直到Object 类(顶级父类) 9) 子类最多只能继承一个父类(指直接继承),即java 中是单继承机制。 10) 不能滥用继承,子类和父类之间必须满足is-a 的逻辑关系

5.13.3 多态

多态是同一个行为具有多个不同表现形式或形态的能力。

多态的优点

  • 消除类型之间的耦合关系

  • 可替换性

  • 可扩充性

  • 接口性

  • 灵活性

  • 简化性

多态存在的三个必要条件

  • 继承

  • 重写

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

class Shape {
    void draw() {}
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Circle.draw()");
    }
}
 
class Square extends Shape {
    void draw() {
        System.out.println("Square.draw()");
    }
}
 
class Triangle extends Shape {
    void draw() {
        System.out.println("Triangle.draw()");
    }
}

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

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

5.14 重写(Override)与重载(Overload)

5.14.1 重写(Override)

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。

在面向对象原则里,重写意味着可以重写任何现有方法。实例如下:

class Animal{
   public void move(){
      System.out.println("动物可以移动");
   }
}
 
class Dog extends Animal{
   public void move(){
      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 类的方法
   }
}

方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。

  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 父类的成员方法只能被它的子类重写。

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。

  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

  • 构造方法不能被重写。

  • 如果不能继承一个类,则不能重写该类的方法。

5.14.2 重载(Overload)

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

重载规则:

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);

  • 被重载的方法可以改变返回类型;

  • 被重载的方法可以改变访问修饰符;

  • 被重载的方法可以声明新的或更广的检查异常;

  • 方法能够在同一个类中或者在一个子类中被重载。

  • 无法以返回值类型作为重载函数的区分标准。

public class Overloading {
    public int test(){
        System.out.println("test1");
        return 1;
    }
 
    public void test(int a){
        System.out.println("test2");
    }   
 
    //以下两个参数类型顺序不同
    public String test(int a,String s){
        System.out.println("test3");
        return "returntest3";
    }   
 
    public String test(String s,int a){
        System.out.println("test4");
        return "returntest4";
    }   
 
    public static void main(String[] args){
        Overloading o = new Overloading();
        System.out.println(o.test());
        o.test(1);
        System.out.println(o.test(1,"test3"));
        System.out.println(o.test("test4",1));
    }
}

区别点

重载方法

重写方法

参数列表

必须修改

一定不能修改

返回类型

可以修改

一定不能修改

异常

可以修改

可以减少或删除,一定不能抛出新的或者更广的异常

访问

可以修改

一定不能做更严格的限制(可以降低限制)

方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。

  • 方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。

  • 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

5.15 Object 类

Java Object 类是所有类的父类,也就是说 Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法

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

序号

类型和返回值

方法

描述

1

protected Object

clone()

创建并返回一个对象的拷贝

2

boolean

equals(Object obj)

比较两个对象是否相等

3

protected void

finalize()

当 GC (垃圾回收器)确定不存在对该对象的有更多引用时,由对象的垃圾回收器调用此方法。

4

Class

getClass()

获取对象的运行时对象的类

5

int

hashCode()

获取对象的 hash 值

6

void

notify()

唤醒在该对象上等待的某个线程

7

void

notifyAll()

唤醒在该对象上等待的所有线程

8

String

toString()

返回对象的字符串表示形式

9

void

wait()

让当前线程进入等待状态。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。

10

void

wait(long timeout)

让当前线程处于等待(阻塞)状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过参数设置的timeout超时时间。

11

void

wait(long timeout, int nanos)

与 wait(long timeout) 方法类似,多了一个 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。

6 面向对象编程(下)

6.1 类变量和类方法

6.1.1 类变量

  • 类变量也称为静态变量,在类中以 static 关键字声明,但必须在方法之外。

  • 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。

  • 静态变量除了被声明为常量外很少使用,静态变量是指声明为 public/private,final 和 static 类型的变量。静态变量初始化后不可改变。

  • 静态变量储存在静态存储区。经常被声明为常量,很少单独使用 static 声明变量。

  • 静态变量在第一次被访问时创建,在程序结束时销毁。

  • 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为 public 类型。

  • 默认值和实例变量相似。数值型变量默认值是 0,布尔型默认值是 false,引用类型默认值是 null。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。

  • 类变量被声明为 public static final 类型时,类变量名称一般建议使用大写字母。如果静态变量不是 public 和 final 类型,其命名方式与实例变量以及局部变量的命名方式一致。

类变量的定义

访问修饰符 static 数据类型 变量名;
static 访问修饰符 数据类型 变量名;

类变量的访问

类名.类变量名
对象名.类变量名

举例:

import java.io.*;
 
public class Employee {
    //salary是静态的私有变量
    private static double salary;
    // DEPARTMENT是一个常量
    public static final String DEPARTMENT = "开发人员";
    public static void main(String[] args){
    salary = 10000;
        System.out.println(DEPARTMENT+"平均工资:"+salary);
    }
}
  • 什么时候需要用类变量

当我们需要让某个类的所有对象都共享一个变量时 , 就可以考虑使用类变量 ( 静态变量: 比如 : 定义学生类 , 统计所有学生共交多少钱 。

  • 类变量与实例变量 ( 普通属性 ) 区别

类变量是该类的所有对象共享的 , 而实例变量是每个对象独享的 。

  • 加上 static 称为类变量或静态变量 , 否则称为实例变量 / 普通变量 / 非静态变量

  • 类变量可以通过类名.类变量名或者对象名.类变量名来访问但 java 设计者推荐我们使用类名.类变量名方式访问 。 [前提是满足访问修饰符的访问权限和范围]

  • 实例变量不能通过类名.类变量名方式访问 。

  • 类变量是在类加载时就初始化了 ,也就是说 ,即使你没有创建对象 ,只要类加载了 ,就可以使用类变量了 。

  • 类变量的生命周期是随类的加载开始 , 随着类消亡而销毁 。

6.1.2 类方法

类方法也叫静态方法

创建静态方法的格式如下

访问修饰符 static 数据返回类型 方法名(){}
static 访问修饰符 数据返回类型 方法名(){}

类方法的调用采用

类名.类方法名
对象名.类方法名

例子:

package com.hspedu.static_;
public class StaticMethod {
    public static void main(String[] args) {
        Stu tom = new Stu("tom");
        //tom.payFee(100);
        Stu.payFee(100);
        Stu mary = new Stu("mary");
        //mary.payFee(200);
        Stu.payFee(200);
        Stu.showFee();//300
    }
}
​
class Stu {
    private String name;//普通成员
    //定义一个静态变量,来累积学生的学费
    private static double fee = 0;
    public Stu(String name) {
        this.name = name;
    }
    //1. 当方法使用了static 修饰后,该方法就是静态方法
    //2. 静态方法就可以访问静态属性/变量
    public static void payFee(double fee) {
        Stu.fee += fee;//累积到
    }
    public static void showFee() {
        System.out.println("总学费有:" + Stu.fee);
    }
}

6.2 main方法

public static void main(String[] args){}
  • mian方法时虚拟机调用

  • java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public

  • java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static

  • 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数

1) 在main()方法中,我们可以直接调用main 方法所在类的静态方法或静态属性。 2) 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静 态成员

6.3 代码块

代码块又称为初始化块,属于类中的成员,类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用

基本语法如下

[修饰符] {
    代码
}[;]
  • 修饰符可选,要写也只能写static

  • 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块

  • 逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等)

  • ;号可以写上,也可以省略

例如

package com.hspedu.codeblock_;
public class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie = new Movie("你好,李焕英");
        Movie movie2 = new Movie("唐探3", 100, "陈思诚");
    }
}
​
class Movie {
    private String name;
    private double price;
    private String director;
    //3 个构造器-》重载
    //这时我们可以把相同的语句,放入到一个代码块中,即可
    //这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
    //代码块调用的顺序优先于构造器..
    {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正是开始...");
    };
    public Movie(String name) {
        System.out.println("Movie(String name) 被调用...");
        this.name = name;
    }
    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
    }
    public Movie(String name, double price, String director) {
        System.out.println("Movie(String name, double price, String director) 被调用...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}
  • static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象,就执行

  • 类什么时候被加载

    • 创建对象实例时(new)

    • 创建子类对象实例,父类也会被加载

    • 使用类的静态成员时(静态属性、静态方法)

  • 普通的代码块,在创建对象实例时,会被隐式的调用。被创建一次就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。

  • 创建一个对象时,在一个类的调用顺序时

    • 调用静态代码块和静态属性初始化

    • 调用普通代码块和普通属性的初始化

    • 调用构造方法

  • 构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕。因此是优于构造器和普通代码块执行的

  • 静态代码块只能直接调用静态成员,普通代码块可以调用任意成员

6.4 单例设计模式

单例(单个的实例)

  • 所为类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

  • 单例模式有两种方式:1)饿汉式 2)懒汉式

饿汉式步骤如下:

  • 构造器私有化[防止直接new]

  • 类的内部创建对象

  • 向外暴露一个静态的公共方法

class GirlFriend{
    private String name;
    // 为了能在静态方法中返回gf对象,需要将其修饰为static
    // 在内部直接创建对象
    private static GirlFriend gf = new GirlFriend("小红");
    // 私有化构造器
    private GirlFriend(String name){
        this.name = name;
    }
    
    // 提供一个公共的static方法,返回gf对象
    public static GirlFriend getInstance(){
        return gf;
    }
}

懒汉式的步骤如下:

  • 仍然构造器私有化

  • 定义一個static 静态属性对象

  • 提供一个public 的static 方法,可以返回一个Cat 对象

class Cat {
    private String name;
    private static Cat cat ; //默认是null
    // 提供一个public 的static 方法,可以返回一个Cat 对象
    // 只有当用户使用getInstance 時,才返回cat 对象, 后面再次调用时,会返回上次创建的cat 对象
    private Cat(String name) {
        System.out.println("构造器调用...");
        this.name = name;
    }
    public static Cat getInstance() {
        if(cat == null) {//如果没有创建cat 对象
            cat = new Cat("小可爱");
        }
        return cat;
    }
}
  1. 二者最主要的区别在于创建对象的时机不同 : 饿汉式是在类加载就创建了对象实例 ,而懒汉式是在使用时才创建 。

  2. 饿汉式不存在线程安全问题 , 懒汉式存在线程安全问题 。

  3. 饿汉式存在浪费资源的可能 。 因为如果程序员一个对象实例都没有使用 , 那么又式创建的对象就浪费了 , 懒汉式是使用时才创建 , 就不存在这个问题 。

  4. 在我们 javaSE 标准类中 , java.lang.Runtime 就是经典的单例模式 。

6.5 final关键字

final 可以修饰类、属性、方法和局部变量

  • final 变量:变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。

  • final 方法:父类中的 final 方法可以被子类继承,但是不能被子类重写。声明 final 方法的主要目的是防止该方法的内容被修改。

  • final 类:final 类不能被继承,没有类能够继承 final 类的任何特性。

注意事项:

  • final修饰的属性又叫常量,一般用XX_XX__XX来命名

  • final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以加在以下位置

    • 定义时,如public final double TAX_RATE=0.08;

    • 在构造器中

    • 在代码块中

  • 如果final修饰的属性是静态的,则初始化的位置只能是

    • 定义时

    • 在静态代码块,不能在构造器中赋值

  • final类不能继承,但是可以实例化对象

  • 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

  • 一般来说,如果一个类已经是final类了,就没必要再将方法修饰成final方法

  • final不能修饰构造方法(即构造器)

  • final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

  • 包装类(Integer, Double, Float等)都是final,String也是final类

6.6 抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

  • 父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

  • 在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

  • 抽象类不一定要包含abstract 方法。也就是说,抽象类可以没有abstract 方法

  • 抽象方法不能使用private、 final和static修饰,因为这些关键字都是和重写相违背的

在 Java 语言中使用 abstract class 来定义抽象类。

public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   public Employee(String name, String address, int number)
   {
      System.out.println("Constructing an Employee");
      this.name = name;
      this.address = address;
      this.number = number;
   }
   public double computePay()
   {
     System.out.println("Inside Employee computePay");
     return 0.0;
   }
   public void mailCheck()
   {
      System.out.println("Mailing a check to " + this.name
       + " " + this.address);
   }
   public String toString()
   {
      return name + " " + address + " " + number;
   }
   public String getName()
   {
      return name;
   }
   public String getAddress()
   {
      return address;
   }
   public void setAddress(String newAddress)
   {
      address = newAddress;
   }
   public int getNumber()
   {
     return number;
   }
}

6.7 模板设计模式

abstract public class Template {
    public abstract void job();//抽象方法
    public void calculateTime() {//实现方法,调用job 方法
        //得到开始的时间
        long start = System.currentTimeMillis();
        job(); //动态绑定机制
        //得的结束的时间
        long end = System.currentTimeMillis();
        System.out.println("任务执行时间" + (end - start));
    }
}
​
public class AA extends Template {
    //计算任务
    //1+....+ 800000
    @Override
    public void job() { //实现Template 的抽象方法job
        long num = 0;
        for (long i = 1; i <= 800000; i++) {
            num += i;
        }
    }
}

6.8 接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

6.8.1 接口的基本使用

接口的声明语法格式如下:

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}
  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。

  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。

  • 接口中的方法都是公有的。

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。实现一个接口的语法,可以使用这个公式:

...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

例如:

/* 文件名 : Animal.java */
interface Animal {
   public void eat();
   public void travel();
}
​
​
/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{
 
   public void eat(){
      System.out.println("Mammal eats");
   }
 
   public void travel(){
      System.out.println("Mammal travels");
   } 
 
   public int noOfLegs(){
      return 0;
   }
 
   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。

  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。

  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。

  • 一个类只能继承一个类,但是能实现多个接口。

  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

6.8.2 接口的继承

一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。

在Java中,类的多继承是不合法,但接口允许多继承。在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:

public interface Hockey extends Sports, Event

6.9 内部类

一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。是我们类的第五大成员【类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】,内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系

class Outer{    // 外部类s
    class Inner{
        //内部类
    }
}

6.9.1 内部类的分类

如果定义类在局部位置(方法中/代码块) :

  • 局部内部类(有类名)

  • 匿名内部类(没有类名)

定义在成员位置

  • 成员内部类(没用static修饰)

  • 静态内部类(有用static修饰)

6.9.2 局部内部类

局部内部类是定义在外部类的局部位置,比如方法中,并且有类名。

  • 可以直接访问外部类的所有成员,包含私有的

  • 不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用final 修饰,因为局部变量也可以使用final

  • 作用域:仅仅在定义它的方法或代码块中。

  • 局部内部类---访问---->外部类的成员[访问方式:直接访问]

  • 外部类---访问---->局部内部类的成员[访问方式:创建对象,再访问]

  • 外部其他类---不能访问----->局部内部类(因为局部内部类地位是一个局部变量)

  • 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

class Outer02 {//外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    }//私有方法
    public void m1() {//方法
        final class Inner02 {//局部内部类(本质仍然是一个类)
        // 可以直接访问外部类的所有成员,包含私有的
            private int n1 = 800;
            public void f1() {
                // 局部内部类可以直接访问外部类的成员,比如下面外部类n1 和m2()
                // 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,使用外部类名.this.成员去访问
                System.out.println("n1=" + n1 + " 外部类的n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                m2();
            }
        }
        //6. 外部类在方法中,可以创建Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

6.9.3 匿名内部类

匿名内部类本质是类,是一个内部类,该类没有名字(无法直接显示,该类的名字其实是由系统分配的),同时还是一个对象,定义在外部类的局部位置,比如方法中。其语法如下所示

new 类或接口(参数列表){
    类体
}

例如

class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
        // 基于接口的匿名内部类
        // 想使用IA 接口,并创建对象,类只是使用一次,后面再不使用,可以使用匿名内部类来简化开发
        /** 我们看底层会分配类名Outer04$1
            class Outer04$1 implements IA {
                @Override
                public void cry() {
                    System.out.println("老虎叫唤...");
                }
            }
        */
        // jdk 底层在创建匿名内部类Outer04$1,立即马上就创建了Outer04$1 实例,并且把地址返回给tiger
        // 匿名内部类使用一次,就不能再使用
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger 的运行类型=" + tiger.getClass());
        tiger.cry();
        
interface IA {//接口
    public void cry();
}
  • 匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,对前面代码分析可以看出这个特点,因此可以调用匿名内部类方法。

  • 可以直接访问外部类的所有成员,包含私有的

  • 不能添加访问修饰符,因为它的地位就是一个局部变量。

  • 作用域:仅仅在定义它的方法或代码块中。

  • 匿名内部类---访问---->外部类成员[访问方式:直接访问]

  • 外部其他类---不能访问----->匿名内部类(因为匿名内部类地位是一个局部变量)

  • 如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

6.9.4 成员内部类

成员内部类是定义在外部类的成员位置,并且没有static修饰。

  • 可以直接访问外部类的所有成员,包含私有的

  • 可以添加任意访问修饰符(public、protected、默认、private),因为它的地位就是一个成员。

  • 作用域和外部类的其他成员一样,为整个类体比如前面案例,在外部类的成员方法中创建成员内部类对象,再调用方法.

  • 成员内部类---访问---->外部类成员(比如:属性)[访问方式:直接访问]

  • 外部类---访问------>成员内部类(访问方式:创建对象,再访问)

  • 外部其他类----访问------>成员内部类(两种方式)

Outer08 outer08 = new Outer08();
outer08.t1();
​
// 法1
Outer08.Inner08 inner08 = outer08.new Inner08();
// 法2 外部类中编写一个方法 返回内部类对象,如下例中的getInner08Instance
Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
  • 如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问

class Outer08 { // 外部类
    private int n1 = 10;
    public String name = "张三";
    private void hi() {
        System.out.println("hi()方法...");
    }
    
    public class Inner08 {// 成员内部类
        private double sal = 99.8;
        private int n1 = 66;
        public void say() {
        // 可以直接访问外部类的所有成员,包含私有的
        // 如果成员内部类的成员和外部类的成员重名,会遵守就近原则.
        // 可以通过外部类名.this.属性来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name + " 外部类的n1=" + Outer08.this.n1);
            hi();
        }
    }
    // 方法,返回一个Inner08 实例
    public Inner08 getInner08Instance(){
        return new Inner08();
    }
    
    public void t1() {
        // 使用成员内部类
        // 创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}

6.9.5 静态内部类

静态内部类是定义在外部类的成员位置,并且有static修饰

  • 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员

  • 可以添加任意访问修饰符(public.protected、默认、private),因为它的地位就是一个成员。

  • 作用域:同其他的成员,为整个类体

  • 静态内部类---访问---->外部类(比如:静态属性)[访问方式:直接访问所有静态成员]

  • 外部类---访问------>静态内部类[访问方式:创建对象,再访问]

  • 外部其他类---访问----->静态内部类

// 方式1 因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
Outer10.Inner10 inner10 = new Outer10.Inner10();
inner10.say();
​
// 方式2 编写一个方法,可以返回静态内部类的对象实例.
Outer10.Inner10 inner101 = outer10.getInner10();
inner101.say();
  • 如果外部类和静态内部类的成员重名时,静态内部类访问的时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问

class Outer10 { //外部类
    private int n1 = 10;
    private static String name = "张三";
    private static void cry() {}
    static class Inner10 {// Inner10 就是静态内部类
        private static String name = "李四";
        public void say() {
            // 如果外部类和静态内部类的成员重名时,静态内部类访问的时,
            // 默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)
            System.out.println(name + " 外部类name= " + Outer10.name);
            cry();
        }
    }
    
    public void m1() { //外部类---访问------>静态内部类访问方式:创建对象,再访问
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
    
    public Inner10 getInner10() {
        return new Inner10();
    }
    
    public static Inner10 getInner10_() {
        return new Inner10();
    }
}

7 枚举和注解

7.1 枚举的基本使用

Java 枚举是一个特殊的类,一般表示一组常量

Java 枚举类使用 enum 关键字来定义,各个常量使用逗号,来分割。

// 定义一个颜色的枚举类。
enum Color 
{ 
    RED, GREEN, BLUE; 
} 

使用枚举可以采用

public class Test
{
    // 执行输出结果
    public static void main(String[] args)
    {
        Color c1 = Color.RED;
        System.out.println(c1); //RED
    }
}

每个枚举都是通过 Class 在内部实现的,且所有的枚举值都是 public static final 的。

7.2 枚举的两种实现方式

7.2.1 自定义类枚举

  • 不需要提供setXxx方法,因为枚举对象值通常为只读.

  • 对枚举对象/属性使用final + static 共同修饰,实现底层优化.

  • 枚举对象名通常使用全部大写,常量的命名规范.

  • 枚举对象根据需要,也可以有多个属性

class Season {//类
    private String name;
    private String desc;//描述
    // 定义了四个对象, 固定
    public static final Season SPRING = new Season("春天", "温暖");
    public static final Season WINTER = new Season("冬天", "寒冷");
    public static final Season AUTUMN = new Season("秋天", "凉爽");
    public static final Season SUMMER = new Season("夏天", "炎热");
    // 优化,可以加入final 修饰符
    private Season(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    public String getName() {
        return name;
    }
    public String getDesc() {
        return desc;
    }
    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

7.2.2 enum 关键字实现枚举

enum Season2 {//类
    SPRING("春天", "温暖"), 
    WINTER("冬天", "寒冷"), 
    AUTUMN("秋天", "凉爽"),
    SUMMER("夏天", "炎热")/*, What()*/;
    private String name;
    private String desc;//描述
    
    private Season2() {//无参构造器
    }
    
    private Season2(String name, String desc) {
        this.name = name;
        this.desc = desc;
    }
    
    public String getDesc() {
        return desc;
    }
    
    @Override
    public String toString() {
        return "Season{" +
                "name='" + name + '\'' +
                ", desc='" + desc + '\'' +
                '}';
    }
}

1) 当我们使用enum 关键字开发一个枚举类时,默认会继承Enum 类, 而且是一个final 类 2) 传统的public static final Season2 SPRING = new Season2("春天", "温暖"); 简化成SPRING("春天", "温暖"), 这里必 须知道,它调用的是哪个构造器. 3) 如果使用无参构造器创建枚举对象,则实参列表和小括号都可以省略 3) 当有多个枚举对象时,使用,间隔,最后有一个分号结尾 3) 枚举对象必须放在枚举类的行首.

7.3 enum的常用方法

方法名

详细描述

valueOf

传递枚举类型的Class对象和枚举常量名称给静态方法valueOf,会得到与参数匹配的枚举常量

toString

得到当前枚举常量的名称。可以通过重写这个方法来使得到的结果更易读

equals

再枚举类型中可以直接使用==来比较两个枚举常量是否相等。equal()方法也是采用==实现的。它的存在是为了再Set、List和Map中使用。注意:equals()是不可变的

hashCode

Enum实现了hashCode()来和equals()保持一致。它也是不可变的

getDeclaringClass

得到枚举常量所属枚举类型的Class对象。可以用它来判断两个枚举常量是否属于同一个枚举类型。

name

得到当前枚举常量的名称。建议优先使用toString

ordinal

得到当前枚举常量的次序

compareTo

枚举类型实现了Comparable接口,这样可以比较两个枚举常量的大小(按照声明的顺序排列)

clone

枚举类型不能被Clone。为了防止子类实现克隆方法,Enum实现了一个禁抛出CloneNotSupporterException异常的不变Clone()

values

返回当前枚举类中所有的常量

7.4 enum 实现接口

使用enum 关键字后,就不能再继承其它类了,因为enum 会隐式继承Enum,而Java 是单继承机制。

枚举类和普通类一样,可以实现接口,如下形式。

enum 类名 implements 接口1,接口2{}

例如

interface IPlaying {
    public void playing();
}
​
enum Music implements IPlaying {
    CLASSICMUSIC;
    @Override
    public void playing() {
        System.out.println("播放好听的音乐...");
    }
}

7.5 Java 注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

7.5.1 三个基本的Annotation

  • @Override - 检查该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

    • 如果不写@Override 注解,而父类仍有该方法,仍然构成重写

    • @Override 只能修饰方法,不能修饰其他类、包、属性等等

  • @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。

    • @Deprecated 修饰某个元素, 表示该元素已经过时,即不在推荐使用,但是仍然可以使用

    • 可以修饰方法,类,字段, 包, 参数等等

    • @Deprecated 可以做版本升级过渡使用

  • @SuppressWarnings - 指示编译器去忽略注解中声明的警告。

    • 当我们不希望看到这些警告的时候,可以使用SuppressWarnings 注解来抑制警告信息

    • 在{""} 中,可以写入你希望抑制(不显示)警告信息@SuppressWarnings({"rawtypes", "unchecked", "unused"})

    • SuppressWarnings 作用范围是和你放置的位置相关通常我们可以放置具体的语句, 方法, 类.

警告类型

说明

all

抑制所有警告

boxing

抑制与封装/拆装作业相关的警告

cast

抑制与强制转型作业相关的警告

dep-ann

抑制与淘汰注释相关的警告

deprecation

抑制与淘汰的相关警告

fallthrough

抑制与switch 陈述式中遗漏break 相关的警告

finally

抑制与未传回finally 区块相关的警告

hiding

抑制与隐藏变数的区域变数相关的警告

incomplete-switch

抑制与switch 陈述式(enum case)中遗漏项目相关的警告

javadoc

抑制与javadoc 相关的警告//nls,抑制与非nls 字串文字相关的警告

null

抑制与空值分析相关的警告

rawtypes

抑制与使用raw 类型相关的警告

resource

抑制与使用Closeable 类型的资源相关的警告

restriction

抑制与使用不建议或禁止参照相关的警告

serial

抑制与可序列化的类别遗漏serialVersionUID 栏位相关的警告

static-access

抑制与静态存取不正确相关的警告

static-method

抑制与可能宣告为static 的方法相关的警告

super

抑制与置换方法相关但不含super 呼叫的警告

synthetic-access

抑制与内部类别的存取未最佳化相关的警告

sync-override

抑制因为置换同步方法而遗漏同步化的警告

unchecked

抑制与未检查的作业相关的警告

unqualified-field-access

抑制与栏位存取不合格相关的警告

unused

抑制与未用的程式码及停用的程式码相关的警告

7.5.2 元注解

修饰注解的注解称为元注解

  • @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。@Rentention 包含一个RetentionPolicy类型的成员变量, 使用@Rentention 时必须为该value 成员变量指定值:

    • RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释

    • RetentionPolicy.CLASS: 编译器将把注解记录在class 文件中. 当运行Java 程序时, JVM 不会保留注解。这是默认值

    • RetentionPolicy.RUNTIME:编译器将把注解记录在class 文件中. 当运行Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注解

  • @Documented - 标记这些注解是否包含在用户文档中。

    • 定义为@Documented的注解必须设置Retention值为RUNTIME

  • @Target - 标记这个注解应该是哪种 Java 成员。

    • @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})

  • @Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

    • 被他修饰的Annotation将具有继承性。如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解

8 异常-Exception

8.1 异常处理

异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。

异常发生的原因有很多,通常包含以下几大类:

  • 用户输入了非法数据。

  • 要打开的文件不存在。

  • 网络通信时连接中断,或者JVM内存溢出。

这些异常有的是因为用户错误引起,有的是程序错误引起的,还有其它一些是因为物理错误引起的。

异常可以分为三种类型

  • 检查性异常:最具代表的检查性异常是用户错误或问题引起的异常,这是程序员无法预见的。例如要打开一个不存在文件时,一个异常就发生了,这些异常在编译时不能被简单地忽略。

  • 运行时异常: 运行时异常是可能被程序员避免的异常。与检查性异常相反,运行时异常可以在编译时被忽略。

  • 错误: 错误不是异常,而是脱离程序员控制的问题。错误在代码中通常被忽略。例如,当栈溢出时,一个错误就发生了,它们在编译也检查不到的。

8.2 Exception类的层次

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类是 Throwable 类的子类。除了Exception类外,Throwable还有一个子类Error 。

Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。

Error 用来指示运行时环境发生的错误。

例如,JVM 内存溢出。一般地,程序不会从错误中恢复。

异常类有两个主要的子类:IOException 类和 RuntimeException 类。

  • 异常分为两大类,运行时异常和编译时异常.

  • 运行时异常,编译器检查不出来。一般是指编程时的逻辑错误,是程序员应该避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常

  • 对于运行时异常,可以不作处理,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响

  • 编译时异常,是编译器要求必须处置的异常。

8.3 常见的运行时异常

常见的运行时异常包括

1) NullPointerException 空指针异常 2) ArithmeticException 数学运算异常 3) ArrayIndexOutOfBoundsException 数组下标越界异常 4) ClassCastException 类型转换异常 5) NumberFormatException 数字格式不正确异常[]

8.3.1 NullPointerException 空指针异

当应用程序试图在需要对象的地方使用null 时,抛出该异常,例如

public static void main(String[] args) {
    String name = null;
    System.out.println(name.length());
}

8.3.2 ArithmeticException 数学运算异常

当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例

public static void main(String[] args) {
    System.out.println(6/0);
}

8.3.3 ArrayIndexOutOfBoundsException 数组下标越界异常

用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引

public static void main(String[] args) {
    int[] arr = {1,2,4};
    for (int i = 0; i <= arr.length; i++) {
        System.out.println(arr[i]);
    }
}

8.3.4 ClassCastException 类型转换异常

当试图将对象强制转换为不是实例的子类时,抛出该异常。

public static void main(String[] args) {
    A b = new B(); //向上转型
    B b2 = (B)b;//向下转型,这里是OK
    C c2 = (C)b;//这里抛出ClassCastException
}
​
class A {}
class B extends A {}
class C extends A {}

8.3.5 NumberFormatException 数字格式不正确异常

当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常

public static void main(String[] args) {
    String name = "张三";
    int num = Integer.parseInt(name);//抛出NumberFormatException
    System.out.println(num);//1234
}

8.4 编译异常

编译异常是指在编译期间,就必须处理的异常,否则代码不能通过编译。常见的编译异常有

  • SQLException //操作数据库时,查询表可能发生异常

  • IOException //操作文件时,发生的异常

  • FileNotFoundException //当操作一个不存在的文件时,发生异常

  • ClassNotFoundException //加载类,而该类不存在时,异常

  • EOFException //操作文件,到文件未尾,发生异常

  • IllegalArguementException //参数异常

8.5 异常处理

异常处理就是当异常发生时,对异常处理的方式。

8.5.1 try- catch- finally

程序员在代码中捕获发生的异常,自行处理

try{
    // 代码/有可能有异常
} catch (NullPointerException e) {
    System.out.println("空指针异常=" + e.getMessage());
} catch (ArithmeticException e) {
    System.out.println("算术异常=" + e.getMessage());
} catch(Exception e){
    // 可以使用多个catch 分别捕获不同的异常,相应处理
    // 当异常发生时, 系统将异常封装成Exception对象e,传递给catch
    // 得到异常对象后,程序员,自己处理 
    // 如果没有发生异常,catch代码块不执行
} finally{
    // 不管try代码块是否有异常发生,始终要执行finally
    // 所以,通常将释放资源的代码,放在finally
}

8.5.2 throws

将发生的异常抛出,交给使用者(方法) 来处理,最顶级的处理者就是JVM

  • 如果一个方法(中的语句执行时)可能生成某种异常,但是并不能确定如何处理这种异常,则此方法应显示地声明抛出异常,表明该方法将不对这些异常进行处理,而由该方法的调用者负责处理。

  • 在方法声明中用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。

例如JVM调用main,main中调用f1(),f1()中调用f2(),f2中抛出异常,f2可以使用try-catch,也可以不管,throws给f1,f1同理,如果一直往上throws,最后到JVM机,JVM机采用直接输出异常,并且结束程序的操作。

import java.io.FileInputStream;
import java.io.FileNotFoundException;
​
public class ThrowsDetail {
    public static void main(String[] args) {
        f2();
    }
    
    public static void f2() /*throws ArithmeticException*/ {
        //1.对于编译异常,程序中必须处理,比如try-catch 或者throws
        //2.对于运行时异常,程序中如果没有处理,默认就是throws 的方式处理
        int n1 = 10;
        int n2 = 0;
        double res = n1 / n2;
    }
    
    public static void f1() throws FileNotFoundException {
        //这里大家思考问题调用f3() 报错
        //老韩解读
        //1. 因为f3() 方法抛出的是一个编译异常
        //2. 即这时,就要f1() 必须处理这个编译异常
        //3. 在f1() 中,要么try-catch-finally ,或者继续throws 这个编译异常
        f3(); // 抛出异常
    }
    
    public static void f3() throws FileNotFoundException {
        FileInputStream fis = new FileInputStream("d://aa.txt");
    }
    
    public static void f4() {
        //老韩解读:
        //1. 在f4()中调用方法f5() 是OK
        //2. 原因是f5() 抛出的是运行异常
        //3. 而java 中,并不要求程序员显示处理,因为有默认处理机制
        f5();
    }
    
    public static void f5() throws ArithmeticException {
    }
}

.

意义

位置

后面跟的东西

throws

异常处理的一种方式

方法声明处

异常类型

throw

手动生成异常对象的关键字

方法体中

异常对象

8.6 自定义异常

在 Java 中你可以自定义异常。编写自己的异常类时需要记住下面的几点。

  • 所有异常都必须是 Throwable 的子类。

  • 如果希望写一个检查性异常类,则需要继承 Exception 类。

  • 如果你想写一个运行时异常类,那么需要继承 RuntimeException 类。

只继承Exception 类来创建的异常类是检查性异常类。

public class CustomException {
    public static void main(String[] args) /*throws AgeException*/ {
        int age = 180;
        //要求范围在18 – 120 之间,否则抛出一个自定义异常
        if(!(age >= 18 && age <= 120)) {
            //这里我们可以通过构造器,设置信息
            throw new AgeException("年龄需要在18~120 之间");
        }
        System.out.println("你的年龄范围正确.");
    }
}
​
class AgeException extends RuntimeException {
    public AgeException(String message) {//构造器
        super(message);
    }
}

9 常用类

9.1 包装类Warpper

9.1.1 包装类介绍

针对八种基本数据类型相应的引用类型—包装类

有了类的特点,就可以调用类中的方法。

包装类

基本数据类型

Boolean

boolean

Byte

byte

Short

short

Integer

int

Long

long

Character

char

Float

float

Double

double

9.1.2 包装类和基本数据的转换

这种由编译器特别支持的包装称为装箱,所以当内置数据类型被当作对象使用的时候,编译器会把内置类型装箱为包装类。相似的,编译器也可以把一个对象拆箱为内置类型。

  • jdk5前的手动装箱和拆箱方式,装箱:基本类型->包装类型,反之,拆箱

  • jdk5 以后(含jdk5)的自动装箱和拆箱方式

  • 自动装箱底层调用的是valueOf方法,比如Integer.valueOf

  • 其它包装类的用法类似,不一一举例

public static void main(String[] args) {
    //手动装箱 int->Integer
    int n1 = 100;
    Integer integer = new Integer(n1);
    Integer integer1 = Integer.valueOf(n1);
    
    //手动拆箱 Integer -> int
    int i = integer.intValue();
    
    int n2 = 200;
    //jdk5 后,就可以自动装箱和自动拆箱
    //自动装箱int->Integer
    Integer integer2 = n2; //底层使用的是Integer.valueOf(n2)
    //自动拆箱Integer->int
    int n3 = integer2; //底层仍然使用的是intValue()方法
}

9.1.3 包装类型和String 类型的相互转换

public static void main(String[] args) {
    //包装类(Integer)->String
    Integer i = 100;//自动装箱
    
    //方式1
    String str1 = i + "";
    
    //方式2
    String str2 = i.toString();
    
    //方式3
    String str3 = String.valueOf(i);
    
    //String -> 包装类(Integer)
    String str4 = "12345";
    Integer i2 = Integer.parseInt(str4);//使用到自动装箱
    Integer i3 = new Integer(str4);//构造器
}

9.1.4 Integer 类和Character 类的常用方法

public static void main(String[] args) {
    System.out.println(Integer.MIN_VALUE); //返回最小值
    System.out.println(Integer.MAX_VALUE);//返回最大值
    System.out.println(Character.isDigit('a'));//判断是不是数字
    System.out.println(Character.isLetter('a'));//判断是不是字母
    System.out.println(Character.isUpperCase('a'));//判断是不是大写
    System.out.println(Character.isLowerCase('a'));//判断是不是小写
    System.out.println(Character.isWhitespace('a'));//判断是不是空格
    System.out.println(Character.toUpperCase('a'));//转成大写
    System.out.println(Character.toLowerCase('A'));//转成小写
}

9.2 String类

字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串。

  • String实现了Serializable,说明String可以串行化(可以在网络中传输)

  • String实现了Comparable,说明String可以相互比较

9.2.1 创建字符串

方法1:直接赋值

String str = "jack";

方法2:调用构造器

String str2 = new String("edu");

两种方式的区别:

  • 方式一:先从常量池查看是否有"hsp”数据空间,如果有,直接指向;如果没有则重新创建,然后指向。S最终指向的是常量池的空间地址

  • 方式二:先在堆中创建空间,里面维护了value属性,指向常量池的hsp空间。如果常量池没有"hsp",重新创建,如果有,直接通过value指向。最终指向的是堆中的空间地址。

String属性private final char value[]用于存放字符串内容,final类型地址不可以修改,内容可以修改

String.intern() 返回常量池的地址

9.2.2 String类的常用方法

String类是保存字符串常量的。每次更新都需要重新开辟空间,效率较低,因此java设计者还提供了StringBuilder 和 StringBuffer来增强String的功能,并提高效率。[后面我们还会详细介绍StringBuilder 和 StringBuffer]

String 类的常见方法有

  • equals区分大小写,判断内容是否相等

  • equalsIgnoreCase忽略大小写的判断内容是否相等

  • length获取字符的个数,字符串的长度

  • indexOf获取字符在字符串中第一次出现的索引,索引从0开始,如果找不到,返回-1

  • lastIndexOf获取字符在字符串中最后一次出现的索引,索引从0开始,如果找不到,返回-1

  • substring截取指定范围的字串

  • trim去前后空格

  • charAt获取某索引处的字符,注意不能用Str[index]这种方式

  • toUpperCase转换成大写

  • toLowerCase转换成小写

  • concat拼接字符串

  • replace替换字符串中的字符

  • split分割字符串, 对于某些分割字符,我们需要转义

  • compareTo比较两个字符串的大小,如果前者大, 则返回正数,后者大,则返回负数,如果相等,返回0

  • toCharArray转换成字符数组

  • format格式字符串

9.3 StringBuffer 类和 StringBuilder 类

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。

9.3.1 StringBuffer 类

  • java.lang.StringBuffer代表可变的字符序列,可以对字符串内容进行增删。

  • 很多方法与String相同,但StringBuffer是可变长度的。

  • StringBuffer是一个容器。

StringBuffer stringBuffer = new StringBuffer("hello");

String VS StringBuffer

1) String保存的是字符串常量,里面的值不能更改,每次String类的更新实际上就是更改地址,效率较低//private final char valuell; 2) StringBuffer保存的是字符串变量,里面的值可以更改,每次 StringBuffer的更新实际上可以更新内容,不用每次更新地址,效率较高char[] value; //这个放在堆.

String 和StringBuffer 相互转换

String str = "hello tom";
// String——>StringBuffer
// 方式1 使用构造器
StringBuffer stringBuffer = new StringBuffer(str);
// 方式2 使用的是append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
​
StringBuffer stringBuffer3 = new StringBuffer("jack");
// StringBuffer ->String
// 方式1 使用StringBuffer 提供的toString 方法
String s = stringBuffer3.toString();
// 方式2: 使用构造器来搞定
String s1 = new String(stringBuffer3);

StringBuffer 类常见方法

StringBuffer s = new StringBuffer("hello");
// 增
s.append(',abc');   // "hello,abc"
// 删
s.delete(1, 2);     // "hllo,abc"
// 改
s.replace(1, 2, "cccc");    // "hcccclo,abc"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("ab");  // 8
//插
s.insert(3, "ob");      // "hccobcclo,abc"
//长度
System.out.println(s.length());     // 13

9.3.2 StringBuilder 类

  • 一个可变的字符序列。此类提供一个与 StringBuffer 兼容的API,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候。如果可能,建议优先采用该类因为在大多数实现中,它比StringBuffer 要快。

  • 在 StringBuilder上的主要操作是append 和 insert方法,可重载这些方法,以接受任意类型的数据。

StringBuffer stringBuffer = new StringBuffer("hello");

StringBuilder 常用方法

方法

描述

public StringBuffer append(String s)

将指定的字符串追加到此字符序列。

public StringBuffer reverse()

将此字符序列用其反转形式取代。

public delete(int start, int end)

移除此序列的子字符串中的字符。

public insert(int offset, int i)

int 参数的字符串表示形式插入此序列中。

insert(int offset, String str)

str 参数的字符串插入此序列中。

replace(int start, int end, String str)

使用给定 String 中的字符替换此序列的子字符串中的字符。

  • 如果字符串存在大量的修改操作,一般使用StringBuffer 或StringBuilder

  • 如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuilder

  • 如果字符串存在大量的修改操作,并在多线程的情况,使用StringBuffer

  • 如果我们字符串很少修改,被多个对象引用,使用String, 比如配置信息等;

9.4 Math 类

Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

其常用方法有

// abs 绝对值
int abs = Math.abs(-9);			// 9

// pow 求幂
double pow = Math.pow(2, 4); 	// 16

// ceil 向上取整,返回>=该参数的最小整数(转成double);
double ceil = Math.ceil(3.9);	// 4.0

// floor 向下取整,返回<=该参数的最大整数(转成double)
double floor = Math.floor(4.001);// 4.0

// round 四舍五入Math.floor(该参数+0.5)
long round = Math.round(5.51);	// 6

// sqrt 求开方
double sqrt = Math.sqrt(9.0);	// 3.0

// random 求随机数
// random 返回的是0 <= x < 1 之间的一个随机小数
// Math.random() * (b-a) 返回的就是0 <= 数<= b-a

// max , min 返回最大值和最小值
int min = Math.min(1, 9);		// 1
int max = Math.max(45, 90);		// 90

9.5 Arrays 类

java.util.Arrays 类能方便地操作数组,它提供的所有方法都是静态的。

Integer[] integers = {1, 20, 90};
// toString是创建一个StringBuider,拼接,显示数组
System.out.println(Arrays.toString(integers)); // [1, 20, 90]
​
// sort方法排序,默认是升序
// 也可以通过传入一个接口Comparator 实现定制排序
Integer arr[] = {1, -1, 7, 0, 89};
Array.sort(arr);    // {-1, 0, 1, 7, 89}
// 实现了Comparator 接口的匿名内部类, 要求实现compare 方法
Integer arr[] = {1, -1, 7, 0, 89};
Arrays.sort(arr, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2){
        return o2 - o1;
    }
});
System.out.println(Arrays.toString(arr));// [89, 7, 1, 0, -1]
​
// 使用binarySearch 二叉查找
// 要求该数组是有序的. 如果该数组是无序的,不能使用binarySearch
// 如果数组中不存在该元素,就返回return -(low + 1);  key not found.
int index = Arrays.binarySearch(arr, -1);   // 1
​
// copyOf 数组元素的复制
// 从arr 数组中,拷贝arr.length 个元素到newArr 数组中
// 如果拷贝的长度> arr.length 就在新数组的后面增加null
// 如果拷贝长度< 0 就抛出异常NegativeArraySizeException
// 该方法的底层使用的是System.arraycopy()
Integer[] newArr = Arrays.copyOf(arr, arr.length);
​
Integer[] num = new Integer[]{9,3,2};
// fill 数组元素的填充 可以理解成是替换原来的元素
Arrays.fill(num, 99);   // [99,99,99]
​
// equals 比较两个数组元素内容是否完全一致
boolean equals = Arrays.equals(arr, num);   //False
​
//asList 将一组值,转换成list
// asList 运行类型java.util.Arrays#ArrayList, 是Arrays 类的静态内部类
// 返回的asList 编译类型List(接口)
List asList = Arrays.asList(2,3,4,5,6,1);

9.6 System 类

System类包含几个有用的类字段和方法。它无法实例化。System类提供的设施包括

  • 标准输入,标准输出和错误输出流;

  • 访问外部定义的属性和环境变量;

  • 加载文件和库的方法;

  • 以及用于快速复制阵列的一部分的实用方法。

System 类常见方法

  • public static long currentTimeMillis()以毫秒为单位返回当前时间。 当前时间与UTC时间1970年1月1日午夜之间的差异,以毫秒为单位。

  • public static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)将指定源数组中的数组从指定位置开始复制到目标数组的指定位置。

    • src:源数组

    • srcPos:从源数组的哪个索引位置开始拷贝

    • dest:目标数组,即把原数组的数据拷贝到哪个数组

    • destPos:把源数组的数据拷贝到目标数组的哪个索引

    • length:从源数组拷贝多少个数据到目标数组

  • public static void exit(int status)终止当前运行的Java虚拟机。该参数用作状态代码; 非零状态代码表示异常终止。

  • public static void gc()运行垃圾收集器。调用gc方法表明Java虚拟机花费了大量精力来回收未使用的对象,以使其当前占用的内存可用于快速重用。当控制从方法调用返回时,Java虚拟机已尽最大努力从所有丢弃的对象中回收空间。

9.7 日期类

在JDK 1.1之前, Date类还有两个附加功能。它允许将日期解释为年,月,日,小时,分钟和秒值。 它还允许格式化和解析日期字符串。 不幸的是,这些功能的API不适合国际化。 从JDK 1.1开始, Calendar类应该用于在日期和时间字段之间进行转换,而DateFormat类应该用于格式化和解析日期字符串。 不推荐使用Date中的相应方法。

9.7.1 第一代日期类

Date d1 = new Date(); //获取当前系统时间
Date d2 = new Date(9234567); //通过指定毫秒数得到时间

SimpleDateFormat是可以格式和解析日期的类,它允许进行格式化(日期--->文本)、解析(文本--->日期)和规范化

SimpleDateFormat sdf = new SimpleDateFormat("yyyy 年MM 月dd 日hh:mm:ss E");
String format = sdf.format(d1); // format:将日期转换成指定格式的字符串
System.out.println("当前日期=" + format);

9.7.2 第二代日期类

第二代日期类主要就是Calendar类

public abstract class Calendar
extends Object
implements Serializable, Cloneable, Comparable<Calendar>

所述Calendar类是一个抽象类,可以为在某一特定时刻和一组之间的转换的方法calendar fieldsYEARMONTHDAY_OF_MONTHHOUR ,等等,以及用于操纵该日历字段,如获取的日期下个星期。瞬间可以用毫秒值表示,该值是1970年1月1日格林威治标准时间1970年1月1日00:00:00,000(格里高利)的Epoch的偏移量。

// 可以通过getInstance() 来获取实例
Calendar c = Calendar.getInstance(); 
// 获取日历对象的某个日历字段
System.out.println("月:" + (c.get(Calendar.MONTH) + 1));
System.out.println("日:" + c.get(Calendar.DAY_OF_MONTH));
System.out.println("小时:" + c.get(Calendar.HOUR));
System.out.println("分钟:" + c.get(Calendar.MINUTE));
System.out.println("秒:" + c.get(Calendar.SECOND));

9.7.3 第三代日期类

JDK 1.0中包含了一个java.util.Date类,但是它的大多数方法已经在JDK 1.1引入Calendar类之后被弃用了。而Calendar也存在问题是:

  • 可变性:像日期和时间这样的类应该是不可变的。

  • 偏移性:Date中的年份是从1900开始的,而月份都从0开始。

  • 格式化:格式化只对Date有用,Calendar则不行。

  • 此外,它们也不是线程安全的;不能处理闰秒等(每隔2天,多出1s)。

第三代日期类常见方法:(JDK8加入)

// 使用now() 返回表示当前日期时间的对象
LocalDateTime ldt = LocalDateTime.now(); 
​
// 使用DateTimeFormatter 对象来进行格式化
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
​
// 获取年月日
LocalDate now = LocalDate.now();
// 获取到时分秒
LocalTime now2 = LocalTime.now();
// 提供plus 和minus 方法可以对当前时间进行加或者减
// 看看890 天后,是什么时候
LocalDateTime localDateTime = ldt.plusDays(890);
// 看看在3456 分钟前是什么时候
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);

9.7.4 Instant 时间戳

// 通过静态方法now() 获取表示当前时间戳的对象
Instant now = Instant.now();
// 通过from 可以把Instant 转成Date
Date date = Date.from(now);
// 通过date 的toInstant() 可以把date 转成Instant 对象
Instant instant = date.toInstant();